<?php
// $Id: backup_migrate.module,v 1.1.2.10 2008/03/06 20:54:10 ronan Exp $

// Implementation of hook_menu().
function backup_migrate_menu($may_cache) {
  $items = array();
  
  if ($may_cache) {
    $items[] = array(
      'path' => '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,
    ); 
  }
  else {
  }
  return $items;
}

/**
 * implementation of hook_cron(). 
 *    takes care of schedule backups
 */
function backup_migrate_cron() {
  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();
    }
  }
}

// Implementation of hook_perm().
function backup_migrate_perm() {
  return array('perform backup', 'access backup files', 'delete backup files', 'restore from backup');
}


/**
 * Menu callback. Delete a previous backup
 */
function _backup_migrate_delete() {
  $path = "";
  if ( arg(4) ) {
    $path = $_GET['q'];
    $path = substr( $path, strlen( "admin/content/backup_migrate/delete/" ) );
  }
  if ( $path && _backup_migrate_path_is_in_save_dir( $path ) ) {
    return drupal_get_form('backup_migrate_delete_confirm', $path);
  }
  drupal_goto( 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 );
  }
  drupal_goto( 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" => "None" );
  if ( @function_exists( "gzencode" ) ) {
    $compression_options['gzip'] = "GZip";
  }
  if ( @function_exists( "bzcompress" ) ) {
    $compression_options['bzip'] = "BZip";
  }
  if ( class_exists( 'ZipArchive' ) ) {
    $compression_options['zip'] = "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 <a href="!url">date()</a> 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'] );
  }

  $out = _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 $out;
}

/**
 * Restore a backup file (from the server)
 */
function _backup_migrate_restore_from_server() {
  $path = "";
  if ( arg(4) ) {
    $path = $_GET['q'];
    $path = substr( $path, strlen( "admin/content/backup_migrate/restorefile/" ) );
  }
  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. <strong>Always test your backups on a non-production server!</strong>'), 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 );
  }
  drupal_goto( 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('<p>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. <strong>Always test your backups on a non-production server!</strong><p>'),
  );
  
  $form[] = array(
    '#type' => 'submit',
    '#value' => t('Restore Database'),
  );
  if ( user_access('access backup files') ) {
     $form[] = array(
      '#type' => 'markup',
      '#value' => t('<p>Or you can restore one of the files in the <a href="!url">saved backup directory.</a></p>', 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 );
  }
  
  return $out;
}

/**
 * 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.'),
    ),
  );
}

function backup_migrate_action_backup() {
  _backup_migrate_backup_with_defaults();
}

/**
 * Backup the database with the default settings
 */
function _backup_migrate_backup_with_defaults( $type = "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" ),
        $type,
        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", $dir="manual", $append_timestamp=false ) {
  $file_mime = "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, $out, $dir );
      break;
      case "download": 
        _backup_migrate_send_file_to_download( $filename, $file_mime, $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 );
  
  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" ) ) {
    $exclude = variable_get( "backup_migrate_exclude_tables", _backup_migrate_default_exclude_tables() );
    $nodata = variable_get( "backup_migrate_nodata_tables", _backup_migrate_default_structure_only_tables() );
    fwrite( $dst, _backup_migrate_get_sql_file_header() );
    $alltables = _backup_migrate_get_tables();
    foreach ( $alltables as $table ) {
      if ( $table['Name'] && !isset( $exclude[$table['Name']] ) ) {
        fwrite( $dst, _backup_migrate_get_table_structure_sql( $table ) );
        if ( !in_array( $table['Name'], $nodata ) ) {
          _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 "SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT;
SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS;
SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION;
SET NAMES utf8;

/*!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 */;

";
}

/**
 * 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 */;
SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT;
SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS;
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;
  }
  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;
  }
  $goto = "admin/content/backup_migrate/";
  
  $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 ( substr( $filename, -4, 4 ) == ".sql" ) {
    // no compression
  }
  if ( substr( $filename, -3, 3 ) == ".gz" ) {
    if ( function_exists( "gzopen" ) ) {
      $open_func  = "gzopen";
      $read_func  = "gzgets";
      $close_func = "gzclose";
    } 
    else {
      // gz 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" );    
    }
  }
  // bz compression
  if ( 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 {
      // bz compression... not supported
      drupal_set_message(t( "This version of PHP does not support bzip comressed files. Please try using an uncompressed backup." ), 'error');
      drupal_goto( "admin/content/backup_migrate/restore" );    
    }
  }
  // zip compression
  if ( 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 bacup." ), '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 );
    $goto = "admin/content/backup_migrate/restore";
  }
  else {
    drupal_set_message(t( "Unable to open file %file to restore database", array( "%file" => $filepath )), 'error');
    $goto = "admin/content/backup_migrate/restore";
  }
  
  // delete any temp files we've created
  _backup_migrate_temp_file( "", true );          
  drupal_goto( $goto );
}

