74cc57d3a6138e3c341d2557e8dafe9271ada04e
[openemr.git] / library / classes / Installer.class.php
blob74cc57d3a6138e3c341d2557e8dafe9271ada04e
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->development_translations = $cgi_variables['development_translations'];
31 // Make this true for IPPF.
32 $this->ippf_specific = false;
34 // Record name of sql access file
35 $GLOBALS['OE_SITES_BASE'] = dirname(__FILE__) . '/../../sites';
36 $GLOBALS['OE_SITE_DIR'] = $GLOBALS['OE_SITES_BASE'] . '/' . $this->site;
37 $this->conffile = $GLOBALS['OE_SITE_DIR'] . '/sqlconf.php';
39 // Record names of sql table files
40 $this->main_sql = dirname(__FILE__) . '/../../sql/database.sql';
41 $this->translation_sql = dirname(__FILE__) . '/../../contrib/util/language_translations/currentLanguage_utf8.sql';
42 $this->devel_translation_sql = "http://github.com/openemr/translations_development_openemr/raw/master/languageTranslations_utf8.sql";
43 $this->ippf_sql = dirname(__FILE__) . "/../../sql/ippf_layout.sql";
44 $this->icd9 = dirname(__FILE__) . "/../../sql/icd9.sql";
45 $this->cvx = dirname(__FILE__) . "/../../sql/cvx_codes.sql";
46 $this->additional_users = dirname(__FILE__) . "/../../sql/official_additional_users.sql";
48 // Record name of php-gacl installation files
49 $this->gaclSetupScript1 = dirname(__FILE__) . "/../../gacl/setup.php";
50 $this->gaclSetupScript2 = dirname(__FILE__) . "/../../acl_setup.php";
52 // Prepare the dumpfile list
53 $this->initialize_dumpfile_list();
55 // Entities to hold error and debug messages
56 $this->error_message = '';
57 $this->debug_message = '';
59 // Entity to hold sql connection
60 $this->dbh = false;
63 public function login_is_valid()
65 if ( ($this->login == '') || (! isset( $this->login )) ) {
66 $this->error_message = "login is invalid: '$this->login'";
67 return FALSE;
69 return TRUE;
72 public function iuser_is_valid()
74 if ( strpos($this->iuser, " ") ) {
75 $this->error_message = "Initial user is invalid: '$this->iuser'";
76 return FALSE;
78 return TRUE;
81 public function password_is_valid()
83 if ( $this->pass == "" || !isset($this->pass) ) {
84 $this->error_message = "The password for the new database account is invalid: '$this->pass'";
85 return FALSE;
87 return TRUE;
90 public function user_password_is_valid()
92 if ( $this->iuserpass == "" || !isset($this->iuserpass) ) {
93 $this->error_message = "The password for the user is invalid: '$this->iuserpass'";
94 return FALSE;
96 return TRUE;
99 public function root_database_connection()
101 $this->dbh = $this->connect_to_database( $this->server, $this->root, $this->rootpass, $this->port );
102 if ( $this->dbh ) {
103 return TRUE;
104 } else {
105 $this->error_message = 'unable to connect to database as root';
106 return FALSE;
110 public function user_database_connection()
112 $this->dbh = $this->connect_to_database( $this->server, $this->login, $this->pass, $this->port );
113 if ( ! $this->dbh ) {
114 $this->error_message = "unable to connect to database as user: '$this->login'";
115 return FALSE;
117 if ( ! $this->set_collation() ) {
118 return FALSE;
120 if ( ! mysql_select_db($this->dbname, $this->dbh) ) {
121 $this->error_message = "unable to select database: '$this->dbname'";
122 return FALSE;
124 return TRUE;
127 public function create_database() {
128 $sql = "create database $this->dbname";
129 if ($this->collate) {
130 $sql .= " character set utf8 collate $this->collate";
131 $this->set_collation();
133 return $this->execute_sql($sql);
136 public function drop_database() {
137 $sql = "drop database if exists $this->dbname";
138 return $this->execute_sql($sql);
141 public function grant_privileges() {
142 return $this->execute_sql( "GRANT ALL PRIVILEGES ON $this->dbname.* TO '$this->login'@'$this->loginhost' IDENTIFIED BY '$this->pass'" );
145 public function disconnect() {
146 return mysql_close($this->dbh);
150 * This method creates any dumpfiles necessary.
151 * This is actually only done if we're cloning an existing site
152 * and we need to dump their database into a file.
153 * @return bool indicating success
155 public function create_dumpfiles() {
156 return $this->dumpSourceDatabase();
159 public function load_dumpfiles() {
160 $sql_results = ''; // information string which is returned
161 foreach ($this->dumpfiles as $filename => $title) {
162 $sql_results_temp = '';
163 $sql_results_temp = $this->load_file($filename,$title);
164 if ($sql_results_temp == FALSE) return FALSE;
165 $sql_results .= $sql_results_temp;
167 return $sql_results;
170 public function load_file($filename,$title) {
171 $sql_results = ''; // information string which is returned
172 $sql_results .= "Creating $title tables...\n";
173 $fd = fopen($filename, 'r');
174 if ($fd == FALSE) {
175 $this->error_message = "ERROR. Could not open dumpfile '$filename'.\n";
176 return FALSE;
178 $query = "";
179 $line = "";
180 while (!feof ($fd)){
181 $line = fgets($fd,1024);
182 $line = rtrim($line);
183 if (substr($line,0,2) == "--") // Kill comments
184 continue;
185 if (substr($line,0,1) == "#") // Kill comments
186 continue;
187 if ($line == "")
188 continue;
189 $query = $query.$line; // Check for full query
190 $chr = substr($query,strlen($query)-1,1);
191 if ($chr == ";") { // valid query, execute
192 $query = rtrim($query,";");
193 $this->execute_sql( $query );
194 $query = "";
197 $sql_results .= "OK<br>\n";
198 fclose($fd);
199 return $sql_results;
202 public function add_version_info() {
203 include dirname(__FILE__) . "/../../version.php";
204 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) {
205 $this->error_message = "ERROR. Unable insert version information into database\n" .
206 "<p>".mysql_error()." (#".mysql_errno().")\n";
207 return FALSE;
209 return TRUE;
212 public function add_initial_user() {
213 if ($this->execute_sql("INSERT INTO groups (id, name, user) VALUES (1,'$this->igroup','$this->iuser')") == FALSE) {
214 $this->error_message = "ERROR. Unable to add initial user group\n" .
215 "<p>".mysql_error()." (#".mysql_errno().")\n";
216 return FALSE;
218 $password_hash = sha1( $this->iuserpass );
219 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) {
220 $this->error_message = "ERROR. Unable to add initial user\n" .
221 "<p>".mysql_error()." (#".mysql_errno().")\n";
222 return FALSE;
224 // Add the official openemr users (services)
225 if ($this->load_file($this->additional_users,"Additional Official Users") == FALSE) return FALSE;
227 return TRUE;
231 * Create site directory if it is missing.
232 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
233 * @return name of the site directory or False
235 public function create_site_directory() {
236 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
237 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id;
238 $destination_directory = $GLOBALS['OE_SITE_DIR'];
239 if ( ! $this->recurse_copy( $source_directory, $destination_directory ) ) {
240 $this->error_message = "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message;
241 return False;
244 return True;
247 public function write_configuration_file() {
248 @touch($this->conffile); // php bug
249 $fd = @fopen($this->conffile, 'w');
250 if ( ! $fd ) {
251 $this->error_message = 'unable to open configuration file for writing: ' . $this->conffile;
252 return False;
254 $string = '<?php
255 // OpenEMR
256 // MySQL Config
260 $it_died = 0; //fmg: variable keeps running track of any errors
262 fwrite($fd,$string) or $it_died++;
263 fwrite($fd,"\$host\t= '$this->server';\n") or $it_died++;
264 fwrite($fd,"\$port\t= '$this->port';\n") or $it_died++;
265 fwrite($fd,"\$login\t= '$this->login';\n") or $it_died++;
266 fwrite($fd,"\$pass\t= '$this->pass';\n") or $it_died++;
267 fwrite($fd,"\$dbase\t= '$this->dbname';\n\n") or $it_died++;
268 fwrite($fd,"//Added ability to disable\n") or $it_died++;
269 fwrite($fd,"//utf8 encoding - bm 05-2009\n") or $it_died++;
270 fwrite($fd,"global \$disable_utf8_flag;\n") or $it_died++;
271 fwrite($fd,"\$disable_utf8_flag = false;\n") or $it_died++;
273 $string = '
274 $sqlconf = array();
275 global $sqlconf;
276 $sqlconf["host"]= $host;
277 $sqlconf["port"] = $port;
278 $sqlconf["login"] = $login;
279 $sqlconf["pass"] = $pass;
280 $sqlconf["dbase"] = $dbase;
281 //////////////////////////
282 //////////////////////////
283 //////////////////////////
284 //////DO NOT TOUCH THIS///
285 $config = 1; /////////////
286 //////////////////////////
287 //////////////////////////
288 //////////////////////////
291 ?><?php // done just for coloring
293 fwrite($fd,$string) or $it_died++;
294 fclose($fd) or $it_died++;
296 //it's rather irresponsible to not report errors when writing this file.
297 if ($it_died != 0) {
298 $this->error_message = "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
299 return FALSE;
302 return TRUE;
305 public function insert_globals() {
306 function xl($s) { return $s; }
307 require(dirname(__FILE__) . '/../globals.inc.php');
308 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
309 foreach ($grparr as $fldid => $fldarr) {
310 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
311 if (substr($fldtype, 0, 2) !== 'm_') {
312 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '$fldid'");
313 $row = @mysql_fetch_array($res, MYSQL_ASSOC);
314 if (empty($row['count'])) {
315 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
316 "VALUES ( '$fldid', '0', '$flddef' )");
321 return TRUE;
324 public function install_gacl()
326 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1);
327 if (! $install_results_1 ) {
328 $this->error_message = "install_gacl failed: unable to require gacl script 1";
329 return FALSE;
332 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2);
333 if (! $install_results_2 ) {
334 $this->error_message = "install_gacl failed: unable to require gacl script 2";
335 return FALSE;
337 $this->debug_message .= $install_results_1 . $install_results_2;
338 return TRUE;
341 public function quick_install() {
342 // Validation of OpenEMR user settings
343 // (applicable if not cloning from another database)
344 if (empty($this->clone_database)) {
345 if ( ! $this->login_is_valid() ) {
346 return False;
348 if ( ! $this->iuser_is_valid() ) {
349 return False;
351 if ( ! $this->user_password_is_valid() ) {
352 return False;
355 // Validation of mysql database password
356 if ( ! $this->password_is_valid() ) {
357 return False;
359 // Connect to mysql via root user
360 if (! $this->root_database_connection() ) {
361 return False;
363 // Create the dumpfile
364 // (applicable if cloning from another database)
365 if (! empty($this->clone_database)) {
366 if ( ! $this->create_dumpfiles() ) {
367 return False;
370 // Create the site directory
371 // (applicable if mirroring another local site)
372 if ( ! empty($this->source_site_id) ) {
373 if ( ! $this->create_site_directory() ) {
374 return False;
377 // Create the mysql database
378 if ( ! $this->create_database()) {
379 return False;
381 // Grant user privileges to the mysql database
382 if ( ! $this->grant_privileges() ) {
383 return False;
385 // Connect to mysql via created user
386 $this->disconnect();
387 if ( ! $this->user_database_connection() ) {
388 return False;
390 // Build the database
391 if ( ! $this->load_dumpfiles() ) {
392 return False;
394 // Write the sql configuration file
395 if ( ! $this->write_configuration_file() ) {
396 return False;
398 // Load the version information, globals settings,
399 // initial user, and set up gacl access controls.
400 // (applicable if not cloning from another database)
401 if (empty($this->clone_database)) {
402 if ( ! $this->add_version_info() ) {
403 return False;
405 if ( ! $this->insert_globals() ) {
406 return False;
408 if ( ! $this->add_initial_user() ) {
409 return False;
411 if ( ! $this->install_gacl()) {
412 return False;
416 return True;
419 private function execute_sql( $sql ) {
420 $this->error_message = '';
421 if ( ! $this->dbh ) {
422 $this->user_database_connection();
424 $results = mysql_query($sql, $this->dbh);
425 if ( $results ) {
426 return $results;
427 } else {
428 $this->error_message = "unable to execute SQL: '$sql' due to: " . mysql_error();
429 return False;
433 private function connect_to_database( $server, $user, $password, $port )
435 if ($server == "localhost")
436 $dbh = mysql_connect($server, $user, $password);
437 else
438 $dbh = mysql_connect("$server:$port", $user, $password);
439 return $dbh;
442 private function set_collation()
444 if ($this->collate) {
445 return $this->execute_sql("SET NAMES 'utf8'");
447 return TRUE;
451 * innitialize $this->dumpfiles, an array of the dumpfiles that will
452 * be loaded into the database, including the correct translation
453 * dumpfile.
454 * The keys are the paths of the dumpfiles, and the values are the titles
455 * @return array
457 private function initialize_dumpfile_list() {
458 if ( $this->clone_database ) {
459 $this->dumpfiles = array( $this->get_backup_filename() => 'clone database' );
460 } else {
461 $dumpfiles = array( $this->main_sql => 'Main' );
462 if (! empty($this->development_translations)) {
463 // Use the online development translation set
464 $dumpfiles[ $this->devel_translation_sql ] = "Online Development Language Translations (utf8)";
466 else {
467 // Use the local translation set
468 $dumpfiles[ $this->translation_sql ] = "Language Translation (utf8)";
470 if ($this->ippf_specific) {
471 $dumpfiles[ $this->ippf_sql ] = "IPPF Layout";
473 // Load ICD-9 codes if present.
474 if (file_exists( $this->icd9 )) {
475 $dumpfiles[ $this->icd9 ] = "ICD-9";
477 // Load CVX codes if present
478 if (file_exists( $this->cvx )) {
479 $dumpfiles[ $this->cvx ] = "CVX Immunization Codes";
481 $this->dumpfiles = $dumpfiles;
483 return $this->dumpfiles;
486 // http://www.php.net/manual/en/function.include.php
487 private function get_require_contents($filename) {
488 if (is_file($filename)) {
489 ob_start();
490 require $filename;
491 $contents = ob_get_contents();
492 ob_end_clean();
493 return $contents;
495 return false;
500 * Directory copy logic borrowed from a user comment at
501 * http://www.php.net/manual/en/function.copy.php
502 * @param string $src name of the directory to copy
503 * @param string $dst name of the destination to copy to
504 * @return bool indicating success
506 private function recurse_copy($src, $dst) {
507 $dir = opendir($src);
508 if ( ! @mkdir($dst) ) {
509 $this->error_message = "unable to create directory: '$dst'";
510 return False;
512 while(false !== ($file = readdir($dir))) {
513 if ($file != '.' && $file != '..') {
514 if (is_dir($src . '/' . $file)) {
515 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
517 else {
518 copy($src . '/' . $file, $dst . '/' . $file);
522 closedir($dir);
523 return True;
528 * dump a site's database to a temporary file.
529 * @param string $source_site_id the site_id of the site to dump
530 * @return filename of the backup
532 private function dumpSourceDatabase() {
533 global $OE_SITES_BASE;
534 $source_site_id = $this->source_site_id;
536 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
538 if (empty($config)) die("Source site $source_site_id has not been set up!");
540 $backup_file = $this->get_backup_filename();
541 $cmd = "mysqldump -u " . escapeshellarg($login) .
542 " -p" . escapeshellarg($pass) .
543 " --opt --skip-extended-insert --quote-names -r $backup_file " .
544 escapeshellarg($dbase);
546 $tmp0 = exec($cmd, $tmp1=array(), $tmp2);
547 if ($tmp2) die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
549 return $backup_file;
553 * @return filename of the source backup database for cloning
555 private function get_backup_filename() {
556 if (stristr(PHP_OS, 'WIN')) {
557 $backup_file = 'C:/windows/temp/setup_dump.sql';
559 else {
560 $backup_file = '/tmp/setup_dump.sql';
562 return $backup_file;
568 This file is free software: you can redistribute it and/or modify it under the
569 terms of the GNU General Public License as publish by the Free Software
570 Foundation.
572 This file is distributed in the hope that it will be useful, but WITHOUT ANY
573 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
574 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
576 You should have received a copy of the GNU General Public Licence along with
577 this file. If not see <http://www.gnu.org/licenses/>.