cbb0716cb040aff2eabe3779bec97f157e330f23
[openemr.git] / library / classes / Installer.class.php
blobcbb0716cb040aff2eabe3779bec97f157e330f23
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 .= $this->load_file($filename,$title);
163 if ($sql_results == FALSE) return FALSE;
165 return $sql_results;
168 public function load_file($filename,$title) {
169 $sql_results = ''; // information string which is returned
170 $sql_results .= "Creating $title tables...\n";
171 $fd = fopen($filename, 'r');
172 if ($fd == FALSE) {
173 $this->error_message = "ERROR. Could not open dumpfile '$filename'.\n";
174 return FALSE;
176 $query = "";
177 $line = "";
178 while (!feof ($fd)){
179 $line = fgets($fd,1024);
180 $line = rtrim($line);
181 if (substr($line,0,2) == "--") // Kill comments
182 continue;
183 if (substr($line,0,1) == "#") // Kill comments
184 continue;
185 if ($line == "")
186 continue;
187 $query = $query.$line; // Check for full query
188 $chr = substr($query,strlen($query)-1,1);
189 if ($chr == ";") { // valid query, execute
190 $query = rtrim($query,";");
191 $this->execute_sql( $query );
192 $query = "";
195 $sql_results .= "OK<br>\n";
196 fclose($fd);
197 return $sql_results;
200 public function add_version_info() {
201 include dirname(__FILE__) . "/../../version.php";
202 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) {
203 $this->error_message = "ERROR. Unable insert version information into database\n" .
204 "<p>".mysql_error()." (#".mysql_errno().")\n";
205 return FALSE;
207 return TRUE;
210 public function add_initial_user() {
211 if ($this->execute_sql("INSERT INTO groups (id, name, user) VALUES (1,'$this->igroup','$this->iuser')") == FALSE) {
212 $this->error_message = "ERROR. Unable to add initial user group\n" .
213 "<p>".mysql_error()." (#".mysql_errno().")\n";
214 return FALSE;
216 $password_hash = sha1( $this->iuserpass );
217 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) {
218 $this->error_message = "ERROR. Unable to add initial user\n" .
219 "<p>".mysql_error()." (#".mysql_errno().")\n";
220 return FALSE;
222 // Add the official openemr users (services)
223 if ($this->load_file($this->additional_users,"Additional Official Users") == FALSE) return FALSE;
225 return TRUE;
229 * Create site directory if it is missing.
230 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
231 * @return name of the site directory or False
233 public function create_site_directory() {
234 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
235 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id;
236 $destination_directory = $GLOBALS['OE_SITE_DIR'];
237 if ( ! $this->recurse_copy( $source_directory, $destination_directory ) ) {
238 $this->error_message = "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message;
239 return False;
242 return True;
245 public function write_configuration_file() {
246 @touch($this->conffile); // php bug
247 $fd = @fopen($this->conffile, 'w');
248 if ( ! $fd ) {
249 $this->error_message = 'unable to open configuration file for writing: ' . $this->conffile;
250 return False;
252 $string = '<?php
253 // OpenEMR
254 // MySQL Config
258 $it_died = 0; //fmg: variable keeps running track of any errors
260 fwrite($fd,$string) or $it_died++;
261 fwrite($fd,"\$host\t= '$this->server';\n") or $it_died++;
262 fwrite($fd,"\$port\t= '$this->port';\n") or $it_died++;
263 fwrite($fd,"\$login\t= '$this->login';\n") or $it_died++;
264 fwrite($fd,"\$pass\t= '$this->pass';\n") or $it_died++;
265 fwrite($fd,"\$dbase\t= '$this->dbname';\n\n") or $it_died++;
266 fwrite($fd,"//Added ability to disable\n") or $it_died++;
267 fwrite($fd,"//utf8 encoding - bm 05-2009\n") or $it_died++;
268 fwrite($fd,"global \$disable_utf8_flag;\n") or $it_died++;
269 fwrite($fd,"\$disable_utf8_flag = false;\n") or $it_died++;
271 $string = '
272 $sqlconf = array();
273 global $sqlconf;
274 $sqlconf["host"]= $host;
275 $sqlconf["port"] = $port;
276 $sqlconf["login"] = $login;
277 $sqlconf["pass"] = $pass;
278 $sqlconf["dbase"] = $dbase;
279 //////////////////////////
280 //////////////////////////
281 //////////////////////////
282 //////DO NOT TOUCH THIS///
283 $config = 1; /////////////
284 //////////////////////////
285 //////////////////////////
286 //////////////////////////
289 ?><?php // done just for coloring
291 fwrite($fd,$string) or $it_died++;
292 fclose($fd) or $it_died++;
294 //it's rather irresponsible to not report errors when writing this file.
295 if ($it_died != 0) {
296 $this->error_message = "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
297 return FALSE;
300 return TRUE;
303 public function insert_globals() {
304 function xl($s) { return $s; }
305 require(dirname(__FILE__) . '/../globals.inc.php');
306 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
307 foreach ($grparr as $fldid => $fldarr) {
308 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
309 if (substr($fldtype, 0, 2) !== 'm_') {
310 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '$fldid'");
311 $row = @mysql_fetch_array($res, MYSQL_ASSOC);
312 if (empty($row['count'])) {
313 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
314 "VALUES ( '$fldid', '0', '$flddef' )");
319 return TRUE;
322 public function install_gacl()
324 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1);
325 if (! $install_results_1 ) {
326 $this->error_message = "install_gacl failed: unable to require gacl script 1";
327 return FALSE;
330 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2);
331 if (! $install_results_2 ) {
332 $this->error_message = "install_gacl failed: unable to require gacl script 2";
333 return FALSE;
335 $this->debug_message .= $install_results_1 . $install_results_2;
336 return TRUE;
339 public function quick_install() {
340 // Validation of OpenEMR user settings
341 // (applicable if not cloning from another database)
342 if (empty($this->clone_database)) {
343 if ( ! $this->login_is_valid() ) {
344 return False;
346 if ( ! $this->iuser_is_valid() ) {
347 return False;
349 if ( ! $this->user_password_is_valid() ) {
350 return False;
353 // Validation of mysql database password
354 if ( ! $this->password_is_valid() ) {
355 return False;
357 // Connect to mysql via root user
358 if (! $this->root_database_connection() ) {
359 return False;
361 // Create the dumpfile
362 // (applicable if cloning from another database)
363 if (! empty($this->clone_database)) {
364 if ( ! $this->create_dumpfiles() ) {
365 return False;
368 // Create the site directory
369 // (applicable if mirroring another local site)
370 if ( ! empty($this->source_site_id) ) {
371 if ( ! $this->create_site_directory() ) {
372 return False;
375 // Create the mysql database
376 if ( ! $this->create_database()) {
377 return False;
379 // Grant user privileges to the mysql database
380 if ( ! $this->grant_privileges() ) {
381 return False;
383 // Connect to mysql via created user
384 $this->disconnect();
385 if ( ! $this->user_database_connection() ) {
386 return False;
388 // Build the database
389 if ( ! $this->load_dumpfiles() ) {
390 return False;
392 // Write the sql configuration file
393 if ( ! $this->write_configuration_file() ) {
394 return False;
396 // Load the version information, globals settings,
397 // initial user, and set up gacl access controls.
398 // (applicable if not cloning from another database)
399 if (empty($this->clone_database)) {
400 if ( ! $this->add_version_info() ) {
401 return False;
403 if ( ! $this->insert_globals() ) {
404 return False;
406 if ( ! $this->add_initial_user() ) {
407 return False;
409 if ( ! $this->install_gacl()) {
410 return False;
414 return True;
417 private function execute_sql( $sql ) {
418 $this->error_message = '';
419 if ( ! $this->dbh ) {
420 $this->user_database_connection();
422 $results = mysql_query($sql, $this->dbh);
423 if ( $results ) {
424 return $results;
425 } else {
426 $this->error_message = "unable to execute SQL: '$sql' due to: " . mysql_error();
427 return False;
431 private function connect_to_database( $server, $user, $password, $port )
433 if ($server == "localhost")
434 $dbh = mysql_connect($server, $user, $password);
435 else
436 $dbh = mysql_connect("$server:$port", $user, $password);
437 return $dbh;
440 private function set_collation()
442 if ($this->collate) {
443 return $this->execute_sql("SET NAMES 'utf8'");
445 return TRUE;
449 * innitialize $this->dumpfiles, an array of the dumpfiles that will
450 * be loaded into the database, including the correct translation
451 * dumpfile.
452 * The keys are the paths of the dumpfiles, and the values are the titles
453 * @return array
455 private function initialize_dumpfile_list() {
456 if ( $this->clone_database ) {
457 $this->dumpfiles = array( $this->get_backup_filename() => 'clone database' );
458 } else {
459 $dumpfiles = array( $this->main_sql => 'Main' );
460 if (! empty($this->development_translations)) {
461 // Use the online development translation set
462 $dumpfiles[ $this->devel_translation_sql ] = "Online Development Language Translations (utf8)";
464 else {
465 // Use the local translation set
466 $dumpfiles[ $this->translation_sql ] = "Language Translation (utf8)";
468 if ($this->ippf_specific) {
469 $dumpfiles[ $this->ippf_sql ] = "IPPF Layout";
471 // Load ICD-9 codes if present.
472 if (file_exists( $this->icd9 )) {
473 $dumpfiles[ $this->icd9 ] = "ICD-9";
475 // Load CVX codes if present
476 if (file_exists( $this->cvx )) {
477 $dumpfiles[ $this->cvx ] = "CVX Immunization Codes";
479 $this->dumpfiles = $dumpfiles;
481 return $this->dumpfiles;
484 // http://www.php.net/manual/en/function.include.php
485 private function get_require_contents($filename) {
486 if (is_file($filename)) {
487 ob_start();
488 require $filename;
489 $contents = ob_get_contents();
490 ob_end_clean();
491 return $contents;
493 return false;
498 * Directory copy logic borrowed from a user comment at
499 * http://www.php.net/manual/en/function.copy.php
500 * @param string $src name of the directory to copy
501 * @param string $dst name of the destination to copy to
502 * @return bool indicating success
504 private function recurse_copy($src, $dst) {
505 $dir = opendir($src);
506 if ( ! @mkdir($dst) ) {
507 $this->error_message = "unable to create directory: '$dst'";
508 return False;
510 while(false !== ($file = readdir($dir))) {
511 if ($file != '.' && $file != '..') {
512 if (is_dir($src . '/' . $file)) {
513 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
515 else {
516 copy($src . '/' . $file, $dst . '/' . $file);
520 closedir($dir);
521 return True;
526 * dump a site's database to a temporary file.
527 * @param string $source_site_id the site_id of the site to dump
528 * @return filename of the backup
530 private function dumpSourceDatabase() {
531 global $OE_SITES_BASE;
532 $source_site_id = $this->source_site_id;
534 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
536 if (empty($config)) die("Source site $source_site_id has not been set up!");
538 $backup_file = $this->get_backup_filename();
539 $cmd = "mysqldump -u " . escapeshellarg($login) .
540 " -p" . escapeshellarg($pass) .
541 " --opt --skip-extended-insert --quote-names -r $backup_file " .
542 escapeshellarg($dbase);
544 $tmp0 = exec($cmd, $tmp1=array(), $tmp2);
545 if ($tmp2) die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
547 return $backup_file;
551 * @return filename of the source backup database for cloning
553 private function get_backup_filename() {
554 if (stristr(PHP_OS, 'WIN')) {
555 $backup_file = 'C:/windows/temp/setup_dump.sql';
557 else {
558 $backup_file = '/tmp/setup_dump.sql';
560 return $backup_file;
566 This file is free software: you can redistribute it and/or modify it under the
567 terms of the GNU General Public License as publish by the Free Software
568 Foundation.
570 This file is distributed in the hope that it will be useful, but WITHOUT ANY
571 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
572 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
574 You should have received a copy of the GNU General Public Licence along with
575 this file. If not see <http://www.gnu.org/licenses/>.