skip view when backup (#5102)
[openemr.git] / library / classes / Installer.class.php
blobb0602183863485bdc787647c3793d151eeb5f9f9
1 <?php
3 /**
5 * Installer class.
7 * @package OpenEMR
8 * @link https://www.open-emr.org
9 * @author Andrew Moore <amoore@cpan.org>
10 * @author Ranganath Pathak <pathak@scrs1.org>
11 * @author Brady Miller <brady.g.miller@gmail.com>
12 * @copyright Copyright (c) 2010 Andrew Moore <amoore@cpan.org>
13 * @copyright Copyright (c) 2019 Ranganath Pathak <pathak@scrs1.org>
14 * @copyright Copyright (c) 2019 Brady Miller <brady.g.miller@gmail.com>
15 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
18 use OpenEMR\Gacl\GaclApi;
20 class Installer
22 public function __construct($cgi_variables)
24 // Installation variables
25 // For a good explanation of these variables, see documentation in
26 // the contrib/util/installScripts/InstallerAuto.php file.
27 $this->iuser = isset($cgi_variables['iuser']) ? ($cgi_variables['iuser']) : '';
28 $this->iuserpass = isset($cgi_variables['iuserpass']) ? ($cgi_variables['iuserpass']) : '';
29 $this->iuname = isset($cgi_variables['iuname']) ? ($cgi_variables['iuname']) : '';
30 $this->iufname = isset($cgi_variables['iufname']) ? ($cgi_variables['iufname']) : '';
31 $this->igroup = isset($cgi_variables['igroup']) ? ($cgi_variables['igroup']) : '';
32 $this->i2faEnable = isset($cgi_variables['i2faenable']) ? ($cgi_variables['i2faenable']) : '';
33 $this->i2faSecret = isset($cgi_variables['i2fasecret']) ? ($cgi_variables['i2fasecret']) : '';
34 $this->server = isset($cgi_variables['server']) ? ($cgi_variables['server']) : ''; // mysql server (usually localhost)
35 $this->loginhost = isset($cgi_variables['loginhost']) ? ($cgi_variables['loginhost']) : ''; // php/apache server (usually localhost)
36 $this->port = isset($cgi_variables['port']) ? ($cgi_variables['port']) : '';
37 $this->root = isset($cgi_variables['root']) ? ($cgi_variables['root']) : '';
38 $this->rootpass = isset($cgi_variables['rootpass']) ? ($cgi_variables['rootpass']) : '';
39 $this->login = isset($cgi_variables['login']) ? ($cgi_variables['login']) : '';
40 $this->pass = isset($cgi_variables['pass']) ? ($cgi_variables['pass']) : '';
41 $this->dbname = isset($cgi_variables['dbname']) ? ($cgi_variables['dbname']) : '';
42 $this->collate = isset($cgi_variables['collate']) ? ($cgi_variables['collate']) : '';
43 $this->site = isset($cgi_variables['site']) ? ($cgi_variables['site']) : '';
44 $this->source_site_id = isset($cgi_variables['source_site_id']) ? ($cgi_variables['source_site_id']) : '';
45 $this->clone_database = isset($cgi_variables['clone_database']) ? ($cgi_variables['clone_database']) : '';
46 $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
47 $this->development_translations = isset($cgi_variables['development_translations']) ? ($cgi_variables['development_translations']) : '';
48 $this->new_theme = isset($cgi_variables['new_theme']) ? ($cgi_variables['new_theme']) : '';
49 // Make this true for IPPF.
50 $this->ippf_specific = false;
52 // Record name of sql access file
53 $GLOBALS['OE_SITES_BASE'] = dirname(__FILE__) . '/../../sites';
54 $GLOBALS['OE_SITE_DIR'] = $GLOBALS['OE_SITES_BASE'] . '/' . $this->site;
55 $this->conffile = $GLOBALS['OE_SITE_DIR'] . '/sqlconf.php';
57 // Record names of sql table files
58 $this->main_sql = dirname(__FILE__) . '/../../sql/database.sql';
59 $this->translation_sql = dirname(__FILE__) . '/../../contrib/util/language_translations/currentLanguage_utf8.sql';
60 $this->devel_translation_sql = "http://translations.openemr.io/languageTranslations_utf8.sql";
61 $this->ippf_sql = dirname(__FILE__) . "/../../sql/ippf_layout.sql";
62 $this->icd9 = dirname(__FILE__) . "/../../sql/icd9.sql";
63 $this->cvx = dirname(__FILE__) . "/../../sql/cvx_codes.sql";
64 $this->additional_users = dirname(__FILE__) . "/../../sql/official_additional_users.sql";
66 // Prepare the dumpfile list
67 $this->initialize_dumpfile_list();
69 // Entities to hold error and debug messages
70 $this->error_message = '';
71 $this->debug_message = '';
73 // Entity to hold sql connection
74 $this->dbh = false;
77 public function login_is_valid()
79 if (($this->login == '') || (! isset($this->login))) {
80 $this->error_message = "login is invalid: '$this->login'";
81 return false;
84 return true;
87 public function char_is_valid($input_text)
89 // to prevent php injection
90 trim($input_text);
91 if ($input_text == '') {
92 return false;
95 if (preg_match('@[\\\\;()<>/\'"]@', $input_text)) {
96 return false;
99 return true;
102 public function databaseNameIsValid($name)
104 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
105 return false;
107 return true;
110 public function collateNameIsValid($name)
112 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
113 return false;
115 return true;
118 public function iuser_is_valid()
120 if (strpos($this->iuser, " ")) {
121 $this->error_message = "Initial user is invalid: '$this->iuser'";
122 return false;
125 return true;
128 public function iuname_is_valid()
130 if ($this->iuname == "" || !isset($this->iuname)) {
131 $this->error_message = "Initial user last name is invalid: '$this->iuname'";
132 return false;
135 return true;
138 public function password_is_valid()
140 if ($this->pass == "" || !isset($this->pass)) {
141 $this->error_message = "The password for the new database account is invalid: '$this->pass'";
142 return false;
145 return true;
148 public function user_password_is_valid()
150 if ($this->iuserpass == "" || !isset($this->iuserpass)) {
151 $this->error_message = "The password for the user is invalid: '$this->iuserpass'";
152 return false;
155 return true;
160 public function root_database_connection()
162 $this->dbh = $this->connect_to_database($this->server, $this->root, $this->rootpass, $this->port);
163 if ($this->dbh) {
164 if (! $this->set_sql_strict()) {
165 $this->error_message = 'unable to set strict sql setting';
166 return false;
169 return true;
170 } else {
171 $this->error_message = 'unable to connect to database as root';
172 return false;
176 public function user_database_connection()
178 $this->dbh = $this->connect_to_database($this->server, $this->login, $this->pass, $this->port, $this->dbname);
179 if (! $this->dbh) {
180 $this->error_message = "unable to connect to database as user: '$this->login'";
181 return false;
184 if (! $this->set_sql_strict()) {
185 $this->error_message = 'unable to set strict sql setting';
186 return false;
189 if (! $this->set_collation()) {
190 $this->error_message = 'unable to set sql collation';
191 return false;
194 if (! mysqli_select_db($this->dbh, $this->dbname)) {
195 $this->error_message = "unable to select database: '$this->dbname'";
196 return false;
199 return true;
202 public function create_database()
204 $sql = "create database " . $this->escapeDatabaseName($this->dbname);
205 if (empty($this->collate) || ($this->collate == 'utf8_general_ci')) {
206 $this->collate = 'utf8mb4_general_ci';
208 $sql .= " character set utf8mb4 collate " . $this->escapeCollateName($this->collate);
209 $this->set_collation();
211 return $this->execute_sql($sql);
214 public function drop_database()
216 $sql = "drop database if exists " . $this->escapeDatabaseName($this->dbname);
217 return $this->execute_sql($sql);
220 public function create_database_user()
222 // First, check for database user in the mysql.user table (this works for all except mariadb 10.4+)
223 $checkUser = $this->execute_sql("SELECT user FROM mysql.user WHERE user = '" . $this->escapeSql($this->login) . "' AND host = '" . $this->escapeSql($this->loginhost) . "'", false);
224 if ($checkUser === false) {
225 // Above caused error, so is MariaDB 10.4+, and need to do below query instead in the mysql.global_priv table
226 $checkUser = $this->execute_sql("SELECT user FROM mysql.global_priv WHERE user = '" . $this->escapeSql($this->login) . "' AND host = '" . $this->escapeSql($this->loginhost) . "'");
229 if ($checkUser === false) {
230 // there was an error in the check database user query, so return false
231 return false;
232 } elseif ($checkUser->num_rows > 0) {
233 // the mysql user already exists, so do not need to create the user, but need to set the password
234 // Note need to try two different methods, first is for newer mysql versions and second is for older mysql versions (if the first method fails)
235 $returnSql = $this->execute_sql("ALTER USER '" . $this->escapeSql($this->login) . "'@'" . $this->escapeSql($this->loginhost) . "' IDENTIFIED BY '" . $this->escapeSql($this->pass) . "'", false);
236 if ($returnSql === false) {
237 error_log("Using older mysql version method to set password for the mysql user");
238 $returnSql = $this->execute_sql("SET PASSWORD FOR '" . $this->escapeSql($this->login) . "'@'" . $this->escapeSql($this->loginhost) . "' = PASSWORD('" . $this->escapeSql($this->pass) . "')");
240 return $returnSql;
241 } else {
242 // the mysql user does not yet exist, so create the user
243 return $this->execute_sql("CREATE USER '" . $this->escapeSql($this->login) . "'@'" . $this->escapeSql($this->loginhost) . "' IDENTIFIED BY '" . $this->escapeSql($this->pass) . "'");
247 public function grant_privileges()
249 return $this->execute_sql("GRANT ALL PRIVILEGES ON " . $this->escapeDatabaseName($this->dbname) . ".* TO '" . $this->escapeSql($this->login) . "'@'" . $this->escapeSql($this->loginhost) . "'");
252 public function disconnect()
254 return mysqli_close($this->dbh);
258 * This method creates any dumpfiles necessary.
259 * This is actually only done if we're cloning an existing site
260 * and we need to dump their database into a file.
261 * @return bool indicating success
263 public function create_dumpfiles()
265 return $this->dumpSourceDatabase();
268 public function load_dumpfiles()
270 $sql_results = ''; // information string which is returned
271 foreach ($this->dumpfiles as $filename => $title) {
272 $sql_results_temp = '';
273 $sql_results_temp = $this->load_file($filename, $title);
274 if ($sql_results_temp == false) {
275 return false;
278 $sql_results .= $sql_results_temp;
281 return $sql_results;
284 public function load_file($filename, $title)
286 $sql_results = ''; // information string which is returned
287 $sql_results .= "Creating $title tables...\n";
288 $fd = fopen($filename, 'r');
289 if ($fd == false) {
290 $this->error_message = "ERROR. Could not open dumpfile '$filename'.\n";
291 return false;
294 $query = "";
295 $line = "";
297 // Settings to drastically speed up installation with InnoDB
298 if (! $this->execute_sql("SET autocommit=0;")) {
299 return false;
302 if (! $this->execute_sql("START TRANSACTION;")) {
303 return false;
306 while (!feof($fd)) {
307 $line = fgets($fd, 1024);
308 $line = rtrim($line);
309 if (substr($line, 0, 2) == "--") { // Kill comments
310 continue;
313 if (substr($line, 0, 1) == "#") { // Kill comments
314 continue;
317 if ($line == "") {
318 continue;
321 $query = $query . $line; // Check for full query
322 $chr = substr($query, strlen($query) - 1, 1);
323 if ($chr == ";") { // valid query, execute
324 $query = rtrim($query, ";");
325 if (! $this->execute_sql($query)) {
326 return false;
329 $query = "";
333 // Settings to drastically speed up installation with InnoDB
334 if (! $this->execute_sql("COMMIT;")) {
335 return false;
338 if (! $this->execute_sql("SET autocommit=1;")) {
339 return false;
342 $sql_results .= "<span class='text-success'><b>OK</b></span>.<br>\n";
343 fclose($fd);
344 return $sql_results;
347 public function add_version_info()
349 include dirname(__FILE__) . "/../../version.php";
350 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) {
351 $this->error_message = "ERROR. Unable insert version information into database\n" .
352 "<p>" . mysqli_error($this->dbh) . " (#" . mysqli_errno($this->dbh) . ")\n";
353 return false;
356 return true;
359 public function add_initial_user()
361 if ($this->execute_sql("INSERT INTO `groups` (id, name, user) VALUES (1,'" . $this->escapeSql($this->igroup) . "','" . $this->escapeSql($this->iuser) . "')") == false) {
362 $this->error_message = "ERROR. Unable to add initial user group\n" .
363 "<p>" . mysqli_error($this->dbh) . " (#" . mysqli_errno($this->dbh) . ")\n";
364 return false;
367 if ($this->execute_sql("INSERT INTO users (id, username, password, authorized, lname, fname, facility_id, calendar, cal_ui) VALUES (1,'" . $this->escapeSql($this->iuser) . "','NoLongerUsed',1,'" . $this->escapeSql($this->iuname) . "','" . $this->escapeSql($this->iufname) . "',3,1,3)") == false) {
368 $this->error_message = "ERROR. Unable to add initial user\n" .
369 "<p>" . mysqli_error($this->dbh) . " (#" . mysqli_errno($this->dbh) . ")\n";
370 return false;
373 $hash = password_hash($this->iuserpass, PASSWORD_DEFAULT);
374 if (empty($hash)) {
375 // Something is seriously wrong
376 error_log('OpenEMR Error : OpenEMR is not working because unable to create a hash.');
377 die("OpenEMR Error : OpenEMR is not working because unable to create a hash.");
379 if ($this->execute_sql("INSERT INTO users_secure (id, username, password, last_update_password) VALUES (1,'" . $this->escapeSql($this->iuser) . "','" . $this->escapeSql($hash) . "',NOW())") == false) {
380 $this->error_message = "ERROR. Unable to add initial user login credentials\n" .
381 "<p>" . mysqli_error($this->dbh) . " (#" . mysqli_errno($this->dbh) . ")\n";
382 return false;
385 // Create new 2fa if enabled
386 if (($this->i2faEnable) && (!empty($this->i2faSecret)) && (class_exists('Totp')) && (class_exists('OpenEMR\Common\Crypto\CryptoGen'))) {
387 // Encrypt the new secret with the hashed password
388 $cryptoGen = new OpenEMR\Common\Crypto\CryptoGen();
389 $secret = $cryptoGen->encryptStandard($this->i2faSecret, $hash);
390 if ($this->execute_sql("INSERT INTO login_mfa_registrations (user_id, name, method, var1, var2) VALUES (1, 'App Based 2FA', 'TOTP', '" . $this->escapeSql($secret) . "', '')") == false) {
391 $this->error_message = "ERROR. Unable to add initial user's 2FA credentials\n" .
392 "<p>" . mysqli_error($this->dbh) . " (#" . mysqli_errno($this->dbh) . ")\n";
393 return false;
397 return true;
401 * Handle the additional users now that our gacl's have finished installing.
402 * @return bool
404 public function install_additional_users()
406 // Add the official openemr users (services)
407 if ($this->load_file($this->additional_users, "Additional Official Users") == false) {
408 return false;
410 return true;
413 public function on_care_coordination()
415 $resource = $this->execute_sql("SELECT `mod_id` FROM `modules` WHERE `mod_name` = 'Carecoordination' LIMIT 1");
416 $resource_array = mysqli_fetch_array($resource, MYSQLI_ASSOC);
417 $modId = $resource_array['mod_id'];
418 if (empty($modId)) {
419 $this->error_message = "ERROR configuring Care Coordination module. Unable to get mod_id for Carecoordination module\n";
420 return false;
423 $resource = $this->execute_sql("SELECT `section_id` FROM `module_acl_sections` WHERE `section_identifier` = 'carecoordination' LIMIT 1");
424 $resource_array = mysqli_fetch_array($resource, MYSQLI_ASSOC);
425 $sectionId = $resource_array['section_id'];
426 if (empty($sectionId)) {
427 $this->error_message = "ERROR configuring Care Coordination module. Unable to get section_id for carecoordination module section\n";
428 return false;
431 $resource = $this->execute_sql("SELECT `id` FROM `gacl_aro_groups` WHERE `value` = 'admin' LIMIT 1");
432 $resource_array = mysqli_fetch_array($resource, MYSQLI_ASSOC);
433 $groupId = $resource_array['id'];
434 if (empty($groupId)) {
435 $this->error_message = "ERROR configuring Care Coordination module. Unable to get id for gacl_aro_groups admin section\n";
436 return false;
439 if ($this->execute_sql("INSERT INTO `module_acl_group_settings` (`module_id`, `group_id`, `section_id`, `allowed`) VALUES ('" . $this->escapeSql($modId) . "', '" . $this->escapeSql($groupId) . "', '" . $this->escapeSql($sectionId) . "', 1)") == false) {
440 $this->error_message = "ERROR configuring Care Coordination module. Unable to add the module_acl_group_settings acl entry\n";
441 return false;
444 return true;
448 * Generates the initial user's 2FA QR Code
449 * @return bool|string|void
451 public function get_initial_user_2fa_qr()
453 if (($this->i2faEnable) && (!empty($this->i2faSecret)) && (class_exists('Totp'))) {
454 $adminTotp = new Totp($this->i2faSecret, $this->iuser);
455 $qr = $adminTotp->generateQrCode();
456 return $qr;
458 return false;
462 * Create site directory if it is missing.
463 * @global string $GLOBALS['OE_SITE_DIR'] contains the name of the site directory to create
464 * @return name of the site directory or False
466 public function create_site_directory()
468 if (!file_exists($GLOBALS['OE_SITE_DIR'])) {
469 $source_directory = $GLOBALS['OE_SITES_BASE'] . "/" . $this->source_site_id;
470 $destination_directory = $GLOBALS['OE_SITE_DIR'];
471 if (! $this->recurse_copy($source_directory, $destination_directory)) {
472 $this->error_message = "unable to copy directory: '$source_directory' to '$destination_directory'. " . $this->error_message;
473 return false;
475 // the new site will create it's own keys so okay to delete these copied from the source site
476 if (!$this->clone_database) {
477 array_map('unlink', glob($destination_directory . "/documents/logs_and_misc/methods/*"));
481 return true;
484 public function write_configuration_file()
486 @touch($this->conffile); // php bug
487 $fd = @fopen($this->conffile, 'w');
488 if (! $fd) {
489 $this->error_message = 'unable to open configuration file for writing: ' . $this->conffile;
490 return false;
493 $string = '<?php
494 // OpenEMR
495 // MySQL Config
499 $it_died = 0; //fmg: variable keeps running track of any errors
501 fwrite($fd, $string) or $it_died++;
502 fwrite($fd, "global \$disable_utf8_flag;\n") or $it_died++;
503 fwrite($fd, "\$disable_utf8_flag = false;\n\n") or $it_died++;
504 fwrite($fd, "\$host\t= '$this->server';\n") or $it_died++;
505 fwrite($fd, "\$port\t= '$this->port';\n") or $it_died++;
506 fwrite($fd, "\$login\t= '$this->login';\n") or $it_died++;
507 fwrite($fd, "\$pass\t= '$this->pass';\n") or $it_died++;
508 fwrite($fd, "\$dbase\t= '$this->dbname';\n") or $it_died++;
509 fwrite($fd, "\$db_encoding\t= 'utf8mb4';\n") or $it_died++;
511 $string = '
512 $sqlconf = array();
513 global $sqlconf;
514 $sqlconf["host"]= $host;
515 $sqlconf["port"] = $port;
516 $sqlconf["login"] = $login;
517 $sqlconf["pass"] = $pass;
518 $sqlconf["dbase"] = $dbase;
519 $sqlconf["db_encoding"] = $db_encoding;
521 //////////////////////////
522 //////////////////////////
523 //////////////////////////
524 //////DO NOT TOUCH THIS///
525 $config = 1; /////////////
526 //////////////////////////
527 //////////////////////////
528 //////////////////////////
532 fwrite($fd, $string) or $it_died++;
533 fclose($fd) or $it_died++;
535 //it's rather irresponsible to not report errors when writing this file.
536 if ($it_died != 0) {
537 $this->error_message = "ERROR. Couldn't write $it_died lines to config file '$this->conffile'.\n";
538 return false;
541 // Tell PHP that its cached bytecode version of sqlconf.php is no longer usable.
542 if (function_exists('opcache_invalidate')) {
543 opcache_invalidate($this->conffile, true);
546 return true;
549 public function insert_globals()
551 if (!(function_exists('xl'))) {
552 function xl($s)
554 return $s;
556 } else {
557 $GLOBALS['temp_skip_translations'] = true;
559 $skipGlobalEvent = true; //use in globals.inc.php script to skip event stuff
560 require(dirname(__FILE__) . '/../globals.inc.php');
561 foreach ($GLOBALS_METADATA as $grpname => $grparr) {
562 foreach ($grparr as $fldid => $fldarr) {
563 list($fldname, $fldtype, $flddef, $flddesc) = $fldarr;
564 if (is_array($fldtype) || substr($fldtype, 0, 2) !== 'm_') {
565 $res = $this->execute_sql("SELECT count(*) AS count FROM globals WHERE gl_name = '" . $this->escapeSql($fldid) . "'");
566 $row = mysqli_fetch_array($res, MYSQLI_ASSOC);
567 if (empty($row['count'])) {
568 $this->execute_sql("INSERT INTO globals ( gl_name, gl_index, gl_value ) " .
569 "VALUES ( '" . $this->escapeSql($fldid) . "', '0', '" . $this->escapeSql($flddef) . "' )");
575 return true;
578 public function install_gacl()
581 $gacl = new GaclApi();
583 // Create the ACO sections. Every ACO must have a section.
585 if ($gacl->add_object_section('Accounting', 'acct', 10, 0, 'ACO') === false) {
586 $this->error_message = "ERROR, Unable to create the access controls for OpenEMR.";
587 return false;
589 // xl('Accounting')
590 $gacl->add_object_section('Administration', 'admin', 10, 0, 'ACO');
591 // xl('Administration')
592 $gacl->add_object_section('Encounters', 'encounters', 10, 0, 'ACO');
593 // xl('Encounters')
594 $gacl->add_object_section('Lists', 'lists', 10, 0, 'ACO');
595 // xl('Lists')
596 $gacl->add_object_section('Patients', 'patients', 10, 0, 'ACO');
597 // xl('Patients')
598 $gacl->add_object_section('Squads', 'squads', 10, 0, 'ACO');
599 // xl('Squads')
600 $gacl->add_object_section('Sensitivities', 'sensitivities', 10, 0, 'ACO');
601 // xl('Sensitivities')
602 $gacl->add_object_section('Placeholder', 'placeholder', 10, 0, 'ACO');
603 // xl('Placeholder')
604 $gacl->add_object_section('Nation Notes', 'nationnotes', 10, 0, 'ACO');
605 // xl('Nation Notes')
606 $gacl->add_object_section('Patient Portal', 'patientportal', 10, 0, 'ACO');
607 // xl('Patient Portal')
608 $gacl->add_object_section('Menus', 'menus', 10, 0, 'ACO');
609 // xl('Menus')
610 $gacl->add_object_section('Groups', 'groups', 10, 0, 'ACO');
611 // xl('Groups')
612 $gacl->add_object_section('Inventory', 'inventory', 10, 0, 'ACO');
613 // xl('Inventory')
615 // Create Accounting ACOs.
617 $gacl->add_object('acct', 'Billing (write optional)', 'bill', 10, 0, 'ACO');
618 // xl('Billing (write optional)')
619 $gacl->add_object('acct', 'Price Discounting', 'disc', 10, 0, 'ACO');
620 // xl('Price Discounting')
621 $gacl->add_object('acct', 'EOB Data Entry', 'eob', 10, 0, 'ACO');
622 // xl('EOB Data Entry')
623 $gacl->add_object('acct', 'Financial Reporting - my encounters', 'rep', 10, 0, 'ACO');
624 // xl('Financial Reporting - my encounters')
625 $gacl->add_object('acct', 'Financial Reporting - anything', 'rep_a', 10, 0, 'ACO');
626 // xl('Financial Reporting - anything')
628 // Create Administration ACOs.
630 $gacl->add_object('admin', 'Superuser', 'super', 10, 0, 'ACO');
631 // xl('Superuser')
632 $gacl->add_object('admin', 'Calendar Settings', 'calendar', 10, 0, 'ACO');
633 // xl('Calendar Settings')
634 $gacl->add_object('admin', 'Database Reporting', 'database', 10, 0, 'ACO');
635 // xl('Database Reporting')
636 $gacl->add_object('admin', 'Forms Administration', 'forms', 10, 0, 'ACO');
637 // xl('Forms Administration')
638 $gacl->add_object('admin', 'Practice Settings', 'practice', 10, 0, 'ACO');
639 // xl('Practice Settings')
640 $gacl->add_object('admin', 'Superbill Codes Administration', 'superbill', 10, 0, 'ACO');
641 // xl('Superbill Codes Administration')
642 $gacl->add_object('admin', 'Users/Groups/Logs Administration', 'users', 10, 0, 'ACO');
643 // xl('Users/Groups/Logs Administration')
644 $gacl->add_object('admin', 'Batch Communication Tool', 'batchcom', 10, 0, 'ACO');
645 // xl('Batch Communication Tool')
646 $gacl->add_object('admin', 'Language Interface Tool', 'language', 10, 0, 'ACO');
647 // xl('Language Interface Tool')
648 $gacl->add_object('admin', 'Inventory Administration', 'drugs', 10, 0, 'ACO');
649 // xl('Inventory Administration')
650 $gacl->add_object('admin', 'ACL Administration', 'acl', 10, 0, 'ACO');
651 // xl('ACL Administration')
652 $gacl->add_object('admin', 'Multipledb', 'multipledb', 10, 0, 'ACO');
653 // xl('Multipledb')
654 $gacl->add_object('admin', 'Menu', 'menu', 10, 0, 'ACO');
655 // xl('Menu')
656 $gacl->add_object('admin', 'Manage modules', 'manage_modules', 10, 0, 'ACO');
657 // xl('Manage modules')
660 // Create ACOs for encounters.
662 $gacl->add_object('encounters', 'Authorize - my encounters', 'auth', 10, 0, 'ACO');
663 // xl('Authorize - my encounters')
664 $gacl->add_object('encounters', 'Authorize - any encounters', 'auth_a', 10, 0, 'ACO');
665 // xl('Authorize - any encounters')
666 $gacl->add_object('encounters', 'Coding - my encounters (write,wsome optional)', 'coding', 10, 0, 'ACO');
667 // xl('Coding - my encounters (write,wsome optional)')
668 $gacl->add_object('encounters', 'Coding - any encounters (write,wsome optional)', 'coding_a', 10, 0, 'ACO');
669 // xl('Coding - any encounters (write,wsome optional)')
670 $gacl->add_object('encounters', 'Notes - my encounters (write,addonly optional)', 'notes', 10, 0, 'ACO');
671 // xl('Notes - my encounters (write,addonly optional)')
672 $gacl->add_object('encounters', 'Notes - any encounters (write,addonly optional)', 'notes_a', 10, 0, 'ACO');
673 // xl('Notes - any encounters (write,addonly optional)')
674 $gacl->add_object('encounters', 'Fix encounter dates - any encounters', 'date_a', 10, 0, 'ACO');
675 // xl('Fix encounter dates - any encounters')
676 $gacl->add_object('encounters', 'Less-private information (write,addonly optional)', 'relaxed', 10, 0, 'ACO');
677 // xl('Less-private information (write,addonly optional)')
679 // Create ACOs for lists.
681 $gacl->add_object('lists', 'Default List (write,addonly optional)', 'default', 10, 0, 'ACO');
682 // xl('Default List (write,addonly optional)')
683 $gacl->add_object('lists', 'State List (write,addonly optional)', 'state', 10, 0, 'ACO');
684 // xl('State List (write,addonly optional)')
685 $gacl->add_object('lists', 'Country List (write,addonly optional)', 'country', 10, 0, 'ACO');
686 // xl('Country List (write,addonly optional)')
687 $gacl->add_object('lists', 'Language List (write,addonly optional)', 'language', 10, 0, 'ACO');
688 // xl('Language List (write,addonly optional)')
689 $gacl->add_object('lists', 'Ethnicity-Race List (write,addonly optional)', 'ethrace', 10, 0, 'ACO');
690 // xl('Ethnicity-Race List (write,addonly optional)')
692 // Create ACOs for patientportal.
694 $gacl->add_object('patientportal', 'Patient Portal', 'portal', 10, 0, 'ACO');
695 // xl('Patient Portal')
697 // Create ACOs for modules.
699 $gacl->add_object('menus', 'Modules', 'modle', 10, 0, 'ACO');
700 // xl('Modules')
702 // Create ACOs for patients.
704 $gacl->add_object('patients', 'Appointments (write,wsome optional)', 'appt', 10, 0, 'ACO');
705 // xl('Appointments (write,wsome optional)')
706 $gacl->add_object('patients', 'Demographics (write,addonly optional)', 'demo', 10, 0, 'ACO');
707 // xl('Demographics (write,addonly optional)')
708 $gacl->add_object('patients', 'Medical/History (write,addonly optional)', 'med', 10, 0, 'ACO');
709 // xl('Medical/History (write,addonly optional)')
710 $gacl->add_object('patients', 'Transactions (write optional)', 'trans', 10, 0, 'ACO');
711 // xl('Transactions (write optional)')
712 $gacl->add_object('patients', 'Documents (write,addonly optional)', 'docs', 10, 0, 'ACO');
713 // xl('Documents (write,addonly optional)')
714 $gacl->add_object('patients', 'Documents Delete', 'docs_rm', 10, 0, 'ACO');
715 // xl('Documents Delete')
716 $gacl->add_object('patients', 'Patient Notes (write,addonly optional)', 'notes', 10, 0, 'ACO');
717 // xl('Patient Notes (write,addonly optional)')
718 $gacl->add_object('patients', 'Sign Lab Results (write,addonly optional)', 'sign', 10, 0, 'ACO');
719 // xl('Sign Lab Results (write,addonly optional)')
720 $gacl->add_object('patients', 'Patient Reminders (write,addonly optional)', 'reminder', 10, 0, 'ACO');
721 // xl('Patient Reminders (write,addonly optional)')
722 $gacl->add_object('patients', 'Clinical Reminders/Alerts (write,addonly optional)', 'alert', 10, 0, 'ACO');
723 // xl('Clinical Reminders/Alerts (write,addonly optional)')
724 $gacl->add_object('patients', 'Disclosures (write,addonly optional)', 'disclosure', 10, 0, 'ACO');
725 // xl('Disclosures (write,addonly optional)')
726 $gacl->add_object('patients', 'Prescriptions (write,addonly optional)', 'rx', 10, 0, 'ACO');
727 // xl('Prescriptions (write,addonly optional)')
728 $gacl->add_object('patients', 'Amendments (write,addonly optional)', 'amendment', 10, 0, 'ACO');
729 // xl('Amendments (write,addonly optional)')
730 $gacl->add_object('patients', 'Lab Results (write,addonly optional)', 'lab', 10, 0, 'ACO');
731 // xl('Lab Results (write,addonly optional)')
732 $gacl->add_object('patients', 'Patient Report', 'pat_rep', 10, 0, 'ACO');
733 // xl('Patient Report')
736 $gacl->add_object('groups', 'View/Add/Update groups', 'gadd', 10, 0, 'ACO');
737 // xl('View/Add/Update groups')
738 $gacl->add_object('groups', 'View/Create/Update groups appointment in calendar', 'gcalendar', 10, 0, 'ACO');
739 // xl('View/Create/Update groups appointment in calendar')
740 $gacl->add_object('groups', 'Group encounter log', 'glog', 10, 0, 'ACO');
741 // xl('Group encounter log')
742 $gacl->add_object('groups', 'Group detailed log of appointment in patient record', 'gdlog', 10, 0, 'ACO');
743 // xl('Group detailed log of appointment in patient record')
744 $gacl->add_object('groups', 'Send message from the permanent group therapist to the personal therapist', 'gm', 10, 0, 'ACO');
745 // xl('Send message from the permanent group therapist to the personal therapist')
747 // Create ACOs for sensitivities.
749 $gacl->add_object('sensitivities', 'Normal', 'normal', 10, 0, 'ACO');
750 // xl('Normal')
751 $gacl->add_object('sensitivities', 'High', 'high', 20, 0, 'ACO');
752 // xl('High')
754 // Create ACO for placeholder.
756 $gacl->add_object('placeholder', 'Placeholder (Maintains empty ACLs)', 'filler', 10, 0, 'ACO');
757 // xl('Placeholder (Maintains empty ACLs)')
759 // Create ACO for nationnotes.
761 $gacl->add_object('nationnotes', 'Nation Notes Configure', 'nn_configure', 10, 0, 'ACO');
762 // xl('Nation Notes Configure')
764 // Create ACOs for Inventory.
766 $gacl->add_object('inventory', 'Lots', 'lots', 10, 0, 'ACO');
767 // xl('Lots')
768 $gacl->add_object('inventory', 'Sales', 'sales', 20, 0, 'ACO');
769 // xl('Sales')
770 $gacl->add_object('inventory', 'Purchases', 'purchases', 30, 0, 'ACO');
771 // xl('Purchases')
772 $gacl->add_object('inventory', 'Transfers', 'transfers', 40, 0, 'ACO');
773 // xl('Transfers')
774 $gacl->add_object('inventory', 'Adjustments', 'adjustments', 50, 0, 'ACO');
775 // xl('Adjustments')
776 $gacl->add_object('inventory', 'Consumption', 'consumption', 60, 0, 'ACO');
777 // xl('Consumption')
778 $gacl->add_object('inventory', 'Destruction', 'destruction', 70, 0, 'ACO');
779 // xl('Destruction')
780 $gacl->add_object('inventory', 'Reporting', 'reporting', 80, 0, 'ACO');
781 // xl('Reporting')
783 // Create ARO groups.
785 $users = $gacl->add_group('users', 'OpenEMR Users', 0, 'ARO');
786 // xl('OpenEMR Users')
787 $admin = $gacl->add_group('admin', 'Administrators', $users, 'ARO');
788 // xl('Administrators')
789 $clin = $gacl->add_group('clin', 'Clinicians', $users, 'ARO');
790 // xl('Clinicians')
791 $doc = $gacl->add_group('doc', 'Physicians', $users, 'ARO');
792 // xl('Physicians')
793 $front = $gacl->add_group('front', 'Front Office', $users, 'ARO');
794 // xl('Front Office')
795 $back = $gacl->add_group('back', 'Accounting', $users, 'ARO');
796 // xl('Accounting')
797 $breakglass = $gacl->add_group('breakglass', 'Emergency Login', $users, 'ARO');
798 // xl('Emergency Login')
801 // Create a Users section for the AROs (humans).
803 $gacl->add_object_section('Users', 'users', 10, 0, 'ARO');
804 // xl('Users')
806 // Create the Administrator in the above-created "users" section
807 // and add him/her to the above-created "admin" group.
808 // If this script is being used by OpenEMR's setup, then will
809 // incorporate the installation values. Otherwise will
810 // hardcode the 'admin' user.
811 if (isset($this) && isset($this->iuser)) {
812 $gacl->add_object('users', $this->iuname, $this->iuser, 10, 0, 'ARO');
813 $gacl->add_group_object($admin, 'users', $this->iuser, 'ARO');
814 } else {
815 $gacl->add_object('users', 'Administrator', 'admin', 10, 0, 'ARO');
816 $gacl->add_group_object($admin, 'users', 'admin', 'ARO');
819 // Declare return terms for language translations
820 // xl('write') xl('wsome') xl('addonly') xl('view')
822 // Set permissions for administrators.
824 $gacl->add_acl(
825 array(
826 'acct' => array('bill', 'disc', 'eob', 'rep', 'rep_a'),
827 'admin' => array('calendar', 'database', 'forms', 'practice', 'superbill', 'users', 'batchcom', 'language', 'super', 'drugs', 'acl','multipledb','menu','manage_modules'),
828 'encounters' => array('auth_a', 'auth', 'coding_a', 'coding', 'notes_a', 'notes', 'date_a', 'relaxed'),
829 'inventory' => array('lots', 'sales', 'purchases', 'transfers', 'adjustments', 'consumption', 'destruction', 'reporting'),
830 'lists' => array('default','state','country','language','ethrace'),
831 'patients' => array('appt', 'demo', 'med', 'trans', 'docs', 'notes', 'sign', 'reminder', 'alert', 'disclosure', 'rx', 'amendment', 'lab', 'docs_rm','pat_rep'),
832 'sensitivities' => array('normal', 'high'),
833 'nationnotes' => array('nn_configure'),
834 'patientportal' => array('portal'),
835 'menus' => array('modle'),
836 'groups' => array('gadd','gcalendar','glog','gdlog','gm')
838 null,
839 array($admin),
840 null,
841 null,
844 'write',
845 'Administrators can do anything'
847 // xl('Administrators can do anything')
849 // Set permissions for physicians.
851 $gacl->add_acl(
852 array(
853 'patients' => array('pat_rep')
855 null,
856 array($doc),
857 null,
858 null,
861 'view',
862 'Things that physicians can only read'
864 // xl('Things that physicians can only read')
865 $gacl->add_acl(
866 array(
867 'placeholder' => array('filler')
869 null,
870 array($doc),
871 null,
872 null,
875 'addonly',
876 'Things that physicians can read and enter but not modify'
878 // xl('Things that physicians can read and enter but not modify')
879 $gacl->add_acl(
880 array(
881 'placeholder' => array('filler')
883 null,
884 array($doc),
885 null,
886 null,
889 'wsome',
890 'Things that physicians can read and partly modify'
892 // xl('Things that physicians can read and partly modify')
893 $gacl->add_acl(
894 array(
895 'acct' => array('disc', 'rep'),
896 'admin' => array('drugs'),
897 'encounters' => array('auth_a', 'auth', 'coding_a', 'coding', 'notes_a', 'notes', 'date_a', 'relaxed'),
898 'patients' => array('appt', 'demo', 'med', 'trans', 'docs', 'notes', 'sign', 'reminder', 'alert',
899 'disclosure', 'rx', 'amendment', 'lab'),
900 'sensitivities' => array('normal', 'high'),
901 'groups' => array('gcalendar','glog')
903 null,
904 array($doc),
905 null,
906 null,
909 'write',
910 'Things that physicians can read and modify'
912 // xl('Things that physicians can read and modify')
914 // Set permissions for clinicians.
916 $gacl->add_acl(
917 array(
918 'patients' => array('pat_rep')
920 null,
921 array($clin),
922 null,
923 null,
926 'view',
927 'Things that clinicians can only read'
929 // xl('Things that clinicians can only read')
930 $gacl->add_acl(
931 array(
932 'encounters' => array('notes', 'relaxed'),
933 'patients' => array('demo', 'med', 'docs', 'notes','trans', 'reminder', 'alert', 'disclosure', 'rx', 'amendment', 'lab'),
934 'sensitivities' => array('normal')
936 null,
937 array($clin),
938 null,
939 null,
942 'addonly',
943 'Things that clinicians can read and enter but not modify'
945 // xl('Things that clinicians can read and enter but not modify')
946 $gacl->add_acl(
947 array(
948 'placeholder' => array('filler')
950 null,
951 array($clin),
952 null,
953 null,
956 'wsome',
957 'Things that clinicians can read and partly modify'
959 // xl('Things that clinicians can read and partly modify')
960 $gacl->add_acl(
961 array(
962 'admin' => array('drugs'),
963 'encounters' => array('auth', 'coding', 'notes'),
964 'patients' => array('appt'),
965 'groups' => array('gcalendar', 'glog')
967 null,
968 array($clin),
969 null,
970 null,
973 'write',
974 'Things that clinicians can read and modify'
976 // xl('Things that clinicians can read and modify')
978 // Set permissions for front office staff.
980 $gacl->add_acl(
981 array(
982 'patients' => array('alert')
984 null,
985 array($front),
986 null,
987 null,
990 'view',
991 'Things that front office can only read'
993 // xl('Things that front office can only read')
994 $gacl->add_acl(
995 array(
996 'placeholder' => array('filler')
998 null,
999 array($front),
1000 null,
1001 null,
1004 'addonly',
1005 'Things that front office can read and enter but not modify'
1007 // xl('Things that front office can read and enter but not modify')
1008 $gacl->add_acl(
1009 array(
1010 'placeholder' => array('filler')
1012 null,
1013 array($front),
1014 null,
1015 null,
1018 'wsome',
1019 'Things that front office can read and partly modify'
1021 // xl('Things that front office can read and partly modify')
1022 $gacl->add_acl(
1023 array(
1024 'patients' => array('appt', 'demo'),
1025 'groups' => array('gcalendar')
1027 null,
1028 array($front),
1029 null,
1030 null,
1033 'write',
1034 'Things that front office can read and modify'
1036 // xl('Things that front office can read and modify')
1038 // Set permissions for back office staff.
1040 $gacl->add_acl(
1041 array(
1042 'patients' => array('alert')
1044 null,
1045 array($back),
1046 null,
1047 null,
1050 'view',
1051 'Things that back office can only read'
1053 // xl('Things that back office can only read')
1054 $gacl->add_acl(
1055 array(
1056 'placeholder' => array('filler')
1058 null,
1059 array($back),
1060 null,
1061 null,
1064 'addonly',
1065 'Things that back office can read and enter but not modify'
1067 // xl('Things that back office can read and enter but not modify')
1068 $gacl->add_acl(
1069 array(
1070 'placeholder' => array('filler')
1072 null,
1073 array($back),
1074 null,
1075 null,
1078 'wsome',
1079 'Things that back office can read and partly modify'
1081 // xl('Things that back office can read and partly modify')
1082 $gacl->add_acl(
1083 array(
1084 'acct' => array('bill', 'disc', 'eob', 'rep', 'rep_a'),
1085 'admin' => array('practice', 'superbill'),
1086 'encounters' => array('auth_a', 'coding_a', 'date_a'),
1087 'patients' => array('appt', 'demo')
1089 null,
1090 array($back),
1091 null,
1092 null,
1095 'write',
1096 'Things that back office can read and modify'
1098 // xl('Things that back office can read and modify')
1100 // Set permissions for Emergency Login.
1102 $gacl->add_acl(
1103 array(
1104 'acct' => array('bill', 'disc', 'eob', 'rep', 'rep_a'),
1105 'admin' => array('calendar', 'database', 'forms', 'practice', 'superbill', 'users', 'batchcom', 'language', 'super', 'drugs', 'acl','multipledb','menu','manage_modules'),
1106 'encounters' => array('auth_a', 'auth', 'coding_a', 'coding', 'notes_a', 'notes', 'date_a', 'relaxed'),
1107 'inventory' => array('lots', 'sales', 'purchases', 'transfers', 'adjustments', 'consumption', 'destruction', 'reporting'),
1108 'lists' => array('default','state','country','language','ethrace'),
1109 'patients' => array('appt', 'demo', 'med', 'trans', 'docs', 'notes', 'sign', 'reminder', 'alert', 'disclosure', 'rx', 'amendment', 'lab', 'docs_rm','pat_rep'),
1110 'sensitivities' => array('normal', 'high'),
1111 'nationnotes' => array('nn_configure'),
1112 'patientportal' => array('portal'),
1113 'menus' => array('modle'),
1114 'groups' => array('gadd','gcalendar','glog','gdlog','gm')
1116 null,
1117 array($breakglass),
1118 null,
1119 null,
1122 'write',
1123 'Emergency Login user can do anything'
1125 // xl('Emergency Login user can do anything')
1127 return true;
1130 public function quick_install()
1132 // Validation of OpenEMR user settings
1133 // (applicable if not cloning from another database)
1134 if (empty($this->clone_database)) {
1135 if (! $this->login_is_valid()) {
1136 return false;
1139 if (! $this->iuser_is_valid()) {
1140 return false;
1143 if (! $this->user_password_is_valid()) {
1144 return false;
1148 // Validation of mysql database password
1149 if (! $this->password_is_valid()) {
1150 return false;
1153 if (! $this->no_root_db_access) {
1154 // Connect to mysql via root user
1155 if (! $this->root_database_connection()) {
1156 return false;
1159 // Create the dumpfile
1160 // (applicable if cloning from another database)
1161 if (! empty($this->clone_database)) {
1162 if (! $this->create_dumpfiles()) {
1163 return false;
1167 // Create the site directory
1168 // (applicable if mirroring another local site)
1169 if (! empty($this->source_site_id)) {
1170 if (! $this->create_site_directory()) {
1171 return false;
1175 $this->disconnect();
1176 // Using @ in below call to hide the php warning in cases where the
1177 // below connection does not work, which is expected behavior.
1178 // Using try in below call to catch the mysqli exception when the
1179 // below connection does not work, which is expected behavior (needed to
1180 // add this try/catch clause for PHP 8.1).
1181 try {
1182 $checkUserDatabaseConnection = @$this->user_database_connection();
1183 } catch (Exception $e) {
1184 $checkUserDatabaseConnection = false;
1186 if (! $checkUserDatabaseConnection) {
1187 // Re-connect to mysql via root user
1188 if (! $this->root_database_connection()) {
1189 return false;
1192 // Create the mysql database
1193 if (! $this->create_database()) {
1194 return false;
1197 // Create the mysql user
1198 if (! $this->create_database_user()) {
1199 return false;
1202 // Grant user privileges to the mysql database
1203 if (! $this->grant_privileges()) {
1204 return false;
1208 $this->disconnect();
1211 // Connect to mysql via created user
1212 if (! $this->user_database_connection()) {
1213 return false;
1216 // Build the database
1217 if (! $this->load_dumpfiles()) {
1218 return false;
1221 // Write the sql configuration file
1222 if (! $this->write_configuration_file()) {
1223 return false;
1226 // Load the version information, globals settings,
1227 // initial user, and set up gacl access controls.
1228 // (applicable if not cloning from another database)
1229 if (empty($this->clone_database)) {
1230 if (! $this->add_version_info()) {
1231 return false;
1234 if (! $this->insert_globals()) {
1235 return false;
1238 if (! $this->add_initial_user()) {
1239 return false;
1242 if (! $this->install_gacl()) {
1243 return false;
1246 if (! $this->install_additional_users()) {
1247 return false;
1250 if (! $this->on_care_coordination()) {
1251 return false;
1255 return true;
1258 private function escapeSql($sql)
1260 return mysqli_real_escape_string($this->dbh, $sql);
1263 private function escapeDatabaseName($name)
1265 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
1266 error_log("Illegal character(s) in database name");
1267 die("Illegal character(s) in database name");
1269 return $name;
1272 private function escapeCollateName($name)
1274 if (preg_match('/[^A-Za-z0-9_-]/', $name)) {
1275 error_log("Illegal character(s) in collation name");
1276 die("Illegal character(s) in collation name");
1278 return $name;
1281 private function execute_sql($sql, $showError = true)
1283 $this->error_message = '';
1284 if (! $this->dbh) {
1285 $this->user_database_connection();
1288 $results = mysqli_query($this->dbh, $sql);
1289 if ($results) {
1290 return $results;
1291 } else {
1292 if ($showError) {
1293 $error_mes = mysqli_error($this->dbh);
1294 $this->error_message = "unable to execute SQL: '$sql' due to: " . $error_mes;
1295 error_log("ERROR IN OPENEMR INSTALL: Unable to execute SQL: " . htmlspecialchars($sql, ENT_QUOTES) . " due to: " . htmlspecialchars($error_mes, ENT_QUOTES));
1297 return false;
1301 private function connect_to_database($server, $user, $password, $port, $dbname = '')
1303 $pathToCerts = __DIR__ . "/../../sites/" . $this->site . "/documents/certificates/";
1304 $mysqlSsl = false;
1305 $mysqli = mysqli_init();
1306 if (defined('MYSQLI_CLIENT_SSL') && file_exists($pathToCerts . "mysql-ca")) {
1307 $mysqlSsl = true;
1308 if (
1309 file_exists($pathToCerts . "mysql-key") &&
1310 file_exists($pathToCerts . "mysql-cert")
1312 // with client side certificate/key
1313 mysqli_ssl_set(
1314 $mysqli,
1315 $pathToCerts . "mysql-key",
1316 $pathToCerts . "mysql-cert",
1317 $pathToCerts . "mysql-ca",
1318 null,
1319 null
1321 } else {
1322 // without client side certificate/key
1323 mysqli_ssl_set(
1324 $mysqli,
1325 null,
1326 null,
1327 $pathToCerts . "mysql-ca",
1328 null,
1329 null
1333 if ($mysqlSsl) {
1334 $ok = mysqli_real_connect($mysqli, $server, $user, $password, $dbname, (int)$port != 0 ? (int)$port : 3306, '', MYSQLI_CLIENT_SSL);
1335 } else {
1336 $ok = mysqli_real_connect($mysqli, $server, $user, $password, $dbname, (int)$port != 0 ? (int)$port : 3306);
1338 if (!$ok) {
1339 $this->error_message = 'unable to connect to sql server because of: (' . mysqli_connect_errno() . ') ' . mysqli_connect_error();
1340 return false;
1342 return $mysqli;
1345 private function set_sql_strict()
1347 // Turn off STRICT SQL
1348 return $this->execute_sql("SET sql_mode = ''");
1351 private function set_collation()
1353 return $this->execute_sql("SET NAMES 'utf8mb4'");
1357 * innitialize $this->dumpfiles, an array of the dumpfiles that will
1358 * be loaded into the database, including the correct translation
1359 * dumpfile.
1360 * The keys are the paths of the dumpfiles, and the values are the titles
1361 * @return array
1363 private function initialize_dumpfile_list()
1365 if ($this->clone_database) {
1366 $this->dumpfiles = array( $this->get_backup_filename() => 'clone database' );
1367 } else {
1368 $dumpfiles = array( $this->main_sql => 'Main' );
1369 if (! empty($this->development_translations)) {
1370 // Use the online development translation set
1371 $dumpfiles[ $this->devel_translation_sql ] = "Online Development Language Translations (utf8)";
1372 } else {
1373 // Use the local translation set
1374 $dumpfiles[ $this->translation_sql ] = "Language Translation (utf8)";
1377 if ($this->ippf_specific) {
1378 $dumpfiles[ $this->ippf_sql ] = "IPPF Layout";
1381 // Load ICD-9 codes if present.
1382 if (file_exists($this->icd9)) {
1383 $dumpfiles[ $this->icd9 ] = "ICD-9";
1386 // Load CVX codes if present
1387 if (file_exists($this->cvx)) {
1388 $dumpfiles[ $this->cvx ] = "CVX Immunization Codes";
1391 $this->dumpfiles = $dumpfiles;
1394 return $this->dumpfiles;
1399 * Directory copy logic borrowed from a user comment at
1400 * http://www.php.net/manual/en/function.copy.php
1401 * @param string $src name of the directory to copy
1402 * @param string $dst name of the destination to copy to
1403 * @return bool indicating success
1405 private function recurse_copy($src, $dst)
1407 $dir = opendir($src);
1408 if (! @mkdir($dst)) {
1409 $this->error_message = "unable to create directory: '$dst'";
1410 return false;
1413 while (false !== ($file = readdir($dir))) {
1414 if ($file != '.' && $file != '..') {
1415 if (is_dir($src . '/' . $file)) {
1416 $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
1417 } else {
1418 copy($src . '/' . $file, $dst . '/' . $file);
1423 closedir($dir);
1424 return true;
1429 * dump a site's database to a temporary file.
1430 * @param string $source_site_id the site_id of the site to dump
1431 * @return filename of the backup
1433 private function dumpSourceDatabase()
1435 global $OE_SITES_BASE;
1436 $source_site_id = $this->source_site_id;
1438 include("$OE_SITES_BASE/$source_site_id/sqlconf.php");
1440 if (empty($config)) {
1441 die("Source site $source_site_id has not been set up!");
1444 $backup_file = $this->get_backup_filename();
1445 $cmd = "mysqldump -u " . escapeshellarg($login) .
1446 " -h " . $host .
1447 " -p" . escapeshellarg($pass) .
1448 " --ignore-table=" . escapeshellarg($dbase . ".onsite_activity_view") . " --hex-blob --opt --skip-extended-insert --quote-names -r $backup_file " .
1449 escapeshellarg($dbase);
1451 $tmp1 = [];
1452 $tmp0 = exec($cmd, $tmp1, $tmp2);
1453 if ($tmp2) {
1454 die("Error $tmp2 running \"$cmd\": $tmp0 " . implode(' ', $tmp1));
1457 return $backup_file;
1461 * @return filename of the source backup database for cloning
1463 private function get_backup_filename()
1465 if (stristr(PHP_OS, 'WIN')) {
1466 $backup_file = 'C:/windows/temp/setup_dump.sql';
1467 } else {
1468 $backup_file = '/tmp/setup_dump.sql';
1471 return $backup_file;
1473 //RP_ADDED
1474 public function getCurrentTheme()
1476 $current_theme = $this->execute_sql("SELECT gl_value FROM globals WHERE gl_name LIKE '%css_header%'");
1477 $current_theme = mysqli_fetch_array($current_theme);
1478 return $current_theme[0];
1481 public function setCurrentTheme()
1483 $current_theme = $this->getCurrentTheme();
1484 // for cloned sites since they're not asked about a new theme
1485 if (!$this->new_theme) {
1486 $this->new_theme = $current_theme;
1488 return $this->execute_sql("UPDATE globals SET gl_value='" . $this->escapeSql($this->new_theme) . "' WHERE gl_name LIKE '%css_header%'");
1491 public function listThemes()
1493 $themes_img_dir = "public/images/stylesheets/";
1494 $arr_themes_img = array_values(array_filter(scandir($themes_img_dir), function ($item) {
1495 return $item[0] !== '.';
1496 }));
1497 return $arr_themes_img;
1500 private function extractFileName($theme_file_name = '')
1502 $this->theme_file_name = $theme_file_name;
1503 $under_score = strpos($theme_file_name, '_') + 1;
1504 $dot = strpos($theme_file_name, '.');
1505 $theme_value = substr($theme_file_name, $under_score, ($dot - $under_score));
1506 $theme_title = ucwords(str_replace("_", " ", $theme_value));
1507 return array('theme_value' => $theme_value, 'theme_title' => $theme_title);
1510 public function displayThemesDivs()
1512 $themes_number = count($this->listThemes());
1513 for ($i = 0; $i < $themes_number; $i++) {
1514 $id = $i + 1;
1515 $arr_theme_name = $this->listThemes();
1516 $theme_file_name = $arr_theme_name[$i];
1517 $arr_extracted_file_name = $this->extractFileName($theme_file_name);
1518 $theme_value = $arr_extracted_file_name['theme_value'];
1519 $theme_title = $arr_extracted_file_name['theme_title'];
1520 $img_path = "public/images/stylesheets/";
1521 $theme_file_path = $img_path . $theme_file_name;
1522 $div_start = " <div class='row'>";
1523 $div_end = " </div>";
1524 $img_div = " <div class='col-sm-2 checkboxgroup'>
1525 <label for='my_radio_button_id" . attr($id) . "'><img height='160px' src='" . attr($theme_file_path) . "' width='100%'></label>
1526 <p class='m-0'>" . text($theme_title) . "</p><input id='my_radio_button_id" . attr($id) . "' name='stylesheet' type='radio' value='" . attr($theme_value) . "'>
1527 </div>";
1528 $theme_img_number = $i % 6; //to ensure that last file in array will always generate 5 and will end the row
1529 switch ($theme_img_number) {
1530 case 0: //start row
1531 echo $div_start . "\r\n";
1532 echo $img_div . "\r\n";
1533 break;
1535 case 1:
1536 case 2:
1537 case 3:
1538 case 4:
1539 echo $img_div . "\r\n";
1540 break;
1542 case 5://end row
1543 echo $img_div . "\r\n";
1544 echo $div_end . "\r\n";
1545 echo "<br />" . "\r\n";
1546 break;
1548 default:
1549 echo $div_start . "\r\n";
1550 echo "<h5>Sorry no stylesheet images in directory</h5>";
1551 echo $div_end . "\r\n";
1552 break;
1555 return;
1558 public function displaySelectedThemeDiv()
1560 $theme_file_name = $this->getCurrentTheme();
1561 $arr_extracted_file_name = $this->extractFileName($theme_file_name);
1562 $theme_value = $arr_extracted_file_name['theme_value'];
1563 $theme_title = $arr_extracted_file_name['theme_title'];
1564 $img_path = "public/images/stylesheets/";
1565 $theme_file_path = $img_path . "style_" . $theme_value . ".png";
1567 $display_selected_theme_div = <<<DSTD
1568 <div class="row">
1569 <div class="col-sm-12">
1570 <h4>Current Theme:</h4>
1571 <div class="col-sm-4 offset-sm-4 checkboxgroup">
1572 <label for="nothing"><img id="current_theme" src="{$theme_file_path}" width="100%"></label>
1573 <p id="current_theme_title"style="margin:0">{$theme_title}</p>
1574 </div>
1575 </div>
1576 </div>
1577 <br />
1578 DSTD;
1579 echo $display_selected_theme_div . "\r\n";
1580 return;
1583 public function displayNewThemeDiv()
1585 // cloned sites don't get a chance to set a new theme
1586 if (!$this->new_theme) {
1587 $this->new_theme = $this->getCurrentTheme();
1589 $theme_file_name = $this->new_theme;
1590 $arr_extracted_file_name = $this->extractFileName($theme_file_name);
1591 $theme_value = $arr_extracted_file_name['theme_value'];
1592 $theme_title = $arr_extracted_file_name['theme_title'];
1593 $img_path = "public/images/stylesheets/";
1594 $theme_file_path = $img_path . "style_" . $theme_value . ".png";
1596 $display_selected_theme_div = <<<DSTD
1597 <div class="row">
1598 <div class="col-sm-12">
1599 <div class="col-sm-4 offset-sm-4 checkboxgroup">
1600 <label for="nothing"><img id="current_theme" src="{$theme_file_path}" width="75%"></label>
1601 <p id="current_theme_title"style="margin:0">{$theme_title}</p>
1602 </div>
1603 </div>
1604 </div>
1605 <br />
1606 DSTD;
1607 echo $display_selected_theme_div . "\r\n";
1608 return;
1611 public function setupHelpModal()
1613 $setup_help_modal = <<<SETHLP
1614 <div class="row">
1615 <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1616 <div class="modal-dialog modal-lg">
1617 <div class="modal-content oe-modal-content" style="height:700px">
1618 <div class="modal-header clearfix">
1619 <button type="button" class="close" data-dismiss="modal" aria-label=Close>
1620 <span aria-hidden="true" style="color:var(--black); font-size:1.5em;">×</span></button>
1621 </div>
1622 <div class="modal-body" style="height:80%;">
1623 <iframe src="" id="targetiframe" style="height:100%; width:100%; overflow-x: hidden; border:none"
1624 allowtransparency="true"></iframe>
1625 </div>
1626 <div class="modal-footer" style="margin-top:0px;">
1627 <button class="btn btn-link btn-cancel oe-pull-away" data-dismiss="modal" type="button">Close</button>
1628 <!--<button class="btn btn-secondary btn-print oe-pull-away" data-dismiss="modal" id="print-help-href" type="button">Print</button>-->
1629 </div>
1630 </div>
1631 </div>
1632 </div>
1633 </div>
1634 <script>
1635 $(function () {
1636 $('#help-href').click (function(){
1637 document.getElementById('targetiframe').src = "Documentation/help_files/openemr_installation_help.php";
1640 $(function () {
1641 $('#print-help-href').click (function(){
1642 $("#targetiframe").get(0).contentWindow.print();
1645 // Jquery draggable
1646 $(".modal-dialog").addClass('drag-action');
1647 $(".modal-content").addClass('resize-action');
1648 </script>
1649 SETHLP;
1650 echo $setup_help_modal . "\r\n";
1651 return;