'admin/content/backup_migrate', 'title' => t('Backup and Migrate'), 'description' => t('Backup/restore your database or migrate data to or from another Drupal site.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('backup_migrate_backup'), 'access' => user_access('perform backup'), 'type' => MENU_NORMAL_ITEM, ); $items[] = array( 'path' => 'admin/content/backup_migrate/export', 'title' => t('Backup/Export DB'), 'description' => t('Backup the database.'), 'callback' => '_backup_migrate_export', 'access' => user_access('perform backup'), 'weight' => 0, 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/content/backup_migrate/restore', 'title' => t('Restore/Import DB'), 'description' => t('Restore the database from a previous backup'), 'callback' => 'drupal_get_form', 'callback arguments' => array('backup_migrate_restore'), 'access' => user_access('restore from backup'), 'weight' => 1, 'type' => MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/content/backup_migrate/files', 'title' => t('Saved Backups'), 'description' => t('View existing backup files'), 'callback' => '_backup_migrate_list_files', 'access' => user_access('access backup files'), 'weight' => 2, 'type' => MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/content/backup_migrate/files/manual', 'title' => t('Manual Backups'), 'callback' => '_backup_migrate_list_files', 'callback arguments' => array('manual'), 'access' => user_access('access backup files'), 'weight' => 1, 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/content/backup_migrate/files/scheduled', 'title' => t('Scheduled Backups'), 'callback' => '_backup_migrate_list_files', 'callback arguments' => array('scheduled'), 'access' => user_access('access backup files'), 'weight' => 2, 'type' => MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/content/backup_migrate/schedule', 'title' => t('Backup Schedule'), 'description' => t('View existing backup files'), 'callback' => 'drupal_get_form', 'callback arguments' => array('backup_migrate_schedule'), 'access' => user_access('access backup files'), 'weight' => 3, 'type' => MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/content/backup_migrate/restorefile', 'title' => t('restore from backup'), 'description' => t('Restore database from a backup file on the server'), 'callback' => '_backup_migrate_restore_from_server', 'access' => user_access('restore from backup'), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/content/backup_migrate/delete', 'title' => t('Delete File'), 'description' => t('Delete a backup file'), 'callback' => '_backup_migrate_delete', 'access' => user_access('delete backup files'), 'type' => MENU_CALLBACK, ); } return $items; } /** * Implementation of hook_cron(), * * Takes care of scheduled backups. */ function backup_migrate_cron() { // Backing up requires a full bootstrap as it uses the file functionality in // files.inc. Running poormanscron with caching on can cause cron to run without // a full bootstrap so we manually finish bootstrapping here. require_once './includes/common.inc'; _drupal_bootstrap_full(); if ($backup_schedule_period = variable_get("backup_migrate_schedule_backup_period", 0)) { $last_backup = variable_get("backup_migrate_schedule_last_backup", 0); $now = time(); if ($last_backup < ($now - ($backup_schedule_period * 60 * 60))) { // time to run the backup _backup_migrate_dump_tables( variable_get("backup_migrate_file_name", _backup_migrate_default_file_name()), variable_get("backup_migrate_exclude_tables", _backup_migrate_default_exclude_tables()), variable_get("backup_migrate_nodata_tables", _backup_migrate_default_structure_only_tables()), 'sql', "save", variable_get("backup_migrate_compression", "none"), "scheduled", variable_get("backup_migrate_timestamp_format", 'Y-m-d\TH-i-s') ); // Set the timestamp to indecate last backup time. variable_set("backup_migrate_schedule_last_backup", $now); // Delete older backups if needed. _backup_migrate_remove_expired_backups(); } } // Delete temp files abandoned for 6 or more hours. $dir = file_directory_temp(); $expire = time() - variable_get('backup_migrate_cleanup_time', 21600); if (file_exists($dir) && is_dir($dir) && is_readable($dir) && $handle = opendir($dir)) { while (FALSE !== ($file = @readdir($handle))) { // Delete 'backup_migrate_' files in the temp directory that are older than the expire time. // We should only attempt to delete writable files to prevent errors in shared environments. // This could still cause issues in shared environments with poorly configured file permissions. if (strpos($file, 'backup_migrate_') === 0 && is_writable("$dir/$file") && @filectime("$dir/$file") < $expire) { unlink("$dir/$file"); } } closedir($handle); } } /** * Implementation of hook_perm(). */ function backup_migrate_perm() { return array('perform backup', 'access backup files', 'delete backup files', 'restore from backup'); } /** * Implementation of hook_simpletest(). */ function backup_migrate_simpletest() { $dir = drupal_get_path('module', 'backup_migrate') .'/tests'; $tests = file_scan_directory($dir, '\.test$'); return array_keys($tests); } /** * Menu callback. Delete a previous backup. */ function _backup_migrate_delete() { // Merge remainder of arguments from GET['q'], into relative file path. $args = func_get_args(); $path = implode('/', $args); if ($path && _backup_migrate_path_is_in_save_dir($path)) { return drupal_get_form('backup_migrate_delete_confirm', $path); } return user_access('access backup files') ? "admin/content/backup_migrate/files" : "admin/content/backup_migrate"; } /** * Ask confirmation for file deletion. */ function backup_migrate_delete_confirm($path) { $form['path'] = array('#type' => 'value', '#value' => $path); return confirm_form($form, t('Are you sure you want to delete the backup file at %path?', array('%path' => $path)), 'admin/content/backup_migrate/files', t('This action cannot be undone.'), t('Delete'), t('Cancel')); } function backup_migrate_delete_confirm_submit($form_id, $form_values) { $path = $form_values['path']; if ($path && _backup_migrate_path_is_in_save_dir($path)) { file_delete($path); } watchdog('backup_migrate', t('Database backup file deleted: %file', array('%file' => $path))); return user_access('access backup files') ? "admin/content/backup_migrate/files" : "admin/content/backup_migrate"; } /** * The schedule form. */ function backup_migrate_schedule() { $form = array(); $form['backup_migrate_schedule_backup_period'] = array( "#type" => "textfield", "#title" => t("Backup every"), "#field_suffix" => t("Hour(s)"), "#description" => t("Use 0 for no scheduled backup. Cron must be configured to run for backups to work."), "#default_value" => variable_get("backup_migrate_schedule_backup_period", 0), ); $form['backup_migrate_schedule_backup_keep'] = array( "#type" => "textfield", "#title" => t("Number of Backup files to keep"), "#description" => t("The number of backup files to keep before deleting old ones. Use 0 to never delete backups"), "#default_value" => variable_get("backup_migrate_schedule_backup_keep", 0), ); if (!_backup_migrate_check_destination_dir('scheduled')) { $form['backup_migrate_schedule_backup_period']['#disabled'] = TRUE; $form['backup_migrate_schedule_backup_keep']['#disabled'] = TRUE; } return system_settings_form($form); } /** * The backup/export form. */ function backup_migrate_backup() { $form = array(); $tables = _backup_migrate_get_table_names(); $form['backup_migrate_exclude_tables'] = array( "#type" => "select", "#multiple" => TRUE, "#title" => t("Exclude the following tables altogether"), "#options" => $tables, "#default_value" => variable_get("backup_migrate_exclude_tables", _backup_migrate_default_exclude_tables()), "#description" => t("The selected tables will not be added to the backup file."), ); $form['backup_migrate_nodata_tables'] = array( "#type" => "select", "#multiple" => TRUE, "#title" => t("Exclude the data from the following tables"), "#options" => $tables, "#default_value" => variable_get("backup_migrate_nodata_tables", _backup_migrate_default_structure_only_tables()), "#description" => t("The selected tables will have their structure backed up but not their contents. This is useful for excluding cache data to reduce file size."), ); $form['backup_migrate_file_name'] = array( "#type" => "textfield", "#title" => t("Backup file name"), "#default_value" => variable_get("backup_migrate_file_name", _backup_migrate_default_file_name()), ); if (module_exists('token')) { $form['token_help'] = array( '#title' => t('Replacement patterns'), '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, '#description' => t('Prefer raw-text replacements for text to avoid problems with HTML entities!'), ); $form['token_help']['help'] = array( '#value' => theme('token_help', ''), ); } $compression_options = array("none" => t("No Compression")); if (@function_exists("gzencode")) { $compression_options['gzip'] = t("GZip"); } if (@function_exists("bzcompress")) { $compression_options['bzip'] = t("BZip"); } if (class_exists('ZipArchive')) { $compression_options['zip'] = t("Zip"); } $form['backup_migrate_compression'] = array( "#type" => "radios", "#title" => t("Compression"), "#options" => $compression_options, "#default_value" => variable_get("backup_migrate_compression", "none"), ); $destination_options = array( "download" => t("Download"), ); if (_backup_migrate_check_destination_dir('manual')) { $destination_options['save'] = t("Save to Files Directory"); } $form['backup_migrate_destination'] = array( "#type" => "radios", "#title" => t("Destination"), "#options" => $destination_options, "#default_value" => variable_get("backup_migrate_destination", "download"), ); $form['backup_migrate_append_timestamp'] = array( "#type" => "checkbox", "#title" => t("Append a timestamp."), "#default_value" => variable_get("backup_migrate_append_timestamp", 1), ); $form['backup_migrate_timestamp_format'] = array( "#type" => "textfield", "#title" => t("Timestamp format"), "#default_value" => variable_get("backup_migrate_timestamp_format", 'Y-m-d\TH-i-s'), "#description" => t('Should be a PHP date() format string.', array('!url' => 'http://www.php.net/date')), ); $form['backup_migrate_save_settings'] = array( "#type" => "checkbox", "#title" => t("Save these settings."), "#default_value" => 1, ); $form[] = array( '#type' => 'submit', '#value' => t('Backup Database'), ); return $form; } /** * Submit the form. Save the values as defaults if desired and output the backup file. */ function backup_migrate_backup_submit($form_id, $form_values) { if ($form_values['backup_migrate_save_settings']) { variable_set("backup_migrate_exclude_tables", $form_values['backup_migrate_exclude_tables']); variable_set("backup_migrate_nodata_tables", $form_values['backup_migrate_nodata_tables']); variable_set("backup_migrate_file_name", $form_values['backup_migrate_file_name']); variable_set("backup_migrate_destination", $form_values['backup_migrate_destination']); variable_set("backup_migrate_compression", $form_values['backup_migrate_compression']); variable_set("backup_migrate_append_timestamp", $form_values['backup_migrate_append_timestamp']); variable_set("backup_migrate_timestamp_format", $form_values['backup_migrate_timestamp_format']); } _backup_migrate_dump_tables( $form_values['backup_migrate_file_name'], $form_values['backup_migrate_exclude_tables'], $form_values['backup_migrate_nodata_tables'], 'sql', $form_values['backup_migrate_destination'], $form_values['backup_migrate_compression'], "manual", $form_values['backup_migrate_append_timestamp'] ? $form_values['backup_migrate_timestamp_format'] : false ); return "admin/content/backup_migrate"; } /** * Restore a backup file (from the server). */ function _backup_migrate_restore_from_server() { // Merge remainder of arguments from GET['q'], into relative file path. $args = func_get_args(); $path = implode('/', $args); if ($path && _backup_migrate_path_is_in_save_dir($path)) { return drupal_get_form('backup_migrate_restore_confirm', $path); } drupal_goto(user_access('access backup files') ? "admin/content/backup_migrate/files" : "admin/content/backup_migrate"); } /** * Ask confirmation for file restore. */ function backup_migrate_restore_confirm($path) { $form['path'] = array('#type' => 'value', '#value' => $path); return confirm_form($form, t('Are you sure you want to restore the database from the backup at %path?', array('%path' => $path)), 'admin/content/backup_migrate/files', t('This will delete some or all of your data and cannot be undone. Always test your backups on a non-production server!'), t('Restore'), t('Cancel')); } function backup_migrate_restore_confirm_submit($form_id, $form_values) { $path = $form_values['path']; if ($path && _backup_migrate_path_is_in_save_dir($path)) { _backup_migrate_restore_file($path); watchdog('backup_migrate', t('Database restored from %file', array('%file' => $path))); } return user_access('access backup files') ? "admin/content/backup_migrate/files" : "admin/content/backup_migrate"; } /** * The restore/import upload form. */ function backup_migrate_restore() { $form = array(); $form['backup_migrate_restore_upload'] = array( '#title' => t('Upload a Backup File'), '#type' => 'file', '#description' => t("Upload a backup file created by this version of this module. For other database backups please use another tool for import. Max file size: %size", array("%size" => format_size(file_upload_max_size()))), ); $form[] = array( '#type' => 'markup', '#value' => t('

This will delete some or all of your data and cannot be undone. If there is a sessions table in the backup file, you and all other currently logged in users will be logged out. Always test your backups on a non-production server!

'), ); $form[] = array( '#type' => 'submit', '#value' => t('Restore Database'), ); if (user_access('access backup files')) { $form[] = array( '#type' => 'markup', '#value' => t('

Or you can restore one of the files in the saved backup directory.

', array("!url" => url("admin/content/backup_migrate/files"))), ); } $form['#attributes'] = array('enctype' => 'multipart/form-data'); return $form; } /** * The restore submit. Do the restore. */ function backup_migrate_restore_submit($form_id, $form_values) { if ($file = file_check_upload('backup_migrate_restore_upload')) { _backup_migrate_restore_file($file->filepath, $file->filename, true); watchdog('backup_migrate', t('Database restored from upload %file', array('%file' => $file->filename))); } return 'admin/content/backup_migrate/restore'; } /** * Action to backup the drupal site. Requires actions.module. */ function action_backup_migrate_backup($op, $edit = array()) { switch ($op) { case 'do': _backup_migrate_backup_with_defaults(); watchdog('action', t('Backed up database')); break; case 'metadata': return array( 'description' => t('Backup the database with the default settings'), 'type' => t('Backup and Migrate'), 'batchable' => TRUE, 'configurable' => FALSE, ); // Return an HTML config form for the action. case 'form': return ''; // Validate the HTML form. case 'validate': return TRUE; // Process the HTML form to store configuration. case 'submit': return ''; } } /* * Implementation of hook_action_info(). */ function backup_migrate_action_info() { return array( 'backup_migrate_action_backup' => array( '#label' => t('Backup the database'), '#module' => t('Backup and Migrate'), '#description' => t('Backup the database with the default settings.'), ), ); } /* * Action callback. */ function backup_migrate_action_backup() { _backup_migrate_backup_with_defaults(); } /** * Backup the database with the default settings. */ function _backup_migrate_backup_with_defaults($mode = "manual") { _backup_migrate_dump_tables( variable_get("backup_migrate_file_name", _backup_migrate_default_file_name()), variable_get("backup_migrate_exclude_tables", _backup_migrate_default_exclude_tables()), variable_get("backup_migrate_nodata_tables", _backup_migrate_default_structure_only_tables()), 'sql', 'save', variable_get("backup_migrate_compression", "none"), $mode, variable_get("backup_migrate_append_timestamp", 1) ? variable_get("backup_migrate_timestamp_format", 'Y-m-d\TH-i-s') : FALSE ); } /** * Build the database dump file. Takes a list of tables to exclude and some formatting options. */ function _backup_migrate_dump_tables($filename, $exclude_tables, $nodata_tables, $type = "sql", $destination = "download", $compression = "none", $mode = "manual", $append_timestamp = FALSE) { $filemime = "text/plain"; $success = FALSE; if ($append_timestamp) { $filename .= "-". date($append_timestamp); } $filename = _backup_migrate_clean_filename($filename); // Dump the database. $temp_file = _backup_migrate_temp_file(); switch ($type) { case "sql": $success = _backup_migrate_get_dump_sql($temp_file, $exclude_tables, $nodata_tables); $filename .= ".sql"; $filemime = 'text/x-sql'; break; } // Compress the results. if ($success) { switch ($compression) { case "gzip": $temp_gz = _backup_migrate_temp_file('gz'); if ($success = _backup_migrate_gzip_encode($temp_file, $temp_gz, 9)) { $temp_file = $temp_gz; $filename .= ".gz"; $filemime = 'application/x-gzip'; } break; case "bzip": $temp_bz = _backup_migrate_temp_file('bz'); if ($success = _backup_migrate_bzip_encode($temp_file, $temp_bz)) { $temp_file = $temp_bz; $filename .= ".bz"; $filemime = 'application/x-bzip'; } break; case "zip": $temp_zip = _backup_migrate_temp_file('zip'); if ($success = _backup_migrate_zip_encode($temp_file, $temp_zip, $filename)) { $temp_file = $temp_zip; $filename .= ".zip"; $filemime = 'application/zip'; } break; } } // Save or download the results. if ($success) { switch ($destination) { case "save": _backup_migrate_save_to_disk($temp_file, $filename, $mode); break; case "download": _backup_migrate_send_file_to_download($filename, $filemime, $temp_file); break; } } // Delete any temporary files we've created. _backup_migrate_temp_file("", TRUE); } /** * Force a browser download for the file. */ function _backup_migrate_send_file_to_download($filename, $filetype, $file_path) { header('Content-Type: '. $filetype); header('Expires: '. gmdate('D, d M Y H:i:s') .' GMT'); header('Content-Length: '. filesize($file_path)); header('Content-Disposition: attachment; filename="'. $filename .'"'); // Transfer file in 1024 byte chunks to save memory usage. if ($fd = fopen($file_path, 'rb')) { while (!feof($fd)) { print fread($fd, 1024); } fclose($fd); } // Delete any temporary files we've created. _backup_migrate_temp_file("", TRUE); watchdog('backup_migrate', 'Database backup downloaded'); module_invoke_all('exit'); exit(); } /** * Get the sql dump file. Returns a list of sql commands, one command per line. * That makes it easier to import without loading the whole file into memory. * The files are a little harder to read, but human-readability is not a priority */ function _backup_migrate_get_dump_sql($file, $exclude_tables, $nodata_tables) { if ($dst = fopen($file, "w")) { fwrite($dst, _backup_migrate_get_sql_file_header()); $alltables = _backup_migrate_get_tables(); foreach ($alltables as $table) { if ($table['Name'] && !isset($exclude_tables[$table['Name']])) { fwrite($dst, _backup_migrate_get_table_structure_sql($table)); if (!in_array($table['Name'], $nodata_tables)) { _backup_migrate_dump_table_data_sql_to_handle($dst, $table); } } } fwrite($dst, _backup_migrate_get_sql_file_footer()); fclose($dst); return TRUE; } else { return FALSE; } } /** * Get the sql for the structure of the given table. */ function _backup_migrate_get_table_structure_sql($table) { $out = ""; $result = db_query("SHOW CREATE TABLE `". $table['Name'] ."`"); if ($create = db_fetch_array($result)) { $out .= "DROP TABLE IF EXISTS `". $table['Name'] ."`;\n"; $out .= strtr($create['Create Table'], "\n", " "); if ($table['Auto_increment']) { $out .= " AUTO_INCREMENT=". $table['Auto_increment']; } $out .= ";\n"; } return $out; } /** * Get the sql to insert the data for a given table */ function _backup_migrate_dump_table_data_sql_to_handle($dst, $table) { $data = db_query("SELECT * FROM `". $table['Name'] ."`"); while ($row = db_fetch_array($data)) { $items = array(); foreach ($row as $key => $value) { $items[] = is_null($value) ? "null" : "'". db_escape_string($value) ."'"; } if ($items) { fwrite( $dst, "INSERT INTO `". $table['Name'] ."` VALUES (". implode(",", $items) .");\n" ); } } } /** * The header for the top of the sql dump file. These commands set the connection * character encoding to help prevent encoding conversion issues. */ function _backup_migrate_get_sql_file_header() { return " /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=NO_AUTO_VALUE_ON_ZERO */; SET NAMES utf8; "; } /** * The footer of the sql dump file. */ function _backup_migrate_get_sql_file_footer() { return " /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; "; } /** * Get a list of tables in the db. Works with MySQL, Postgres not tested. */ function _backup_migrate_get_tables() { $out = ""; // get auto_increment values and names of all tables $tables = db_query("show table status"); while ($table = db_fetch_array($tables)) { $out[$table['Name']] = $table; } return $out; } /** * Get the list of table names. */ function _backup_migrate_get_table_names() { $out = ""; // Get auto_increment values and names of all tables. $tables = db_query("show table status"); while ($table = db_fetch_array($tables)) { $out[$table['Name']] = $table['Name']; } return $out; } /** * Restore from a previously backed up files. Accepts any file created by the backup function. */ function _backup_migrate_restore_file($filepath, $filename = "", $delete = FALSE) { if (!$filename) { $filename = $filepath; } $file_is_temp = $delete; $open_func = "fopen"; $read_func = "fgets"; $close_func = "fclose"; // figure out if the file is compressed by the file extention if (drupal_substr($filename, -4, 4) == ".sql") { // No compression. } if (drupal_substr($filename, -3, 3) == ".gz") { if (function_exists("gzopen")) { $open_func = "gzopen"; $read_func = "gzgets"; $close_func = "gzclose"; } else { // GZip compression... not supported. drupal_set_message(t("This version of PHP does not support gzip comressed files. Please try using an uncompressed sql backup."), 'error'); drupal_goto("admin/content/backup_migrate/restore"); } } // BZip compression. if (drupal_substr($filename, -3, 3) == ".bz") { if (function_exists("bzopen")) { $open_func = "fopen"; $read_func = "fgets"; // Decompress the file to a temp file. $tmp = tempnam(file_directory_temp(), 'tmp_'); if (($dst = fopen($tmp, "w")) && ($src = bzopen($filepath, "r"))) { while ($data = bzread($src)) { fwrite($dst, $data); } fclose($dst); bzclose($src); if ($delete) { unlink($filepath); } $filepath = $tmp; $delete = TRUE; } else { drupal_set_message(t("Unable to decompress bzip file. Please try using an uncompressed backup."), 'error'); drupal_goto("admin/content/backup_migrate/restore"); } } else { // BZip compression... not supported. drupal_set_message(t("This version of PHP does not support bzip compressed files. Please try using an uncompressed backup."), 'error'); drupal_goto("admin/content/backup_migrate/restore"); } } // Zip compression. if (drupal_substr($filename, -4, 4) == ".zip") { if (class_exists('ZipArchive')) { if ($filepath != $filename) { rename($filepath, $filepath .".zip"); $filepath .= ".zip"; } $tmp = tempnam(file_directory_temp(), 'tmp_'); $zip = new ZipArchive; if (($dst = fopen($tmp, "w")) && ($src = $zip->open($filepath))) { if ($data = $zip->getFromIndex(0)) { fwrite($dst, $data); } fclose($dst); $zip->close(); if ($delete) { unlink($filepath); } $filepath = $tmp; $delete = TRUE; } else { drupal_set_message(t("Unable to decompress zip file. Please try using an uncompressed backup."), 'error'); drupal_goto("admin/content/backup_migrate/restore"); } } else { // Zip compression... not supported. drupal_set_message(t("This version of PHP does not support zip comressed files. Please try using an uncompressed backup."), 'error'); drupal_goto("admin/content/backup_migrate/restore"); } } // Open the file (with fopen or gzopen depending on file format). if ($handle = @$open_func($filepath, "r")) { $num = 0; // Read one line at a time and run the query. while ($line = $read_func($handle)) { $line = trim($line); if ($line) { // Use the helper instead of the api function to avoid substitution of '{' etc. _db_query($line); $num++; } } // Close the file with fclose/gzclose. $close_func($handle); // Delete the file if it is temporary. if ($delete) { unlink($filepath); } $message = t("Restore complete. %num SQL commands executed.", array("%num" => $num)); $message .= $file_is_temp ? "" : "(". l(t("Restore Again..."), "admin/content/backup_migrate/restorefile/". $filepath) .")"; drupal_set_message($message); } else { drupal_set_message(t("Unable to open file %file to restore database", array("%file" => $filepath)), 'error'); } // Release cron semaphore that was set during scheduled backup. variable_del('cron_semaphore'); // Delete any temp files we've created. _backup_migrate_temp_file("", TRUE); } /* Backup File Management */ /** * Return a list of backup filetypes. */ function _backup_migrate_filetypes() { return array( "sql" => array( "extension" => ".sql", "filemime" => "text/x-sql", ), "gzip" => array( "extension" => ".gz", "filemime" => "application/x-gzip", ), "bzip" => array( "extension" => ".bz", "filemime" => "application/x-bzip", ), "zip" => array( "extension" => ".zip", "filemime" => "application/zip", ), ); } /** * Get the basic info for a backup file on the server. */ function _backup_migrate_file_info($path) { $types = _backup_migrate_filetypes(); foreach ($types as $type) { $extlen = drupal_strlen($type['extension']); if (drupal_substr($path, -$extlen, $extlen) === $type['extension']) { $out = $type; $out['filesize'] = filesize($path); $out['filename'] = basename($path); $out['filemtime'] = filemtime($path); $out['filectime'] = filectime($path); $out['filepath'] = $path; return $out; } } return NULL; } /** * Save the backup file to the appropriete folder on the server. */ function _backup_migrate_save_to_disk($temp_file, $filename, $mode = "manual") { if ($dir = _backup_migrate_check_destination_dir($mode)) { $filepath = $dir ."/". $filename; rename($temp_file, $filepath); $message = t('Database backup saved to %file. ', array('%file' => $filepath)); watchdog('backup_migrate', $message); if ($mode == "manual" && user_access('perform backup')) { $message .= user_access("access backup files") ? "(". l(t("Download"), "system/files/". $filepath) .") " : ""; $message .= user_access("delete backup files") ? "(". l(t("Delete..."), "admin/content/backup_migrate/delete/". $filepath) .") " : ""; $message .= user_access("restore from backup") ? "(". l(t("Restore..."), "admin/content/backup_migrate/restorefile/". $filepath) .")" : ""; drupal_set_message($message); } } } /** * Implementation of hook_file_download.() * * Allow users with the appropriate permissions to download backup files. */ function backup_migrate_file_download($path) { if (_backup_migrate_path_is_in_save_dir($path)) { if (user_access('access backup files') && $info = _backup_migrate_file_info($path)) { return array( 'Content-Type: '. $info['filemime'], 'Content-Length: '. $info['filesize'], 'Content-Disposition: attachment; filename="'. $info['filename'] .'"', ); } else { return -1; } } return NULL; } /** * Get a temp file. Store it in the normal save path for slightly better security * in shared environments. */ function _backup_migrate_temp_file($extension = "", $delete_all = FALSE) { static $files = array(); if ($delete_all) { _backup_migrate_temp_files_delete($files); } else { $file = tempnam(file_directory_temp(), 'backup_migrate_'); if (!empty($extension)) { unlink($file); $file .= '.'. $extension; } $files[] = $file; return $file; } } /** * Delete all temporary files. */ function _backup_migrate_temp_files_delete($files) { foreach ($files as $file) { if (file_exists($file)) { @unlink($file); } } } /** * List the previously created backup files. */ function _backup_migrate_list_files($mode = "manual") { $files = array(); if ($dir = _backup_migrate_check_destination_dir($mode)) { if ($handle = opendir($dir)) { while (FALSE !== ($file = readdir($handle))) { $filepath = $dir ."/". $file; if ($info = _backup_migrate_file_info($filepath)) { $files[$file] = array( $file, format_date($info['filemtime']), format_size($info['filesize']), l(t("Download"), "system/files/". $filepath), user_access('restore from backup') ? l(t("Restore"), "admin/content/backup_migrate/restorefile/". $filepath) : '', user_access('delete backup files') ? l(t("Delete"), "admin/content/backup_migrate/delete/". $filepath) : '', ); } } } } krsort($files); return theme("table", array(), $files); } /** * Remove older backups keeping only the number specified by the aministrator. */ function _backup_migrate_remove_expired_backups() { $num_to_keep = variable_get("backup_migrate_schedule_backup_keep", 0); // If num to keep is not 0 (0 is infinity). if ($num_to_keep && $dir = _backup_migrate_check_destination_dir("scheduled")) { // Create a list of backup files indexed by time (with an aditional index to // prevent two files with the same create time from overwriting each other). $res = opendir($dir); $files = array(); $i = 0; if ($res) { // Read all files and sort them by modified time. while ($file = readdir($res)) { $filepath = $dir ."/". $file; if ($info = _backup_migrate_file_info($filepath)) { $files[str_pad($info['filemtime'], 10, "0", STR_PAD_LEFT) ."-". $i++] = $filepath; } } } // If we are beyond our limit, remove as many as we need. $num_files = count($files); if ($num_files > $num_to_keep) { $num_to_delete = $num_files - $num_to_keep; // Sort by date. ksort($files); // Delete from the start of the list (earliest). for ($i = 0; $i < $num_to_delete; $i++) { $filepath = array_shift($files); file_delete($filepath); } } } } /** * Return the path on the server to save the dump files. */ function _backup_migrate_path_is_in_save_dir($path, $mode = "") { $backup_dir = _backup_migrate_get_save_path($mode); return file_exists($backup_dir) && file_check_location($path, $backup_dir); } /** * Return the path on the server to save the dump files. */ function _backup_migrate_get_save_path($mode = "") { $dir = file_directory_path() ."/backup_migrate"; if ($mode) { $dir .= $mode == "manual" ? "/manual" : "/scheduled"; } return $dir; } /** * Prepare the destination directory for the backups. */ function _backup_migrate_check_destination_dir($mode = "") { $directory = _backup_migrate_get_save_path(); $out = $subdir = rtrim($directory ."/". $mode, "/"); // Check for the main directory. if (!file_check_directory($directory, TRUE)) { // unable to create main directory $message = t("Unable to create or write to the save directory '%directory'. Please check the file permissions on your files directory.", array('%directory' => $directory)); drupal_set_message($message, "error"); return FALSE; } // Create subdir if needed. if (($subdir != $directory) && !file_check_directory($subdir, TRUE)) { // Unable to create sub directory. $message = t("Unable to create or write to the save directory '%directory'. Please check the file permissions on your files directory.", array('%directory' => $subdir)); drupal_set_message($message, "error"); return FALSE; } // If the files are saved inside the webroot (ie: may be web-accesible). if (strtolower(substr(realpath($directory), 0, strlen($_SERVER['DOCUMENT_ROOT']))) === strtolower($_SERVER['DOCUMENT_ROOT'])) { // Check for a htaccess file which adequately protects the backup files. $htaccess_lines = "order allow,deny\ndeny from all\n"; if (!is_file($directory .'/.htaccess') || strpos(file_get_contents($directory .'/.htaccess'), $htaccess_lines) === FALSE) { // Attempt to protect the backup files from public access using htaccess. if (($fp = @fopen($directory .'/.htaccess', 'w')) && @fputs($fp, $htaccess_lines)) { fclose($fp); chmod($directory .'/.htaccess', 0664); } // Unable to create htaccess... warn the user. else { $message = "Security warning: Couldn't modify .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: !htaccess or add them to the existing .htaccess file"; $replace = array('%directory' => $directory, '!htaccess' => '
'. nl2br(check_plain($htaccess_lines))); drupal_set_message(t($message, $replace), "error"); watchdog('security', t($message, $replace), WATCHDOG_ERROR); return FALSE; } } // Check the user agent to make sure we're not responding to a request from drupal itself. // That should prevent infinite loops which could be caused by poormanscron in some circumstances. if (strpos($_SERVER['HTTP_USER_AGENT'], 'Drupal') !== FALSE) { return FALSE; } // Check to see if the destination is publicly accessible $test_contents = "this file should not be publicly accesible"; // Create the the text.txt file if it's not already there. if (!is_file($subdir .'/test.txt') || file_get_contents($subdir .'/test.txt') != $test_contents) { if ($fp = fopen($subdir .'/test.txt', 'w')) { @fputs($fp, $test_contents); fclose($fp); } else { $message = t("Security notice: Backup and Migrate was unable to write a test text file to the destination directory %directory, and is therefore unable to check the security of the backup destination. Backups to the server will be disabled until the destination becomes writable and secure.", array('%directory' => $directory)); drupal_set_message($message, "error"); return FALSE; } } // Attempt to read the test file via http. This may fail for other reasons, so it's not a bullet-proof check. $path = trim(substr($subdir .'/test.txt', strlen(file_directory_path())), '\\/'); if (_backup_migrate_test_file_readable_remotely($path, $test_contents)) { $message = t("Security notice: Backup and Migrate will not save backup files to the server because the destination directory is publicly accessible. If you want to save files to the server, please secure the '%directory' directory", array('%directory' => $directory)); drupal_set_message($message, "error"); return FALSE; } } return $out; } /** * Check if a file can be read remotely via http. */ function _backup_migrate_test_file_readable_remotely($path, $contents) { $url = $GLOBALS['base_url'] .'/'. file_directory_path() .'/'. str_replace('\\', '/', $path); $result = drupal_http_request($url); if (strpos($result->data, $contents) !== FALSE) { return TRUE; } return FALSE; } /** * Gzip encode a file. */ function _backup_migrate_gzip_encode($source, $dest, $level = 9) { $success = FALSE; if (@function_exists("gzopen")) { if (($fp_out = gzopen($dest, 'wb'. $level)) && ($fp_in = fopen($source, 'rb'))) { while (!feof($fp_in)) { gzwrite($fp_out, fread($fp_in, 1024 * 512)); } $success = TRUE; } @fclose($fp_in); @gzclose($fp_out); } return $success; } /** * Bzip encode a file. */ function _backup_migrate_bzip_encode($source, $dest) { $success = FALSE; if (@function_exists("bzopen")) { if (($fp_out = bzopen($dest, 'w')) && ($fp_in = fopen($source, 'rb'))) { while (!feof($fp_in)) { bzwrite($fp_out, fread($fp_in, 1024 * 512)); } $success = TRUE; } else { $error = TRUE; } @fclose($fp_in); @bzclose($fp_out); } return $success; } /** * Zip encode a file. */ function _backup_migrate_zip_encode($source, $dest, $filename) { $success = FALSE; if (class_exists('ZipArchive')) { $zip = new ZipArchive; $res = $zip->open($dest, constant("ZipArchive::CREATE")); if ($res === TRUE) { $zip->addFile($source, $filename); $success = $zip->close(); } } return $success; } /* Defaults */ /** * Construct a default filename using the site's name. */ function _backup_migrate_default_file_name() { if (module_exists('token')) { return '[site-name]'; } else { return _backup_migrate_clean_filename(variable_get('site_name', "backup_migrate")); } } /** * Construct a default filename using token and some cleaning. */ function _backup_migrate_clean_filename($filename) { if (module_exists('token') && function_exists('token_replace')) { $filename = token_replace($filename, 'global'); } $filename = preg_replace("/[^a-zA-Z0-9\.\-_]/", "", $filename); if (strlen($filename) > 50) { $filename = drupal_substr($filename, 0, 50); } if (strlen($filename) == 0) { $filename = 'untitled'; } return $filename; } /** * Tables to ingore altogether. None by default. */ function _backup_migrate_default_exclude_tables() { return array(); } /** * Return the default tables whose data can be ignored. These tables mostly contain * info which can be easily reproducted (such as cache or search index) * but also tables which can become quite bloated but are not necessarily extremely * important to back up or migrate during development (such ass access log and watchdog) */ function _backup_migrate_default_structure_only_tables() { $core = array( 'cache', 'cache_filter', 'cache_calendar_ical', 'cache_menu', 'cache_page', 'cache_views', 'sessions', 'search_dataset', 'search_index', 'search_keywords_log', 'search_total', 'watchdog', 'accesslog', 'devel_queries', 'devel_times', ); $alltables = array_merge($core, module_invoke_all('devel_caches')); global $db_prefix; foreach ($alltables as $table) { $prefixed_tables[] = $db_prefix . $table; } return $prefixed_tables; }