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 create_database_user()
198 return $this->execute_sql("CREATE USER '" . $this->escapeSql($this->login
) . "'@'" . $this->escapeSql($this->loginhost
) . "' IDENTIFIED BY '" . $this->escapeSql($this->pass
) . "'");
201 public function grant_privileges()
203 return $this->execute_sql("GRANT ALL PRIVILEGES ON " . $this->escapeDatabaseName($this->dbname
) . ".* TO '" . $this->escapeSql($this->login
) . "'@'" . $this->escapeSql($this->loginhost
) . "'");
206 public function disconnect()
208 return mysqli_close($this->dbh
);
212 * This method creates any dumpfiles necessary.
213 * This is actually only done if we're cloning an existing site
214 * and we need to dump their database into a file.
215 * @return bool indicating success
217 public function create_dumpfiles()
219 return $this->dumpSourceDatabase();
222 public function load_dumpfiles()
224 $sql_results = ''; // information string which is returned
225 foreach ($this->dumpfiles
as $filename => $title) {
226 $sql_results_temp = '';
227 $sql_results_temp = $this->load_file($filename, $title);
228 if ($sql_results_temp == false) {
232 $sql_results .= $sql_results_temp;
238 public function load_file($filename, $title)
240 $sql_results = ''; // information string which is returned
241 $sql_results .= "Creating $title tables...\n";
242 $fd = fopen($filename, 'r');
244 $this->error_message
= "ERROR. Could not open dumpfile '$filename'.\n";
251 // Settings to drastically speed up installation with InnoDB
252 if (! $this->execute_sql("SET autocommit=0;")) {
256 if (! $this->execute_sql("START TRANSACTION;")) {
261 $line = fgets($fd, 1024);
262 $line = rtrim($line);
263 if (substr($line, 0, 2) == "--") { // Kill comments
267 if (substr($line, 0, 1) == "#") { // Kill comments
275 $query = $query.$line; // Check for full query
276 $chr = substr($query, strlen($query)-1, 1);
277 if ($chr == ";") { // valid query, execute
278 $query = rtrim($query, ";");
279 if (! $this->execute_sql($query)) {
287 // Settings to drastically speed up installation with InnoDB
288 if (! $this->execute_sql("COMMIT;")) {
292 if (! $this->execute_sql("SET autocommit=1;")) {
296 $sql_results .= "OK<br>\n";
301 // Please note that the plain sql is used over the Doctrine ORM for
302 // `version` table interactions because it cannot connect due to a
303 // lack of context (this code is ran outside of the OpenEMR context).
304 public function add_version_info()
306 include dirname(__FILE__
) . "/../../version.php";
307 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) {
308 $this->error_message
= "ERROR. Unable insert version information into database\n" .
309 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
316 public function add_initial_user()
318 if ($this->execute_sql("INSERT INTO `groups` (id, name, user) VALUES (1,'" . $this->escapeSql($this->igroup
) . "','" . $this->escapeSql($this->iuser
) . "')") == false) {
319 $this->error_message
= "ERROR. Unable to add initial user group\n" .
320 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
324 $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.
325 $salt=oemr_password_salt(); // Uses the functions defined in library/authentication/password_hashing.php
326 $hash=oemr_password_hash($this->iuserpass
, $salt);
327 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) {
328 $this->error_message
= "ERROR. Unable to add initial user\n" .
329 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
333 // Create the new style login credentials with blowfish and salt
334 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) {
335 $this->error_message
= "ERROR. Unable to add initial user login credentials\n" .
336 "<p>".mysqli_error($this->dbh
)." (#".mysqli_errno($this->dbh
).")\n";
340 // Add the official openemr users (services)
341 if ($this->load_file($this->additional_users
, "Additional Official Users") == false) {
349 * Create site directory if it is missing.
350 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
351 * @return name of the site directory or False
353 public function create_site_directory()
355 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
356 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id
;
357 $destination_directory = $GLOBALS['OE_SITE_DIR'];
358 if (! $this->recurse_copy($source_directory, $destination_directory)) {
359 $this->error_message
= "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message
;
367 public function write_configuration_file()
369 @touch
($this->conffile
); // php bug
370 $fd = @fopen
($this->conffile
, 'w');
372 $this->error_message
= 'unable to open configuration file for writing: ' . $this->conffile
;
382 $it_died = 0; //fmg: variable keeps running track of any errors
384 fwrite($fd, $string) or $it_died++
;
385 fwrite($fd, "\$host\t= '$this->server';\n") or $it_died++
;
386 fwrite($fd, "\$port\t= '$this->port';\n") or $it_died++
;
387 fwrite($fd, "\$login\t= '$this->login';\n") or $it_died++
;
388 fwrite($fd, "\$pass\t= '$this->pass';\n") or $it_died++
;
389 fwrite($fd, "\$dbase\t= '$this->dbname';\n\n") or $it_died++
;
390 fwrite($fd, "//Added ability to disable\n") or $it_died++
;
391 fwrite($fd, "//utf8 encoding - bm 05-2009\n") or $it_died++
;
392 fwrite($fd, "global \$disable_utf8_flag;\n") or $it_died++
;
393 fwrite($fd, "\$disable_utf8_flag = false;\n") or $it_died++
;
398 $sqlconf["host"]= $host;
399 $sqlconf["port"] = $port;
400 $sqlconf["login"] = $login;
401 $sqlconf["pass"] = $pass;
402 $sqlconf["dbase"] = $dbase;
403 //////////////////////////
404 //////////////////////////
405 //////////////////////////
406 //////DO NOT TOUCH THIS///
407 $config = 1; /////////////
408 //////////////////////////
409 //////////////////////////
410 //////////////////////////
413 ?
><?php
// done just for coloring
415 fwrite($fd, $string) or $it_died++
;
416 fclose($fd) or $it_died++
;
418 //it's rather irresponsible to not report errors when writing this file.
420 $this->error_message
= "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
427 public function insert_globals()
433 require(dirname(__FILE__
) . '/../globals.inc.php');
434 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
435 foreach ($grparr as $fldid => $fldarr) {
436 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
437 if (is_array($fldtype) ||
substr($fldtype, 0, 2) !== 'm_') {
438 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '" . $this->escapeSql($fldid) . "'");
439 $row = mysqli_fetch_array($res, MYSQLI_ASSOC
);
440 if (empty($row['count'])) {
441 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
442 "VALUES ( '" . $this->escapeSql($fldid) . "', '0', '" . $this->escapeSql($flddef) . "' )");
451 public function install_gacl()
453 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1
);
454 if (! $install_results_1) {
455 $this->error_message
= "install_gacl failed: unable to require gacl script 1";
459 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2
);
460 if (! $install_results_2) {
461 $this->error_message
= "install_gacl failed: unable to require gacl script 2";
465 $this->debug_message
.= $install_results_1 . $install_results_2;
469 public function quick_install()
471 // Validation of OpenEMR user settings
472 // (applicable if not cloning from another database)
473 if (empty($this->clone_database
)) {
474 if (! $this->login_is_valid()) {
478 if (! $this->iuser_is_valid()) {
482 if (! $this->user_password_is_valid()) {
487 // Validation of mysql database password
488 if (! $this->password_is_valid()) {
492 if (! $this->no_root_db_access
) {
493 // Connect to mysql via root user
494 if (! $this->root_database_connection()) {
498 // Create the dumpfile
499 // (applicable if cloning from another database)
500 if (! empty($this->clone_database
)) {
501 if (! $this->create_dumpfiles()) {
506 // Create the site directory
507 // (applicable if mirroring another local site)
508 if (! empty($this->source_site_id
)) {
509 if (! $this->create_site_directory()) {
515 if (! $this->user_database_connection()) {
516 // Re-connect to mysql via root user
517 if (! $this->root_database_connection()) {
521 // Create the mysql database
522 if (! $this->create_database()) {
526 // Create the mysql user
527 if (! $this->create_database_user()) {
531 // Grant user privileges to the mysql database
532 if (! $this->grant_privileges()) {
540 // Connect to mysql via created user
541 if (! $this->user_database_connection()) {
545 // Build the database
546 if (! $this->load_dumpfiles()) {
550 // Write the sql configuration file
551 if (! $this->write_configuration_file()) {
555 // Load the version information, globals settings,
556 // initial user, and set up gacl access controls.
557 // (applicable if not cloning from another database)
558 if (empty($this->clone_database
)) {
559 if (! $this->add_version_info()) {
563 if (! $this->insert_globals()) {
567 if (! $this->add_initial_user()) {
571 if (! $this->install_gacl()) {
579 private function escapeSql($sql)
581 return mysqli_real_escape_string($this->dbh
, $sql);
584 private function escapeDatabaseName($name)
586 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
587 error_log("Illegal character(s) in database name");
588 die("Illegal character(s) in database name");
593 private function escapeCollateName($name)
595 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
596 error_log("Illegal character(s) in collation name");
597 die("Illegal character(s) in collation name");
602 private function execute_sql($sql)
604 $this->error_message
= '';
606 $this->user_database_connection();
609 $results = mysqli_query($this->dbh
, $sql);
613 $error_mes = mysqli_error($this->dbh
);
614 $this->error_message
= "unable to execute SQL: '$sql' due to: " . $error_mes;
615 error_log("ERROR IN OPENEMR INSTALL: Unable to execute SQL: ".$sql." due to: ".$error_mes);
620 private function connect_to_database($server, $user, $password, $port, $dbname = '')
622 if ($server == "localhost") {
623 $dbh = mysqli_connect($server, $user, $password, $dbname);
625 $dbh = mysqli_connect($server, $user, $password, $dbname, $port);
631 private function set_sql_strict()
633 // Turn off STRICT SQL
634 return $this->execute_sql("SET sql_mode = ''");
637 private function set_collation()
639 if ($this->collate
) {
640 return $this->execute_sql("SET NAMES 'utf8'");
647 * innitialize $this->dumpfiles, an array of the dumpfiles that will
648 * be loaded into the database, including the correct translation
650 * The keys are the paths of the dumpfiles, and the values are the titles
653 private function initialize_dumpfile_list()
655 if ($this->clone_database
) {
656 $this->dumpfiles
= array( $this->get_backup_filename() => 'clone database' );
658 $dumpfiles = array( $this->main_sql
=> 'Main' );
659 if (! empty($this->development_translations
)) {
660 // Use the online development translation set
661 $dumpfiles[ $this->devel_translation_sql
] = "Online Development Language Translations (utf8)";
663 // Use the local translation set
664 $dumpfiles[ $this->translation_sql
] = "Language Translation (utf8)";
667 if ($this->ippf_specific
) {
668 $dumpfiles[ $this->ippf_sql
] = "IPPF Layout";
671 // Load ICD-9 codes if present.
672 if (file_exists($this->icd9
)) {
673 $dumpfiles[ $this->icd9
] = "ICD-9";
676 // Load CVX codes if present
677 if (file_exists($this->cvx
)) {
678 $dumpfiles[ $this->cvx
] = "CVX Immunization Codes";
681 $this->dumpfiles
= $dumpfiles;
684 return $this->dumpfiles
;
687 // http://www.php.net/manual/en/function.include.php
688 private function get_require_contents($filename)
690 if (is_file($filename)) {
693 $contents = ob_get_contents();
703 * Directory copy logic borrowed from a user comment at
704 * http://www.php.net/manual/en/function.copy.php
705 * @param string $src name of the directory to copy
706 * @param string $dst name of the destination to copy to
707 * @return bool indicating success
709 private function recurse_copy($src, $dst)
711 $dir = opendir($src);
712 if (! @mkdir
($dst)) {
713 $this->error_message
= "unable to create directory: '$dst'";
717 while (false !== ($file = readdir($dir))) {
718 if ($file != '.' && $file != '..') {
719 if (is_dir($src . '/' . $file)) {
720 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
722 copy($src . '/' . $file, $dst . '/' . $file);
733 * dump a site's database to a temporary file.
734 * @param string $source_site_id the site_id of the site to dump
735 * @return filename of the backup
737 private function dumpSourceDatabase()
739 global $OE_SITES_BASE;
740 $source_site_id = $this->source_site_id
;
742 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
744 if (empty($config)) {
745 die("Source site $source_site_id has not been set up!");
748 $backup_file = $this->get_backup_filename();
749 $cmd = "mysqldump -u " . escapeshellarg($login) .
750 " -p" . escapeshellarg($pass) .
751 " --opt --skip-extended-insert --quote-names -r $backup_file " .
752 escapeshellarg($dbase);
754 $tmp0 = exec($cmd, $tmp1 = array(), $tmp2);
756 die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
763 * @return filename of the source backup database for cloning
765 private function get_backup_filename()
767 if (stristr(PHP_OS
, 'WIN')) {
768 $backup_file = 'C:/windows/temp/setup_dump.sql';
770 $backup_file = '/tmp/setup_dump.sql';
778 This file is free software: you can redistribute it and/or modify it under the
779 terms of the GNU General Public License as publish by the Free Software
782 This file is distributed in the hope that it will be useful, but WITHOUT ANY
783 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
784 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
786 You should have received a copy of the GNU General Public Licence along with
787 this file. If not see <http://www.gnu.org/licenses/>.