minor improvement to tabs style
[openemr.git] / library / classes / Installer.class.php
blob729d9679e7a090ed757a9910b28ca28494a4cc10
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 // Please note that the plain sql is used over the Doctrine ORM for
248 // `version` table interactions because it cannot connect due to a
249 // lack of context (this code is ran outside of the OpenEMR context).
250 public function add_version_info() {
251 include dirname(__FILE__) . "/../../version.php";
252 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) {
253 $this->error_message = "ERROR. Unable insert version information into database\n" .
254 "<p>".mysqli_error($this->dbh)." (#".mysqli_errno($this->dbh).")\n";
255 return FALSE;
257 return TRUE;
260 public function add_initial_user() {
261 if ($this->execute_sql("INSERT INTO groups (id, name, user) VALUES (1,'$this->igroup','$this->iuser')") == FALSE) {
262 $this->error_message = "ERROR. Unable to add initial user group\n" .
263 "<p>".mysqli_error($this->dbh)." (#".mysqli_errno($this->dbh).")\n";
264 return FALSE;
266 $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.
267 $salt=oemr_password_salt(); // Uses the functions defined in library/authentication/password_hashing.php
268 $hash=oemr_password_hash($this->iuserpass,$salt);
269 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) {
270 $this->error_message = "ERROR. Unable to add initial user\n" .
271 "<p>".mysqli_error($this->dbh)." (#".mysqli_errno($this->dbh).")\n";
272 return FALSE;
276 // Create the new style login credentials with blowfish and salt
277 if ($this->execute_sql("INSERT INTO users_secure (id, username, password, salt) VALUES (1,'$this->iuser','$hash','$salt')") == FALSE) {
278 $this->error_message = "ERROR. Unable to add initial user login credentials\n" .
279 "<p>".mysqli_error($this->dbh)." (#".mysqli_errno($this->dbh).")\n";
280 return FALSE;
282 // Add the official openemr users (services)
283 if ($this->load_file($this->additional_users,"Additional Official Users") == FALSE) return FALSE;
285 return TRUE;
289 * Create site directory if it is missing.
290 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
291 * @return name of the site directory or False
293 public function create_site_directory() {
294 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
295 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id;
296 $destination_directory = $GLOBALS['OE_SITE_DIR'];
297 if ( ! $this->recurse_copy( $source_directory, $destination_directory ) ) {
298 $this->error_message = "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message;
299 return False;
302 return True;
305 public function write_configuration_file() {
306 @touch($this->conffile); // php bug
307 $fd = @fopen($this->conffile, 'w');
308 if ( ! $fd ) {
309 $this->error_message = 'unable to open configuration file for writing: ' . $this->conffile;
310 return False;
312 $string = '<?php
313 // OpenEMR
314 // MySQL Config
318 $it_died = 0; //fmg: variable keeps running track of any errors
320 fwrite($fd,$string) or $it_died++;
321 fwrite($fd,"\$host\t= '$this->server';\n") or $it_died++;
322 fwrite($fd,"\$port\t= '$this->port';\n") or $it_died++;
323 fwrite($fd,"\$login\t= '$this->login';\n") or $it_died++;
324 fwrite($fd,"\$pass\t= '$this->pass';\n") or $it_died++;
325 fwrite($fd,"\$dbase\t= '$this->dbname';\n\n") or $it_died++;
326 fwrite($fd,"//Added ability to disable\n") or $it_died++;
327 fwrite($fd,"//utf8 encoding - bm 05-2009\n") or $it_died++;
328 fwrite($fd,"global \$disable_utf8_flag;\n") or $it_died++;
329 fwrite($fd,"\$disable_utf8_flag = false;\n") or $it_died++;
331 $string = '
332 $sqlconf = array();
333 global $sqlconf;
334 $sqlconf["host"]= $host;
335 $sqlconf["port"] = $port;
336 $sqlconf["login"] = $login;
337 $sqlconf["pass"] = $pass;
338 $sqlconf["dbase"] = $dbase;
339 //////////////////////////
340 //////////////////////////
341 //////////////////////////
342 //////DO NOT TOUCH THIS///
343 $config = 1; /////////////
344 //////////////////////////
345 //////////////////////////
346 //////////////////////////
349 ?><?php // done just for coloring
351 fwrite($fd,$string) or $it_died++;
352 fclose($fd) or $it_died++;
354 //it's rather irresponsible to not report errors when writing this file.
355 if ($it_died != 0) {
356 $this->error_message = "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
357 return FALSE;
360 return TRUE;
363 public function insert_globals() {
364 function xl($s) { return $s; }
365 require(dirname(__FILE__) . '/../globals.inc.php');
366 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
367 foreach ($grparr as $fldid => $fldarr) {
368 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
369 if (is_array($fldtype) || substr($fldtype, 0, 2) !== 'm_') {
370 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '$fldid'");
371 $row = mysqli_fetch_array($res, MYSQLI_ASSOC);
372 if (empty($row['count'])) {
373 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
374 "VALUES ( '$fldid', '0', '$flddef' )");
379 return TRUE;
382 public function install_gacl()
384 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1);
385 if (! $install_results_1 ) {
386 $this->error_message = "install_gacl failed: unable to require gacl script 1";
387 return FALSE;
390 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2);
391 if (! $install_results_2 ) {
392 $this->error_message = "install_gacl failed: unable to require gacl script 2";
393 return FALSE;
395 $this->debug_message .= $install_results_1 . $install_results_2;
396 return TRUE;
399 public function quick_install() {
400 // Validation of OpenEMR user settings
401 // (applicable if not cloning from another database)
402 if (empty($this->clone_database)) {
403 if ( ! $this->login_is_valid() ) {
404 return False;
406 if ( ! $this->iuser_is_valid() ) {
407 return False;
409 if ( ! $this->user_password_is_valid() ) {
410 return False;
413 // Validation of mysql database password
414 if ( ! $this->password_is_valid() ) {
415 return False;
417 if (! $this->no_root_db_access) {
418 // Connect to mysql via root user
419 if (! $this->root_database_connection() ) {
420 return False;
422 // Create the dumpfile
423 // (applicable if cloning from another database)
424 if (! empty($this->clone_database)) {
425 if ( ! $this->create_dumpfiles() ) {
426 return False;
429 // Create the site directory
430 // (applicable if mirroring another local site)
431 if ( ! empty($this->source_site_id) ) {
432 if ( ! $this->create_site_directory() ) {
433 return False;
436 $this->disconnect();
437 if (! $this->user_database_connection()) {
438 // Re-connect to mysql via root user
439 if (! $this->root_database_connection() ) {
440 return False;
442 // Create the mysql database
443 if ( ! $this->create_database()) {
444 return False;
446 // Grant user privileges to the mysql database
447 if ( ! $this->grant_privileges() ) {
448 return False;
451 $this->disconnect();
453 // Connect to mysql via created user
454 if ( ! $this->user_database_connection() ) {
455 return False;
457 // Build the database
458 if ( ! $this->load_dumpfiles() ) {
459 return False;
461 // Write the sql configuration file
462 if ( ! $this->write_configuration_file() ) {
463 return False;
465 // Load the version information, globals settings,
466 // initial user, and set up gacl access controls.
467 // (applicable if not cloning from another database)
468 if (empty($this->clone_database)) {
469 if ( ! $this->add_version_info() ) {
470 return False;
472 if ( ! $this->insert_globals() ) {
473 return False;
475 if ( ! $this->add_initial_user() ) {
476 return False;
478 if ( ! $this->install_gacl()) {
479 return False;
483 return True;
486 private function execute_sql( $sql ) {
487 $this->error_message = '';
488 if ( ! $this->dbh ) {
489 $this->user_database_connection();
491 $results = mysqli_query($this->dbh, $sql);
492 if ( $results ) {
493 return $results;
494 } else {
495 $error_mes = mysqli_error($this->dbh);
496 $this->error_message = "unable to execute SQL: '$sql' due to: " . $error_mes;
497 error_log("ERROR IN OPENEMR INSTALL: Unable to execute SQL: ".$sql." due to: ".$error_mes);
498 return False;
502 private function connect_to_database( $server, $user, $password, $port, $dbname='' )
504 if ($server == "localhost")
505 $dbh = mysqli_connect($server, $user, $password, $dbname);
506 else
507 $dbh = mysqli_connect($server, $user, $password, $dbname, $port);
508 return $dbh;
511 private function set_sql_strict()
513 // Turn off STRICT SQL
514 return $this->execute_sql("SET sql_mode = ''");
517 private function set_collation()
519 if ($this->collate) {
520 return $this->execute_sql("SET NAMES 'utf8'");
522 return TRUE;
526 * innitialize $this->dumpfiles, an array of the dumpfiles that will
527 * be loaded into the database, including the correct translation
528 * dumpfile.
529 * The keys are the paths of the dumpfiles, and the values are the titles
530 * @return array
532 private function initialize_dumpfile_list() {
533 if ( $this->clone_database ) {
534 $this->dumpfiles = array( $this->get_backup_filename() => 'clone database' );
535 } else {
536 $dumpfiles = array( $this->main_sql => 'Main' );
537 if (! empty($this->development_translations)) {
538 // Use the online development translation set
539 $dumpfiles[ $this->devel_translation_sql ] = "Online Development Language Translations (utf8)";
541 else {
542 // Use the local translation set
543 $dumpfiles[ $this->translation_sql ] = "Language Translation (utf8)";
545 if ($this->ippf_specific) {
546 $dumpfiles[ $this->ippf_sql ] = "IPPF Layout";
548 // Load ICD-9 codes if present.
549 if (file_exists( $this->icd9 )) {
550 $dumpfiles[ $this->icd9 ] = "ICD-9";
552 // Load CVX codes if present
553 if (file_exists( $this->cvx )) {
554 $dumpfiles[ $this->cvx ] = "CVX Immunization Codes";
556 $this->dumpfiles = $dumpfiles;
558 return $this->dumpfiles;
561 // http://www.php.net/manual/en/function.include.php
562 private function get_require_contents($filename) {
563 if (is_file($filename)) {
564 ob_start();
565 require $filename;
566 $contents = ob_get_contents();
567 ob_end_clean();
568 return $contents;
570 return false;
575 * Directory copy logic borrowed from a user comment at
576 * http://www.php.net/manual/en/function.copy.php
577 * @param string $src name of the directory to copy
578 * @param string $dst name of the destination to copy to
579 * @return bool indicating success
581 private function recurse_copy($src, $dst) {
582 $dir = opendir($src);
583 if ( ! @mkdir($dst) ) {
584 $this->error_message = "unable to create directory: '$dst'";
585 return False;
587 while(false !== ($file = readdir($dir))) {
588 if ($file != '.' && $file != '..') {
589 if (is_dir($src . '/' . $file)) {
590 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
592 else {
593 copy($src . '/' . $file, $dst . '/' . $file);
597 closedir($dir);
598 return True;
603 * dump a site's database to a temporary file.
604 * @param string $source_site_id the site_id of the site to dump
605 * @return filename of the backup
607 private function dumpSourceDatabase() {
608 global $OE_SITES_BASE;
609 $source_site_id = $this->source_site_id;
611 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
613 if (empty($config)) die("Source site $source_site_id has not been set up!");
615 $backup_file = $this->get_backup_filename();
616 $cmd = "mysqldump -u " . escapeshellarg($login) .
617 " -p" . escapeshellarg($pass) .
618 " --opt --skip-extended-insert --quote-names -r $backup_file " .
619 escapeshellarg($dbase);
621 $tmp0 = exec($cmd, $tmp1=array(), $tmp2);
622 if ($tmp2) die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
624 return $backup_file;
628 * @return filename of the source backup database for cloning
630 private function get_backup_filename() {
631 if (stristr(PHP_OS, 'WIN')) {
632 $backup_file = 'C:/windows/temp/setup_dump.sql';
634 else {
635 $backup_file = '/tmp/setup_dump.sql';
637 return $backup_file;
643 This file is free software: you can redistribute it and/or modify it under the
644 terms of the GNU General Public License as publish by the Free Software
645 Foundation.
647 This file is distributed in the hope that it will be useful, but WITHOUT ANY
648 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
649 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
651 You should have received a copy of the GNU General Public Licence along with
652 this file. If not see <http://www.gnu.org/licenses/>.