/*
  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 = strlen( $type['extension'] );
    if ( 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, $data, $type="manual" ) {
  if ( $dir = _backup_migrate_check_destination_dir( $type ) ) {
    $filepath = $dir ."/". $filename;
    
    rename( $temp_file, $filepath );
    
    $message = t( 'Database backup saved to %file. ', array( '%file' => $filename ) );
    $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 ) && user_access( 'access backup files' ) ) {
    if ( $info = _backup_migrate_file_info( $path ) ) {
      return array(
        'Content-Type: '. $info['filemime'],
        'Content-Length: '. $info['filesize'],
        'Content-Disposition: attachment; filename="'. $info['filename'] .'"',
      ); 
    }
  }
}


/**
 * 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 );
   }
   $file = tempnam(file_directory_temp(), 'tmp_');
   $file .= $extension ? ".". $extension : "";
   $files[] = $file;
   return $file;
}

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( $type = "manual" ) {
  $files = array();
  if ( $dir = _backup_migrate_check_destination_dir( $type ) ) { 
    if ( $handle = opendir( $dir ) ) {
      while ( false !== ($file = readdir($handle))) {
        $filepath = $dir ."/". $file;
        if ( $info = _backup_migrate_file_info( $filepath ) ) {
          $files[$file] = array( 
            $file, 
            date( "m/d/Y h:i a", $info['filemtime'] ),
            format_size( $info['filesize'] ),
            l( "download", "system/files/". $filepath ),
            l( "restore", "admin/content/backup_migrate/restorefile/". $filepath ),
            l( "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[$info['filectime'] ." ". $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, $type = "" ) {
  return ( file_check_location( $path, _backup_migrate_get_save_path($type) ) );
}

/**
 * Return the path on the server to save the dump files.
 */
function _backup_migrate_get_save_path( $type="" ) {
  $dir = file_directory_path() ."/backup_migrate";
  if ( $type ) {
    $dir .= $type == "manual" ? "/manual" : "/scheduled";  
  }
  return $dir;
}

/**
 * Prepare the destination directory for the backups
 */
function _backup_migrate_check_destination_dir( $type="" ) {
  $directory = _backup_migrate_get_save_path();
  $out = $subdir = trim( $directory ."/". $type, "/" );

  // check for the main directory
  if ( !file_check_directory( $directory, true ) ) {    
    // unable to create main directory
    $out = false;
    $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");
  } 
  
  // create subdir if needed
  if ( ($subdir != $directory) && !file_check_directory( $subdir, true ) ) {
    // unable to create main directory
    $out = false;
    $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");
  }
  
  // check for htaccess
  if ( $out && !is_file("$directory/.htaccess") ) {
    // attempt to protect the backup files from public access using htaccess
    $htaccess_lines = "order allow,deny\ndeny from all\n";
    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 = t("Security warning: Couldn't modify .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code> or add them to the existing .htaccess file", array('%directory' => $directory, '!htaccess' => '<br />'. nl2br(check_plain($htaccess_lines))));
      drupal_set_message($message, "error");
      watchdog('security', $message, WATCHDOG_ERROR);
    }
  }
  
  // check to see if we can read from the dir
  if ( $out ) {
    $test_contents = "this file should not be publicly accesible";
    if (($fp = @fopen($subdir .'/test.txt', 'w')) && @fputs($fp, $test_contents)) {
      fclose($fp);
      $result = drupal_http_request( url( file_create_url( $subdir .'/test.txt' ), NULL, NULL, true ));
      if ( $result->data === $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");
        $out = false;
      }
    } 
    else {
      $out = false;
    }
  }

  return $out;
}


/**
 * 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; 
}

/**
 * bzip 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 );
  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'));
  return $alltables;
}

