...
 
Commits (187)
-------------------------------------------------------------------------------
Backup and Migrate for Drupal 5.x
by Ronan Dowling, Gorton Studios - ronan (at) gortonstudios (dot) com
Backup and Migrate 2 for Drupal 6.x
by Ronan Dowling, Gorton Studios - ronan (at) gortonstudios (dot) com
-------------------------------------------------------------------------------
DESCRIPTION:
......@@ -20,31 +21,71 @@ such as phpMyAdmin or the command-line mysql client.
-------------------------------------------------------------------------------
INSTALLATION:
Put the module in your drupal modules directory and enable it in
admin/build/modules.
Configure and use the module at admin/content/backup_migrate
* Put the module in your drupal modules directory and enable it in
admin/build/modules.
* Go to admin/user/permissions and grant permission to any roles that need to be
able to backup or restore the databse.
* Configure and use the module at admin/content/backup_migrate
OPTIONAL:
* Enable token.module to allow token replacement in backup file names.
* To Backup to Amazon S3:
- Download the S3 library from http://undesigned.org.za/2007/10/22/amazon-s3-php-class
and place the file 'S3.php' in the includes directory in this module.
The stable version (0.4.0 – 20th Jul 2009) works best with Backup and Migrate.
LIGHTTPD USERS:
Add the following code to your lighttp.conf to secure your backup directories:
$HTTP["url"] =~ "^/sites/default/files/backup_migrate/" {
url.access-deny = ( "" )
}
You may need to adjust the path to reflect the actual path to the files.
IIS 7 USERS:
Add the following code to your web.config code to secire your backup directories:
<rule name="postinst-redirect" stopProcessing="true">
<match url="sites/default/files/backup_migrate" />
<action type="Rewrite" url=""/>
</rule>
You may need to adjust the path to reflect the actual path to the files.
NGINX USERS:
Add the following to your configuration:
location ^~ /backup_migrate/ {
internal;
}
-------------------------------------------------------------------------------
WARNINGS:
VERY IMPORTANT SECURITY NOTE:
Backup files may contain sensitive data and by default, are saved to your web
server in a directory normally accessible by the public. This could lead to a
very serious security vulnerability. Backup and Migrate attempts to protect
backup files using a .htaccess file, but this is not guaranteed to work on all
environments (and is guaranteed to fail on web servers that are not apache). You
should test to see if your backup files are publicly accessible, and if in doubt
do not save backups to the server, or use the destinations feature to save to a
folder outside of your webroot.
OTHER WARNINGS:
A failed restore can destroy your database and therefore your entire Drupal
installation. ALWAYS TEST BACKUP FILES ON A TEST ENVIRONMENT FIRST. If in doubt
do not use this module.
This module has only be tested with MySQL and probably does not work with any
other dbms. If you have experiences with Postres or any other dbms and are
willing to help test and modify the module to work with it, please contact the
developer at ronan (at) gortonstudios (dot) com.
This module has only be tested with MySQL and does not work with anyother dbms.
If you have experiences with Postres or any other dbms and are willing to help
test and modify the module to work with it, please contact the developer at
ronan (at) gortonstudios (dot) com.
Make sure your php timeout is set high enough to complete a backup or restore
operation. Larger databases require more time. Also, while the module attempts
to keep memory needs to a minimum, a backup or restore will require
significantly more memory then most Drupal operations.
If your backup file contains the 'sessions' table you will likely be logged out
after you run a restore. To avoid this, exclude the sessions table when creating
your backups. Be aware though that you will need to recreate the sessions table
if you use this backup on an empty database.
If your backup file contains the 'sessions' table all other users will be logged
out after you run a restore. To avoid this, exclude the sessions table when
creating your backups. Be aware though that you will need to recreate the
sessions table if you use this backup on an empty database.
Do not change the file extension of backup files or the restore function will be
unable to determine the compression type the file and will not function
......@@ -53,11 +94,7 @@ correctly.
IF A RESTORE FAILS:
Don't panic, the restore file should work with phpMyAdmin's import function, or
with the mysql command line tool. If it does not, then it is likely corrupt; you
may panic now. Make sure that this module is not your only form of backup.
NOTE TO DEVEL USERS:
If you experience memory limit errors while restoring, try
disabling the 'Collect query info' setting in devel.
may panic now. MAKE SURE THAT THIS MODULE IS NOT YOUR ONLY FORM OF BACKUP.
-------------------------------------------------------------------------------
.schedule-list-disabled {
filter:alpha(opacity=50);
-moz-opacity: .50;
opacity: .50;
}
.backup-migrate-cache-time {
font-size: 0.85em;
}
.backup-migrate-tables-checkboxes .form-item {
margin: 0;
}
.backup-migrate-tables-checkboxes .form-item label {
width: 15em;
float: left;
overflow: hidden;
white-space: nowrap;
height: 1.75em;
margin: .25em .25em 0 0;
}
.backup-migrate-tables-checkboxes .form-item label.checked {
background-color: #eee;
}
div.backup-migrate-tables-checkboxes {
max-height: 40em;
overflow: auto;
}
div.row-error {
color: #FF0000;
font-weight: bold;
}
; $Id$
name = Backup and Migrate
description = "Backup or migrate the Drupal Database quickly and without unnecessary data."
\ No newline at end of file
description = "Backup or migrate the Drupal Database quickly and without unnecessary data."
core = 6.x
This diff is collapsed.
Drupal.backup_migrate = {
callbackURL : "",
autoAttach : function() {
if (Drupal.settings.backup_migrate !== undefined) {
if ($("#edit-save-settings").length && !$("#edit-save-settings").attr("checked")) {
// Disable input and hide its description.
// Set display none instead of using hide(), because hide() doesn't work when parent is hidden.
$('div.backup-migrate-save-options').css('display', 'none');
}
$("#edit-save-settings").bind("click", function() {
if (!$("#edit-save-settings").attr("checked")) {
$("div.backup-migrate-save-options").slideUp('slow');
}
else {
// Save unchecked; enable input.
$("div.backup-migrate-save-options").slideDown('slow');
}
});
$('#backup-migrate-ui-manual-backup-form select[multiple], #backup-migrate-crud-edit-form select[multiple]').each(function() {
$(this).after(
$('<div class="description backup-migrate-checkbox-link"></div>').append(
$('<a href="javascript:null(0);"></a>').text(Drupal.settings.backup_migrate.checkboxLinkText).click(function() {
Drupal.backup_migrate.selectToCheckboxes($(this).parents('.form-item').find('select'));
})
)
);
}
);
}
},
selectToCheckboxes : function($select) {
var field_id = $select.attr('id');
var $checkboxes = $('<div></div>').addClass('backup-migrate-tables-checkboxes');
$('option', $select).each(function(i) {
var self = this;
$box = $('<input type="checkbox" class="backup-migrate-tables-checkbox">').bind('change click', function() {
$select.find('option[value="'+self.value+'"]').attr('selected', this.checked);
if (this.checked) {
$(this).parent().addClass('checked');
}
else {
$(this).parent().removeClass('checked');
}
}).attr('checked', this.selected ? 'checked' : '');
$checkboxes.append($('<div class="form-item"></div>').append($('<label class="option backup-migrate-table-select">'+this.value+'</label>').prepend($box)));
});
$select.parent().find('.backup-migrate-checkbox-link').remove();
$select.before($checkboxes);
$select.hide();
}
}
// Global Killswitch
if (Drupal.jsEnabled) {
$(document).ready(Drupal.backup_migrate.autoAttach);
}
This diff is collapsed.
<?php
/**
* @file
* Drush commands for backup and migrate.
*/
/**
* Implementation of hook_drush_command().
*/
function backup_migrate_drush_command() {
$items['bam-backup'] = array(
'callback' => 'backup_migrate_drush_backup',
'description' => dt('Backup the site\'s database with Backup and Migrate.'),
'aliases' => array('bb'),
'examples' => array(
'drush bam-backup' => 'Backup the default database to the manual backup directory using the default settings.',
'drush bam-backup db scheduled mysettings' => 'Backup the database to the scheduled directory using a settings profile called "mysettings"',
'drush bam-backup files' => 'Backup the files directory to the manual directory using the default settings. The Backup and Migrate Files module is required for files backups.',
),
'arguments' => array(
'source' => "Optional. The id of the source (usually a database) to backup. Use 'drush bam-sources' to get a list of sources. Defaults to 'db'",
'destination' => "Optional. The id of destination to send the backup file to. Use 'drush bam-destinations' to get a list of destinations. Defaults to 'manual'",
'profile' => "Optional. The id of a settings profile to use. Use 'drush bam-profiles' to get a list of available profiles. Defaults to 'default'",
),
);
$items['bam-restore'] = array(
'callback' => 'backup_migrate_drush_restore',
'description' => dt('Restore the site\'s database with Backup and Migrate.'),
'arguments' => array(
'source' => "Required. The id of the source (usually a database) to restore the backup to. Use 'drush bam-sources' to get a list of sources. Defaults to 'db'",
'destination' => "Required. The id of destination to send the backup file to. Use 'drush bam-destinations' to get a list of destinations. Defaults to 'manual'",
'backup id' => "Required. The id of a backup file restore. Use 'drush bam-backups' to get a list of available backup files.",
),
'options' => array(
'yes' => 'Skip confirmation',
),
);
$items['bam-destinations'] = array(
'callback' => 'backup_migrate_drush_destinations',
'description' => dt('Get a list of available destinations.'),
);
$items['bam-sources'] = array(
'callback' => 'backup_migrate_drush_sources',
'description' => dt('Get a list of available sources.'),
);
$items['bam-profiles'] = array(
'callback' => 'backup_migrate_drush_profiles',
'description' => dt('Get a list of available settings profiles.'),
);
$items['bam-backups'] = array(
'callback' => 'backup_migrate_drush_destination_files',
'description' => dt('Get a list of previously created backup files.'),
'arguments' => array(
'destination' => "Required. The id of destination to list backups from. Use 'drush bam-destinations' to get a list of destinations.",
),
);
return $items;
}
/**
* Implementation of hook_drush_help().
*/
function backup_migrate_drush_help($section) {
switch ($section) {
case 'drush:bam-backup':
return dt("Backup the site's database using default settings.");
case 'drush:bam-restore':
return dt('Restore the site\'s database with Backup and Migrate.');
case 'drush:bam-destinations':
return dt('Get a list of available destinations.');
case 'drush:bam-profiles':
return dt('Get a list of available settings profiles.');
case 'drush:bam-backups':
return dt('Get a list of previously created backup files.');
}
}
/**
* Backup the default database.
*/
function backup_migrate_drush_backup($source_id = 'db', $destination_id = 'manual', $profile_id = 'default') {
backup_migrate_include('profiles', 'destinations');
// Set the message mode to logging.
_backup_migrate_message_callback('_backup_migrate_message_drush');
if (!backup_migrate_get_destination($source_id)) {
_backup_migrate_message("Could not find the source '@source'. Try using 'drush bam-sources' to get a list of available sources or use 'db' to backup the Drupal database.", array('@source' => $source_id), 'error');
return;
}
if (!backup_migrate_get_destination($destination_id)) {
_backup_migrate_message("Could not find the destination '@destination'. Try using 'drush bam-destinations' to get a list of available destinations.", array('@destination' => $destination_id), 'error');
return;
}
$settings = backup_migrate_get_profile($profile_id);
if(!$settings) {
_backup_migrate_message("Could not find the profile '@profile'. Try using 'drush bam-profiles' to get a list of available profiles.", array('@profile' => $profile_id), 'error');
return;
}
_backup_migrate_message('Starting backup...');
$settings->destination_id = $destination_id;
$settings->source_id = $source_id;
backup_migrate_perform_backup($settings);
}
/**
* Restore to the default database.
*/
function backup_migrate_drush_restore($source_id = '', $destination_id = '', $file_id = '') {
drush_print(dt('Restoring will delete some or all of your data and cannot be undone. ALWAYS TEST YOUR BACKUPS ON A NON-PRODUCTION SERVER!'));
if (!drush_confirm(dt('Are you sure you want to restore the database?'))) {
return drush_user_abort();
}
backup_migrate_include('profiles', 'destinations');
// Set the message mode to drush output.
_backup_migrate_message_callback('_backup_migrate_message_drush');
if (!backup_migrate_get_destination($source_id)) {
_backup_migrate_message("Could not find the source '@source'. Try using 'drush bam-sources' to get a list of available sources or use 'db' to backup the Drupal database.", array('@source' => $source_id), 'error');
return;
}
if (!$destination = backup_migrate_get_destination($destination_id)) {
_backup_migrate_message("Could not find the destination '@destination'. Try using 'drush bam-destinations' to get a list of available destinations.", array('@destination' => $destination_id), 'error');
return;
}
if (!$file_id || !$file = backup_migrate_destination_get_file($destination_id, $file_id)) {
_backup_migrate_message("Could not find the file '@file'. Try using 'drush bam-backups @destination' to get a list of available backup files in this destination destinations.", array('@destination' => $destination_id, '@file' => $file_id), 'error');
return;
}
_backup_migrate_message('Starting restore...');
$settings = array('source_id' => $source_id);
backup_migrate_perform_restore($destination_id, $file_id, $settings);
}
/**
* Get a list of available destinations.
*/
function backup_migrate_drush_destinations() {
return _backup_migrate_drush_destinations('all');
}
/**
* Get a list of available sources.
*/
function backup_migrate_drush_sources() {
return _backup_migrate_drush_destinations('source');
}
/**
* Get a list of available destinations with the given op.
*/
function _backup_migrate_drush_destinations($op = NULL) {
backup_migrate_include('destinations');
$rows = array(array(dt('ID'), dt('Name'), dt('Operations')));
foreach (backup_migrate_get_destinations($op) as $destination) {
$rows[] = array(
$destination->get_id(),
$destination->get_name(),
implode (', ', $destination->ops()),
);
}
drush_print_table($rows, TRUE, array(32, 32));
}
/**
* Get a list of available profiles.
*/
function backup_migrate_drush_profiles() {
backup_migrate_include('profiles');
$rows = array(array(dt('ID'), dt('Name')));
foreach (backup_migrate_get_profiles() as $profile) {
$rows[] = array(
$profile->get_id(),
$profile->get_name(),
);
}
drush_print_table($rows, TRUE, array(32, 32));
}
/**
* Get a list of files in a given destination
*/
function backup_migrate_drush_destination_files($destination_id = NULL) {
backup_migrate_include('destinations');
// Set the message mode to drush output.
_backup_migrate_message_callback('_backup_migrate_message_drush');
if (!$destination_id) {
_backup_migrate_message("You must specify an existing destination. Try using 'drush bam-destinations' to get a list of available destinations.", array('@destination' => $destination_id), 'error');
return;
}
if (!$destination = backup_migrate_get_destination($destination_id)) {
_backup_migrate_message("Could not find the destination '@destination'. Try using 'drush bam-destinations' to get a list of available destinations.", array('@destination' => $destination_id), 'error');
return;
}
$out = array(array(
dt('Filename'),
dt('Date'),
dt('Age'),
dt('Size'),
));
$files = $destination->list_files();
$i = 0;
foreach ((array)$files as $file) {
// Show only files that can be restored from.
if ($file->is_recognized_type()) {
$info = $file->info();
$out[] = array(
check_plain($info['filename']),
format_date($info['filetime'], 'small'),
format_interval(time() - $info['filetime'], 1),
format_size($info['filesize']),
);
}
}
if (count($out) > 1) {
drush_print_table($out, TRUE);
}
else {
drush_print(dt('There are no backup files to display.'));
}
}
/**
* Send a message to the drush log.
*/
function _backup_migrate_message_drush($message, $replace, $type) {
// Use drush_log to display to the user.
drush_log(strip_tags(dt($message, $replace)), str_replace('status', 'notice', $type));
// Watchdog log the message as well for admins.
_backup_migrate_message_log($message, $replace, $type);
}
This diff is collapsed.
<?php
/**
* @file
* Functions to handle the browser upload/download backup destination.
*/
/**
* A destination type for browser upload/download.
*
* @ingroup backup_migrate_destinations
*/
class backup_migrate_destination_browser extends backup_migrate_destination {
/**
* Get a row of data to be used in a list of items of this type.
*/
function get_list_row() {
// Return none as this type should not be displayed.
return array();
}
}
/**
* A destination type for browser upload.
*
* @ingroup backup_migrate_destinations
*/
class backup_migrate_destination_browser_upload extends backup_migrate_destination_browser {
var $supported_ops = array('restore');
function __construct() {
$params = array();
$params['name'] = "Upload";
$params['destination_id'] = 'upload';
parent::__construct($params);
}
/**
* File load destination callback.
*/
function load_file($file_id) {
backup_migrate_include('files');
if ($file = file_save_upload('backup_migrate_restore_upload')) {
$out = new backup_file(array('filepath' => $file->filepath));
backup_migrate_temp_files_add($file->filepath);
return $out;
}
return NULL;
}
}
/**
* A destination type for browser download.
*
* @ingroup backup_migrate_destinations
*/
class backup_migrate_destination_browser_download extends backup_migrate_destination_browser {
var $supported_ops = array('manual backup');
function __construct() {
$params = array();
$params['name'] = "Download";
$params['destination_id'] = 'download';
parent::__construct($params);
}
/**
* File save destination callback.
*/
function save_file($file, $settings) {
backup_migrate_include('files');
$file->transfer();
}
}
<?php
/**
* @file
* Functions to handle the direct to database destination.
*/
function _backup_migrate_destination_complete_table_list($element, &$form_state) {
form_set_value($element, array_merge(array_fill_keys($element['#options'], 0), $element['#value']), $form_state);
}
/**
* A destination type for saving to a database server.
*
* @ingroup backup_migrate_destinations
*/
class backup_migrate_destination_db extends backup_migrate_destination_remote {
var $supported_ops = array('scheduled backup', 'manual backup', 'configure', 'source');
function type_name() {
return t("Database");
}
/**
* Save the info by importing it into the database.
*/
function save_file($file, $settings) {
backup_migrate_include('files');
// Set the source_id to the destination_id in the settings since for a restore, the source_id is the
// database that gets restored to.
$settings->set_source($this->get_id());
// Restore the file to the source database.
$file = backup_migrate_perform_restore($this->get_id(), $file, $settings);
return $file;
}
/**
* Destination configuration callback.
*/
function edit_form() {
$form = parent::edit_form();
$form['scheme']['#title'] = t('Database type');
$form['scheme']['#options'] = array($GLOBALS['db_type'] => $GLOBALS['db_type']);
$form['scheme']['#description'] = t('The type of the database. Drupal only supports one database type at a time, so this must be the same as the current database type.');
$form['path']['#title'] = t('Database name');
$form['path']['#description'] = t('The name of the database. The database must exist, it will not be created for you.');
$form['user']['#description'] = t('Enter the name of a user who has write access to the database.');
return $form;
}
/**
* Validate the configuration form. Make sure the db info is valid.
*/
function edit_form_validate($form, &$form_state) {
if (!preg_match('/[a-zA-Z0-9_\$]+/', $form_state['values']['path'])) {
form_set_error('path', t('The database name is not valid.'));
}
parent::edit_form_validate($form, $form_state);
}
/**
* Get the form for the settings for this destination.
*
* 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_settings_default() {
$core = array(
'cache',
'cache_filter',
'cache_calendar_ical',
'cache_menu',
'cache_page',
'cache_views',
'cache_block',
'cache_update',
'cache_form',
'sessions',
'search_dataset',
'search_index',
'search_keywords_log',
'search_total',
'watchdog',
'accesslog',
'devel_queries',
'devel_times',
);
$nodata_tables = array_merge(array_combine($core, $core), module_invoke_all('devel_caches'));
return array(
'nodata_tables' => $nodata_tables,
'exclude_tables' => array(),
'utils_lock_tables' => FALSE,
);
}
/**
* Get the form for the backup settings for this destination.
*/
function backup_settings_form($settings) {
$objects = $this->get_object_names();
$form['#description'] = t("You may omit specific tables, or specific table data from the backup file. Only omit data that you know you will not need such as cache data, or tables from other applications. Excluding tables can break your Drupal install, so <strong>do not change these settings unless you know what you're doing</strong>.");
$form['exclude_tables'] = array(
"#type" => "select",
"#multiple" => TRUE,
"#title" => t("Exclude the following tables altogether"),
"#options" => $objects,
"#default_value" => array_filter($settings['exclude_tables']),
'#element_validate' => array('_backup_migrate_destination_complete_table_list'),
"#description" => t("The selected tables will not be added to the backup file."),
);
$tables = $this->get_table_names();
$form['nodata_tables'] = array(
"#type" => "select",
"#multiple" => TRUE,
"#title" => t("Exclude the data from the following tables"),
"#options" => $tables,
"#default_value" => array_filter($settings['nodata_tables']),
'#element_validate' => array('_backup_migrate_destination_complete_table_list'),
"#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['utils_lock_tables'] = array(
'#type' => 'checkbox',
'#title' => t('Lock tables during backup'),
'#default_value' => !empty($settings['utils_lock_tables']) ? $settings['utils_lock_tables'] : NULL,
'#description' => t('This can help reduce data corruption, but will make your site unresponsive.'),
);
return $form;
}
/**
* Backup from this source.
*/
function backup_to_file($file, $settings) {
$file->push_type($this->get_file_type_id());
backup_migrate_filters_invoke_all('pre_backup', $this, $file, $settings);
// Switch to a different db if specified.
$this->switch_db();
$this->lock_tables($settings);
$success = $this->_backup_db_to_file($file, $settings);
$this->unlock_tables($settings);
// Switch back to the previous db.
$this->switch_db(TRUE);
backup_migrate_filters_invoke_all('post_backup', $this, $file, $settings, $success);
return $success ? $file : FALSE;
}
/**
* Restore to this source.
*/
function restore_from_file($file, &$settings) {
$num = 0;
$type = $this->get_file_type_id();
// Open the file using the file wrapper. Check that the dump is of the right type (allow .sql for legacy reasons).
if ($file->type_id() !== $this->get_file_type_id() && $file->type_id() !== 'sql') {
_backup_migrate_message("Unable to restore from file %file because a %type file can't be restored to this database.", array("%file" => $file->filepath(), '%type' => $file->type_id()), 'error');
}
else {
backup_migrate_filters_invoke_all('pre_restore', $file, $settings);
// Switch to a different db if specified.
$this->switch_db();
// Restore the database.
$num = $this->_restore_db_from_file($file, $settings);
$settings->performed_action = $num ? t('%num SQL commands executed.', array('%num' => $num)) : '';
// Switch back to the previous db.
$this->switch_db(TRUE);
backup_migrate_filters_invoke_all('post_restore', $file, $settings, $num);
}
return $num;
}
/**
* Backup the databases to a file.
*/
function _backup_db_to_file($file, $settings) {
// Must be overridden.
}
/**
* Backup the databases to a file.
*/
function _restore_db_from_file($file, $settings) {
// Must be overridden.
}
/**
* Get a list of objects in the database.
*/
function get_object_names() {
// Switch to a different db if specified.
$this->switch_db();
// Must be overridden.
$out = $this->_get_table_names();
if (method_exists($this, '_get_view_names')) {
$out += $this->_get_view_names();
}
$this->switch_db(TRUE);
return $out;
}
/**
* Get a list of tables in the database.
*/
function get_table_names() {
// Switch to a different db if specified.
$this->switch_db();
// Must be overridden.
$out = $this->_get_table_names();
$this->switch_db(TRUE);
return $out;
}
/**
* Get a list of tables in the database.
*/
function _get_table_names() {
// Must be overridden.
return array();
}
/**
* Switch to the current database. Pass true to switch back to the previous db.
*/
function switch_db($switch_back = FALSE) {
static $db_stack = array();
global $db_url;
// If switch back is specified, pop the previous db and activate it.
if ($switch_back && $db_stack) {
db_set_active(array_pop($db_stack));
return;
}
// If there is a valid DB URL, switch to it.
if ($url = $this->get_location()) {
// Make the db_url into an array if needed.
if (!is_array($db_url)) {
$db_url = array('default' => $db_url);
}
// Add the new db to the db_url array.
$db_url[$url] = $url;
// Switch to the new db and push the old one on the stack
$db_stack[] = db_set_active($url);
}
}
/**
* Lock the database in anticipation of a backup.
*/
function lock_tables($settings) {
if ($settings->filters['utils_lock_tables']) {
$tables = array();
foreach ($this->get_table_names() as $table) {
// There's no need to lock excluded or structure only tables because it doesn't matter if they change.
if (empty($settings->filters['exclude_tables']) || !in_array($table, (array)$settings->filters['exclude_tables'])) {
$tables[] = $table;
}
}
$this->_lock_tables($tables);
}
}
/**
* Lock the list of given tables in the database.
*/
function _lock_tables($tables) {
// Must be overridden.
}
/**
* Unlock any tables that have been locked.
*/
function unlock_tables($settings) {
if ($settings->filters['utils_lock_tables']) {
$this->_unlock_tables();
}
}
/**
* Unlock the list of given tables in the database.
*/
function _unlock_tables($tables) {
// Must be overridden.
}
/**
* Get the file type for to backup this destination to.
*/
function get_file_type_id() {
return 'sql';
}
}
This diff is collapsed.
<?php
/**
* @file
* Functions to handle the email backup destination.
*/
/**
* A destination for emailing database backups.
*
* @ingroup backup_migrate_destinations
*/
class backup_migrate_destination_email extends backup_migrate_destination {
var $supported_ops = array('scheduled backup', 'manual backup', 'configure');
/**
* Save to (ie. email the file) to the email destination.
*/
function save_file($file, $settings) {
$size = filesize($file->filepath());
$max = variable_get('backup_migrate_max_email_size', 20971520);
if ($size > $max) {
_backup_migrate_message('Could not email the file @file because it is @size and Backup and Migrate only supports emailing files smaller than @max.', array('@file' => $file->filename(), '@size' => format_size($size), '@max' => format_size($max)), 'error');
return FALSE;
}
$attachment = new stdClass();
$attachment->filename = $file->filename();
$attachment->path = $file->filepath();
_backup_migrate_destination_email_mail_backup($attachment, $this->get_location());
return $file;
}
/**
* Get the form for the settings for this filter.
*/
function edit_form() {
$form = parent::edit_form();
$form['location'] = array(
"#type" => "textfield",
"#title" => t("Email Address"),
"#default_value" => $this->get_location(),
"#required" => TRUE,
"#description" => t('Enter the email address to send the backup files to. Make sure the email server can handle large file attachments.'),
);
return $form;
}
/**
* Validate the configuration form. Make sure the email address is valid.
*/
function settings_form_validate($values) {
if (!valid_email_address($values['location'])) {
form_set_error('[location]', t('The e-mail address %mail is not valid.', array('%mail' => $form_state['values']['location'])));
}
}
}
/**
* @function
* Temporary mail handler class.
*
* Defines a mail class to send a message with an attachment. Eventually Drupal
* core should provide this functionality, at which time this code will be
* removed.
*
* More info on sending email at <http://php.net/function.mail>.
* This function taken from dba.module.
*
* @param $attachment
* An object which contains two variables "path" the path to the file and
* filename and "filename" which is just the filename.
*/
function _backup_migrate_destination_email_mail_backup($attachment, $to) {
// Send mail
$attach = fread(fopen($attachment->path, "r"), filesize($attachment->path));
$mail = new mime_mail();
$mail->from = variable_get('site_mail', ini_get('sendmail_from'));
$mail->headers = 'Errors-To: [EMAIL='. $mail->from .']'. $mail->from .'[/EMAIL]';
$mail->to = $to;
$mail->subject = t('Database backup from !site: !file', array('!site' => variable_get('site_name', 'drupal'), '!file' => $attachment->filename));
$mail->body = t('Database backup attached.') ."\n\n";
$mail->add_attachment("$attach", $attachment->filename, "Content-Transfer-Encoding: base64 /9j/4AAQSkZJRgABAgEASABIAAD/7QT+UGhvdG9zaG", NULL, TRUE);
$mail->send();
}
class mime_mail {
var $parts;
var $to;
var $from;
var $headers;
var $subject;
var $body;
function mime_mail() {
$this->parts = array();
$this->to = "";
$this->from = "";
$this->headers = "";
$this->subject = "";
$this->body = "";
}
function add_attachment($message, $name = "", $ctype = "application/octet-stream", $encode = NULL, $attach = FALSE) {
$this->parts[] = array(
"ctype" => $ctype,
"message" => $message,
"encode" => $encode,
"name" => $name,
"attach" => $attach,
);
}
function build_message($part) {
$message = $part["message"];
$message = chunk_split(base64_encode($message));
$encoding = "base64";
$disposition = $part['attach'] ? "Content-Disposition: attachment; filename=$part[name]\n" : '';
return "Content-Type: ". $part["ctype"] . ($part["name"] ? "; name = \"". $part["name"] ."\"" : "") ."\nContent-Transfer-Encoding: $encoding\n$disposition\n$message\n";
}
function build_multipart() {
$boundary = "b". md5(uniqid(time()));
$multipart = "Content-Type: multipart/mixed; boundary = $boundary\n\nThis is a MIME encoded message.\n\n--$boundary";
for ($i = sizeof($this->parts) - 1; $i >= 0; $i--) {
$multipart .= "\n". $this->build_message($this->parts[$i]) ."--$boundary";
}
return $multipart .= "--\n";
}
function send() {
$mime = "";
if (!empty($this->from)) $mime .= "From: ". $this->from ."\n";
if (!empty($this->headers)) $mime .= $this->headers ."\n";
if (!empty($this->body)) $this->add_attachment($this->body, "", "text/plain");
$mime .= "MIME-Version: 1.0\n". $this->build_multipart();
mail(trim($this->to), $this->subject, "", $mime);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?php
/**
* @file
* Functions to handle the s3 backup destination.
*/
/**
* A destination for sending database backups to an s3 server.
*
* @ingroup backup_migrate_destinations
*/
class backup_migrate_destination_s3 extends backup_migrate_destination_remote {
var $supported_ops = array('scheduled backup', 'manual backup', 'restore', 'list files', 'configure', 'delete');
var $s3 = NULL;
var $cache_files = TRUE;
/**
* Save to to the s3 destination.
*/
function _save_file($file, $settings) {
if ($s3 = $this->s3_object()) {
$path = $file->filename();
if ($s3->putObject($s3->inputFile($file->filepath(), FALSE), $this->get_bucket(), $this->remote_path($file->filename()), S3::ACL_PRIVATE)) {
return $file;
}
}
return FALSE;
}
/**
* Load from the s3 destination.
*/
function load_file($file_id) {
backup_migrate_include('files');
$file = new backup_file(array('filename' => $file_id));
if ($s3 = $this->s3_object()) {
$data = $s3->getObject($this->get_bucket(), $this->remote_path($file_id), $file->filepath());
if (!$data->error) {
return $file;
}
}
return NULL;
}
/**
* Delete from the s3 destination.
*/
function _delete_file($file_id) {
if ($s3 = $this->s3_object()) {
$s3->deleteObject($this->get_bucket(), $this->remote_path($file_id));
}
}
/**
* List all files from the s3 destination.
*/
function _list_files() {
backup_migrate_include('files');
$files = array();
if ($s3 = $this->s3_object()) {
$s3_files = $s3->getBucket($this->get_bucket(), $this->get_subdir());
foreach ((array)$s3_files as $id => $file) {
$info = array(
'filename' => $this->local_path($file['name']),
'filesize' => $file['size'],
'filetime' => $file['time'],
);
$files[$info['filename']] = new backup_file($info);
}
}
return $files;
}
/**
* Get the form for the settings for this filter.
*/
function edit_form() {
// Check for the library.
$this->s3_object();
$form = parent::edit_form();
$form['scheme']['#type'] = 'value';
$form['scheme']['#value'] = 'https';
$form['host']['#default_value'] = 's3.amazonaws.com';
$form['path']['#title'] = 'S3 Bucket';
$form['path']['#default_value'] = $this->get_bucket();
$form['path']['#description'] = 'This bucket must already exist. It will not be created for you.';
$form['user']['#title'] = 'Access Key ID';
$form['pass']['#title'] = 'Secret Access Key';
$form['subdir'] = array(
'#type' => 'textfield',
'#title' => t('Subdirectory'),
'#default_value' => $this->get_subdir(),
'#weight' => 25
);
$form['settings']['#weight'] = 50;
return $form;
}
/**
* Submit the form for the settings for the s3 destination.
*/
function edit_form_submit($form, &$form_state) {
// Append the subdir onto the path.
if (!empty($form_state['values']['subdir'])) {
$form_state['values']['path'] .= '/'. trim($form_state['values']['subdir'], '/');
}
parent::edit_form_submit($form, $form_state);
}
/**
* Generate a filepath with the correct prefix.
*/
function remote_path($path) {
if ($subdir = $this->get_subdir()) {
$path = $subdir .'/'. $path;
}
return $path;
}
/**
* Generate a filepath with the correct prefix.
*/
function local_path($path) {
if ($subdir = $this->get_subdir()) {
$path = str_replace($subdir .'/', '', $path);
}
return $path;
}
/**
* Get the bucket which is the first part of the path.
*/
function get_bucket() {
$parts = explode('/', @$this->dest_url['path']);
return $parts[0];
}
/**
* Get the bucket which is the first part of the path.
*/
function get_subdir() {
// Support the older style of subdir saving.
if ($subdir = $this->settings('subdir')) {
return $subdir;
}
$parts = explode('/', @$this->dest_url['path']);
array_shift($parts);
return implode('/', array_filter($parts));
}
function s3_object() {
// Try to use libraries module if available to find the path.
if (function_exists('libraries_get_path')) {
$library_paths[] = libraries_get_path('s3-php5-curl');
}
else {
$library_paths[] = 'sites/all/libraries/s3-php5-curl';
}
$library_paths[] = drupal_get_path('module', 'backup_migrate') . '/includes/s3-php5-curl';
$library_paths[] = drupal_get_path('module', 'backup_migrate') . '/includes';
foreach($library_paths as $path) {
if (file_exists($path . '/S3.php')) {
require_once $path . '/S3.php';
if (!$this->s3 && !empty($this->dest_url['user'])) {
$this->s3 = new S3($this->dest_url['user'], $this->dest_url['pass'], FALSE, $this->dest_url['host']);
}
return $this->s3;
}
}
drupal_set_message(t('To back up to S3 you must download version 0.4 of the PHP S3 library from !link. Extract the S3.php file and add it the directory %dir.',
array('!link' => l('http://undesigned.org.za/2007/10/22/amazon-s3-php-class', 'http://undesigned.org.za/2007/10/22/amazon-s3-php-class'), '%dir' => 'sites/all/libraries/s3-php5-curl')), 'error', FALSE);
return NULL;
}
}
This diff is collapsed.
<?php
/**
* @file
* This filter performs tha actual backup or restore operation. Not technically a filter per-se, but it does need to fit in the call chain.
*/
/**
* A filter backup or migrate the specified source.
*
* @ingroup backup_migrate_filters
*/
class backup_migrate_filter_backup_restore extends backup_migrate_filter {
var $op_weights = array('backup' => 0, 'restore' => 0);
/**
* Get the default destinations for this filter.
*/
function destinations() {
$out = array();
foreach ($this->_get_destination_types() as $destination) {
if (method_exists($destination, 'destinations')) {
$out += $destination->destinations();
}
}
return $out;
}
/**
* Get the default backup settings for this filter.
*/
function backup_settings_default() {
backup_migrate_include('destinations');
$out = array();
foreach (backup_migrate_get_destinations('source') as $destination) {
$out['destinations'][$destination->get_id()] = $destination->backup_settings_default();
}
return $out;
}
/**
* Get the form for the settings for this filter.
*/
function backup_settings_form_validate($form, &$form_state) {
foreach ($this->_get_destination_types() as $destination) {
$destination->backup_settings_form_validate($form, $form_state);
}
}
/**
* Submit the settings form. Any values returned will be saved.
*/
function backup_settings_form_submit($form, &$form_state) {
foreach ($this->_get_destination_types() as $destination) {
$destination->backup_settings_form_submit($form, $form_state);
}
}
/**
* Get the default restore settings for this filter.
*/
function restore_settings_default() {
$out = array();
foreach ($this->_get_destination_types() as $destination) {
$out += $destination->restore_settings_default();
}
return $out;
}
/**
* Get the form for the backup settings for this filter.
*/
function backup_settings_form($settings) {
backup_migrate_include('destinations');
$out = array('destinations' => array(
'#tree' => TRUE,
));
foreach (backup_migrate_get_destinations('source') as $destination) {
$destination_settings = (array)(@$settings['destinations'][$destination->get_id()]) + $settings;
if ($form = $destination->backup_settings_form($destination_settings)) {
$out['destinations'][$destination->get_id()] = array(
'#type' => 'fieldset',
'#title' => t('!name Backup Options', array('!name' => $destination->get('name'))),
"#collapsible" => TRUE,
"#collapsed" => TRUE,
'#tree' => TRUE,
'#parents' => array('filters', 'destinations', $destination->get_id()),
) + $form;
}
}
return $out;
}
/**
* Get the form for the restore settings for this filter.
*/
function restore_settings_form($settings) {
$form = array();
foreach ($this->_get_destination_types() as $destination) {
$destination->restore_settings_form($form, $settings);
}
return $form;
}
/**
* Get the file types supported by this destination.
*/
function file_types() {
$types = array();
foreach ($this->_get_destination_types() as $destination) {
$types += $destination->file_types();
}
return $types;
}
/**
* Backup the data from the source specified in the settings.
*/
function backup($file, &$settings) {
if ($source = $settings->get_source()) {
if (!empty($settings->filters['destinations'][$source->get_id()])) {
$settings->filters = (array)($settings->filters['destinations'][$source->get_id()]) + $settings->filters;
}
$file = $source->backup_to_file($file, $settings);
return $file;
}
backup_migrate_backup_fail("Could not run backup because the source '%source' is missing.", array('%source' => $settings->source_id), $settings);
return FALSE;
}
/**
* Restore the data from to source specified in the settings.
*/
function restore($file, &$settings) {
if ($source = $settings->get_source()) {
if (!empty($settings->filters['destinations'][$source->get_id()])) {
$settings->filters = (array)($settings->filters['destinations'][$source->get_id()]) + $settings->filters;
}
$num = $source->restore_from_file($file, $settings);
return $num ? $file : FALSE;
}
backup_migrate_restore_fail("Could not run restore because the source '%source' is missing.", array('%source' => $settings->source_id), $settings);
return FALSE;
}
/**
* Get a list of dummy destinations representing each of the available destination types.
*/
function _get_destination_types() {
backup_migrate_include('destinations');
static $destinations = NULL;
if (!is_array($destinations)) {
$destinations = array();
$types = backup_migrate_get_destination_types();
// If no (valid) node type has been provided, display a node type overview.
foreach ($types as $key => $type) {
// Include the necessary file if specified by the type.
if (!empty($type['file'])) {
require_once './'. $type['file'];
}
$destinations[] = new $type['class'](array());
}
}
return $destinations;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.