Quick Install Script Improvements
[openemr.git] / library / classes / Installer.class.php
blob02a7d7b6748a33db5f69c664d96842ed9625debe
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->igroup = $cgi_variables['igroup'];
17 $this->server = $cgi_variables['server']; // mysql server (usually localhost)
18 $this->loginhost = $cgi_variables['loginhost']; // php/apache server (usually localhost)
19 $this->port = $cgi_variables['port'];
20 $this->root = $cgi_variables['root'];
21 $this->rootpass = $cgi_variables['rootpass'];
22 $this->login = $cgi_variables['login'];
23 $this->pass = $cgi_variables['pass'];
24 $this->dbname = $cgi_variables['dbname'];
25 $this->collate = $cgi_variables['collate'];
26 $this->site = $cgi_variables['site'];
27 $this->source_site_id = $cgi_variables['source_site_id'];
28 $this->clone_database = $cgi_variables['clone_database'];
29 $this->no_root_db_access = $cgi_variables['no_root_db_access']; // no root access to database. user/privileges pre-configured
30 $this->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://github.com/openemr/translations_development_openemr/raw/master/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 iuser_is_valid()
75 if ( strpos($this->iuser, " ") ) {
76 $this->error_message = "Initial user is invalid: '$this->iuser'";
77 return FALSE;
79 return TRUE;
82 public function password_is_valid()
84 if ( $this->pass == "" || !isset($this->pass) ) {
85 $this->error_message = "The password for the new database account is invalid: '$this->pass'";
86 return FALSE;
88 return TRUE;
91 public function user_password_is_valid()
93 if ( $this->iuserpass == "" || !isset($this->iuserpass) ) {
94 $this->error_message = "The password for the user is invalid: '$this->iuserpass'";
95 return FALSE;
97 return TRUE;
100 public function root_database_connection()
102 $this->dbh = $this->connect_to_database( $this->server, $this->root, $this->rootpass, $this->port );
103 if ( $this->dbh ) {
104 return TRUE;
105 } else {
106 $this->error_message = 'unable to connect to database as root';
107 return FALSE;
111 public function user_database_connection()
113 $this->dbh = $this->connect_to_database( $this->server, $this->login, $this->pass, $this->port );
114 if ( ! $this->dbh ) {
115 $this->error_message = "unable to connect to database as user: '$this->login'";
116 return FALSE;
118 if ( ! $this->set_collation() ) {
119 return FALSE;
121 if ( ! mysql_select_db($this->dbname, $this->dbh) ) {
122 $this->error_message = "unable to select database: '$this->dbname'";
123 return FALSE;
125 return TRUE;
128 public function create_database() {
129 $sql = "create database $this->dbname";
130 if ($this->collate) {
131 $sql .= " character set utf8 collate $this->collate";
132 $this->set_collation();
134 return $this->execute_sql($sql);
137 public function drop_database() {
138 $sql = "drop database if exists $this->dbname";
139 return $this->execute_sql($sql);
142 public function grant_privileges() {
143 return $this->execute_sql( "GRANT ALL PRIVILEGES ON $this->dbname.* TO '$this->login'@'$this->loginhost' IDENTIFIED BY '$this->pass'" );
146 public function disconnect() {
147 return mysql_close($this->dbh);
151 * This method creates any dumpfiles necessary.
152 * This is actually only done if we're cloning an existing site
153 * and we need to dump their database into a file.
154 * @return bool indicating success
156 public function create_dumpfiles() {
157 return $this->dumpSourceDatabase();
160 public function load_dumpfiles() {
161 $sql_results = ''; // information string which is returned
162 foreach ($this->dumpfiles as $filename => $title) {
163 $sql_results_temp = '';
164 $sql_results_temp = $this->load_file($filename,$title);
165 if ($sql_results_temp == FALSE) return FALSE;
166 $sql_results .= $sql_results_temp;
168 return $sql_results;
171 public function load_file($filename,$title) {
172 $sql_results = ''; // information string which is returned
173 $sql_results .= "Creating $title tables...\n";
174 $fd = fopen($filename, 'r');
175 if ($fd == FALSE) {
176 $this->error_message = "ERROR. Could not open dumpfile '$filename'.\n";
177 return FALSE;
179 $query = "";
180 $line = "";
181 while (!feof ($fd)){
182 $line = fgets($fd,1024);
183 $line = rtrim($line);
184 if (substr($line,0,2) == "--") // Kill comments
185 continue;
186 if (substr($line,0,1) == "#") // Kill comments
187 continue;
188 if ($line == "")
189 continue;
190 $query = $query.$line; // Check for full query
191 $chr = substr($query,strlen($query)-1,1);
192 if ($chr == ";") { // valid query, execute
193 $query = rtrim($query,";");
194 $this->execute_sql( $query );
195 $query = "";
198 $sql_results .= "OK<br>\n";
199 fclose($fd);
200 return $sql_results;
203 public function add_version_info() {
204 include dirname(__FILE__) . "/../../version.php";
205 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) {
206 $this->error_message = "ERROR. Unable insert version information into database\n" .
207 "<p>".mysql_error()." (#".mysql_errno().")\n";
208 return FALSE;
210 return TRUE;
213 public function add_initial_user() {
214 if ($this->execute_sql("INSERT INTO groups (id, name, user) VALUES (1,'$this->igroup','$this->iuser')") == FALSE) {
215 $this->error_message = "ERROR. Unable to add initial user group\n" .
216 "<p>".mysql_error()." (#".mysql_errno().")\n";
217 return FALSE;
219 $password_hash = sha1( $this->iuserpass );
220 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','',3,1,3)") == FALSE) {
221 $this->error_message = "ERROR. Unable to add initial user\n" .
222 "<p>".mysql_error()." (#".mysql_errno().")\n";
223 return FALSE;
225 // Add the official openemr users (services)
226 if ($this->load_file($this->additional_users,"Additional Official Users") == FALSE) return FALSE;
228 return TRUE;
232 * Create site directory if it is missing.
233 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
234 * @return name of the site directory or False
236 public function create_site_directory() {
237 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
238 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id;
239 $destination_directory = $GLOBALS['OE_SITE_DIR'];
240 if ( ! $this->recurse_copy( $source_directory, $destination_directory ) ) {
241 $this->error_message = "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message;
242 return False;
245 return True;
248 public function write_configuration_file() {
249 @touch($this->conffile); // php bug
250 $fd = @fopen($this->conffile, 'w');
251 if ( ! $fd ) {
252 $this->error_message = 'unable to open configuration file for writing: ' . $this->conffile;
253 return False;
255 $string = '<?php
256 // OpenEMR
257 // MySQL Config
261 $it_died = 0; //fmg: variable keeps running track of any errors
263 fwrite($fd,$string) or $it_died++;
264 fwrite($fd,"\$host\t= '$this->server';\n") or $it_died++;
265 fwrite($fd,"\$port\t= '$this->port';\n") or $it_died++;
266 fwrite($fd,"\$login\t= '$this->login';\n") or $it_died++;
267 fwrite($fd,"\$pass\t= '$this->pass';\n") or $it_died++;
268 fwrite($fd,"\$dbase\t= '$this->dbname';\n\n") or $it_died++;
269 fwrite($fd,"//Added ability to disable\n") or $it_died++;
270 fwrite($fd,"//utf8 encoding - bm 05-2009\n") or $it_died++;
271 fwrite($fd,"global \$disable_utf8_flag;\n") or $it_died++;
272 fwrite($fd,"\$disable_utf8_flag = false;\n") or $it_died++;
274 $string = '
275 $sqlconf = array();
276 global $sqlconf;
277 $sqlconf["host"]= $host;
278 $sqlconf["port"] = $port;
279 $sqlconf["login"] = $login;
280 $sqlconf["pass"] = $pass;
281 $sqlconf["dbase"] = $dbase;
282 //////////////////////////
283 //////////////////////////
284 //////////////////////////
285 //////DO NOT TOUCH THIS///
286 $config = 1; /////////////
287 //////////////////////////
288 //////////////////////////
289 //////////////////////////
292 ?><?php // done just for coloring
294 fwrite($fd,$string) or $it_died++;
295 fclose($fd) or $it_died++;
297 //it's rather irresponsible to not report errors when writing this file.
298 if ($it_died != 0) {
299 $this->error_message = "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
300 return FALSE;
303 return TRUE;
306 public function insert_globals() {
307 function xl($s) { return $s; }
308 require(dirname(__FILE__) . '/../globals.inc.php');
309 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
310 foreach ($grparr as $fldid => $fldarr) {
311 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
312 if (is_array($fldtype) || substr($fldtype, 0, 2) !== 'm_') {
313 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '$fldid'");
314 $row = @mysql_fetch_array($res, MYSQL_ASSOC);
315 if (empty($row['count'])) {
316 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
317 "VALUES ( '$fldid', '0', '$flddef' )");
322 return TRUE;
325 public function install_gacl()
327 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1);
328 if (! $install_results_1 ) {
329 $this->error_message = "install_gacl failed: unable to require gacl script 1";
330 return FALSE;
333 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2);
334 if (! $install_results_2 ) {
335 $this->error_message = "install_gacl failed: unable to require gacl script 2";
336 return FALSE;
338 $this->debug_message .= $install_results_1 . $install_results_2;
339 return TRUE;
342 public function quick_install() {
343 // Validation of OpenEMR user settings
344 // (applicable if not cloning from another database)
345 if (empty($this->clone_database)) {
346 if ( ! $this->login_is_valid() ) {
347 return False;
349 if ( ! $this->iuser_is_valid() ) {
350 return False;
352 if ( ! $this->user_password_is_valid() ) {
353 return False;
356 // Validation of mysql database password
357 if ( ! $this->password_is_valid() ) {
358 return False;
360 if (! $this->no_root_db_access) {
361 // Connect to mysql via root user
362 if (! $this->root_database_connection() ) {
363 return False;
365 // Create the dumpfile
366 // (applicable if cloning from another database)
367 if (! empty($this->clone_database)) {
368 if ( ! $this->create_dumpfiles() ) {
369 return False;
372 // Create the site directory
373 // (applicable if mirroring another local site)
374 if ( ! empty($this->source_site_id) ) {
375 if ( ! $this->create_site_directory() ) {
376 return False;
379 $this->disconnect();
380 if (! $this->user_database_connection()) {
381 // Re-connect to mysql via root user
382 if (! $this->root_database_connection() ) {
383 return False;
385 // Create the mysql database
386 if ( ! $this->create_database()) {
387 return False;
389 // Grant user privileges to the mysql database
390 if ( ! $this->grant_privileges() ) {
391 return False;
394 $this->disconnect();
396 // Connect to mysql via created user
397 if ( ! $this->user_database_connection() ) {
398 return False;
400 // Build the database
401 if ( ! $this->load_dumpfiles() ) {
402 return False;
404 // Write the sql configuration file
405 if ( ! $this->write_configuration_file() ) {
406 return False;
408 // Load the version information, globals settings,
409 // initial user, and set up gacl access controls.
410 // (applicable if not cloning from another database)
411 if (empty($this->clone_database)) {
412 if ( ! $this->add_version_info() ) {
413 return False;
415 if ( ! $this->insert_globals() ) {
416 return False;
418 if ( ! $this->add_initial_user() ) {
419 return False;
421 if ( ! $this->install_gacl()) {
422 return False;
426 return True;
429 private function execute_sql( $sql ) {
430 $this->error_message = '';
431 if ( ! $this->dbh ) {
432 $this->user_database_connection();
434 $results = mysql_query($sql, $this->dbh);
435 if ( $results ) {
436 return $results;
437 } else {
438 $this->error_message = "unable to execute SQL: '$sql' due to: " . mysql_error();
439 return False;
443 private function connect_to_database( $server, $user, $password, $port )
445 if ($server == "localhost")
446 $dbh = mysql_connect($server, $user, $password);
447 else
448 $dbh = mysql_connect("$server:$port", $user, $password);
449 return $dbh;
452 private function set_collation()
454 if ($this->collate) {
455 return $this->execute_sql("SET NAMES 'utf8'");
457 return TRUE;
461 * innitialize $this->dumpfiles, an array of the dumpfiles that will
462 * be loaded into the database, including the correct translation
463 * dumpfile.
464 * The keys are the paths of the dumpfiles, and the values are the titles
465 * @return array
467 private function initialize_dumpfile_list() {
468 if ( $this->clone_database ) {
469 $this->dumpfiles = array( $this->get_backup_filename() => 'clone database' );
470 } else {
471 $dumpfiles = array( $this->main_sql => 'Main' );
472 if (! empty($this->development_translations)) {
473 // Use the online development translation set
474 $dumpfiles[ $this->devel_translation_sql ] = "Online Development Language Translations (utf8)";
476 else {
477 // Use the local translation set
478 $dumpfiles[ $this->translation_sql ] = "Language Translation (utf8)";
480 if ($this->ippf_specific) {
481 $dumpfiles[ $this->ippf_sql ] = "IPPF Layout";
483 // Load ICD-9 codes if present.
484 if (file_exists( $this->icd9 )) {
485 $dumpfiles[ $this->icd9 ] = "ICD-9";
487 // Load CVX codes if present
488 if (file_exists( $this->cvx )) {
489 $dumpfiles[ $this->cvx ] = "CVX Immunization Codes";
491 $this->dumpfiles = $dumpfiles;
493 return $this->dumpfiles;
496 // http://www.php.net/manual/en/function.include.php
497 private function get_require_contents($filename) {
498 if (is_file($filename)) {
499 ob_start();
500 require $filename;
501 $contents = ob_get_contents();
502 ob_end_clean();
503 return $contents;
505 return false;
510 * Directory copy logic borrowed from a user comment at
511 * http://www.php.net/manual/en/function.copy.php
512 * @param string $src name of the directory to copy
513 * @param string $dst name of the destination to copy to
514 * @return bool indicating success
516 private function recurse_copy($src, $dst) {
517 $dir = opendir($src);
518 if ( ! @mkdir($dst) ) {
519 $this->error_message = "unable to create directory: '$dst'";
520 return False;
522 while(false !== ($file = readdir($dir))) {
523 if ($file != '.' && $file != '..') {
524 if (is_dir($src . '/' . $file)) {
525 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
527 else {
528 copy($src . '/' . $file, $dst . '/' . $file);
532 closedir($dir);
533 return True;
538 * dump a site's database to a temporary file.
539 * @param string $source_site_id the site_id of the site to dump
540 * @return filename of the backup
542 private function dumpSourceDatabase() {
543 global $OE_SITES_BASE;
544 $source_site_id = $this->source_site_id;
546 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
548 if (empty($config)) die("Source site $source_site_id has not been set up!");
550 $backup_file = $this->get_backup_filename();
551 $cmd = "mysqldump -u " . escapeshellarg($login) .
552 " -p" . escapeshellarg($pass) .
553 " --opt --skip-extended-insert --quote-names -r $backup_file " .
554 escapeshellarg($dbase);
556 $tmp0 = exec($cmd, $tmp1=array(), $tmp2);
557 if ($tmp2) die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
559 return $backup_file;
563 * @return filename of the source backup database for cloning
565 private function get_backup_filename() {
566 if (stristr(PHP_OS, 'WIN')) {
567 $backup_file = 'C:/windows/temp/setup_dump.sql';
569 else {
570 $backup_file = '/tmp/setup_dump.sql';
572 return $backup_file;
578 This file is free software: you can redistribute it and/or modify it under the
579 terms of the GNU General Public License as publish by the Free Software
580 Foundation.
582 This file is distributed in the hope that it will be useful, but WITHOUT ANY
583 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
584 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
586 You should have received a copy of the GNU General Public Licence along with
587 this file. If not see <http://www.gnu.org/licenses/>.