reformat initial comment block.
[openemr.git] / library / classes / Installer.class.php
blob89e176c1a80319e1a3ed2c9afd5ad2ba31ca23d4
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";
47 // Record name of php-gacl installation files
48 $this->gaclSetupScript1 = dirname(__FILE__) . "/../../gacl/setup.php";
49 $this->gaclSetupScript2 = dirname(__FILE__) . "/../../acl_setup.php";
51 // Prepare the dumpfile list
52 $this->initialize_dumpfile_list();
54 // Entities to hold error and debug messages
55 $this->error_message = '';
56 $this->debug_message = '';
58 // Entity to hold sql connection
59 $this->dbh = false;
62 public function login_is_valid()
64 if ( ($this->login == '') || (! isset( $this->login )) ) {
65 $this->error_message = "login is invalid: '$this->login'";
66 return FALSE;
68 return TRUE;
71 public function iuser_is_valid()
73 if ( strpos($this->iuser, " ") ) {
74 $this->error_message = "Initial user is invalid: '$this->iuser'";
75 return FALSE;
77 return TRUE;
80 public function password_is_valid()
82 if ( $this->pass == "" || !isset($this->pass) ) {
83 $this->error_message = "The password for the new database account is invalid: '$this->pass'";
84 return FALSE;
86 return TRUE;
89 public function user_password_is_valid()
91 if ( $this->iuserpass == "" || !isset($this->iuserpass) ) {
92 $this->error_message = "The password for the user is invalid: '$this->iuserpass'";
93 return FALSE;
95 return TRUE;
98 public function root_database_connection()
100 $this->dbh = $this->connect_to_database( $this->server, $this->root, $this->rootpass, $this->port );
101 if ( $this->dbh ) {
102 return TRUE;
103 } else {
104 $this->error_message = 'unable to connect to database as root';
105 return FALSE;
109 public function user_database_connection()
111 $this->dbh = $this->connect_to_database( $this->server, $this->login, $this->pass, $this->port );
112 if ( ! $this->dbh ) {
113 $this->error_message = "unable to connect to database as user: '$this->login'";
114 return FALSE;
116 if ( ! $this->set_collation() ) {
117 return FALSE;
119 if ( ! mysql_select_db($this->dbname, $this->dbh) ) {
120 $this->error_message = "unable to select database: '$this->dbname'";
121 return FALSE;
123 return TRUE;
126 public function create_database() {
127 $sql = "create database $this->dbname";
128 if ($this->collate) {
129 $sql .= " character set utf8 collate $this->collate";
130 $this->set_collation();
132 return $this->execute_sql($sql);
135 public function drop_database() {
136 $sql = "drop database if exists $this->dbname";
137 return $this->execute_sql($sql);
140 public function grant_privileges() {
141 return $this->execute_sql( "GRANT ALL PRIVILEGES ON $this->dbname.* TO '$this->login'@'$this->loginhost' IDENTIFIED BY '$this->pass'" );
144 public function disconnect() {
145 return mysql_close($this->dbh);
149 * This method creates any dumpfiles necessary.
150 * This is actually only done if we're cloning an existing site
151 * and we need to dump their database into a file.
152 * @return bool indicating success
154 public function create_dumpfiles() {
155 return $this->dumpSourceDatabase();
158 public function load_dumpfiles() {
159 $sql_results = ''; // information string which is returned
160 foreach ($this->dumpfiles as $filename => $title) {
161 $sql_results .= "Creating $title tables...\n";
162 $fd = fopen($filename, 'r');
163 if ($fd == FALSE) {
164 $this->error_message = "ERROR. Could not open dumpfile '$filename'.\n";
165 return FALSE;
167 $query = "";
168 $line = "";
169 while (!feof ($fd)){
170 $line = fgets($fd,1024);
171 $line = rtrim($line);
172 if (substr($line,0,2) == "--") // Kill comments
173 continue;
174 if (substr($line,0,1) == "#") // Kill comments
175 continue;
176 if ($line == "")
177 continue;
178 $query = $query.$line; // Check for full query
179 $chr = substr($query,strlen($query)-1,1);
180 if ($chr == ";") { // valid query, execute
181 $query = rtrim($query,";");
182 $this->execute_sql( $query );
183 $query = "";
186 $sql_results .= "OK<br>\n";
187 fclose($fd);
189 return $sql_results;
192 public function add_version_info() {
193 include dirname(__FILE__) . "/../../version.php";
194 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) {
195 $this->error_message = "ERROR. Unable insert version information into database\n" .
196 "<p>".mysql_error()." (#".mysql_errno().")\n";
197 return FALSE;
199 return TRUE;
202 public function add_initial_user() {
203 if ($this->execute_sql("INSERT INTO groups (id, name, user) VALUES (1,'$this->igroup','$this->iuser')") == FALSE) {
204 $this->error_message = "ERROR. Unable to add initial user group\n" .
205 "<p>".mysql_error()." (#".mysql_errno().")\n";
206 return FALSE;
208 $password_hash = sha1( $this->iuserpass );
209 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) {
210 $this->error_message = "ERROR. Unable to add initial user\n" .
211 "<p>".mysql_error()." (#".mysql_errno().")\n";
212 return FALSE;
214 return TRUE;
218 * Create site directory if it is missing.
219 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
220 * @return name of the site directory or False
222 public function create_site_directory() {
223 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
224 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id;
225 $destination_directory = $GLOBALS['OE_SITE_DIR'];
226 if ( ! $this->recurse_copy( $source_directory, $destination_directory ) ) {
227 $this->error_message = "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message;
228 return False;
231 return True;
234 public function write_configuration_file() {
235 @touch($this->conffile); // php bug
236 $fd = @fopen($this->conffile, 'w');
237 if ( ! $fd ) {
238 $this->error_message = 'unable to open configuration file for writing: ' . $this->conffile;
239 return False;
241 $string = '<?php
242 // OpenEMR
243 // MySQL Config
247 $it_died = 0; //fmg: variable keeps running track of any errors
249 fwrite($fd,$string) or $it_died++;
250 fwrite($fd,"\$host\t= '$this->server';\n") or $it_died++;
251 fwrite($fd,"\$port\t= '$this->port';\n") or $it_died++;
252 fwrite($fd,"\$login\t= '$this->login';\n") or $it_died++;
253 fwrite($fd,"\$pass\t= '$this->pass';\n") or $it_died++;
254 fwrite($fd,"\$dbase\t= '$this->dbname';\n\n") or $it_died++;
255 fwrite($fd,"//Added ability to disable\n") or $it_died++;
256 fwrite($fd,"//utf8 encoding - bm 05-2009\n") or $it_died++;
257 fwrite($fd,"global \$disable_utf8_flag;\n") or $it_died++;
258 fwrite($fd,"\$disable_utf8_flag = false;\n") or $it_died++;
260 $string = '
261 $sqlconf = array();
262 global $sqlconf;
263 $sqlconf["host"]= $host;
264 $sqlconf["port"] = $port;
265 $sqlconf["login"] = $login;
266 $sqlconf["pass"] = $pass;
267 $sqlconf["dbase"] = $dbase;
268 //////////////////////////
269 //////////////////////////
270 //////////////////////////
271 //////DO NOT TOUCH THIS///
272 $config = 1; /////////////
273 //////////////////////////
274 //////////////////////////
275 //////////////////////////
278 ?><?php // done just for coloring
280 fwrite($fd,$string) or $it_died++;
281 fclose($fd) or $it_died++;
283 //it's rather irresponsible to not report errors when writing this file.
284 if ($it_died != 0) {
285 $this->error_message = "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
286 return FALSE;
289 return TRUE;
292 public function insert_globals() {
293 function xl($s) { return $s; }
294 require(dirname(__FILE__) . '/../globals.inc.php');
295 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
296 foreach ($grparr as $fldid => $fldarr) {
297 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
298 if (substr($fldtype, 0, 2) !== 'm_') {
299 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '$fldid'");
300 $row = @mysql_fetch_array($res, MYSQL_ASSOC);
301 if (empty($row['count'])) {
302 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
303 "VALUES ( '$fldid', '0', '$flddef' )");
308 return TRUE;
311 public function install_gacl()
313 $install_results_1 = $this->get_require_contents($this->gaclSetupScript1);
314 if (! $install_results_1 ) {
315 $this->error_message = "install_gacl failed: unable to require gacl script 1";
316 return FALSE;
319 $install_results_2 = $this->get_require_contents($this->gaclSetupScript2);
320 if (! $install_results_2 ) {
321 $this->error_message = "install_gacl failed: unable to require gacl script 2";
322 return FALSE;
324 $this->debug_message .= $install_results_1 . $install_results_2;
325 return TRUE;
328 public function quick_install() {
329 // Validation of OpenEMR user settings
330 // (applicable if not cloning from another database)
331 if (empty($this->clone_database)) {
332 if ( ! $this->login_is_valid() ) {
333 return False;
335 if ( ! $this->iuser_is_valid() ) {
336 return False;
338 if ( ! $this->user_password_is_valid() ) {
339 return False;
342 // Validation of mysql database password
343 if ( ! $this->password_is_valid() ) {
344 return False;
346 // Connect to mysql via root user
347 if (! $this->root_database_connection() ) {
348 return False;
350 // Create the dumpfile
351 // (applicable if cloning from another database)
352 if (! empty($this->clone_database)) {
353 if ( ! $this->create_dumpfiles() ) {
354 return False;
357 // Create the site directory
358 // (applicable if mirroring another local site)
359 if ( ! empty($this->source_site_id) ) {
360 if ( ! $this->create_site_directory() ) {
361 return False;
364 // Create the mysql database
365 if ( ! $this->create_database()) {
366 return False;
368 // Grant user privileges to the mysql database
369 if ( ! $this->grant_privileges() ) {
370 return False;
372 // Connect to mysql via created user
373 $this->disconnect();
374 if ( ! $this->user_database_connection() ) {
375 return False;
377 // Build the database
378 if ( ! $this->load_dumpfiles() ) {
379 return False;
381 // Write the sql configuration file
382 if ( ! $this->write_configuration_file() ) {
383 return False;
385 // Load the version information, globals settings,
386 // initial user, and set up gacl access controls.
387 // (applicable if not cloning from another database)
388 if (empty($this->clone_database)) {
389 if ( ! $this->add_version_info() ) {
390 return False;
392 if ( ! $this->insert_globals() ) {
393 return False;
395 if ( ! $this->add_initial_user() ) {
396 return False;
398 if ( ! $this->install_gacl()) {
399 return False;
403 return True;
406 private function execute_sql( $sql ) {
407 $this->error_message = '';
408 if ( ! $this->dbh ) {
409 $this->user_database_connection();
411 $results = mysql_query($sql, $this->dbh);
412 if ( $results ) {
413 return $results;
414 } else {
415 $this->error_message = "unable to execute SQL: '$sql' due to: " . mysql_error();
416 return False;
420 private function connect_to_database( $server, $user, $password, $port )
422 if ($server == "localhost")
423 $dbh = mysql_connect($server, $user, $password);
424 else
425 $dbh = mysql_connect("$server:$port", $user, $password);
426 return $dbh;
429 private function set_collation()
431 if ($this->collate) {
432 return $this->execute_sql("SET NAMES 'utf8'");
434 return TRUE;
438 * innitialize $this->dumpfiles, an array of the dumpfiles that will
439 * be loaded into the database, including the correct translation
440 * dumpfile.
441 * The keys are the paths of the dumpfiles, and the values are the titles
442 * @return array
444 private function initialize_dumpfile_list() {
445 if ( $this->clone_database ) {
446 $this->dumpfiles = array( $this->get_backup_filename() => 'clone database' );
447 } else {
448 $dumpfiles = array( $this->main_sql => 'Main' );
449 if (! empty($this->development_translations)) {
450 // Use the online development translation set
451 $dumpfiles[ $this->devel_translation_sql ] = "Online Development Language Translations (utf8)";
453 else {
454 // Use the local translation set
455 $dumpfiles[ $this->translation_sql ] = "Language Translation (utf8)";
457 if ($this->ippf_specific) {
458 $dumpfiles[ $this->ippf_sql ] = "IPPF Layout";
460 // Load ICD-9 codes if present.
461 if (file_exists( $this->icd9 )) {
462 $dumpfiles[ $this->icd9 ] = "ICD-9";
464 // Load CVX codes if present
465 if (file_exists( $this->cvx )) {
466 $dumpfiles[ $this->cvx ] = "CVX Immunization Codes";
468 $this->dumpfiles = $dumpfiles;
470 return $this->dumpfiles;
473 // http://www.php.net/manual/en/function.include.php
474 private function get_require_contents($filename) {
475 if (is_file($filename)) {
476 ob_start();
477 require $filename;
478 $contents = ob_get_contents();
479 ob_end_clean();
480 return $contents;
482 return false;
487 * Directory copy logic borrowed from a user comment at
488 * http://www.php.net/manual/en/function.copy.php
489 * @param string $src name of the directory to copy
490 * @param string $dst name of the destination to copy to
491 * @return bool indicating success
493 private function recurse_copy($src, $dst) {
494 $dir = opendir($src);
495 if ( ! @mkdir($dst) ) {
496 $this->error_message = "unable to create directory: '$dst'";
497 return False;
499 while(false !== ($file = readdir($dir))) {
500 if ($file != '.' && $file != '..') {
501 if (is_dir($src . '/' . $file)) {
502 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
504 else {
505 copy($src . '/' . $file, $dst . '/' . $file);
509 closedir($dir);
510 return True;
515 * dump a site's database to a temporary file.
516 * @param string $source_site_id the site_id of the site to dump
517 * @return filename of the backup
519 private function dumpSourceDatabase() {
520 global $OE_SITES_BASE;
521 $source_site_id = $this->source_site_id;
523 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
525 if (empty($config)) die("Source site $source_site_id has not been set up!");
527 $backup_file = $this->get_backup_filename();
528 $cmd = "mysqldump -u " . escapeshellarg($login) .
529 " -p" . escapeshellarg($pass) .
530 " --opt --skip-extended-insert --quote-names -r $backup_file " .
531 escapeshellarg($dbase);
533 $tmp0 = exec($cmd, $tmp1=array(), $tmp2);
534 if ($tmp2) die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
536 return $backup_file;
540 * @return filename of the source backup database for cloning
542 private function get_backup_filename() {
543 if (stristr(PHP_OS, 'WIN')) {
544 $backup_file = 'C:/windows/temp/setup_dump.sql';
546 else {
547 $backup_file = '/tmp/setup_dump.sql';
549 return $backup_file;
555 This file is free software: you can redistribute it and/or modify it under the
556 terms of the GNU General Public License as publish by the Free Software
557 Foundation.
559 This file is distributed in the hope that it will be useful, but WITHOUT ANY
560 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
561 PARTICULAR PURPOSE. See the GNU Gneral Public License for more details.
563 You should have received a copy of the GNU General Public Licence along with
564 this file. If not see <http://www.gnu.org/licenses/>.