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 grant_privileges()
198 return $this->execute_sql("GRANT ALL PRIVILEGES ON " . $this->escapeDatabaseName($this->dbname
) . ".* TO '" . $this->escapeSql($this->login
) . "'@'" . $this->escapeSql($this->loginhost
) . "' IDENTIFIED BY '" . $this->escapeSql($this->pass
) . "'");
201 public function disconnect()
203 return mysqli_close($this->dbh
);
207 * This method creates any dumpfiles necessary.
208 * This is actually only done if we're cloning an existing site
209 * and we need to dump their database into a file.
210 * @return bool indicating success
212 public function create_dumpfiles()
214 return $this->dumpSourceDatabase();
217 public function load_dumpfiles()
219 $sql_results = ''; // information string which is returned
220 foreach ($this->dumpfiles
as $filename => $title) {
221 $sql_results_temp = '';
222 $sql_results_temp = $this->load_file($filename, $title);
223 if ($sql_results_temp == false) {
227 $sql_results .= $sql_results_temp;
233 public function load_file($filename, $title)
235 $sql_results = ''; // information string which is returned
236 $sql_results .= "Creating $title tables...\n";
237 $fd = fopen($filename, 'r');
239 $this->error_message
= "ERROR. Could not open dumpfile '$filename'.\n";
246 // Settings to drastically speed up installation with InnoDB
247 if (! $this->execute_sql("SET autocommit=0;")) {
251 if (! $this->execute_sql("START TRANSACTION;")) {
256 $line = fgets($fd, 1024);
257 $line = rtrim($line);
258 if (substr($line, 0, 2) == "--") { // Kill comments
262 if (substr($line, 0, 1) == "#") { // Kill comments
270 $query = $query.$line; // Check for full query
271 $chr = substr($query, strlen($query)-1, 1);
272 if ($chr == ";") { // valid query, execute
273 $query = rtrim($query, ";");
274 if (! $this->execute_sql($query)) {
282 // Settings to drastically speed up installation with InnoDB
283 if (! $this->execute_sql("COMMIT;")) {
287 if (! $this->execute_sql("SET autocommit=1;")) {
291 $sql_results .= "OK<br>\n";
296 // Please note that the plain sql is used over the Doctrine ORM for
297 // `version` table interactions because it cannot connect due to a
298 // lack of context (this code is ran outside of the OpenEMR context).
299 public function add_version_info()
301 include dirname(__FILE__
) . "/../../version.php";
302 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) {
303 $this->error_message
= "ERROR. Unable insert version information into database\n" .
304 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
311 public function add_initial_user()
313 if ($this->execute_sql("INSERT INTO groups (id, name, user) VALUES (1,'" . $this->escapeSql($this->igroup
) . "','" . $this->escapeSql($this->iuser
) . "')") == false) {
314 $this->error_message
= "ERROR. Unable to add initial user group\n" .
315 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
319 $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.
320 $salt=oemr_password_salt(); // Uses the functions defined in library/authentication/password_hashing.php
321 $hash=oemr_password_hash($this->iuserpass
, $salt);
322 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) {
323 $this->error_message
= "ERROR. Unable to add initial user\n" .
324 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
328 // Create the new style login credentials with blowfish and salt
329 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) {
330 $this->error_message
= "ERROR. Unable to add initial user login credentials\n" .
331 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
335 // Add the official openemr users (services)
336 if ($this->load_file($this->additional_users
, "Additional Official Users") == false) {
344 * Create site directory if it is missing.
345 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
346 * @return name of the site directory or False
348 public function create_site_directory()
350 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
351 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id
;
352 $destination_directory = $GLOBALS['OE_SITE_DIR'];
353 if (! $this->recurse_copy($source_directory, $destination_directory)) {
354 $this->error_message
= "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message
;
362 public function write_configuration_file()
364 @touch
($this->conffile
); // php bug
365 $fd = @fopen
($this->conffile
, 'w');
367 $this->error_message
= 'unable to open configuration file for writing: ' . $this->conffile
;
377 $it_died = 0; //fmg: variable keeps running track of any errors
379 fwrite($fd, $string) or $it_died++
;
380 fwrite($fd, "\$host\t= '$this->server';\n") or $it_died++
;
381 fwrite($fd, "\$port\t= '$this->port';\n") or $it_died++
;
382 fwrite($fd, "\$login\t= '$this->login';\n") or $it_died++
;
383 fwrite($fd, "\$pass\t= '$this->pass';\n") or $it_died++
;
384 fwrite($fd, "\$dbase\t= '$this->dbname';\n\n") or $it_died++
;
385 fwrite($fd, "//Added ability to disable\n") or $it_died++
;
386 fwrite($fd, "//utf8 encoding - bm 05-2009\n") or $it_died++
;
387 fwrite($fd, "global \$disable_utf8_flag;\n") or $it_died++
;
388 fwrite($fd, "\$disable_utf8_flag = false;\n") or $it_died++
;
393 $sqlconf["host"]= $host;
394 $sqlconf["port"] = $port;
395 $sqlconf["login"] = $login;
396 $sqlconf["pass"] = $pass;
397 $sqlconf["dbase"] = $dbase;
398 //////////////////////////
399 //////////////////////////
400 //////////////////////////
401 //////DO NOT TOUCH THIS///
402 $config = 1; /////////////
403 //////////////////////////
404 //////////////////////////
405 //////////////////////////
408 ?
><?php
// done just for coloring
410 fwrite($fd, $string) or $it_died++
;
411 fclose($fd) or $it_died++
;
413 //it's rather irresponsible to not report errors when writing this file.
415 $this->error_message
= "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
422 public function insert_globals()
428 require(dirname(__FILE__
) . '/../globals.inc.php');
429 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
430 foreach ($grparr as $fldid => $fldarr) {
431 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
432 if (is_array($fldtype) ||
substr($fldtype, 0, 2) !== 'm_') {
433 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = ' " . $this->escapeSql($fldid) . "'");
434 $row = mysqli_fetch_array($res, MYSQLI_ASSOC
);
435 if (empty($row['count'])) {
436 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
437 "VALUES ( '" . $this->escapeSql($fldid) . "', '0', '" . $this->escapeSql($flddef) . "' )");
446 public function install_gacl()
448 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1
);
449 if (! $install_results_1) {
450 $this->error_message
= "install_gacl failed: unable to require gacl script 1";
454 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2
);
455 if (! $install_results_2) {
456 $this->error_message
= "install_gacl failed: unable to require gacl script 2";
460 $this->debug_message
.= $install_results_1 . $install_results_2;
464 public function quick_install()
466 // Validation of OpenEMR user settings
467 // (applicable if not cloning from another database)
468 if (empty($this->clone_database
)) {
469 if (! $this->login_is_valid()) {
473 if (! $this->iuser_is_valid()) {
477 if (! $this->user_password_is_valid()) {
482 // Validation of mysql database password
483 if (! $this->password_is_valid()) {
487 if (! $this->no_root_db_access
) {
488 // Connect to mysql via root user
489 if (! $this->root_database_connection()) {
493 // Create the dumpfile
494 // (applicable if cloning from another database)
495 if (! empty($this->clone_database
)) {
496 if (! $this->create_dumpfiles()) {
501 // Create the site directory
502 // (applicable if mirroring another local site)
503 if (! empty($this->source_site_id
)) {
504 if (! $this->create_site_directory()) {
510 if (! $this->user_database_connection()) {
511 // Re-connect to mysql via root user
512 if (! $this->root_database_connection()) {
516 // Create the mysql database
517 if (! $this->create_database()) {
521 // Grant user privileges to the mysql database
522 if (! $this->grant_privileges()) {
530 // Connect to mysql via created user
531 if (! $this->user_database_connection()) {
535 // Build the database
536 if (! $this->load_dumpfiles()) {
540 // Write the sql configuration file
541 if (! $this->write_configuration_file()) {
545 // Load the version information, globals settings,
546 // initial user, and set up gacl access controls.
547 // (applicable if not cloning from another database)
548 if (empty($this->clone_database
)) {
549 if (! $this->add_version_info()) {
553 if (! $this->insert_globals()) {
557 if (! $this->add_initial_user()) {
561 if (! $this->install_gacl()) {
569 private function escapeSql($sql)
571 return mysqli_real_escape_string($this->dbh
, $sql);
574 private function escapeDatabaseName($name)
576 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
577 error_log("Illegal character(s) in database name");
578 die("Illegal character(s) in database name");
583 private function escapeCollateName($name)
585 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
586 error_log("Illegal character(s) in collation name");
587 die("Illegal character(s) in collation name");
592 private function execute_sql($sql)
594 $this->error_message
= '';
596 $this->user_database_connection();
599 $results = mysqli_query($this->dbh
, $sql);
603 $error_mes = mysqli_error($this->dbh
);
604 $this->error_message
= "unable to execute SQL: '$sql' due to: " . $error_mes;
605 error_log("ERROR IN OPENEMR INSTALL: Unable to execute SQL: ".$sql." due to: ".$error_mes);
610 private function connect_to_database($server, $user, $password, $port, $dbname = '')
612 if ($server == "localhost") {
613 $dbh = mysqli_connect($server, $user, $password, $dbname);
615 $dbh = mysqli_connect($server, $user, $password, $dbname, $port);
621 private function set_sql_strict()
623 // Turn off STRICT SQL
624 return $this->execute_sql("SET sql_mode = ''");
627 private function set_collation()
629 if ($this->collate
) {
630 return $this->execute_sql("SET NAMES 'utf8'");
637 * innitialize $this->dumpfiles, an array of the dumpfiles that will
638 * be loaded into the database, including the correct translation
640 * The keys are the paths of the dumpfiles, and the values are the titles
643 private function initialize_dumpfile_list()
645 if ($this->clone_database
) {
646 $this->dumpfiles
= array( $this->get_backup_filename() => 'clone database' );
648 $dumpfiles = array( $this->main_sql
=> 'Main' );
649 if (! empty($this->development_translations
)) {
650 // Use the online development translation set
651 $dumpfiles[ $this->devel_translation_sql
] = "Online Development Language Translations (utf8)";
653 // Use the local translation set
654 $dumpfiles[ $this->translation_sql
] = "Language Translation (utf8)";
657 if ($this->ippf_specific
) {
658 $dumpfiles[ $this->ippf_sql
] = "IPPF Layout";
661 // Load ICD-9 codes if present.
662 if (file_exists($this->icd9
)) {
663 $dumpfiles[ $this->icd9
] = "ICD-9";
666 // Load CVX codes if present
667 if (file_exists($this->cvx
)) {
668 $dumpfiles[ $this->cvx
] = "CVX Immunization Codes";
671 $this->dumpfiles
= $dumpfiles;
674 return $this->dumpfiles
;
677 // http://www.php.net/manual/en/function.include.php
678 private function get_require_contents($filename)
680 if (is_file($filename)) {
683 $contents = ob_get_contents();
693 * Directory copy logic borrowed from a user comment at
694 * http://www.php.net/manual/en/function.copy.php
695 * @param string $src name of the directory to copy
696 * @param string $dst name of the destination to copy to
697 * @return bool indicating success
699 private function recurse_copy($src, $dst)
701 $dir = opendir($src);
702 if (! @mkdir
($dst)) {
703 $this->error_message
= "unable to create directory: '$dst'";
707 while (false !== ($file = readdir($dir))) {
708 if ($file != '.' && $file != '..') {
709 if (is_dir($src . '/' . $file)) {
710 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
712 copy($src . '/' . $file, $dst . '/' . $file);
723 * dump a site's database to a temporary file.
724 * @param string $source_site_id the site_id of the site to dump
725 * @return filename of the backup
727 private function dumpSourceDatabase()
729 global $OE_SITES_BASE;
730 $source_site_id = $this->source_site_id
;
732 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
734 if (empty($config)) {
735 die("Source site $source_site_id has not been set up!");
738 $backup_file = $this->get_backup_filename();
739 $cmd = "mysqldump -u " . escapeshellarg($login) .
740 " -p" . escapeshellarg($pass) .
741 " --opt --skip-extended-insert --quote-names -r $backup_file " .
742 escapeshellarg($dbase);
744 $tmp0 = exec($cmd, $tmp1 = array(), $tmp2);
746 die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
753 * @return filename of the source backup database for cloning
755 private function get_backup_filename()
757 if (stristr(PHP_OS
, 'WIN')) {
758 $backup_file = 'C:/windows/temp/setup_dump.sql';
760 $backup_file = '/tmp/setup_dump.sql';
768 This file is free software: you can redistribute it and/or modify it under the
769 terms of the GNU General Public License as publish by the Free Software
772 This file is distributed in the hope that it will be useful, but WITHOUT ANY
773 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
774 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
776 You should have received a copy of the GNU General Public Licence along with
777 this file. If not see <http://www.gnu.org/licenses/>.