hosting_site_backup_manager.module in Hosting Site Backup Manager 6
Same filename and directory in other branches
Hosting site backup manager module.
Adds a backup tab to the site node.
File
hosting_site_backup_manager.moduleView source
<?php
/**
* @file
* Hosting site backup manager module.
*
* Adds a backup tab to the site node.
*/
/**
* Implements of hook_menu().
*
* @return array
* An array of menu items.
*/
function hosting_site_backup_manager_menu() {
$items = array();
$items['node/%hosting_site_node/backups'] = array(
'title' => 'Backups',
'description' => 'List of backups of this website',
'page callback' => 'hosting_site_backup_manager_page',
'page arguments' => array(
1,
),
'access callback' => 'node_access',
'access arguments' => array(
'view',
1,
),
'type' => MENU_LOCAL_TASK,
);
$items['node/%hosting_site_node/ajax/backups'] = array(
'title' => 'Backup list',
'description' => 'AJAX callback for refreshing backups list',
'page callback' => 'hosting_site_backup_manager_ajax_list',
'page arguments' => array(
1,
),
'access callback' => 'node_access',
'access arguments' => array(
'view',
1,
),
'type' => MENU_CALLBACK,
);
$items['node/%hosting_site_node/backup/download/%'] = array(
'title' => 'Download backup',
'description' => 'Download the selected backup',
'page callback' => 'hosting_site_backup_manager_download',
'page arguments' => array(
1,
4,
),
'access callback' => 'node_access',
'access arguments' => array(
'view',
1,
),
'type' => MENU_CALLBACK,
);
$items['node/%hosting_site_node/backup/delete/%'] = array(
'title' => 'Delete backup',
'description' => 'Delete the selected backup',
'page callback' => 'hosting_site_backup_manager_delete',
'page arguments' => array(
1,
4,
),
'access callback' => 'node_access',
'access arguments' => array(
'view',
1,
),
'type' => MENU_CALLBACK,
);
$items['node/%hosting_site_node/backup/restore/%'] = array(
'title' => 'Restore backup',
'description' => 'Restore the selected backup',
'page callback' => 'hosting_site_backup_manager_restore',
'page arguments' => array(
1,
4,
),
'access callback' => 'node_access',
'access arguments' => array(
'view',
1,
),
'type' => MENU_CALLBACK,
);
$items['node/%hosting_site_node/backup/export/%'] = array(
'title' => 'Export backup',
'description' => 'Export the selected backup',
'page callback' => 'hosting_site_backup_manager_export',
'page arguments' => array(
1,
4,
),
'access callback' => 'node_access',
'access arguments' => array(
'view',
1,
),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Placeholder function for additional delete checks.
*
* @param node $site
* The site node object.
* @param int $bid
* The backup id.
*
* @return string
* A confirmation form or a error string.
*/
function hosting_site_backup_manager_delete($site, $bid) {
// Get the filename.
$source = db_fetch_object(db_query("SELECT filename FROM {hosting_site_backups} WHERE site=%d AND bid=%d ORDER BY timestamp DESC", $site->nid, $bid));
if ($source) {
// Return a confirmation form.
$output = drupal_get_form('hosting_site_backup_manager_confirm_delete', $source->filename, $site->nid, $bid);
}
else {
$output = t('A valid backup could not be found.');
}
return $output;
}
/**
* Function that renders a confirmation form for the selected deletetion.
*
* @param arrays $form_state
* The form state array. Changes made to this variable will have no effect.
* @param string $filename
* The backup filename to delete.
* @param int $sitenid
* The selected site node id.
* @param int $bid
* The backup id.
*
* @return string
* A confirmation form.
*/
function hosting_site_backup_manager_confirm_delete($form_state, $filename, $sitenid, $bid) {
// Add the hosting_task javascript,
// so we can use the confirm form functionality.
drupal_add_js(drupal_get_path('module', 'hosting_task') . '/hosting_task.js');
// Get the backup data.
$source = db_fetch_object(db_query("SELECT * FROM {hosting_site_backups} WHERE site=%d AND bid=%d", $sitenid, $bid));
// Build the form.
$form = array();
$form['#filename'] = $filename;
$form['#sitenid'] = $sitenid;
$form['#bid'] = $bid;
// Not the best formatted code, but suffices for now.
$form['explanation'] = array(
'#type' => 'markup',
'#value' => '<h2>' . t('Backup information') . '</h2>' . t('Backup description:<br /> %description', array(
'%description' => filter_xss($source->description),
)) . '<br />',
'#weight' => -10,
);
$form['date'] = array(
'#type' => 'markup',
'#value' => t('Backup was created on:<br /> %date', array(
'%date' => format_date($source->timestamp, 'short'),
)) . '<br /><br />',
'#weight' => -8,
);
// Build the confirmation form.
$form = confirm_form($form, t('Are you sure you want to delete this backup?', array()), 'node/' . $sitenid . '/backup', t('This action cannot be undone.'), t('Delete'), t('Cancel'), 'hosting_site_backup_manager_confirm_delete');
// Copied from hosting_task.module
// Add an extra class to the actions to allow us to disable
// the cancel link via javascript for the modal dialog.
$form['actions']['#prefix'] = '<div id="hosting-task-confirm-form-actions" class="container-inline">';
return $form;
}
/**
* The form submit function.
*/
function hosting_site_backup_manager_confirm_delete_submit($form, &$form_state) {
// The deletion has been confirmed, process the result.
$filename = $form['#filename'];
$sitenid = $form['#sitenid'];
$bid = $form['#bid'];
// Add the hosting task.
hosting_add_task($sitenid, 'backup_delete', array(
$bid => $filename,
));
$form_state['redirect'] = 'node/' . $sitenid . '/backup';
modalframe_close_dialog();
}
/**
* Placeholder function for additional restore checks.
*
* @param node $site
* The site node object.
* @param int $bid
* The backup id.
*
* @return string
* A confirmation form.
*/
function hosting_site_backup_manager_restore($site, $bid) {
// @todo: Build in extra checks.
$output = drupal_get_form('hosting_site_backup_manager_confirm_restore', $site->nid, $bid);
return $output;
}
/**
* Function that renders a confirmation form for the selected deletetion.
*
* @param array $form_state
* The form state array. Changes made to this variable will have no effect.
* @param int $sitenid
* The selected site node id.
* @param int $bid
* The backup id
*
* @return string
* A confirmation form.
*/
function hosting_site_backup_manager_confirm_restore($form_state, $sitenid, $bid) {
// Add the hosting_task javascript,
// so we can use the confirm form functionality.
drupal_add_js(drupal_get_path('module', 'hosting_task') . '/hosting_task.js');
// Build the form.
$form = array();
$form['#sitenid'] = $sitenid;
$form['#bid'] = $bid;
// Get the backup data.
$source = db_fetch_object(db_query("SELECT * FROM {hosting_site_backups} WHERE site=%d AND bid=%d", $sitenid, $bid));
// Not the best formatted code, but suffices for now.
$form['explanation'] = array(
'#type' => 'markup',
'#value' => '<h2>' . t('Backup information') . '</h2>' . t('Backup description:<br /> %description', array(
'%description' => filter_xss($source->description),
)) . '<br />',
'#weight' => -10,
);
$form['date'] = array(
'#type' => 'markup',
'#value' => t('Backup was created on:<br /> %date', array(
'%date' => format_date($source->timestamp, 'short'),
)) . '<br /><br />',
'#weight' => -8,
);
// Build the confirmation form.
$form = confirm_form($form, t('Are you sure you want to restore this backup?', array()), 'node/' . $sitenid . '/backup', t('This action cannot be undone.'), t('Restore'), t('Cancel'), 'hosting_site_backup_manager_confirm_restore');
// Copied from hosting_task.module
// add an extra class to the actions to allow us to
// disable the cancel link via javascript for the modal dialog.
$form['actions']['#prefix'] = '<div id="hosting-task-confirm-form-actions" class="container-inline">';
return $form;
}
/**
* The form submit function.
*/
function hosting_site_backup_manager_confirm_restore_submit($form, &$form_state) {
// The restoration has been confirmed, process the result.
$sitenid = $form['#sitenid'];
$bid = $form['#bid'];
hosting_add_task($sitenid, 'restore', array(
'bid' => $bid,
));
$form_state['redirect'] = 'node/' . $sitenid . '/backup';
modalframe_close_dialog();
}
/**
* The form submit function.
*/
function hosting_site_backup_manager_export($site, $bid) {
// Get the filename.
$source = db_fetch_object(db_query("SELECT filename FROM {hosting_site_backups} WHERE site=%d AND bid=%d ORDER BY timestamp DESC", $site->nid, $bid));
// Start the task.
hosting_add_task($site->nid, 'export_backup', array(
'backup' => $source->filename,
));
// Redirect to the backups page.
drupal_goto('node/' . $site->nid . '/backups');
}
/**
* Function to download a backup file.
*
* @param node $site
* The site node object.
* @param int $bid
* The backup id.
*
* @return file
* A file or a 404 page.
*/
function hosting_site_backup_manager_download($site, $bid) {
// @todo: check if the bid is not in a task.
$source = db_fetch_object(db_query("SELECT filename FROM {hosting_site_backups} WHERE site=%d AND bid=%d ORDER BY timestamp DESC", $site->nid, $bid));
if ($source) {
// Determine client name.
$client = hosting_client_node_load($site->client);
// Get Document Root.
$docroot = _hosting_site_backup_manager_getaegirroot();
$symlink = $docroot . '/backup-exports/' . $client->uname . '/' . basename($source->filename);
if ($fd = @fopen($symlink, 'rb')) {
// Construct filename.
$filename = basename($source->filename);
// Set headers.
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=\"{$filename}\"");
header("Content-Type: application/octet-stream");
header("Content-Transfer-Encoding: binary");
while (!feof($fd)) {
print fread($fd, 1024);
}
fclose($fd);
// File downloaded so delete the file.
// Start the task.
hosting_add_task($site->nid, 'remove_export_backup', array(
'export' => $symlink,
));
exit;
}
else {
drupal_not_found();
}
}
else {
drupal_not_found();
}
}
/**
* Show a list of backups for a website.
*
* @param node $site
* The site node.
*
* @return string
* A theme table with a list of backups.
*/
function hosting_site_backup_manager_page($site) {
// Add the js.
drupal_add_js(drupal_get_path('module', 'hosting_site_backup_manager') . '/hosting_site_backup_manager.js');
$settings['hostingSiteBackupManager'] = array(
'nid' => $site->nid,
);
drupal_add_js($settings, 'setting');
$output = '<div id="hosting-site-backup-manager-backupstable">';
$output .= hosting_site_backup_manager_backups_table($site);
$output .= '</div>';
return $output;
}
/**
* Page callback for backups table via AJAX.
*/
function hosting_site_backup_manager_ajax_list($site) {
$return['markup'] = hosting_site_backup_manager_backups_table($site);
drupal_json($return);
exit;
}
/**
* Prepare a table of available backups.
*/
function hosting_site_backup_manager_backups_table($site) {
global $user;
// Determine client name.
$client = hosting_client_node_load($site->client);
$headers[] = t('Backup');
$headers[] = array(
'data' => t('Actions'),
'class' => 'hosting-actions',
);
$rows = array();
// Only allow actions when there's no backup delete or
// restore task running for this node.
$buttonstatus = !(hosting_task_outstanding($site->nid, 'backup_delete') || hosting_task_outstanding($site->nid, 'restore') || hosting_task_outstanding($site->nid, 'export_backup') || hosting_task_outstanding($site->nid, 'remove_export_backup'));
// TODO Make the table reload automatically.
$result = db_query("SELECT bid, description, filename, size, timestamp FROM {hosting_site_backups} WHERE site=%d ORDER BY timestamp DESC", $site->nid);
if (db_affected_rows($result) == 0) {
$output = t("No backups available.");
$options = array(
'query' => array(
'token' => drupal_get_token($user->uid),
),
'attributes' => array(
'class' => 'hosting-button-dialog',
),
);
$output .= ' ' . l(t('Create backup'), 'node/' . $site->nid . '/site_backup', $options);
return $output;
}
while ($object = db_fetch_object($result)) {
$row = array();
$row['description'] = filter_xss($object->description) . "<br />" . format_size($object->size) . " - " . format_date($object->timestamp, 'short');
$actions = array();
// @todo: Add check if the backup can be restored to current platform?
/* Add download button */
$downloadstatus = _hosting_site_backup_manager_isfileavailable($site, $object->bid) && !hosting_task_outstanding($site->nid, 'remove_export_backup');
$actions['download'] = _hosting_task_button(t('Get'), 'node/' . $site->nid . '/backup/download/' . $object->bid, t('Download the backup'), '', $downloadstatus, FALSE, FALSE);
/* Add export backup button */
$exportstatus = $buttonstatus && !$downloadstatus;
$actions['export_backup'] = _hosting_task_button(t('Export'), 'node/' . $site->nid . '/backup/export/' . $object->bid, t('Make the backup exportable'), '', $exportstatus, FALSE, FALSE);
/* Add delete button */
$actions['delete'] = _hosting_task_button(t('Delete'), 'node/' . $site->nid . '/backup/delete/' . $object->bid, t('Delete the backup'), '', $buttonstatus, TRUE, FALSE);
/* Add restore button */
$actions['restore'] = _hosting_task_button(t('Restore'), 'node/' . $site->nid . '/backup/restore/' . $object->bid, t('Restore the backup'), '', $buttonstatus, TRUE, FALSE);
$row['actions'] = array(
'data' => implode('', $actions),
'class' => 'hosting-actions',
);
$rows[] = array(
'data' => $row,
'class' => $info['class'],
);
}
$output .= theme('table', $headers, $rows, array(
'class' => 'hosting-table',
));
return $output;
}
/**
* Implements hook_hosting_tasks().
*/
function hosting_site_backup_manager_hosting_tasks() {
$tasks = array();
$tasks['site']['export_backup'] = array(
'title' => t('Export Backup'),
'description' => t('Make a backup available for download.'),
'dialog' => TRUE,
'hidden' => TRUE,
);
$tasks['site']['remove_export_backup'] = array(
'title' => t('Remove Export Backup'),
'description' => t('Remove an exported backup, making it unavailable for download.'),
'dialog' => FALSE,
'hidden' => TRUE,
);
return $tasks;
}
/**
* Helper function to get the Aegir root directory.
*/
function _hosting_site_backup_manager_getaegirroot() {
// TODO: Make it a variable or determine it from $_SERVER['DOCUMENT_ROOT']
return '/var/aegir';
}
/**
* Helper function to check if a backup file is available.
*/
function _hosting_site_backup_manager_isfileavailable($site, $bid) {
$result = FALSE;
// @todo: check if the bid is not in a task.
$source = db_fetch_object(db_query("SELECT filename FROM {hosting_site_backups} WHERE site=%d AND bid=%d ORDER BY timestamp DESC", $site->nid, $bid));
if ($source) {
// Determine client name.
$client = hosting_client_node_load($site->client);
// Get Document Root.
$docroot = _hosting_site_backup_manager_getaegirroot();
$file = $docroot . '/backup-exports/' . $client->uname . '/' . basename($source->filename);
if (file_exists($file)) {
return TRUE;
}
}
return $result;
}
/**
* Remove backup links if they persist beyond a timeout.
*
* They should normally be cleared directly after download.
*
* Implements hook_cron().
*/
function hosting_site_backup_manager_cron() {
// Get Document Root.
$docroot = _hosting_site_backup_manager_getaegirroot();
$backup_export_expire_timeout = 60 * 10;
$backup_root = $file = $docroot . '/backup-exports/';
if (!is_dir($backup_root)) {
return;
}
try {
// Loop the client names that have a dir under the backup root.
$iterator = new DirectoryIterator($backup_root);
foreach ($iterator as $path) {
if ($path
->isDot() || !$path
->isDir()) {
continue;
}
// Loop all files in the client's backup dir.
$iterator2 = new DirectoryIterator($path
->getPathname());
foreach ($iterator2 as $path2) {
if ($path2
->isDot() || !$path2
->isFile()) {
continue;
}
// Get the inode Change time, not to be confused with the modified time.
// When the inode changed, not the content. e.g. when the extra hardlink was added to the inode.
if ($path2
->getCTime() < time() - $backup_export_expire_timeout) {
echo "wanna unlink: " . $path2
->getPathname();
watchdog('hosting_site_backup_manager', "Unlinking leftover exported backup: @pathname ", array(
'@pathname' => $path2
->getPathname(),
));
unlink($path2
->getPathname());
}
// Were now using hardlinks, but incase we ever want to switch:
// For symlinks, $path2->getCTime() returns that of the link target not the link itself.
// $link_stats = lstat($path2->getPathname());
// if ($link_stats['mtime'] < (time() - $backup_export_expire_timeout)) {
}
}
} catch (UnexpectedValueException $e) {
// TODO: convert to watchdog_exception for D7
watchdog('hosting_site_backup_manager', nl2br(check_plain($e
->getMessage())), NULL, WATCHDOG_WARNING);
}
}
Functions
Name![]() |
Description |
---|---|
hosting_site_backup_manager_ajax_list | Page callback for backups table via AJAX. |
hosting_site_backup_manager_backups_table | Prepare a table of available backups. |
hosting_site_backup_manager_confirm_delete | Function that renders a confirmation form for the selected deletetion. |
hosting_site_backup_manager_confirm_delete_submit | The form submit function. |
hosting_site_backup_manager_confirm_restore | Function that renders a confirmation form for the selected deletetion. |
hosting_site_backup_manager_confirm_restore_submit | The form submit function. |
hosting_site_backup_manager_cron | Remove backup links if they persist beyond a timeout. |
hosting_site_backup_manager_delete | Placeholder function for additional delete checks. |
hosting_site_backup_manager_download | Function to download a backup file. |
hosting_site_backup_manager_export | The form submit function. |
hosting_site_backup_manager_hosting_tasks | Implements hook_hosting_tasks(). |
hosting_site_backup_manager_menu | Implements of hook_menu(). |
hosting_site_backup_manager_page | Show a list of backups for a website. |
hosting_site_backup_manager_restore | Placeholder function for additional restore checks. |
_hosting_site_backup_manager_getaegirroot | Helper function to get the Aegir root directory. |
_hosting_site_backup_manager_isfileavailable | Helper function to check if a backup file is available. |