2 /* Copyright © 2010 by Andrew Moore */
3 /* Licensing information appears at the end of this file. */
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://translations.openemr.io/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
64 public function login_is_valid()
66 if (($this->login
== '') ||
(! isset($this->login
))) {
67 $this->error_message
= "login is invalid: '$this->login'";
74 public function char_is_valid($input_text)
76 // to prevent php injection
78 if ($input_text == '') {
82 if (preg_match('@[\\\\;()<>/\'"]@', $input_text)) {
89 public function databaseNameIsValid($name)
91 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
97 public function collateNameIsValid($name)
99 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
105 public function iuser_is_valid()
107 if (strpos($this->iuser
, " ")) {
108 $this->error_message
= "Initial user is invalid: '$this->iuser'";
115 public function password_is_valid()
117 if ($this->pass
== "" ||
!isset($this->pass
)) {
118 $this->error_message
= "The password for the new database account is invalid: '$this->pass'";
125 public function user_password_is_valid()
127 if ($this->iuserpass
== "" ||
!isset($this->iuserpass
)) {
128 $this->error_message
= "The password for the user is invalid: '$this->iuserpass'";
137 public function root_database_connection()
139 $this->dbh
= $this->connect_to_database($this->server
, $this->root
, $this->rootpass
, $this->port
);
141 if (! $this->set_sql_strict()) {
142 $this->error_message
= 'unable to set strict sql setting';
148 $this->error_message
= 'unable to connect to database as root';
153 public function user_database_connection()
155 $this->dbh
= $this->connect_to_database($this->server
, $this->login
, $this->pass
, $this->port
, $this->dbname
);
157 $this->error_message
= "unable to connect to database as user: '$this->login'";
161 if (! $this->set_sql_strict()) {
162 $this->error_message
= 'unable to set strict sql setting';
166 if (! $this->set_collation()) {
167 $this->error_message
= 'unable to set sql collation';
171 if (! mysqli_select_db($this->dbh
, $this->dbname
)) {
172 $this->error_message
= "unable to select database: '$this->dbname'";
179 public function create_database()
181 $sql = "create database " . $this->escapeDatabaseName($this->dbname
);
182 if ($this->collate
) {
183 $sql .= " character set utf8 collate " . $this->escapeCollateName($this->collate
);
184 $this->set_collation();
187 return $this->execute_sql($sql);
190 public function drop_database()
192 $sql = "drop database if exists " . $this->escapeDatabaseName($this->dbname
);
193 return $this->execute_sql($sql);
196 public function check_database_user()
198 return $this->execute_sql("SELECT user FROM mysql.user WHERE user = '" . $this->escapeSql($this->login
) . "' AND host = '" . $this->escapeSql($this->loginhost
) . "'");
201 public function create_database_user()
203 $checkUser = $this->check_database_user();
205 if ($checkUser === false) {
206 // there was an error in the check database user query, so return false
208 } else if ($checkUser->num_rows
> 0) {
209 // the mysql user already exists, so do not need to create the user, but need to set the password
210 // Note need to try two different methods, first is for newer mysql versions and second is for older mysql versions (if the first method fails)
211 $returnSql = $this->execute_sql("ALTER USER '" . $this->escapeSql($this->login
) . "'@'" . $this->escapeSql($this->loginhost
) . "' IDENTIFIED BY '" . $this->escapeSql($this->pass
) . "'", false);
212 if ($returnSql === false) {
213 error_log("Using older mysql version method to set password for the mysql user");
214 $returnSql = $this->execute_sql("SET PASSWORD FOR '" . $this->escapeSql($this->login
) . "'@'" . $this->escapeSql($this->loginhost
) . "' = PASSWORD('" . $this->escapeSql($this->pass
) . "')");
218 // the mysql user does not yet exist, so create the user
219 return $this->execute_sql("CREATE USER '" . $this->escapeSql($this->login
) . "'@'" . $this->escapeSql($this->loginhost
) . "' IDENTIFIED BY '" . $this->escapeSql($this->pass
) . "'");
223 public function grant_privileges()
225 return $this->execute_sql("GRANT ALL PRIVILEGES ON " . $this->escapeDatabaseName($this->dbname
) . ".* TO '" . $this->escapeSql($this->login
) . "'@'" . $this->escapeSql($this->loginhost
) . "'");
228 public function disconnect()
230 return mysqli_close($this->dbh
);
234 * This method creates any dumpfiles necessary.
235 * This is actually only done if we're cloning an existing site
236 * and we need to dump their database into a file.
237 * @return bool indicating success
239 public function create_dumpfiles()
241 return $this->dumpSourceDatabase();
244 public function load_dumpfiles()
246 $sql_results = ''; // information string which is returned
247 foreach ($this->dumpfiles
as $filename => $title) {
248 $sql_results_temp = '';
249 $sql_results_temp = $this->load_file($filename, $title);
250 if ($sql_results_temp == false) {
254 $sql_results .= $sql_results_temp;
260 public function load_file($filename, $title)
262 $sql_results = ''; // information string which is returned
263 $sql_results .= "Creating $title tables...\n";
264 $fd = fopen($filename, 'r');
266 $this->error_message
= "ERROR. Could not open dumpfile '$filename'.\n";
273 // Settings to drastically speed up installation with InnoDB
274 if (! $this->execute_sql("SET autocommit=0;")) {
278 if (! $this->execute_sql("START TRANSACTION;")) {
283 $line = fgets($fd, 1024);
284 $line = rtrim($line);
285 if (substr($line, 0, 2) == "--") { // Kill comments
289 if (substr($line, 0, 1) == "#") { // Kill comments
297 $query = $query.$line; // Check for full query
298 $chr = substr($query, strlen($query)-1, 1);
299 if ($chr == ";") { // valid query, execute
300 $query = rtrim($query, ";");
301 if (! $this->execute_sql($query)) {
309 // Settings to drastically speed up installation with InnoDB
310 if (! $this->execute_sql("COMMIT;")) {
314 if (! $this->execute_sql("SET autocommit=1;")) {
318 $sql_results .= "OK<br>\n";
323 // Please note that the plain sql is used over the Doctrine ORM for
324 // `version` table interactions because it cannot connect due to a
325 // lack of context (this code is ran outside of the OpenEMR context).
326 public function add_version_info()
328 include dirname(__FILE__
) . "/../../version.php";
329 if ($this->execute_sql("UPDATE version SET v_major = '" . $this->escapeSql($v_major) . "', v_minor = '" . $this->escapeSql($v_minor) . "', v_patch = '" . $this->escapeSql($v_patch) . "', v_realpatch = '" . $this->escapeSql($v_realpatch) . "', v_tag = '" . $this->escapeSql($v_tag) . "', v_database = '" . $this->escapeSql($v_database) . "', v_acl = '" . $this->escapeSql($v_acl) . "'") == false) {
330 $this->error_message
= "ERROR. Unable insert version information into database\n" .
331 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
338 public function add_initial_user()
340 if ($this->execute_sql("INSERT INTO `groups` (id, name, user) VALUES (1,'" . $this->escapeSql($this->igroup
) . "','" . $this->escapeSql($this->iuser
) . "')") == false) {
341 $this->error_message
= "ERROR. Unable to add initial user group\n" .
342 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
346 $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.
347 $salt=oemr_password_salt(); // Uses the functions defined in library/authentication/password_hashing.php
348 $hash=oemr_password_hash($this->iuserpass
, $salt);
349 if ($this->execute_sql("INSERT INTO users (id, username, password, authorized, lname, fname, facility_id, calendar, cal_ui) VALUES (1,'" . $this->escapeSql($this->iuser
) . "','" . $this->escapeSql($password_hash) . "',1,'" . $this->escapeSql($this->iuname
) . "','" . $this->escapeSql($this->iufname
) . "',3,1,3)") == false) {
350 $this->error_message
= "ERROR. Unable to add initial user\n" .
351 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
355 // Create the new style login credentials with blowfish and salt
356 if ($this->execute_sql("INSERT INTO users_secure (id, username, password, salt) VALUES (1,'" . $this->escapeSql($this->iuser
) . "','" . $this->escapeSql($hash) . "','" . $this->escapeSql($salt) . "')") == false) {
357 $this->error_message
= "ERROR. Unable to add initial user login credentials\n" .
358 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
362 // Add the official openemr users (services)
363 if ($this->load_file($this->additional_users
, "Additional Official Users") == false) {
371 * Create site directory if it is missing.
372 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
373 * @return name of the site directory or False
375 public function create_site_directory()
377 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
378 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id
;
379 $destination_directory = $GLOBALS['OE_SITE_DIR'];
380 if (! $this->recurse_copy($source_directory, $destination_directory)) {
381 $this->error_message
= "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message
;
389 public function write_configuration_file()
391 @touch
($this->conffile
); // php bug
392 $fd = @fopen
($this->conffile
, 'w');
394 $this->error_message
= 'unable to open configuration file for writing: ' . $this->conffile
;
404 $it_died = 0; //fmg: variable keeps running track of any errors
406 fwrite($fd, $string) or $it_died++
;
407 fwrite($fd, "\$host\t= '$this->server';\n") or $it_died++
;
408 fwrite($fd, "\$port\t= '$this->port';\n") or $it_died++
;
409 fwrite($fd, "\$login\t= '$this->login';\n") or $it_died++
;
410 fwrite($fd, "\$pass\t= '$this->pass';\n") or $it_died++
;
411 fwrite($fd, "\$dbase\t= '$this->dbname';\n\n") or $it_died++
;
412 fwrite($fd, "//Added ability to disable\n") or $it_died++
;
413 fwrite($fd, "//utf8 encoding - bm 05-2009\n") or $it_died++
;
414 fwrite($fd, "global \$disable_utf8_flag;\n") or $it_died++
;
415 fwrite($fd, "\$disable_utf8_flag = false;\n") or $it_died++
;
420 $sqlconf["host"]= $host;
421 $sqlconf["port"] = $port;
422 $sqlconf["login"] = $login;
423 $sqlconf["pass"] = $pass;
424 $sqlconf["dbase"] = $dbase;
425 //////////////////////////
426 //////////////////////////
427 //////////////////////////
428 //////DO NOT TOUCH THIS///
429 $config = 1; /////////////
430 //////////////////////////
431 //////////////////////////
432 //////////////////////////
435 ?
><?php
// done just for coloring
437 fwrite($fd, $string) or $it_died++
;
438 fclose($fd) or $it_died++
;
440 //it's rather irresponsible to not report errors when writing this file.
442 $this->error_message
= "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
449 public function insert_globals()
455 require(dirname(__FILE__
) . '/../globals.inc.php');
456 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
457 foreach ($grparr as $fldid => $fldarr) {
458 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
459 if (is_array($fldtype) ||
substr($fldtype, 0, 2) !== 'm_') {
460 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '" . $this->escapeSql($fldid) . "'");
461 $row = mysqli_fetch_array($res, MYSQLI_ASSOC
);
462 if (empty($row['count'])) {
463 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
464 "VALUES ( '" . $this->escapeSql($fldid) . "', '0', '" . $this->escapeSql($flddef) . "' )");
473 public function install_gacl()
475 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1
);
476 if (! $install_results_1) {
477 $this->error_message
= "install_gacl failed: unable to require gacl script 1";
481 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2
);
482 if (! $install_results_2) {
483 $this->error_message
= "install_gacl failed: unable to require gacl script 2";
487 $this->debug_message
.= $install_results_1 . $install_results_2;
491 public function quick_install()
493 // Validation of OpenEMR user settings
494 // (applicable if not cloning from another database)
495 if (empty($this->clone_database
)) {
496 if (! $this->login_is_valid()) {
500 if (! $this->iuser_is_valid()) {
504 if (! $this->user_password_is_valid()) {
509 // Validation of mysql database password
510 if (! $this->password_is_valid()) {
514 if (! $this->no_root_db_access
) {
515 // Connect to mysql via root user
516 if (! $this->root_database_connection()) {
520 // Create the dumpfile
521 // (applicable if cloning from another database)
522 if (! empty($this->clone_database
)) {
523 if (! $this->create_dumpfiles()) {
528 // Create the site directory
529 // (applicable if mirroring another local site)
530 if (! empty($this->source_site_id
)) {
531 if (! $this->create_site_directory()) {
537 if (! $this->user_database_connection()) {
538 // Re-connect to mysql via root user
539 if (! $this->root_database_connection()) {
543 // Create the mysql database
544 if (! $this->create_database()) {
548 // Create the mysql user
549 if (! $this->create_database_user()) {
553 // Grant user privileges to the mysql database
554 if (! $this->grant_privileges()) {
562 // Connect to mysql via created user
563 if (! $this->user_database_connection()) {
567 // Build the database
568 if (! $this->load_dumpfiles()) {
572 // Write the sql configuration file
573 if (! $this->write_configuration_file()) {
577 // Load the version information, globals settings,
578 // initial user, and set up gacl access controls.
579 // (applicable if not cloning from another database)
580 if (empty($this->clone_database
)) {
581 if (! $this->add_version_info()) {
585 if (! $this->insert_globals()) {
589 if (! $this->add_initial_user()) {
593 if (! $this->install_gacl()) {
601 private function escapeSql($sql)
603 return mysqli_real_escape_string($this->dbh
, $sql);
606 private function escapeDatabaseName($name)
608 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
609 error_log("Illegal character(s) in database name");
610 die("Illegal character(s) in database name");
615 private function escapeCollateName($name)
617 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
618 error_log("Illegal character(s) in collation name");
619 die("Illegal character(s) in collation name");
624 private function execute_sql($sql, $showError = true)
626 $this->error_message
= '';
628 $this->user_database_connection();
631 $results = mysqli_query($this->dbh
, $sql);
636 $error_mes = mysqli_error($this->dbh
);
637 $this->error_message
= "unable to execute SQL: '$sql' due to: " . $error_mes;
638 error_log("ERROR IN OPENEMR INSTALL: Unable to execute SQL: " . $sql . " due to: " . $error_mes);
644 private function connect_to_database($server, $user, $password, $port, $dbname = '')
646 if ($server == "localhost") {
647 $dbh = mysqli_connect($server, $user, $password, $dbname);
649 $dbh = mysqli_connect($server, $user, $password, $dbname, $port);
655 private function set_sql_strict()
657 // Turn off STRICT SQL
658 return $this->execute_sql("SET sql_mode = ''");
661 private function set_collation()
663 if ($this->collate
) {
664 return $this->execute_sql("SET NAMES 'utf8'");
671 * innitialize $this->dumpfiles, an array of the dumpfiles that will
672 * be loaded into the database, including the correct translation
674 * The keys are the paths of the dumpfiles, and the values are the titles
677 private function initialize_dumpfile_list()
679 if ($this->clone_database
) {
680 $this->dumpfiles
= array( $this->get_backup_filename() => 'clone database' );
682 $dumpfiles = array( $this->main_sql
=> 'Main' );
683 if (! empty($this->development_translations
)) {
684 // Use the online development translation set
685 $dumpfiles[ $this->devel_translation_sql
] = "Online Development Language Translations (utf8)";
687 // Use the local translation set
688 $dumpfiles[ $this->translation_sql
] = "Language Translation (utf8)";
691 if ($this->ippf_specific
) {
692 $dumpfiles[ $this->ippf_sql
] = "IPPF Layout";
695 // Load ICD-9 codes if present.
696 if (file_exists($this->icd9
)) {
697 $dumpfiles[ $this->icd9
] = "ICD-9";
700 // Load CVX codes if present
701 if (file_exists($this->cvx
)) {
702 $dumpfiles[ $this->cvx
] = "CVX Immunization Codes";
705 $this->dumpfiles
= $dumpfiles;
708 return $this->dumpfiles
;
711 // http://www.php.net/manual/en/function.include.php
712 private function get_require_contents($filename)
714 if (is_file($filename)) {
717 $contents = ob_get_contents();
727 * Directory copy logic borrowed from a user comment at
728 * http://www.php.net/manual/en/function.copy.php
729 * @param string $src name of the directory to copy
730 * @param string $dst name of the destination to copy to
731 * @return bool indicating success
733 private function recurse_copy($src, $dst)
735 $dir = opendir($src);
736 if (! @mkdir
($dst)) {
737 $this->error_message
= "unable to create directory: '$dst'";
741 while (false !== ($file = readdir($dir))) {
742 if ($file != '.' && $file != '..') {
743 if (is_dir($src . '/' . $file)) {
744 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
746 copy($src . '/' . $file, $dst . '/' . $file);
757 * dump a site's database to a temporary file.
758 * @param string $source_site_id the site_id of the site to dump
759 * @return filename of the backup
761 private function dumpSourceDatabase()
763 global $OE_SITES_BASE;
764 $source_site_id = $this->source_site_id
;
766 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
768 if (empty($config)) {
769 die("Source site $source_site_id has not been set up!");
772 $backup_file = $this->get_backup_filename();
773 $cmd = "mysqldump -u " . escapeshellarg($login) .
774 " -p" . escapeshellarg($pass) .
775 " --opt --skip-extended-insert --quote-names -r $backup_file " .
776 escapeshellarg($dbase);
778 $tmp0 = exec($cmd, $tmp1 = array(), $tmp2);
780 die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
787 * @return filename of the source backup database for cloning
789 private function get_backup_filename()
791 if (stristr(PHP_OS
, 'WIN')) {
792 $backup_file = 'C:/windows/temp/setup_dump.sql';
794 $backup_file = '/tmp/setup_dump.sql';
802 This file is free software: you can redistribute it and/or modify it under the
803 terms of the GNU General Public License as publish by the Free Software
806 This file is distributed in the hope that it will be useful, but WITHOUT ANY
807 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
808 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
810 You should have received a copy of the GNU General Public Licence along with
811 this file. If not see <http://www.gnu.org/licenses/>.