rename password functions for PHP 5.5 compatibility
[openemr.git] / library / classes / Installer.class.php
blobd255473aaed5f4114e2dfa0f46844cba98d68f95
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 = $cgi_variables['iuser'];
14 $this->iuserpass = $cgi_variables['iuserpass'];
15 $this->iuname = $cgi_variables['iuname'];
16 $this->iufname = $cgi_variables['iufname'];
17 $this->igroup = $cgi_variables['igroup'];
18 $this->server = $cgi_variables['server']; // mysql server (usually localhost)
19 $this->loginhost = $cgi_variables['loginhost']; // php/apache server (usually localhost)
20 $this->port = $cgi_variables['port'];
21 $this->root = $cgi_variables['root'];
22 $this->rootpass = $cgi_variables['rootpass'];
23 $this->login = $cgi_variables['login'];
24 $this->pass = $cgi_variables['pass'];
25 $this->dbname = $cgi_variables['dbname'];
26 $this->collate = $cgi_variables['collate'];
27 $this->site = $cgi_variables['site'];
28 $this->source_site_id = $cgi_variables['source_site_id'];
29 $this->clone_database = $cgi_variables['clone_database'];
30 $this->no_root_db_access = $cgi_variables['no_root_db_access']; // no root access to database. user/privileges pre-configured
31 $this->development_translations = $cgi_variables['development_translations'];
33 // Make this true for IPPF.
34 $this->ippf_specific = false;
36 // Record name of sql access file
37 $GLOBALS['OE_SITES_BASE'] = dirname(__FILE__) . '/../../sites';
38 $GLOBALS['OE_SITE_DIR'] = $GLOBALS['OE_SITES_BASE'] . '/' . $this->site;
39 $this->conffile = $GLOBALS['OE_SITE_DIR'] . '/sqlconf.php';
41 // Record names of sql table files
42 $this->main_sql = dirname(__FILE__) . '/../../sql/database.sql';
43 $this->translation_sql = dirname(__FILE__) . '/../../contrib/util/language_translations/currentLanguage_utf8.sql';
44 $this->devel_translation_sql = "http://github.com/openemr/translations_development_openemr/raw/master/languageTranslations_utf8.sql";
45 $this->ippf_sql = dirname(__FILE__) . "/../../sql/ippf_layout.sql";
46 $this->icd9 = dirname(__FILE__) . "/../../sql/icd9.sql";
47 $this->cvx = dirname(__FILE__) . "/../../sql/cvx_codes.sql";
48 $this->additional_users = dirname(__FILE__) . "/../../sql/official_additional_users.sql";
50 // Record name of php-gacl installation files
51 $this->gaclSetupScript1 = dirname(__FILE__) . "/../../gacl/setup.php";
52 $this->gaclSetupScript2 = dirname(__FILE__) . "/../../acl_setup.php";
54 // Prepare the dumpfile list
55 $this->initialize_dumpfile_list();
57 // Entities to hold error and debug messages
58 $this->error_message = '';
59 $this->debug_message = '';
61 // Entity to hold sql connection
62 $this->dbh = false;
65 public function login_is_valid()
67 if ( ($this->login == '') || (! isset( $this->login )) ) {
68 $this->error_message = "login is invalid: '$this->login'";
69 return FALSE;
71 return TRUE;
74 public function iuser_is_valid()
76 if ( strpos($this->iuser, " ") ) {
77 $this->error_message = "Initial user is invalid: '$this->iuser'";
78 return FALSE;
80 return TRUE;
83 public function password_is_valid()
85 if ( $this->pass == "" || !isset($this->pass) ) {
86 $this->error_message = "The password for the new database account is invalid: '$this->pass'";
87 return FALSE;
89 return TRUE;
92 public function user_password_is_valid()
94 if ( $this->iuserpass == "" || !isset($this->iuserpass) ) {
95 $this->error_message = "The password for the user is invalid: '$this->iuserpass'";
96 return FALSE;
98 return TRUE;
101 public function root_database_connection()
103 $this->dbh = $this->connect_to_database( $this->server, $this->root, $this->rootpass, $this->port );
104 if ( $this->dbh ) {
105 return TRUE;
106 } else {
107 $this->error_message = 'unable to connect to database as root';
108 return FALSE;
112 public function user_database_connection()
114 $this->dbh = $this->connect_to_database( $this->server, $this->login, $this->pass, $this->port );
115 if ( ! $this->dbh ) {
116 $this->error_message = "unable to connect to database as user: '$this->login'";
117 return FALSE;
119 if ( ! $this->set_collation() ) {
120 return FALSE;
122 if ( ! mysql_select_db($this->dbname, $this->dbh) ) {
123 $this->error_message = "unable to select database: '$this->dbname'";
124 return FALSE;
126 return TRUE;
129 public function create_database() {
130 $sql = "create database $this->dbname";
131 if ($this->collate) {
132 $sql .= " character set utf8 collate $this->collate";
133 $this->set_collation();
135 return $this->execute_sql($sql);
138 public function drop_database() {
139 $sql = "drop database if exists $this->dbname";
140 return $this->execute_sql($sql);
143 public function grant_privileges() {
144 return $this->execute_sql( "GRANT ALL PRIVILEGES ON $this->dbname.* TO '$this->login'@'$this->loginhost' IDENTIFIED BY '$this->pass'" );
147 public function disconnect() {
148 return mysql_close($this->dbh);
152 * This method creates any dumpfiles necessary.
153 * This is actually only done if we're cloning an existing site
154 * and we need to dump their database into a file.
155 * @return bool indicating success
157 public function create_dumpfiles() {
158 return $this->dumpSourceDatabase();
161 public function load_dumpfiles() {
162 $sql_results = ''; // information string which is returned
163 foreach ($this->dumpfiles as $filename => $title) {
164 $sql_results_temp = '';
165 $sql_results_temp = $this->load_file($filename,$title);
166 if ($sql_results_temp == FALSE) return FALSE;
167 $sql_results .= $sql_results_temp;
169 return $sql_results;
172 public function load_file($filename,$title) {
173 $sql_results = ''; // information string which is returned
174 $sql_results .= "Creating $title tables...\n";
175 $fd = fopen($filename, 'r');
176 if ($fd == FALSE) {
177 $this->error_message = "ERROR. Could not open dumpfile '$filename'.\n";
178 return FALSE;
180 $query = "";
181 $line = "";
182 while (!feof ($fd)){
183 $line = fgets($fd,1024);
184 $line = rtrim($line);
185 if (substr($line,0,2) == "--") // Kill comments
186 continue;
187 if (substr($line,0,1) == "#") // Kill comments
188 continue;
189 if ($line == "")
190 continue;
191 $query = $query.$line; // Check for full query
192 $chr = substr($query,strlen($query)-1,1);
193 if ($chr == ";") { // valid query, execute
194 $query = rtrim($query,";");
195 $this->execute_sql( $query );
196 $query = "";
199 $sql_results .= "OK<br>\n";
200 fclose($fd);
201 return $sql_results;
204 public function add_version_info() {
205 include dirname(__FILE__) . "/../../version.php";
206 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) {
207 $this->error_message = "ERROR. Unable insert version information into database\n" .
208 "<p>".mysql_error()." (#".mysql_errno().")\n";
209 return FALSE;
211 return TRUE;
214 public function add_initial_user() {
215 if ($this->execute_sql("INSERT INTO groups (id, name, user) VALUES (1,'$this->igroup','$this->iuser')") == FALSE) {
216 $this->error_message = "ERROR. Unable to add initial user group\n" .
217 "<p>".mysql_error()." (#".mysql_errno().")\n";
218 return FALSE;
220 $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.
221 $salt=oemr_password_salt(); // Uses the functions defined in library/authentication/password_hashing.php
222 $hash=oemr_password_hash($this->iuserpass,$salt);
223 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) {
224 $this->error_message = "ERROR. Unable to add initial user\n" .
225 "<p>".mysql_error()." (#".mysql_errno().")\n";
226 return FALSE;
230 // Create the new style login credentials with blowfish and salt
231 if ($this->execute_sql("INSERT INTO users_secure (id, username, password, salt) VALUES (1,'$this->iuser','$hash','$salt')") == FALSE) {
232 $this->error_message = "ERROR. Unable to add initial user login credentials\n" .
233 "<p>".mysql_error()." (#".mysql_errno().")\n";
234 return FALSE;
236 // Add the official openemr users (services)
237 if ($this->load_file($this->additional_users,"Additional Official Users") == FALSE) return FALSE;
239 return TRUE;
243 * Create site directory if it is missing.
244 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
245 * @return name of the site directory or False
247 public function create_site_directory() {
248 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
249 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id;
250 $destination_directory = $GLOBALS['OE_SITE_DIR'];
251 if ( ! $this->recurse_copy( $source_directory, $destination_directory ) ) {
252 $this->error_message = "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message;
253 return False;
256 return True;
259 public function write_configuration_file() {
260 @touch($this->conffile); // php bug
261 $fd = @fopen($this->conffile, 'w');
262 if ( ! $fd ) {
263 $this->error_message = 'unable to open configuration file for writing: ' . $this->conffile;
264 return False;
266 $string = '<?php
267 // OpenEMR
268 // MySQL Config
272 $it_died = 0; //fmg: variable keeps running track of any errors
274 fwrite($fd,$string) or $it_died++;
275 fwrite($fd,"\$host\t= '$this->server';\n") or $it_died++;
276 fwrite($fd,"\$port\t= '$this->port';\n") or $it_died++;
277 fwrite($fd,"\$login\t= '$this->login';\n") or $it_died++;
278 fwrite($fd,"\$pass\t= '$this->pass';\n") or $it_died++;
279 fwrite($fd,"\$dbase\t= '$this->dbname';\n\n") or $it_died++;
280 fwrite($fd,"//Added ability to disable\n") or $it_died++;
281 fwrite($fd,"//utf8 encoding - bm 05-2009\n") or $it_died++;
282 fwrite($fd,"global \$disable_utf8_flag;\n") or $it_died++;
283 fwrite($fd,"\$disable_utf8_flag = false;\n") or $it_died++;
285 $string = '
286 $sqlconf = array();
287 global $sqlconf;
288 $sqlconf["host"]= $host;
289 $sqlconf["port"] = $port;
290 $sqlconf["login"] = $login;
291 $sqlconf["pass"] = $pass;
292 $sqlconf["dbase"] = $dbase;
293 //////////////////////////
294 //////////////////////////
295 //////////////////////////
296 //////DO NOT TOUCH THIS///
297 $config = 1; /////////////
298 //////////////////////////
299 //////////////////////////
300 //////////////////////////
303 ?><?php // done just for coloring
305 fwrite($fd,$string) or $it_died++;
306 fclose($fd) or $it_died++;
308 //it's rather irresponsible to not report errors when writing this file.
309 if ($it_died != 0) {
310 $this->error_message = "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
311 return FALSE;
314 return TRUE;
317 public function insert_globals() {
318 function xl($s) { return $s; }
319 require(dirname(__FILE__) . '/../globals.inc.php');
320 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
321 foreach ($grparr as $fldid => $fldarr) {
322 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
323 if (is_array($fldtype) || substr($fldtype, 0, 2) !== 'm_') {
324 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '$fldid'");
325 $row = @mysql_fetch_array($res, MYSQL_ASSOC);
326 if (empty($row['count'])) {
327 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
328 "VALUES ( '$fldid', '0', '$flddef' )");
333 return TRUE;
336 public function install_gacl()
338 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1);
339 if (! $install_results_1 ) {
340 $this->error_message = "install_gacl failed: unable to require gacl script 1";
341 return FALSE;
344 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2);
345 if (! $install_results_2 ) {
346 $this->error_message = "install_gacl failed: unable to require gacl script 2";
347 return FALSE;
349 $this->debug_message .= $install_results_1 . $install_results_2;
350 return TRUE;
353 public function quick_install() {
354 // Validation of OpenEMR user settings
355 // (applicable if not cloning from another database)
356 if (empty($this->clone_database)) {
357 if ( ! $this->login_is_valid() ) {
358 return False;
360 if ( ! $this->iuser_is_valid() ) {
361 return False;
363 if ( ! $this->user_password_is_valid() ) {
364 return False;
367 // Validation of mysql database password
368 if ( ! $this->password_is_valid() ) {
369 return False;
371 if (! $this->no_root_db_access) {
372 // Connect to mysql via root user
373 if (! $this->root_database_connection() ) {
374 return False;
376 // Create the dumpfile
377 // (applicable if cloning from another database)
378 if (! empty($this->clone_database)) {
379 if ( ! $this->create_dumpfiles() ) {
380 return False;
383 // Create the site directory
384 // (applicable if mirroring another local site)
385 if ( ! empty($this->source_site_id) ) {
386 if ( ! $this->create_site_directory() ) {
387 return False;
390 $this->disconnect();
391 if (! $this->user_database_connection()) {
392 // Re-connect to mysql via root user
393 if (! $this->root_database_connection() ) {
394 return False;
396 // Create the mysql database
397 if ( ! $this->create_database()) {
398 return False;
400 // Grant user privileges to the mysql database
401 if ( ! $this->grant_privileges() ) {
402 return False;
405 $this->disconnect();
407 // Connect to mysql via created user
408 if ( ! $this->user_database_connection() ) {
409 return False;
411 // Build the database
412 if ( ! $this->load_dumpfiles() ) {
413 return False;
415 // Write the sql configuration file
416 if ( ! $this->write_configuration_file() ) {
417 return False;
419 // Load the version information, globals settings,
420 // initial user, and set up gacl access controls.
421 // (applicable if not cloning from another database)
422 if (empty($this->clone_database)) {
423 if ( ! $this->add_version_info() ) {
424 return False;
426 if ( ! $this->insert_globals() ) {
427 return False;
429 if ( ! $this->add_initial_user() ) {
430 return False;
432 if ( ! $this->install_gacl()) {
433 return False;
437 return True;
440 private function execute_sql( $sql ) {
441 $this->error_message = '';
442 if ( ! $this->dbh ) {
443 $this->user_database_connection();
445 $results = mysql_query($sql, $this->dbh);
446 if ( $results ) {
447 return $results;
448 } else {
449 $this->error_message = "unable to execute SQL: '$sql' due to: " . mysql_error();
450 return False;
454 private function connect_to_database( $server, $user, $password, $port )
456 if ($server == "localhost")
457 $dbh = mysql_connect($server, $user, $password);
458 else
459 $dbh = mysql_connect("$server:$port", $user, $password);
460 return $dbh;
463 private function set_collation()
465 if ($this->collate) {
466 return $this->execute_sql("SET NAMES 'utf8'");
468 return TRUE;
472 * innitialize $this->dumpfiles, an array of the dumpfiles that will
473 * be loaded into the database, including the correct translation
474 * dumpfile.
475 * The keys are the paths of the dumpfiles, and the values are the titles
476 * @return array
478 private function initialize_dumpfile_list() {
479 if ( $this->clone_database ) {
480 $this->dumpfiles = array( $this->get_backup_filename() => 'clone database' );
481 } else {
482 $dumpfiles = array( $this->main_sql => 'Main' );
483 if (! empty($this->development_translations)) {
484 // Use the online development translation set
485 $dumpfiles[ $this->devel_translation_sql ] = "Online Development Language Translations (utf8)";
487 else {
488 // Use the local translation set
489 $dumpfiles[ $this->translation_sql ] = "Language Translation (utf8)";
491 if ($this->ippf_specific) {
492 $dumpfiles[ $this->ippf_sql ] = "IPPF Layout";
494 // Load ICD-9 codes if present.
495 if (file_exists( $this->icd9 )) {
496 $dumpfiles[ $this->icd9 ] = "ICD-9";
498 // Load CVX codes if present
499 if (file_exists( $this->cvx )) {
500 $dumpfiles[ $this->cvx ] = "CVX Immunization Codes";
502 $this->dumpfiles = $dumpfiles;
504 return $this->dumpfiles;
507 // http://www.php.net/manual/en/function.include.php
508 private function get_require_contents($filename) {
509 if (is_file($filename)) {
510 ob_start();
511 require $filename;
512 $contents = ob_get_contents();
513 ob_end_clean();
514 return $contents;
516 return false;
521 * Directory copy logic borrowed from a user comment at
522 * http://www.php.net/manual/en/function.copy.php
523 * @param string $src name of the directory to copy
524 * @param string $dst name of the destination to copy to
525 * @return bool indicating success
527 private function recurse_copy($src, $dst) {
528 $dir = opendir($src);
529 if ( ! @mkdir($dst) ) {
530 $this->error_message = "unable to create directory: '$dst'";
531 return False;
533 while(false !== ($file = readdir($dir))) {
534 if ($file != '.' && $file != '..') {
535 if (is_dir($src . '/' . $file)) {
536 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
538 else {
539 copy($src . '/' . $file, $dst . '/' . $file);
543 closedir($dir);
544 return True;
549 * dump a site's database to a temporary file.
550 * @param string $source_site_id the site_id of the site to dump
551 * @return filename of the backup
553 private function dumpSourceDatabase() {
554 global $OE_SITES_BASE;
555 $source_site_id = $this->source_site_id;
557 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
559 if (empty($config)) die("Source site $source_site_id has not been set up!");
561 $backup_file = $this->get_backup_filename();
562 $cmd = "mysqldump -u " . escapeshellarg($login) .
563 " -p" . escapeshellarg($pass) .
564 " --opt --skip-extended-insert --quote-names -r $backup_file " .
565 escapeshellarg($dbase);
567 $tmp0 = exec($cmd, $tmp1=array(), $tmp2);
568 if ($tmp2) die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
570 return $backup_file;
574 * @return filename of the source backup database for cloning
576 private function get_backup_filename() {
577 if (stristr(PHP_OS, 'WIN')) {
578 $backup_file = 'C:/windows/temp/setup_dump.sql';
580 else {
581 $backup_file = '/tmp/setup_dump.sql';
583 return $backup_file;
589 This file is free software: you can redistribute it and/or modify it under the
590 terms of the GNU General Public License as publish by the Free Software
591 Foundation.
593 This file is distributed in the hope that it will be useful, but WITHOUT ANY
594 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
595 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
597 You should have received a copy of the GNU General Public Licence along with
598 this file. If not see <http://www.gnu.org/licenses/>.