Interim autoloaded library/classes via composer classmap, take 4. (#422)
[openemr.git] / library / classes / Installer.class.php
blob0f3f96b42bc8ce18248828c6441c5138dbfe053a
1 <?php
2 /* Copyright © 2010 by Andrew Moore */
3 /* Licensing information appears at the end of this file. */
5 class Installer
8 public function __construct( $cgi_variables )
10 // Installation variables
11 // For a good explanation of these variables, see documentation in
12 // the contrib/util/installScripts/InstallerAuto.php file.
13 $this->iuser = isset($cgi_variables['iuser']) ? ($cgi_variables['iuser']) : '';
14 $this->iuserpass = isset($cgi_variables['iuserpass']) ? ($cgi_variables['iuserpass']) : '';
15 $this->iuname = isset($cgi_variables['iuname']) ? ($cgi_variables['iuname']) : '';
16 $this->iufname = isset($cgi_variables['iufname']) ? ($cgi_variables['iufname']) : '';
17 $this->igroup = isset($cgi_variables['igroup']) ? ($cgi_variables['igroup']) : '';
18 $this->server = isset($cgi_variables['server']) ? ($cgi_variables['server']) : ''; // mysql server (usually localhost)
19 $this->loginhost = isset($cgi_variables['loginhost']) ? ($cgi_variables['loginhost']) : ''; // php/apache server (usually localhost)
20 $this->port = isset($cgi_variables['port']) ? ($cgi_variables['port']): '';
21 $this->root = isset($cgi_variables['root']) ? ($cgi_variables['root']) : '';
22 $this->rootpass = isset($cgi_variables['rootpass']) ? ($cgi_variables['rootpass']) : '';
23 $this->login = isset($cgi_variables['login']) ? ($cgi_variables['login']) : '';
24 $this->pass = isset($cgi_variables['pass']) ? ($cgi_variables['pass']) : '';
25 $this->dbname = isset($cgi_variables['dbname']) ? ($cgi_variables['dbname']) : '';
26 $this->collate = isset($cgi_variables['collate']) ? ($cgi_variables['collate']) : '';
27 $this->site = isset($cgi_variables['site']) ? ($cgi_variables['site']) : '';
28 $this->source_site_id = isset($cgi_variables['source_site_id']) ? ($cgi_variables['source_site_id']) : '';
29 $this->clone_database = isset($cgi_variables['clone_database']) ? ($cgi_variables['clone_database']) : '';
30 $this->no_root_db_access = isset($cgi_variables['no_root_db_access']) ? ($cgi_variables['no_root_db_access']) : ''; // no root access to database. user/privileges pre-configured
31 $this->development_translations = isset($cgi_variables['development_translations']) ? ($cgi_variables['development_translations']) : '';
32 // Make this true for IPPF.
33 $this->ippf_specific = false;
35 // Record name of sql access file
36 $GLOBALS['OE_SITES_BASE'] = dirname(__FILE__) . '/../../sites';
37 $GLOBALS['OE_SITE_DIR'] = $GLOBALS['OE_SITES_BASE'] . '/' . $this->site;
38 $this->conffile = $GLOBALS['OE_SITE_DIR'] . '/sqlconf.php';
40 // Record names of sql table files
41 $this->main_sql = dirname(__FILE__) . '/../../sql/database.sql';
42 $this->translation_sql = dirname(__FILE__) . '/../../contrib/util/language_translations/currentLanguage_utf8.sql';
43 $this->devel_translation_sql = "http://opensourceemr.com/cvs/languageTranslations_utf8.sql";
44 $this->ippf_sql = dirname(__FILE__) . "/../../sql/ippf_layout.sql";
45 $this->icd9 = dirname(__FILE__) . "/../../sql/icd9.sql";
46 $this->cvx = dirname(__FILE__) . "/../../sql/cvx_codes.sql";
47 $this->additional_users = dirname(__FILE__) . "/../../sql/official_additional_users.sql";
49 // Record name of php-gacl installation files
50 $this->gaclSetupScript1 = dirname(__FILE__) . "/../../gacl/setup.php";
51 $this->gaclSetupScript2 = dirname(__FILE__) . "/../../acl_setup.php";
53 // Prepare the dumpfile list
54 $this->initialize_dumpfile_list();
56 // Entities to hold error and debug messages
57 $this->error_message = '';
58 $this->debug_message = '';
60 // Entity to hold sql connection
61 $this->dbh = false;
64 public function login_is_valid()
66 if ( ($this->login == '') || (! isset( $this->login )) ) {
67 $this->error_message = "login is invalid: '$this->login'";
68 return FALSE;
70 return TRUE;
73 public function char_is_valid($input_text)
75 // to prevent php injection
76 trim($input_text);
77 if ($input_text == '')
79 return FALSE;
81 if (preg_match('@[\\\\;()<>/\'"]@', $input_text))
83 return FALSE;
85 return TRUE;
88 public function iuser_is_valid()
90 if ( strpos($this->iuser, " ") ) {
91 $this->error_message = "Initial user is invalid: '$this->iuser'";
92 return FALSE;
94 return TRUE;
97 public function password_is_valid()
99 if ( $this->pass == "" || !isset($this->pass) ) {
100 $this->error_message = "The password for the new database account is invalid: '$this->pass'";
101 return FALSE;
103 return TRUE;
106 public function user_password_is_valid()
108 if ( $this->iuserpass == "" || !isset($this->iuserpass) ) {
109 $this->error_message = "The password for the user is invalid: '$this->iuserpass'";
110 return FALSE;
112 return TRUE;
115 public function root_database_connection()
117 $this->dbh = $this->connect_to_database( $this->server, $this->root, $this->rootpass, $this->port );
118 if ( $this->dbh ) {
119 if (! $this->set_sql_strict()) {
120 $this->error_message = 'unable to set strict sql setting';
121 return FALSE;
123 return TRUE;
124 } else {
125 $this->error_message = 'unable to connect to database as root';
126 return FALSE;
130 public function user_database_connection()
132 $this->dbh = $this->connect_to_database( $this->server, $this->login, $this->pass, $this->port, $this->dbname );
133 if ( ! $this->dbh ) {
134 $this->error_message = "unable to connect to database as user: '$this->login'";
135 return FALSE;
137 if ( ! $this->set_sql_strict() ) {
138 $this->error_message = 'unable to set strict sql setting';
139 return FALSE;
141 if ( ! $this->set_collation() ) {
142 $this->error_message = 'unable to set sql collation';
143 return FALSE;
145 if ( ! mysqli_select_db($this->dbh, $this->dbname) ) {
146 $this->error_message = "unable to select database: '$this->dbname'";
147 return FALSE;
149 return TRUE;
152 public function create_database() {
153 $sql = "create database $this->dbname";
154 if ($this->collate) {
155 $sql .= " character set utf8 collate $this->collate";
156 $this->set_collation();
158 return $this->execute_sql($sql);
161 public function drop_database() {
162 $sql = "drop database if exists $this->dbname";
163 return $this->execute_sql($sql);
166 public function grant_privileges() {
167 return $this->execute_sql( "GRANT ALL PRIVILEGES ON $this->dbname.* TO '$this->login'@'$this->loginhost' IDENTIFIED BY '$this->pass'" );
170 public function disconnect() {
171 return mysqli_close($this->dbh);
175 * This method creates any dumpfiles necessary.
176 * This is actually only done if we're cloning an existing site
177 * and we need to dump their database into a file.
178 * @return bool indicating success
180 public function create_dumpfiles() {
181 return $this->dumpSourceDatabase();
184 public function load_dumpfiles() {
185 $sql_results = ''; // information string which is returned
186 foreach ($this->dumpfiles as $filename => $title) {
187 $sql_results_temp = '';
188 $sql_results_temp = $this->load_file($filename,$title);
189 if ($sql_results_temp == FALSE) return FALSE;
190 $sql_results .= $sql_results_temp;
192 return $sql_results;
195 public function load_file($filename,$title) {
196 $sql_results = ''; // information string which is returned
197 $sql_results .= "Creating $title tables...\n";
198 $fd = fopen($filename, 'r');
199 if ($fd == FALSE) {
200 $this->error_message = "ERROR. Could not open dumpfile '$filename'.\n";
201 return FALSE;
203 $query = "";
204 $line = "";
206 // Settings to drastically speed up installation with InnoDB
207 if ( ! $this->execute_sql("SET autocommit=0;") ){
208 return FALSE;
210 if ( ! $this->execute_sql("START TRANSACTION;") ){
211 return FALSE;
214 while (!feof ($fd)){
215 $line = fgets($fd,1024);
216 $line = rtrim($line);
217 if (substr($line,0,2) == "--") // Kill comments
218 continue;
219 if (substr($line,0,1) == "#") // Kill comments
220 continue;
221 if ($line == "")
222 continue;
223 $query = $query.$line; // Check for full query
224 $chr = substr($query,strlen($query)-1,1);
225 if ($chr == ";") { // valid query, execute
226 $query = rtrim($query,";");
227 if ( ! $this->execute_sql( $query ) ){
228 return FALSE;
230 $query = "";
234 // Settings to drastically speed up installation with InnoDB
235 if ( ! $this->execute_sql("COMMIT;") ){
236 return FALSE;
238 if ( ! $this->execute_sql("SET autocommit=1;") ){
239 return FALSE;
242 $sql_results .= "OK<br>\n";
243 fclose($fd);
244 return $sql_results;
247 public function add_version_info() {
248 include dirname(__FILE__) . "/../../version.php";
249 if ($this->execute_sql("UPDATE version SET v_major = '$v_major', v_minor = '$v_minor', v_patch = '$v_patch', v_realpatch = '$v_realpatch', v_tag = '$v_tag', v_database = '$v_database', v_acl = '$v_acl'") == FALSE) {
250 $this->error_message = "ERROR. Unable insert version information into database\n" .
251 "<p>".mysqli_error($this->dbh)." (#".mysqli_errno($this->dbh).")\n";
252 return FALSE;
254 return TRUE;
257 public function add_initial_user() {
258 if ($this->execute_sql("INSERT INTO groups (id, name, user) VALUES (1,'$this->igroup','$this->iuser')") == FALSE) {
259 $this->error_message = "ERROR. Unable to add initial user group\n" .
260 "<p>".mysqli_error($this->dbh)." (#".mysqli_errno($this->dbh).")\n";
261 return FALSE;
263 $password_hash = "NoLongerUsed"; // This is the value to insert into the password column in the "users" table. password details are now being stored in users_secure instead.
264 $salt=oemr_password_salt(); // Uses the functions defined in library/authentication/password_hashing.php
265 $hash=oemr_password_hash($this->iuserpass,$salt);
266 if ($this->execute_sql("INSERT INTO users (id, username, password, authorized, lname, fname, facility_id, calendar, cal_ui) VALUES (1,'$this->iuser','$password_hash',1,'$this->iuname','$this->iufname',3,1,3)") == FALSE) {
267 $this->error_message = "ERROR. Unable to add initial user\n" .
268 "<p>".mysqli_error($this->dbh)." (#".mysqli_errno($this->dbh).")\n";
269 return FALSE;
273 // Create the new style login credentials with blowfish and salt
274 if ($this->execute_sql("INSERT INTO users_secure (id, username, password, salt) VALUES (1,'$this->iuser','$hash','$salt')") == FALSE) {
275 $this->error_message = "ERROR. Unable to add initial user login credentials\n" .
276 "<p>".mysqli_error($this->dbh)." (#".mysqli_errno($this->dbh).")\n";
277 return FALSE;
279 // Add the official openemr users (services)
280 if ($this->load_file($this->additional_users,"Additional Official Users") == FALSE) return FALSE;
282 return TRUE;
286 * Create site directory if it is missing.
287 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
288 * @return name of the site directory or False
290 public function create_site_directory() {
291 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
292 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id;
293 $destination_directory = $GLOBALS['OE_SITE_DIR'];
294 if ( ! $this->recurse_copy( $source_directory, $destination_directory ) ) {
295 $this->error_message = "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message;
296 return False;
299 return True;
302 public function write_configuration_file() {
303 @touch($this->conffile); // php bug
304 $fd = @fopen($this->conffile, 'w');
305 if ( ! $fd ) {
306 $this->error_message = 'unable to open configuration file for writing: ' . $this->conffile;
307 return False;
309 $string = '<?php
310 // OpenEMR
311 // MySQL Config
315 $it_died = 0; //fmg: variable keeps running track of any errors
317 fwrite($fd,$string) or $it_died++;
318 fwrite($fd,"\$host\t= '$this->server';\n") or $it_died++;
319 fwrite($fd,"\$port\t= '$this->port';\n") or $it_died++;
320 fwrite($fd,"\$login\t= '$this->login';\n") or $it_died++;
321 fwrite($fd,"\$pass\t= '$this->pass';\n") or $it_died++;
322 fwrite($fd,"\$dbase\t= '$this->dbname';\n\n") or $it_died++;
323 fwrite($fd,"//Added ability to disable\n") or $it_died++;
324 fwrite($fd,"//utf8 encoding - bm 05-2009\n") or $it_died++;
325 fwrite($fd,"global \$disable_utf8_flag;\n") or $it_died++;
326 fwrite($fd,"\$disable_utf8_flag = false;\n") or $it_died++;
328 $string = '
329 $sqlconf = array();
330 global $sqlconf;
331 $sqlconf["host"]= $host;
332 $sqlconf["port"] = $port;
333 $sqlconf["login"] = $login;
334 $sqlconf["pass"] = $pass;
335 $sqlconf["dbase"] = $dbase;
336 //////////////////////////
337 //////////////////////////
338 //////////////////////////
339 //////DO NOT TOUCH THIS///
340 $config = 1; /////////////
341 //////////////////////////
342 //////////////////////////
343 //////////////////////////
346 ?><?php // done just for coloring
348 fwrite($fd,$string) or $it_died++;
349 fclose($fd) or $it_died++;
351 //it's rather irresponsible to not report errors when writing this file.
352 if ($it_died != 0) {
353 $this->error_message = "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
354 return FALSE;
357 return TRUE;
360 public function insert_globals() {
361 function xl($s) { return $s; }
362 require(dirname(__FILE__) . '/../globals.inc.php');
363 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
364 foreach ($grparr as $fldid => $fldarr) {
365 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
366 if (is_array($fldtype) || substr($fldtype, 0, 2) !== 'm_') {
367 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '$fldid'");
368 $row = mysqli_fetch_array($res, MYSQLI_ASSOC);
369 if (empty($row['count'])) {
370 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
371 "VALUES ( '$fldid', '0', '$flddef' )");
376 return TRUE;
379 public function install_gacl()
381 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1);
382 if (! $install_results_1 ) {
383 $this->error_message = "install_gacl failed: unable to require gacl script 1";
384 return FALSE;
387 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2);
388 if (! $install_results_2 ) {
389 $this->error_message = "install_gacl failed: unable to require gacl script 2";
390 return FALSE;
392 $this->debug_message .= $install_results_1 . $install_results_2;
393 return TRUE;
396 public function quick_install() {
397 // Validation of OpenEMR user settings
398 // (applicable if not cloning from another database)
399 if (empty($this->clone_database)) {
400 if ( ! $this->login_is_valid() ) {
401 return False;
403 if ( ! $this->iuser_is_valid() ) {
404 return False;
406 if ( ! $this->user_password_is_valid() ) {
407 return False;
410 // Validation of mysql database password
411 if ( ! $this->password_is_valid() ) {
412 return False;
414 if (! $this->no_root_db_access) {
415 // Connect to mysql via root user
416 if (! $this->root_database_connection() ) {
417 return False;
419 // Create the dumpfile
420 // (applicable if cloning from another database)
421 if (! empty($this->clone_database)) {
422 if ( ! $this->create_dumpfiles() ) {
423 return False;
426 // Create the site directory
427 // (applicable if mirroring another local site)
428 if ( ! empty($this->source_site_id) ) {
429 if ( ! $this->create_site_directory() ) {
430 return False;
433 $this->disconnect();
434 if (! $this->user_database_connection()) {
435 // Re-connect to mysql via root user
436 if (! $this->root_database_connection() ) {
437 return False;
439 // Create the mysql database
440 if ( ! $this->create_database()) {
441 return False;
443 // Grant user privileges to the mysql database
444 if ( ! $this->grant_privileges() ) {
445 return False;
448 $this->disconnect();
450 // Connect to mysql via created user
451 if ( ! $this->user_database_connection() ) {
452 return False;
454 // Build the database
455 if ( ! $this->load_dumpfiles() ) {
456 return False;
458 // Write the sql configuration file
459 if ( ! $this->write_configuration_file() ) {
460 return False;
462 // Load the version information, globals settings,
463 // initial user, and set up gacl access controls.
464 // (applicable if not cloning from another database)
465 if (empty($this->clone_database)) {
466 if ( ! $this->add_version_info() ) {
467 return False;
469 if ( ! $this->insert_globals() ) {
470 return False;
472 if ( ! $this->add_initial_user() ) {
473 return False;
475 if ( ! $this->install_gacl()) {
476 return False;
480 return True;
483 private function execute_sql( $sql ) {
484 $this->error_message = '';
485 if ( ! $this->dbh ) {
486 $this->user_database_connection();
488 $results = mysqli_query($this->dbh, $sql);
489 if ( $results ) {
490 return $results;
491 } else {
492 $error_mes = mysqli_error($this->dbh);
493 $this->error_message = "unable to execute SQL: '$sql' due to: " . $error_mes;
494 error_log("ERROR IN OPENEMR INSTALL: Unable to execute SQL: ".$sql." due to: ".$error_mes);
495 return False;
499 private function connect_to_database( $server, $user, $password, $port, $dbname='' )
501 if ($server == "localhost")
502 $dbh = mysqli_connect($server, $user, $password, $dbname);
503 else
504 $dbh = mysqli_connect($server, $user, $password, $dbname, $port);
505 return $dbh;
508 private function set_sql_strict()
510 // Turn off STRICT SQL
511 return $this->execute_sql("SET sql_mode = ''");
514 private function set_collation()
516 if ($this->collate) {
517 return $this->execute_sql("SET NAMES 'utf8'");
519 return TRUE;
523 * innitialize $this->dumpfiles, an array of the dumpfiles that will
524 * be loaded into the database, including the correct translation
525 * dumpfile.
526 * The keys are the paths of the dumpfiles, and the values are the titles
527 * @return array
529 private function initialize_dumpfile_list() {
530 if ( $this->clone_database ) {
531 $this->dumpfiles = array( $this->get_backup_filename() => 'clone database' );
532 } else {
533 $dumpfiles = array( $this->main_sql => 'Main' );
534 if (! empty($this->development_translations)) {
535 // Use the online development translation set
536 $dumpfiles[ $this->devel_translation_sql ] = "Online Development Language Translations (utf8)";
538 else {
539 // Use the local translation set
540 $dumpfiles[ $this->translation_sql ] = "Language Translation (utf8)";
542 if ($this->ippf_specific) {
543 $dumpfiles[ $this->ippf_sql ] = "IPPF Layout";
545 // Load ICD-9 codes if present.
546 if (file_exists( $this->icd9 )) {
547 $dumpfiles[ $this->icd9 ] = "ICD-9";
549 // Load CVX codes if present
550 if (file_exists( $this->cvx )) {
551 $dumpfiles[ $this->cvx ] = "CVX Immunization Codes";
553 $this->dumpfiles = $dumpfiles;
555 return $this->dumpfiles;
558 // http://www.php.net/manual/en/function.include.php
559 private function get_require_contents($filename) {
560 if (is_file($filename)) {
561 ob_start();
562 require $filename;
563 $contents = ob_get_contents();
564 ob_end_clean();
565 return $contents;
567 return false;
572 * Directory copy logic borrowed from a user comment at
573 * http://www.php.net/manual/en/function.copy.php
574 * @param string $src name of the directory to copy
575 * @param string $dst name of the destination to copy to
576 * @return bool indicating success
578 private function recurse_copy($src, $dst) {
579 $dir = opendir($src);
580 if ( ! @mkdir($dst) ) {
581 $this->error_message = "unable to create directory: '$dst'";
582 return False;
584 while(false !== ($file = readdir($dir))) {
585 if ($file != '.' && $file != '..') {
586 if (is_dir($src . '/' . $file)) {
587 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
589 else {
590 copy($src . '/' . $file, $dst . '/' . $file);
594 closedir($dir);
595 return True;
600 * dump a site's database to a temporary file.
601 * @param string $source_site_id the site_id of the site to dump
602 * @return filename of the backup
604 private function dumpSourceDatabase() {
605 global $OE_SITES_BASE;
606 $source_site_id = $this->source_site_id;
608 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
610 if (empty($config)) die("Source site $source_site_id has not been set up!");
612 $backup_file = $this->get_backup_filename();
613 $cmd = "mysqldump -u " . escapeshellarg($login) .
614 " -p" . escapeshellarg($pass) .
615 " --opt --skip-extended-insert --quote-names -r $backup_file " .
616 escapeshellarg($dbase);
618 $tmp0 = exec($cmd, $tmp1=array(), $tmp2);
619 if ($tmp2) die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
621 return $backup_file;
625 * @return filename of the source backup database for cloning
627 private function get_backup_filename() {
628 if (stristr(PHP_OS, 'WIN')) {
629 $backup_file = 'C:/windows/temp/setup_dump.sql';
631 else {
632 $backup_file = '/tmp/setup_dump.sql';
634 return $backup_file;
640 This file is free software: you can redistribute it and/or modify it under the
641 terms of the GNU General Public License as publish by the Free Software
642 Foundation.
644 This file is distributed in the hope that it will be useful, but WITHOUT ANY
645 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
646 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
648 You should have received a copy of the GNU General Public Licence along with
649 this file. If not see <http://www.gnu.org/licenses/>.