incremented patch version 3
[openemr.git] / interface / main / backup.php
blob05b1b2f4ef7e1b40ce2298f1693bb0281440829d
1 <?php
2 /* $Id$ */
3 // Copyright (C) 2008-2010 Rod Roark <rod@sunsetsystems.com>
4 // Adapted for cross-platform operation by Bill Cernansky (www.mi-squared.com)
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This script creates a backup tarball and sends it to the users's
12 // browser for download. The tarball includes:
14 // * an OpenEMR database dump (gzipped)
15 // * a phpGACL database dump (gzipped), if phpGACL is used and has
16 // its own database
17 // * a SQL-Ledger database dump (gzipped), if SQL-Ledger is used
18 // (currently skipped on Windows servers)
19 // * the OpenEMR web directory (.tar.gz)
20 // * the phpGACL web directory (.tar.gz), if phpGACL is used
21 // * the SQL-Ledger web directory (.tar.gz), if SQL-Ledger is used
22 // and its web directory exists as a sister of the openemr directory
23 // and has the name "sql-ledger" (otherwise we do not have enough
24 // information to find it)
26 // The OpenEMR web directory is important because it includes config-
27 // uration files, patient documents, and possible customizations, and
28 // also because the database structure is dependent on the installed
29 // OpenEMR version.
31 // This script depends on execution of some external programs:
32 // mysqldump & pg_dump. It has been tested with Debian and Ubuntu
33 // Linux and with Windows XP.
34 // Do not assume that it works for you until you have successfully
35 // tested a restore!
36 set_time_limit(0);
37 require_once("../globals.php");
38 require_once("$srcdir/acl.inc");
39 require_once("$srcdir/log.inc");
41 if (!acl_check('admin', 'super')) die(xl('Not authorized','','','!'));
43 include_once("Archive/Tar.php");
45 // Set up method, which will depend on OS and if pear tar.php is installed
46 if (class_exists('Archive_Tar')) {
47 # pear tar.php is installed so can use os independent method
48 $newBackupMethod = true;
50 elseif (IS_WINDOWS) {
51 # without the tar.php module, can't run backup in windows
52 die(xl("Error. You need to install the Archive/Tar.php php module."));
54 else {
55 # without the tar.php module, can run via system commands in non-windows
56 $newBackupMethod = false;
59 $BTN_TEXT_CREATE = xl('Create Backup');
60 $BTN_TEXT_EXPORT = xl('Export Configuration');
61 $BTN_TEXT_IMPORT = xl('Import Configuration');
62 // ViSolve: Create Log Backup button
63 $BTN_TEXT_CREATE_EVENTLOG = xl('Create Eventlog Backup');
65 $form_step = isset($_POST['form_step']) ? trim($_POST['form_step']) : '0';
66 $form_status = isset($_POST['form_status' ]) ? trim($_POST['form_status' ]) : '';
68 if (!empty($_POST['form_export'])) $form_step = 101;
69 if (!empty($_POST['form_import'])) $form_step = 201;
70 //ViSolve: Assign Unique Number for the Log Creation
71 if (!empty($_POST['form_backup'])) $form_step = 301;
72 // When true the current form will submit itself after a brief pause.
73 $auto_continue = false;
75 # set up main paths
76 $backup_file_prefix = "emr_backup";
77 $backup_file_suffix = ".tar";
78 $TMP_BASE = $GLOBALS['temporary_files_dir'] . "/openemr_web_backup";
79 $BACKUP_DIR = $TMP_BASE . "/emr_backup";
80 $TAR_FILE_PATH = $TMP_BASE . DIRECTORY_SEPARATOR . $backup_file_prefix . $backup_file_suffix;
81 $EXPORT_FILE = $GLOBALS['temporary_files_dir'] . "/openemr_config.sql";
82 $MYSQL_PATH = $GLOBALS['mysql_bin_dir'];
83 $PERL_PATH = $GLOBALS['perl_bin_dir'];
85 if ($form_step == 8) {
86 header("Pragma: public");
87 header("Expires: 0");
88 header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
89 header("Content-Type: application/force-download");
90 header("Content-Length: " . filesize($TAR_FILE_PATH));
91 header("Content-Disposition: attachment; filename=" . basename($TAR_FILE_PATH));
92 header("Content-Description: File Transfer");
93 readfile($TAR_FILE_PATH);
94 unlink($TAR_FILE_PATH);
95 obliterate_dir($BACKUP_DIR);
96 exit(0);
99 if ($form_step == 104) {
100 header("Pragma: public");
101 header("Expires: 0");
102 header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
103 header("Content-Type: application/force-download");
104 header("Content-Length: " . filesize($EXPORT_FILE));
105 header("Content-Disposition: attachment; filename=" . basename($EXPORT_FILE));
106 header("Content-Description: File Transfer");
107 readfile($EXPORT_FILE);
108 unlink($EXPORT_FILE);
109 exit(0);
112 <html>
114 <head>
115 <link rel="stylesheet" href='<?php echo $css_header ?>' type='text/css'>
116 <title><?php xl('Backup','e'); ?></title>
117 </head>
119 <body class="body_top">
120 <center>
121 &nbsp;<br />
122 <form method='post' action='backup.php' enctype='multipart/form-data'>
124 <table style='width:50em'>
125 <tr>
126 <td>
128 <?php
129 $cmd = '';
130 $mysql_cmd = $MYSQL_PATH . DIRECTORY_SEPARATOR . 'mysql';
131 $mysql_dump_cmd = $mysql_cmd . 'dump';
132 $file_to_compress = ''; // if named, this iteration's file will be gzipped after it is created
133 $eventlog=0; // Eventlog Flag
135 if ($form_step == 0) {
136 echo "<table>\n";
137 echo " <tr>\n";
138 echo " <td><input type='submit' name='form_create' value='$BTN_TEXT_CREATE' /></td>\n";
139 echo " <td>" . xl('Create and download a full backup') . "</td>\n";
140 echo " </tr>\n";
141 // The config import/export feature is optional.
142 if (!empty($GLOBALS['configuration_import_export'])) {
143 echo " <tr>\n";
144 echo " <td><input type='submit' name='form_export' value='$BTN_TEXT_EXPORT' /></td>\n";
145 echo " <td>" . xl('Download configuration data') . "</td>\n";
146 echo " </tr>\n";
147 echo " <tr>\n";
148 echo " <td><input type='submit' name='form_import' value='$BTN_TEXT_IMPORT' /></td>\n";
149 echo " <td>" . xl('Upload configuration data') . "</td>\n";
150 echo " </tr>\n";
152 // ViSolve : Add ' Create Log table backup Button'
153 echo " <tr>\n";
154 echo " <td><input type='submit' name='form_backup' value='$BTN_TEXT_CREATE_EVENTLOG' /></td>\n";
155 echo " <td>" . xl('Create Eventlog Backup') . "</td>\n";
156 echo " </tr>\n";
157 echo " <tr>\n";
158 echo " <td></td><td class='text'><b>" . xl('Note')."</b>&nbsp;" . xl('Please refer to').'&nbsp;README-Log-Backup.txt&nbsp;'.xl('file in the Documentation directory to learn how to automate the process of creating log backups') . "</td>\n";
159 echo " </tr>\n";
160 echo "</table>\n";
163 if ($form_step == 1) {
164 $form_status .= xl('Dumping OpenEMR database') . "...<br />";
165 echo nl2br($form_status);
166 if (file_exists($TAR_FILE_PATH))
167 if (! unlink($TAR_FILE_PATH)) die(xl("Couldn't remove old backup file:") . " " . $TAR_FILE_PATH);
168 if (! obliterate_dir($TMP_BASE)) die(xl("Couldn't remove dir:"). " " . $TMP_BASE);
169 if (! mkdir($BACKUP_DIR, 0777, true)) die(xl("Couldn't create backup dir:") . " " . $BACKUP_DIR);
170 $file_to_compress = "$BACKUP_DIR/openemr.sql"; // gzip this file after creation
172 if($GLOBALS['include_de_identification']==1)
174 //include routines during backup when de-identification is enabled
175 $cmd = "$mysql_dump_cmd -u " . escapeshellarg($sqlconf["login"]) .
176 " -p" . escapeshellarg($sqlconf["pass"]) .
177 " -h" . escapeshellarg($sqlconf["host"]) .
178 " --port=".escapeshellarg($sqlconf["port"]) .
179 " --routines".
180 " --opt --quote-names -r $file_to_compress " .
181 escapeshellarg($sqlconf["dbase"]);
183 else
185 $cmd = "$mysql_dump_cmd -u " . escapeshellarg($sqlconf["login"]) .
186 " -p" . escapeshellarg($sqlconf["pass"]) .
187 " -h" . escapeshellarg($sqlconf["host"]) .
188 " --port=".escapeshellarg($sqlconf["port"]) .
189 " --opt --quote-names -r $file_to_compress " .
190 escapeshellarg($sqlconf["dbase"]);
192 $auto_continue = true;
195 if ($form_step == 2) {
196 if (!empty($phpgacl_location) && $gacl_object->_db_name != $sqlconf["dbase"]) {
197 $form_status .= xl('Dumping phpGACL database') . "...<br />";
198 echo nl2br($form_status);
199 $file_to_compress = "$BACKUP_DIR/phpgacl.sql"; // gzip this file after creation
200 $cmd = "$mysql_dump_cmd -u " . escapeshellarg($gacl_object->_db_user) .
201 " -p" . escapeshellarg($gacl_object->_db_password) .
202 " --opt --quote-names -r $file_to_compress " .
203 escapeshellarg($gacl_object->_db_name);
204 $auto_continue = true;
206 else {
207 ++$form_step;
211 if ($form_step == 3) {
212 if ($GLOBALS['oer_config']['ws_accounting']['enabled'] &&
213 $GLOBALS['oer_config']['ws_accounting']['enabled'] !== 2) {
214 if (IS_WINDOWS) {
215 // Somebody may want to make this work in Windows, if they have SQL-Ledger set up.
216 $form_status .= xl('Skipping SQL-Ledger dump - not implemented for Windows server') . "...<br />";
217 echo nl2br($form_status);
218 ++$form_step;
220 else {
221 $form_status .= xl('Dumping SQL-Ledger database') . "...<br />";
222 echo nl2br($form_status);
223 $file_to_compress = "$BACKUP_DIR/sql-ledger.sql"; // gzip this file after creation
224 $cmd = "PGPASSWORD=" . escapeshellarg($sl_dbpass) . " pg_dump -U " .
225 escapeshellarg($sl_dbuser) . " -h localhost --format=c -f " .
226 "$file_to_compress " . escapeshellarg($sl_dbname);
227 $auto_continue = true;
230 else {
231 ++$form_step;
235 if ($form_step == 4) {
236 $form_status .= xl('Dumping OpenEMR web directory tree') . "...<br />";
237 echo nl2br($form_status);
238 $cur_dir = getcwd();
239 chdir($webserver_root);
241 // Select the files and directories to archive. Basically everything
242 // except site-specific data for other sites.
243 $file_list = array();
244 $dh = opendir($webserver_root);
245 if (!$dh) die("Cannot read directory '$webserver_root'.");
246 while (false !== ($filename = readdir($dh))) {
247 if ($filename == '.' || $filename == '..') continue;
248 if ($filename == 'sites') {
249 // Omit other sites.
250 $file_list[] = "$filename/" . $_SESSION['site_id'];
252 else {
253 $file_list[] = $filename;
256 closedir($dh);
258 $arch_file = $BACKUP_DIR . DIRECTORY_SEPARATOR . "openemr.tar.gz";
259 if (!create_tar_archive($arch_file, "gz", $file_list))
260 die(xl("An error occurred while dumping OpenEMR web directory tree"));
261 chdir($cur_dir);
262 $auto_continue = true;
265 if ($form_step == 5) {
266 if ((!empty($phpgacl_location)) && ($phpgacl_location != $srcdir."/../gacl") ) {
267 $form_status .= xl('Dumping phpGACL web directory tree') . "...<br />";
268 echo nl2br($form_status);
269 $cur_dir = getcwd();
270 chdir($phpgacl_location);
271 $file_list = array('.'); // archive entire directory
272 $arch_file = $BACKUP_DIR . DIRECTORY_SEPARATOR . "phpgacl.tar.gz";
273 if (!create_tar_archive($arch_file, "gz", $file_list))
274 die (xl("An error occurred while dumping phpGACL web directory tree"));
275 chdir($cur_dir);
276 $auto_continue = true;
278 else {
279 ++$form_step;
283 if ($form_step == 6) {
284 if ($GLOBALS['oer_config']['ws_accounting']['enabled'] &&
285 $GLOBALS['oer_config']['ws_accounting']['enabled'] !== 2 &&
286 is_dir("$webserver_root/../sql-ledger"))
288 $form_status .= xl('Dumping SQL-Ledger web directory tree') . "...<br />";
289 echo nl2br($form_status);
290 $cur_dir = getcwd();
291 $arch_dir = $webserver_root . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "sql-ledger";
292 chdir($arch_dir);
293 $file_list = array('.'); // archive entire directory
294 $arch_file = $BACKUP_DIR . DIRECTORY_SEPARATOR . "sql-ledger.tar.gz";
295 if (!create_tar_archive($arch_file, "gz", $file_list))
296 die(xl("An error occurred while dumping SQL-Ledger web directory tree"));
297 chdir($cur_dir);
298 $auto_continue = true;
300 else {
301 ++$form_step;
304 if ($form_step == 7) { // create the final compressed tar containing all files
305 $form_status .= xl('Backup file has been created. Will now send download.') . "<br />";
306 echo nl2br($form_status);
307 $cur_dir = getcwd();
308 chdir($BACKUP_DIR);
309 $file_list = array('.');
310 if (!create_tar_archive($TAR_FILE_PATH, '', $file_list))
311 die(xl("Error: Unable to create downloadable archive"));
312 chdir($cur_dir);
313 /* To log the backup event */
314 if ($GLOBALS['audit_events_backup']){
315 newEvent("backup", $_SESSION['authUser'], $_SESSION['authProvider'], 0,"Backup is completed");
317 $auto_continue = true;
321 if ($form_step == 101) {
322 echo xl('Select the configuration items to export') . ":";
323 echo "<br />&nbsp;<br />\n";
324 echo "<input type='checkbox' name='form_cb_services' value='1' />\n";
325 echo " " . xl('Services') . "<br />\n";
326 echo "<input type='checkbox' name='form_cb_products' value='1' />\n";
327 echo " " . xl('Products') . "<br />\n";
328 echo "<input type='checkbox' name='form_cb_lists' value='1' />\n";
329 echo " " . xl('Lists') . "<br />\n";
330 echo "<input type='checkbox' name='form_cb_layouts' value='1' />\n";
331 echo " " . xl('Layouts') . "<br />\n";
332 echo "<input type='checkbox' name='form_cb_prices' value='1' />\n";
333 echo " " . xl('Prices') . "<br />\n";
334 echo "<input type='checkbox' name='form_cb_categories' value='1' />\n";
335 echo " " . xl('Document Categories') . "<br />\n";
336 echo "<input type='checkbox' name='form_cb_feesheet' value='1' />\n";
337 echo " " . xl('Fee Sheet Options') . "<br />\n";
338 echo "<input type='checkbox' name='form_cb_lang' value='1' />\n";
339 echo " " . xl('Translations') . "<br />\n";
341 echo "&nbsp;<br /><input type='submit' value='" . xl('Continue') . "' />\n";
344 if ($form_step == 102) {
345 $tables = '';
346 if ($_POST['form_cb_services' ]) $tables .= ' codes';
347 if ($_POST['form_cb_products' ]) $tables .= ' drugs drug_templates';
348 if ($_POST['form_cb_lists' ]) $tables .= ' list_options';
349 if ($_POST['form_cb_layouts' ]) $tables .= ' layout_options';
350 if ($_POST['form_cb_prices' ]) $tables .= ' prices';
351 if ($_POST['form_cb_categories']) $tables .= ' categories categories_seq';
352 if ($_POST['form_cb_feesheet' ]) $tables .= ' fee_sheet_options';
353 if ($_POST['form_cb_lang' ]) $tables .= ' lang_languages lang_constants lang_definitions';
354 if ($tables) {
355 $form_status .= xl('Creating export file') . "...<br />";
356 echo nl2br($form_status);
357 if (file_exists($EXPORT_FILE))
358 if (! unlink($EXPORT_FILE)) die(xl("Couldn't remove old export file: ") . $EXPORT_FILE);
360 // The substitutions below use perl because sed's not usually on windows systems.
361 $perl = $PERL_PATH . DIRECTORY_SEPARATOR . 'perl';
362 $cmd = "$mysql_dump_cmd -u " . escapeshellarg($sqlconf["login"]) .
363 " -p" . escapeshellarg($sqlconf["pass"]) .
364 " --opt --quote-names " .
365 escapeshellarg($sqlconf["dbase"]) . " $tables" .
366 " | $perl -pe 's/ DEFAULT CHARSET=utf8//i; s/ collate[ =][^ ;,]*//i;'" .
367 " > $EXPORT_FILE;";
369 else {
370 echo xl('No items were selected!');
371 $form_step = -1;
373 $auto_continue = true;
376 if ($form_step == 103) {
377 $form_status .= xl('Done. Will now send download.') . "<br />";
378 echo nl2br($form_status);
379 $auto_continue = true;
382 if ($form_step == 201) {
383 echo xl('WARNING: This will overwrite configuration information with data from the uploaded file!') . " \n";
384 echo xl('Use this feature only with newly installed sites, ');
385 echo xl('otherwise you will destroy references to/from existing data.') . "\n";
386 echo "<br />&nbsp;<br />\n";
387 echo xl('File to upload') . ":\n";
388 echo "<input type='hidden' name='MAX_FILE_SIZE' value='4000000' />\n";
389 echo "<input type='file' name='userfile' /><br />&nbsp;<br />\n";
390 echo "<input type='submit' value='" . xl('Continue') . "' />\n";
393 if ($form_step == 202) {
394 // Process uploaded config file.
395 if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {
396 if (move_uploaded_file($_FILES['userfile']['tmp_name'], $EXPORT_FILE)) {
397 $form_status .= xl('Applying') . "...<br />";
398 echo nl2br($form_status);
399 $cmd = "$mysql_cmd -u" . escapeshellarg($sqlconf["login"]) .
400 " -p" . escapeshellarg($sqlconf["pass"]) . " " .
401 escapeshellarg($sqlconf["dbase"]) .
402 " < $EXPORT_FILE";
404 else {
405 echo xl('Internal error accessing uploaded file!');
406 $form_step = -1;
409 else {
410 echo xl('Upload failed!');
411 $form_step = -1;
413 $auto_continue = true;
416 if ($form_step == 203) {
417 $form_status .= xl('Done') . ".";
418 echo nl2br($form_status);
421 /// ViSolve : EventLog Backup
422 if ($form_step == 301) {
423 # Get the Current Timestamp, to attach with the log backup file
424 $backuptime=date("Ymd_His");
425 # Eventlog backup directory
426 $BACKUP_EVENTLOG_DIR = $GLOBALS['backup_log_dir'] . "/emr_eventlog_backup";
428 # Check if Eventlog Backup directory exists, if not create it with Write permission
429 if (!file_exists($BACKUP_EVENTLOG_DIR))
431 mkdir($BACKUP_EVENTLOG_DIR);
432 chmod($BACKUP_EVENTLOG_DIR,0777);
434 # Frame the Eventlog Backup File Name
435 $BACKUP_EVENTLOG_FILE=$BACKUP_EVENTLOG_DIR.'/eventlog_'.$backuptime.'.sql';
436 # Create a new table similar to event table, rename the existing table as backup table, and rename the new table to event log table. Then export the contents of the table into a text file and drop the table.
437 $res=sqlStatement("create table if not exists log_new like log");
438 $res=sqlStatement("rename table log to log_backup,log_new to log");
439 echo "<br>";
440 $cmd = "$mysql_dump_cmd -u " . escapeshellarg($sqlconf["login"]) .
441 " -p" . escapeshellarg($sqlconf["pass"]) .
442 " --opt --quote-names -r $BACKUP_EVENTLOG_FILE " .
443 escapeshellarg($sqlconf["dbase"]) ." --tables log_backup";
444 # Set Eventlog Flag when it is done
445 $eventlog=1;
446 // 301 If ends here.
449 ++$form_step;
452 </td>
453 </tr>
454 </table>
456 <input type='hidden' name='form_step' value='<?php echo $form_step; ?>' />
457 <input type='hidden' name='form_status' value='<?php echo $form_status; ?>' />
459 </form>
461 <?php
462 ob_flush();
463 flush();
464 if ($cmd) {
465 $tmp0 = exec($cmd, $tmp1, $tmp2);
467 if ($tmp2)
469 if ($eventlog==1)
471 // ViSolve : Restore previous state, if backup fails.
472 $res=sqlStatement("drop table if exists log");
473 $res=sqlStatement("rename table log_backup to log");
475 die("\"$cmd\" returned $tmp2: $tmp0");
477 // ViSolve: If the Eventlog is set, then clear the temporary table -- Start here
478 if ($eventlog==1) {
479 $res=sqlStatement("drop table if exists log_backup");
480 echo "<br><b>";
481 echo xl('Backup Successfully taken in')." ";
482 echo $BACKUP_EVENTLOG_DIR;
483 echo "</b>";
485 // ViSolve: If the Eventlog is set, then clear the temporary table -- Ends here
487 // If a file was flagged to be gzip-compressed after this cmd, do it.
488 if ($file_to_compress) {
489 if (!gz_compress_file($file_to_compress))
490 die (xl("Error in gzip compression of file: ") . $file_to_compress);
494 </center>
496 <?php if ($auto_continue) { ?>
497 <script language="JavaScript">
498 setTimeout("document.forms[0].submit();", 500);
499 </script>
500 <?php }
502 // Recursive directory remove (like an O/S insensitive "rm -rf dirname")
503 function obliterate_dir($dir) {
504 if (!file_exists($dir)) return true;
505 if (!is_dir($dir) || is_link($dir)) return unlink($dir);
506 foreach (scandir($dir) as $item) {
507 if ($item == '.' || $item == '..') continue;
508 if (!obliterate_dir($dir . DIRECTORY_SEPARATOR . $item)) {
509 chmod($dir . DIRECTORY_SEPARATOR . $item, 0777);
510 if (!obliterate_dir($dir . DIRECTORY_SEPARATOR . $item)) return false;
513 return rmdir($dir);
516 // Create a tar archive given the archive file name, compression method if any, and the
517 // array of file/directory names to archive
518 function create_tar_archive($archiveName, $compressMethod, $itemArray) {
519 global $newBackupMethod;
521 if ($newBackupMethod) {
522 // Create a tar object using the pear library
523 // (this is the preferred method)
524 $tar = new Archive_Tar($archiveName, $compressMethod);
525 if ($tar->create($itemArray)) return true;
527 else {
528 // Create the tar files via command line tools
529 // (this method used when the tar pear library is not available)
530 $files = '"' . implode('" "', $itemArray) . '"';
531 if ($compressMethod == "gz") {
532 $command = "tar --same-owner --ignore-failed-read -zcphf $archiveName $files";
534 else {
535 $command = "tar -cpf $archiveName $files";
537 $temp0 = exec($command, $temp1, $temp2);
538 if ($temp2) die("\"$command\" returned $temp2: $temp0");
539 return true;
541 return false;
544 // Compress a file using gzip. Source file removed, leaving only the compressed
545 // *.gz file, just like gzip command line would behave.
546 function gz_compress_file($source) {
547 $dest=$source.'.gz';
548 $error=false;
549 if ($fp_in=fopen($source,'rb')) {
550 if ($fp_out=gzopen($dest,'wb')) {
551 while(!feof($fp_in))
552 gzwrite($fp_out,fread($fp_in,1024*512));
553 gzclose($fp_out);
554 fclose($fp_in);
555 unlink($source);
557 else $error=true;
559 else $error=true;
560 if($error)
561 return false;
562 else
563 return $dest;
567 </body>
568 </html>