Index: database_pgsql_dump.inc
===================================================================
RCS file: database_pgsql_dump.inc
diff -N database_pgsql_dump.inc
--- database_pgsql_dump.inc	20 Sep 2007 15:34:41 -0000	1.2
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,14 +0,0 @@
-<?php
-// $Id: database_pgsql_dump.inc,v 1.2 2007/09/20 15:34:41 sun Exp $
-
-/**
- * Dump active database.
- *
- * @todo Postgres should utilize pg_dump. See phpPgAdmin's dbexport.php
- * for how to do it.
- */
-function demo_dump_db($filename, $exclude = array()) {
-  drupal_set_message(t('PostgreSQL support not implemented yet.'), 'error');
-  return FALSE;
-}
-
Index: demo.admin.inc
===================================================================
RCS file: demo.admin.inc
diff -N demo.admin.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ demo.admin.inc	28 Oct 2008 18:33:15 -0000
@@ -0,0 +1,475 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Demonstration Site administrative pages
+ */
+
+/**
+ * Current version of SQL dump structure.
+ */
+define('DEMO_DUMP_VERSION', '1.1');
+
+function demo_admin_settings() {
+  global $base_url;
+  
+  $form['status'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Status'),
+    '#collapsible' => false,
+  );
+  if (variable_get('demo_reset_last', 0)) {
+    $reset_date = format_date(variable_get('demo_reset_last', 0));
+  }
+  else {
+    $reset_date = t('Never');
+  }
+  $form['status'][] = array(
+    '#value' => t('<p><strong>Last reset:</strong> !date</p>', array('!date' => $reset_date)),
+  );
+  $form['status'][] = array(
+    '#value' => t('<p><strong>Default snapshot:</strong> !snapshot</p>', array('!snapshot' => variable_get('demo_dump_cron', t('None')))),
+  );
+  
+  $fileconfig = demo_get_fileconfig();
+  
+  $form['dump'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Dump settings'),
+    '#collapsible' => true,
+    '#collapsed' => (variable_get('demo_reset_interval', 0) ? false : true),
+  );
+  $period = drupal_map_assoc(array(0, 1800, 3600, 7200, 10800, 14400, 18000, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
+  $period[0] = t('disabled');
+  $form['dump']['interval'] = array(
+    '#type' => 'select',
+    '#title' => t('Automatically reset site every'),
+    '#default_value' => variable_get('demo_reset_interval', 0),
+    '#options' => $period,
+    '#description' => t('Select how often this demonstration site is automatically reset. Ensure that you have chosen a snapshot for cron runs in <a href="!manage">Manage snapshots</a> first. <strong>Note:</strong> This requires cron to run at least within this interval.', array('!manage' => url('admin/settings/demo/manage'))),
+  );
+  
+  $form['dump']['path'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Dump path'),
+    '#default_value' => $fileconfig['path'],
+    '#size' => 30,
+    '#description' => t('Enter a writable directory where dump files of this demonstration site are stored, f.e. %files. The name of this site (e.g. %confpath) is automatically appended to this directory.<br /><br /><strong>Note: For security reasons you should store site dumps outside of the document root of your webspace!</strong>', array('%files' => file_directory_path() .'/demo', '%confpath' => $fileconfig['site'])),
+  );
+  $form[] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+  
+  return $form;
+}
+
+function demo_admin_settings_submit($form, &$form_state) {
+  if (!file_check_directory($form_state['values']['path'], FILE_CREATE_DIRECTORY)) {
+    form_set_error('path', t('The snapshot directory %directory could not be created.', array('%directory' => $form_state['values']['path'])));
+    return FALSE;
+  }
+  else {
+    variable_set('demo_dump_path', $form_state['values']['path']);
+  }
+  variable_set('demo_reset_interval', $form_state['values']['interval']);
+  drupal_set_message(t('The configuration options have been saved.'));
+}
+
+function demo_manage() {
+  $form['dump'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Available snapshots'),
+  );
+  $form = array_merge_recursive($form, demo_get_dumps());
+  $form[] = array(
+    '#type' => 'submit',
+    '#value' => t('Set as default snapshot for cron'),
+  );
+  $form[] = array(
+    '#type' => 'submit',
+    '#value' => t('Delete selected snapshot'),
+    '#submit' => array('demo_manage_delete_submit'),
+  );
+  
+  return $form;
+}
+
+function demo_manage_submit($form, &$form_state) {
+  variable_set('demo_dump_cron', $form_state['values']['filename']);
+  drupal_set_message(t('Snapshot %title will be used for upcoming cron runs.', array('%title' => $form_state['values']['filename'])));
+}
+
+function demo_manage_delete_submit($form, &$form_state) {
+  $files = demo_get_fileconfig($form_state['values']['filename']);
+  unlink($files['sqlfile']);
+  unlink($files['infofile']);
+  drupal_set_message(t('Snapshot %title has been deleted.', array('%title' => $form_state['values']['filename'])));
+}
+
+function demo_dump() {
+  $form = array();
+  $form['dump']['filename'] = array(
+    '#title' => t('File name'),
+    '#type' => 'textfield',
+    '#autocomplete_path' => 'demo/autocomplete',
+    '#required' => true,
+    '#maxlength' => 128,
+    '#description' => t('Enter the snapshot file name without file extension. Allowed characters are a-z, 0-9, dashes ("-"), underscores ("_") and dots.'),
+  );
+  $form['dump']['description'] = array(
+    '#title' => t('Description'),
+    '#type' => 'textarea',
+    '#rows' => 2,
+    '#description' => t('Optionally enter a description for this snapshot here. If no description is given and a snapshot with the same filename already exists, the previous description is used.'),
+  );
+  return confirm_form($form, t('Are you sure you want to create a new snapshot?'), 'admin/settings/demo', t('If the above filename already exists, creating a new snapshot will overwrite the existing snapshot. This action cannot be undone.'), t('Create'), t('Cancel'));
+}
+
+function demo_dump_submit($form, &$form_state) {
+  global $db_type;
+
+  // Generate info file.
+  $info = demo_set_info($form_state['values']);
+  if (!$info) {
+    return false;
+  }
+  
+  // Include database specific functions.
+  $engine = ($db_type == 'mysqli' ? 'mysql' : $db_type);
+  $inc_file = drupal_get_path('module', 'demo') .'/database_'. $engine .'_dump.inc';
+  if (file_exists($inc_file)) {
+    require_once $inc_file;
+  
+    // Increase PHP's max_execution_time for large dumps.
+    @set_time_limit(600);
+  
+    // Perform dump.
+    $fileconfig = demo_get_fileconfig($info['filename']);
+    $exclude = array('{cache}', '{cache_content}', '{cache_filter}', '{cache_menu}', '{cache_page}', '{cache_views}', '{panels_object_cache}', '{watchdog}');
+    $exclude = array_map('db_prefix_tables', $exclude);
+    demo_dump_db($fileconfig['sqlfile'], $exclude);
+  }
+  else {
+    drupal_set_message(t('@engine support not implemented yet.', array('@engine' => ucfirst($engine))), 'error');
+  }
+
+  $form_state['redirect'] = 'admin/settings/demo/manage';
+}
+
+function demo_reset_confirm() {
+  $form['dump'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Available snapshots'),
+  );
+  $form = array_merge_recursive($form, demo_get_dumps());
+  
+  return confirm_form($form, t('Are you sure you want to reset the site?'), 'admin/settings/demo', t('Resetting the site will overwrite all changes that have been made to this Drupal installation since the chosen snapshot.<br /><br /><div style="color: red; font-weight: bold; font-size: 18px;"><center>THIS ACTION CANNOT BE UNDONE!</center><br /></div>'), t('Reset'), t('Cancel'));
+}
+
+function demo_reset_confirm_submit($form, &$form_state) {
+  // Increase PHP's max_execution_time for large dumps.
+  @set_time_limit(600);
+
+  // Reset site to chosen snapshot.
+  demo_reset($form_state['values']['filename']);
+  // Save time of last reset.
+  variable_set('demo_reset_last', time());
+  
+  $form_state['redirect'] = isset($form_state['values']['redirect']) ? $form_state['values']['redirect'] : 'admin/settings/demo';
+}
+
+function demo_reset($filename = 'demo_site', $verbose = TRUE) {
+  // Load any database information in front of reset.
+  $demo_dump_cron = variable_get('demo_dump_cron', $filename);
+  $fileconfig = demo_get_fileconfig($filename);
+  $version = demo_get_info($fileconfig['infofile'], 'version');
+  $is_version_1_0_dump = version_compare($version, '1.1', '<');
+
+  if (file_exists($fileconfig['sqlfile']) && $fp = fopen($fileconfig['sqlfile'], 'r')) {
+    // Drop tables
+    $dt_watchdog = db_prefix_tables('{watchdog}');
+    foreach (demo_enum_tables() as $table) {
+      // Skip watchdog, except for legacy dumps that included the watchdog table
+      if ($table != $dt_watchdog || $is_version_1_0_dump) {
+        db_query("DROP TABLE %s", $table);
+      }
+    }
+
+    // Load data from snapshot
+    $query = '';
+    $success = TRUE;
+    while (!feof($fp)) {
+      $line = fgets($fp, 16384);
+      if ($line && $line != "\n" && strncmp($line, '--', 2) && strncmp($line, '#', 1)) {
+        $query .= $line;
+        if (substr($line, -2) == ";\n") {
+          if (!_db_query($query, FALSE)) {
+            if ($verbose) {
+              // Don't use t() here, as the locale_* tables might not (yet) exist.
+              drupal_set_message(strtr('Query failed: %query', array('%query' => $query)), 'error');
+            }
+            $success = FALSE;
+          }
+          $query = '';
+        }
+      }
+    }
+    fclose($fp);
+
+    if ($success) {
+      $message = t('Successfully restored database from %filename.', array('%filename' => $fileconfig['sqlfile']));
+    }
+    else {
+      $message = t('Failed restoring database from %filename.', array('%filename' => $fileconfig['sqlfile']));
+    }
+    if ($verbose) {
+      drupal_set_message($message);
+    }
+    watchdog('demo', $message, array(), $success ? WATCHDOG_NOTICE : WATCHDOG_ERROR);
+
+    // Reset default dump to load on cron.
+    variable_set('demo_dump_cron', $demo_dump_cron);
+    return TRUE;
+  }
+  $message = t('Unable to open dump file %filename.', array('%filename' => $fileconfig['sqlfile']));
+  if ($verbose) {
+    drupal_set_message($message, 'error');
+  }
+  watchdog('demo', $message, array(), WATCHDOG_ERROR);
+  return FALSE;
+}
+
+function demo_get_fileconfig($filename = 'demo_site') {
+  $fileconfig = array();
+  
+  // Build dump path.
+  $fileconfig['path'] = variable_get('demo_dump_path', file_directory_path() .'/demo');
+  $fileconfig['site'] = str_replace('sites', '', conf_path());
+  $fileconfig['dumppath'] = $fileconfig['path'] . $fileconfig['site'];
+  
+  // Check if directory exists.
+  file_check_directory($fileconfig['path'], FILE_CREATE_DIRECTORY, 'path');
+  if (!file_check_directory($fileconfig['dumppath'], FILE_CREATE_DIRECTORY, 'path')) {
+    return false;
+  }
+  
+  // Protect dump files.
+  $htaccess = $fileconfig['path'] ."/.htaccess";
+  if (!is_file($htaccess)) {
+    $htaccess_lines = "# demo.module snapshots\n# Do not let the webserver serve anything under here!\n#\nDeny from all\n";
+    if (($fp = fopen($htaccess, 'w')) && fputs($fp, $htaccess_lines)) {
+      fclose($fp);
+      chmod($htaccess, 0664);
+    }
+  }
+    
+  // Build SQL filename.
+  $fileconfig['sql'] = $filename .'.sql';
+  $fileconfig['sqlfile'] = $fileconfig['path'] . $fileconfig['site'] .'/'. $filename .'.sql';
+  
+  // Build info filename.
+  $fileconfig['info'] = $filename .'.info';
+  $fileconfig['infofile'] = $fileconfig['path'] . $fileconfig['site'] .'/'. $filename .'.info';
+  
+  return $fileconfig;
+}
+
+function demo_get_dumps() {
+  $fileconfig = demo_get_fileconfig();
+  
+  // Fetch list of available info files
+  $files = file_scan_directory($fileconfig['dumppath'], '.info$');
+
+  foreach ($files as $file => $object) {
+    $files[$file]->filemtime = filemtime($file);
+    $files[$file]->filesize = filesize(substr($file, 0, -4) .'sql');
+  }
+
+  // Sort snapshots by date (ascending file modification time)
+  uasort($files, create_function('$a, $b', 'return ($a->filemtime < $b->filemtime);'));
+  
+  $options = array();
+  // Forms API does not pass selected value of individual radio buttons,
+  // so we manually insert an internal form value here.
+  $options['dump']['filename'] = array(
+    '#type' => 'value',
+    '#required' => true,
+    '#title' => t('Snapshot'),
+  );
+  foreach ($files as $filename => $file) {
+    // Build basic file info
+    $files[$filename] = (array)$files[$filename];
+    $info = demo_get_info($filename);
+    
+    // Convert file info for Forms API
+    $option = array();
+    $option['#type'] = 'radio';
+    $option['#name'] = 'filename';
+    $option['#title'] = $info['filename'] .' ('. format_date($file->filemtime, 'small') .', '. format_size($file->filesize) .')';
+    $option['#return_value'] = $info['filename'];
+    if ($info['filename'] == variable_get('demo_dump_cron', 'demo_site')) {
+      $option['#value'] = $info['filename'];
+    }
+    $option['#description'] = '';
+    if (!empty($info['description'])) {
+      $option['#description'] .= $info['description'] .'<br /><br />';
+    }
+    if (count($info['modules']) > 1) {
+      // Remove required core modules and obvious modules from module list.
+      $info['modules'] = array_diff($info['modules'], array('block', 'filter', 'node', 'system', 'user', 'watchdog', 'demo'));
+      // Sort module list alphabetically.
+      sort($info['modules']);
+      $option['#description'] .= t('Modules: ') . implode(', ', $info['modules']);
+    }
+    $option['#attributes'] = array('onclick' => "$('.description', this.parentNode.parentNode).slideToggle();");
+    
+    $options['dump'][] = $option;
+  }
+  
+  // Attach stylesheet to initially hide descriptions
+  drupal_add_js("$('div.form-item div.description', $('form')).hide();", 'inline', 'footer');
+  
+  return $options;
+}
+
+function demo_get_info($filename, $field = NULL) {
+  $info = array();
+
+  if (file_exists($filename)) {
+    $info = parse_ini_file($filename);
+
+    if (isset($info['modules'])) {
+      $info['modules'] = explode(" ", $info['modules']);
+    }
+    else {
+      $info['modules'] = null;
+    }
+
+    if (!isset($info['version'])) {
+      $info['version'] = '1.0';
+    }
+  }
+
+  if (isset($field)) {
+    return isset($info[$field]) ? $info[$field] : NULL;
+  }
+  else {
+    return $info;
+  }
+}
+
+function demo_set_info($values = null) {
+  if (isset($values['filename']) && is_array($values)) {
+    // Check for valid filename
+    if (!preg_match('/^[-_\.a-zA-Z0-9]+$/', $values['filename'])) {
+      drupal_set_message(t('Dump filename %title must contain alphanumeric characters, dots, dashes and underscores only. Other characters, including blanks (spaces), are not allowed.', array('%title' => $values['filename'])), 'error');
+      return false;
+    }
+
+    if (!empty($values['description'])) {
+      // parse_ini_file() doesn't allow certain characters in description
+      $s = array("\r\n", "\r", "\n", '"');
+      $r = array(' ', ' ', ' ', "'");
+      $values['description'] = str_replace($s, $r, $values['description']);
+    }
+    else {
+      // If new description is empty, try to use previous description.
+      $old_file = demo_get_fileconfig($values['filename']);
+      $old_description = demo_get_info($old_file['infofile'], 'description');
+      if (!empty($old_description)) {
+        $values['description'] = $old_description;
+      }
+    }
+    
+    // Set values
+    $infos = array();
+    $infos['filename'] = $values['filename'];
+    $infos['description'] = '"'. $values['description'] .'"';
+    $infos['modules'] = implode(' ', module_list());
+    $infos['version'] = DEMO_DUMP_VERSION;
+
+    // Write information to .info file
+    $fileconfig = demo_get_fileconfig($values['filename']);
+    $infofile = fopen($fileconfig['infofile'], 'w');
+    foreach ($infos as $key => $info) {
+      fwrite($infofile, $key .' = '. $info ."\n");
+    }
+    fclose($infofile);
+    
+    return $infos;
+  }
+}
+
+/**
+ * Returns a list of tables in the active database.
+ *
+ * Only returns tables whose prefix matches the configured one (or ones, if
+ * there are multiple).
+ */
+function demo_enum_tables() {
+  global $db_prefix;
+
+  $tables = array();
+
+  if (is_array($db_prefix)) {
+    // Create a regular expression for table prefix matching.
+    $rx = '/^'. implode('|', array_filter($db_prefix)) .'/';
+  }
+  else if ($db_prefix != '') {
+    $rx = '/^'. $db_prefix .'/';
+  }
+
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      $result = db_query("SHOW TABLES");
+      break;
+    case 'pgsql':
+      $result = db_query("SELECT table_name FROM information_schema.tables WHERE table_schema = '%s'", 'public');
+      break;
+  }
+
+  while ($table = db_fetch_array($result)) {
+    $table = reset($table);
+    if (is_array($db_prefix)) {
+      // Check if table name matches a configured prefix.
+      if (preg_match($rx, $table, $matches)) {
+        $table_prefix = $matches[0];
+        $plain_table = substr($table, strlen($table_prefix));
+        if ($db_prefix[$plain_table] == $table_prefix || $db_prefix['default'] == $table_prefix) {
+          $tables[] = $table;
+        }
+      }
+    }
+    else if ($db_prefix != '') {
+      if (preg_match($rx, $table)) {
+        $tables[] = $table;
+      }
+    }
+    else {
+      $tables[] = $table;
+    }
+  }
+
+  return $tables;
+}
+
+/**
+ * Retrieve a pipe delimited string of autocomplete suggestions for existing
+ * snapshot names.
+ */
+function demo_autocomplete($string = '') {
+  $matches = array();
+  if ($string && $fileconfig = demo_get_fileconfig()) {
+    $string = preg_quote($string);
+    $files = file_scan_directory($fileconfig['dumppath'], $string .'.*\.info$');
+    foreach ($files as $file) {
+      $matches[$file->name] = check_plain($file->name);
+   }
+  }
+  print drupal_to_js($matches);
+  exit();
+}
+
Index: demo.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/demo/demo.module,v
retrieving revision 1.19.2.7
diff -u -p -r1.19.2.7 demo.module
--- demo.module	12 Sep 2008 20:45:22 -0000	1.19.2.7
+++ demo.module	28 Oct 2008 18:33:07 -0000
@@ -6,18 +6,6 @@
  * Demonstration Site module
  */
 
-define('DEMO_DUMP_VERSION', '1.1');
-
-/**
- * Implementation of hook_help().
- */
-function demo_help($path, $arg) {
-  switch ($path) {
-    case 'admin/settings/demo':
-      return;
-  }
-}
-
 /**
  * Implementation of hook_perm().
  */
@@ -38,6 +26,7 @@ function demo_menu() {
     'page callback' => 'drupal_get_form',
     'page arguments' => array('demo_admin_settings'),
     'access arguments' => $admin_access,
+    'file' => 'demo.admin.inc',
   );
   $items['admin/settings/demo/maintenance'] = array(
     'title' => 'Status',
@@ -49,6 +38,7 @@ function demo_menu() {
     'page callback' => 'drupal_get_form',
     'page arguments' => array('demo_manage'),
     'access arguments' => $admin_access,
+    'file' => 'demo.admin.inc',
     'type' => MENU_LOCAL_TASK,
     'weight' => 1,
   );
@@ -57,6 +47,7 @@ function demo_menu() {
     'page callback' => 'drupal_get_form',
     'page arguments' => array('demo_dump'),
     'access arguments' => $admin_access,
+    'file' => 'demo.admin.inc',
     'type' => MENU_LOCAL_TASK,
     'weight' => 2,
   );
@@ -65,6 +56,7 @@ function demo_menu() {
     'page callback' => 'drupal_get_form',
     'page arguments' => array('demo_reset_confirm'),
     'access arguments' => $admin_access,
+    'file' => 'demo.admin.inc',
     'type' => MENU_LOCAL_TASK,
     'weight' => 3,
   );
@@ -72,20 +64,20 @@ function demo_menu() {
     'title' => 'Demo Site autocomplete',
     'page callback' => 'demo_autocomplete',
     'access arguments' => $admin_access,
+    'file' => 'demo.admin.inc',
     'type' => MENU_CALLBACK,
   );
   return $items;
 }
 
+/**
+ * Implementation of hook_block().
+ */
 function demo_block($op = 'list', $delta = 0, $edit = array()) {
   switch ($op) {
     case 'list':
-      $blocks[0] = array(
-        'info' => t('Demo site reset'),
-        'weight' => 0,
-        'enabled' => 1,
-        'region' => 'right',
-      );
+      $blocks[0] = array('info' => t('Demo site reset'),
+        'status' => 1, 'region' => 'right', 'cache' => BLOCK_NO_CACHE);
       return $blocks;
     
     case 'view':
@@ -97,184 +89,7 @@ function demo_block($op = 'list', $delta
   }
 }
 
-function demo_admin_settings() {
-  global $base_url;
-  
-  $form['status'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Status'),
-    '#collapsible' => false,
-  );
-  if (variable_get('demo_reset_last', 0)) {
-    $reset_date = format_date(variable_get('demo_reset_last', 0));
-  }
-  else {
-    $reset_date = t('Never');
-  }
-  $form['status'][] = array(
-    '#value' => t('<p><strong>Last reset:</strong> !date</p>', array('!date' => $reset_date)),
-  );
-  $form['status'][] = array(
-    '#value' => t('<p><strong>Default snapshot:</strong> !snapshot</p>', array('!snapshot' => variable_get('demo_dump_cron', t('None')))),
-  );
-  
-  $fileconfig = demo_get_fileconfig();
-  
-  $form['dump'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Dump settings'),
-    '#collapsible' => true,
-    '#collapsed' => (variable_get('demo_reset_interval', 0) ? false : true),
-  );
-  $period = drupal_map_assoc(array(0, 1800, 3600, 7200, 10800, 14400, 18000, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
-  $period[0] = t('disabled');
-  $form['dump']['interval'] = array(
-    '#type' => 'select',
-    '#title' => t('Automatically reset site every'),
-    '#default_value' => variable_get('demo_reset_interval', 0),
-    '#options' => $period,
-    '#description' => t('Select how often this demonstration site is automatically reset. Ensure that you have chosen a snapshot for cron runs in <a href="!manage">Manage snapshots</a> first. <strong>Note:</strong> This requires cron to run at least within this interval.', array('!manage' => url('admin/settings/demo/manage'))),
-  );
-  
-  $form['dump']['path'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Dump path'),
-    '#default_value' => $fileconfig['path'],
-    '#size' => 30,
-    '#description' => t('Enter a writable directory where dump files of this demonstration site are stored, f.e. %files. The name of this site (e.g. %confpath) is automatically appended to this directory.<br /><br /><strong>Note: For security reasons you should store site dumps outside of the document root of your webspace!</strong>', array('%files' => file_directory_path() .'/demo', '%confpath' => $fileconfig['site'])),
-  );
-  $form[] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-  );
-  
-  return $form;
-}
-
-function demo_admin_settings_submit($form, &$form_state) {
-  if (!file_check_directory($form_state['values']['path'], FILE_CREATE_DIRECTORY)) {
-    form_set_error('path', t('The snapshot directory %directory could not be created.', array('%directory' => $form_state['values']['path'])));
-    return FALSE;
-  }
-  else {
-    variable_set('demo_dump_path', $form_state['values']['path']);
-  }
-  variable_set('demo_reset_interval', $form_state['values']['interval']);
-  drupal_set_message(t('The configuration options have been saved.'));
-}
-
-function demo_manage() {
-  $form['dump'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Available snapshots'),
-  );
-  $form = array_merge_recursive($form, demo_get_dumps());
-  $form[] = array(
-    '#type' => 'submit',
-    '#value' => t('Set as default snapshot for cron'),
-  );
-  $form[] = array(
-    '#type' => 'submit',
-    '#value' => t('Delete selected snapshot'),
-    '#submit' => array('demo_manage_delete_submit'),
-  );
-  
-  return $form;
-}
-
-function demo_manage_submit($form, &$form_state) {
-  variable_set('demo_dump_cron', $form_state['values']['filename']);
-  drupal_set_message(t('Snapshot %title will be used for upcoming cron runs.', array('%title' => $form_state['values']['filename'])));
-}
-
-function demo_manage_delete_submit($form, &$form_state) {
-  $files = demo_get_fileconfig($form_state['values']['filename']);
-  unlink($files['sqlfile']);
-  unlink($files['infofile']);
-  drupal_set_message(t('Snapshot %title has been deleted.', array('%title' => $form_state['values']['filename'])));
-}
-
-function demo_dump() {
-  $form = array();
-  $form['dump']['filename'] = array(
-    '#title' => t('File name'),
-    '#type' => 'textfield',
-    '#autocomplete_path' => 'demo/autocomplete',
-    '#required' => true,
-    '#maxlength' => 128,
-    '#description' => t('Enter the snapshot file name without file extension. Allowed characters are a-z, 0-9, dashes ("-"), underscores ("_") and dots.'),
-  );
-  $form['dump']['description'] = array(
-    '#title' => t('Description'),
-    '#type' => 'textarea',
-    '#rows' => 2,
-    '#description' => t('Optionally enter a description for this snapshot here. If no description is given and a snapshot with the same filename already exists, the previous description is used.'),
-  );
-  return confirm_form($form, t('Are you sure you want to create a new snapshot?'), 'admin/settings/demo', t('If the above filename already exists, creating a new snapshot will overwrite the existing snapshot. This action cannot be undone.'), t('Create'), t('Cancel'));
-}
-
-function demo_dump_submit($form, &$form_state) {
-  global $db_type;
-
-  // Write .info file
-  $info = demo_set_info($form_state['values']);
-  if (!$info) {
-    return false;
-  }
-  
-  // Include database specific functions
-  switch ($db_type) {
-    case 'mysqli':
-      $engine = 'mysql';
-      break;
-   default:
-      $engine = $db_type;
-      break;
-  }
-  require_once drupal_get_path('module', 'demo') ."/database_{$engine}_dump.inc";
-
-  // Increase PHP's max_execution_time for large dumps.
-  @set_time_limit(600);
-
-  // Perform dump
-  $fileconfig = demo_get_fileconfig($info['filename']);
-  $exclude = array('{cache}', '{cache_content}', '{cache_filter}', '{cache_menu}', '{cache_page}', '{cache_views}', '{panels_object_cache}', '{watchdog}');
-  $exclude = array_map('db_prefix_tables', $exclude);
-  demo_dump_db($fileconfig['sqlfile'], $exclude);
-  
-  $form_state['redirect'] = 'admin/settings/demo/manage';
-}
-
-function demo_reset_confirm() {
-  $form['dump'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Available snapshots'),
-  );
-  $form = array_merge_recursive($form, demo_get_dumps());
-  
-  return confirm_form($form, t('Are you sure you want to reset the site?'), 'admin/settings/demo', t('Resetting the site will overwrite all changes that have been made to this Drupal installation since the chosen snapshot.<br /><br /><div style="color: red; font-weight: bold; font-size: 18px;"><center>THIS ACTION CANNOT BE UNDONE!</center><br /></div>'), t('Reset'), t('Cancel'));
-}
-
-function demo_reset_confirm_submit($form, &$form_state) {
-  // Increase PHP's max_execution_time for large dumps.
-  @set_time_limit(600);
-
-  // Reset site to chosen snapshot
-  demo_reset($form_state['values']['filename']);
-  // Save time of last reset
-  variable_set('demo_reset_last', time());
-  
-  if (isset($form_state['values']['redirect'])) {
-    $form_state['redirect'] = $form_state['values']['redirect'];
-  }
-  else {
-    $form_state['redirect'] = 'admin/settings/demo';
-  }
-}
-
 function demo_reset_now() {
-  $form = array();
-  $form['#submit'][] = 'demo_reset_confirm_submit';
   $form['redirect'] = array(
     '#type' => 'value',
     '#value' => $_GET['q'],
@@ -290,284 +105,12 @@ function demo_reset_now() {
     '#type' => 'submit',
     '#value' => t('Reset now'),
   );
-  
   return $form;
 }
 
-function demo_reset($filename = 'demo_site', $verbose = TRUE) {
-  // Load any database information in front of reset.
-  $demo_dump_cron = variable_get('demo_dump_cron', $filename);
-  $fileconfig = demo_get_fileconfig($filename);
-  $version = demo_get_info($fileconfig['infofile'], 'version');
-  $is_version_1_0_dump = version_compare($version, '1.1', '<');
-
-  if (file_exists($fileconfig['sqlfile']) && $fp = fopen($fileconfig['sqlfile'], 'r')) {
-    // Drop tables
-    $dt_watchdog = db_prefix_tables('{watchdog}');
-    foreach (demo_enum_tables() as $table) {
-      // Skip watchdog, except for legacy dumps that included the watchdog table
-      if ($table != $dt_watchdog || $is_version_1_0_dump) {
-        db_query("DROP TABLE %s", $table);
-      }
-    }
-
-    // Load data from snapshot
-    $query = '';
-    $success = TRUE;
-    while (!feof($fp)) {
-      $line = fgets($fp, 16384);
-      if ($line && $line != "\n" && strncmp($line, '--', 2) && strncmp($line, '#', 1)) {
-        $query .= $line;
-        if (substr($line, -2) == ";\n") {
-          if (!_db_query($query, FALSE)) {
-            if ($verbose) {
-              // Don't use t() here, as the locale_* tables might not (yet) exist.
-              drupal_set_message(strtr('Query failed: %query', array('%query' => $query)), 'error');
-            }
-            $success = FALSE;
-          }
-          $query = '';
-        }
-      }
-    }
-    fclose($fp);
-
-    if ($success) {
-      $message = t('Successfully restored database from %filename.', array('%filename' => $fileconfig['sqlfile']));
-    }
-    else {
-      $message = t('Failed restoring database from %filename.', array('%filename' => $fileconfig['sqlfile']));
-    }
-    if ($verbose) {
-      drupal_set_message($message);
-    }
-    watchdog('demo', $message, array(), $success ? WATCHDOG_NOTICE : WATCHDOG_ERROR);
-
-    // Reset default dump to load on cron.
-    variable_set('demo_dump_cron', $demo_dump_cron);
-    return TRUE;
-  }
-  $message = t('Unable to open dump file %filename.', array('%filename' => $fileconfig['sqlfile']));
-  if ($verbose) {
-    drupal_set_message($message, 'error');
-  }
-  watchdog('demo', $message, array(), WATCHDOG_ERROR);
-  return FALSE;
-}
-
-function demo_get_fileconfig($filename = 'demo_site') {
-  $fileconfig = array();
-  
-  // Build dump path.
-  $fileconfig['path'] = variable_get('demo_dump_path', file_directory_path() .'/demo');
-  $fileconfig['site'] = str_replace('sites', '', conf_path());
-  $fileconfig['dumppath'] = $fileconfig['path'] . $fileconfig['site'];
-  
-  // Check if directory exists.
-  file_check_directory($fileconfig['path'], FILE_CREATE_DIRECTORY, 'path');
-  if (!file_check_directory($fileconfig['dumppath'], FILE_CREATE_DIRECTORY, 'path')) {
-    return false;
-  }
-  
-  // Protect dump files.
-  $htaccess = $fileconfig['path'] ."/.htaccess";
-  if (!is_file($htaccess)) {
-    $htaccess_lines = "# demo.module snapshots\n# Do not let the webserver serve anything under here!\n#\nDeny from all\n";
-    if (($fp = fopen($htaccess, 'w')) && fputs($fp, $htaccess_lines)) {
-      fclose($fp);
-      chmod($htaccess, 0664);
-    }
-  }
-    
-  // Build SQL filename.
-  $fileconfig['sql'] = $filename .'.sql';
-  $fileconfig['sqlfile'] = $fileconfig['path'] . $fileconfig['site'] .'/'. $filename .'.sql';
-  
-  // Build info filename.
-  $fileconfig['info'] = $filename .'.info';
-  $fileconfig['infofile'] = $fileconfig['path'] . $fileconfig['site'] .'/'. $filename .'.info';
-  
-  return $fileconfig;
-}
-
-function demo_get_dumps() {
-  $fileconfig = demo_get_fileconfig();
-  
-  // Fetch list of available info files
-  $files = file_scan_directory($fileconfig['dumppath'], '.info$');
-
-  foreach ($files as $file => $object) {
-    $files[$file]->filemtime = filemtime($file);
-    $files[$file]->filesize = filesize(substr($file, 0, -4) .'sql');
-  }
-
-  // Sort snapshots by date (ascending file modification time)
-  uasort($files, create_function('$a, $b', 'return ($a->filemtime < $b->filemtime);'));
-  
-  $options = array();
-  // Forms API does not pass selected value of individual radio buttons,
-  // so we manually insert an internal form value here.
-  $options['dump']['filename'] = array(
-    '#type' => 'value',
-    '#required' => true,
-    '#title' => t('Snapshot'),
-  );
-  foreach ($files as $filename => $file) {
-    // Build basic file info
-    $files[$filename] = (array)$files[$filename];
-    $info = demo_get_info($filename);
-    
-    // Convert file info for Forms API
-    $option = array();
-    $option['#type'] = 'radio';
-    $option['#name'] = 'filename';
-    $option['#title'] = $info['filename'] .' ('. format_date($file->filemtime, 'small') .', '. format_size($file->filesize) .')';
-    $option['#return_value'] = $info['filename'];
-    if ($info['filename'] == variable_get('demo_dump_cron', 'demo_site')) {
-      $option['#value'] = $info['filename'];
-    }
-    $option['#description'] = '';
-    if (!empty($info['description'])) {
-      $option['#description'] .= $info['description'] .'<br /><br />';
-    }
-    if (count($info['modules']) > 1) {
-      // Remove required core modules and obvious modules from module list.
-      $info['modules'] = array_diff($info['modules'], array('block', 'filter', 'node', 'system', 'user', 'watchdog', 'demo'));
-      // Sort module list alphabetically.
-      sort($info['modules']);
-      $option['#description'] .= t('Modules: ') . implode(', ', $info['modules']);
-    }
-    $option['#attributes'] = array('onclick' => "$('.description', this.parentNode.parentNode).slideToggle();");
-    
-    $options['dump'][] = $option;
-  }
-  
-  // Attach stylesheet to initially hide descriptions
-  drupal_add_js("$('div.form-item div.description', $('form')).hide();", 'inline', 'footer');
-  
-  return $options;
-}
-
-function demo_get_info($filename, $field = NULL) {
-  $info = array();
-
-  if (file_exists($filename)) {
-    $info = parse_ini_file($filename);
-
-    if (isset($info['modules'])) {
-      $info['modules'] = explode(" ", $info['modules']);
-    }
-    else {
-      $info['modules'] = null;
-    }
-
-    if (!isset($info['version'])) {
-      $info['version'] = '1.0';
-    }
-  }
-
-  if (isset($field)) {
-    return isset($info[$field]) ? $info[$field] : NULL;
-  }
-  else {
-    return $info;
-  }
-}
-
-function demo_set_info($values = null) {
-  if (isset($values['filename']) && is_array($values)) {
-    // Check for valid filename
-    if (!preg_match('/^[-_\.a-zA-Z0-9]+$/', $values['filename'])) {
-      drupal_set_message(t('Dump filename %title must contain alphanumeric characters, dots, dashes and underscores only. Other characters, including blanks (spaces), are not allowed.', array('%title' => $values['filename'])), 'error');
-      return false;
-    }
-
-    if (!empty($values['description'])) {
-      // parse_ini_file() doesn't allow certain characters in description
-      $s = array("\r\n", "\r", "\n", '"');
-      $r = array(' ', ' ', ' ', "'");
-      $values['description'] = str_replace($s, $r, $values['description']);
-    }
-    else {
-      // If new description is empty, try to use previous description.
-      $old_file = demo_get_fileconfig($values['filename']);
-      $old_description = demo_get_info($old_file['infofile'], 'description');
-      if (!empty($old_description)) {
-        $values['description'] = $old_description;
-      }
-    }
-    
-    // Set values
-    $infos = array();
-    $infos['filename'] = $values['filename'];
-    $infos['description'] = '"'. $values['description'] .'"';
-    $infos['modules'] = implode(' ', module_list());
-    $infos['version'] = DEMO_DUMP_VERSION;
-
-    // Write information to .info file
-    $fileconfig = demo_get_fileconfig($values['filename']);
-    $infofile = fopen($fileconfig['infofile'], 'w');
-    foreach ($infos as $key => $info) {
-      fwrite($infofile, $key .' = '. $info ."\n");
-    }
-    fclose($infofile);
-    
-    return $infos;
-  }
-}
-
-/**
- * Returns a list of tables in the active database.
- *
- * Only returns tables whose prefix matches the configured one (or ones, if
- * there are multiple).
- */
-function demo_enum_tables() {
-  global $db_prefix;
-
-  $tables = array();
-
-  if (is_array($db_prefix)) {
-    // Create a regular expression for table prefix matching.
-    $rx = '/^'. implode('|', array_filter($db_prefix)) .'/';
-  }
-  else if ($db_prefix != '') {
-    $rx = '/^'. $db_prefix .'/';
-  }
-
-  switch ($GLOBALS['db_type']) {
-    case 'mysql':
-    case 'mysqli':
-      $result = db_query("SHOW TABLES");
-      break;
-    case 'pgsql':
-      $result = db_query("SELECT table_name FROM information_schema.tables WHERE table_schema = '%s'", 'public');
-      break;
-  }
-
-  while ($table = db_fetch_array($result)) {
-    $table = reset($table);
-    if (is_array($db_prefix)) {
-      // Check if table name matches a configured prefix.
-      if (preg_match($rx, $table, $matches)) {
-        $table_prefix = $matches[0];
-        $plain_table = substr($table, strlen($table_prefix));
-        if ($db_prefix[$plain_table] == $table_prefix || $db_prefix['default'] == $table_prefix) {
-          $tables[] = $table;
-        }
-      }
-    }
-    else if ($db_prefix != '') {
-      if (preg_match($rx, $table)) {
-        $tables[] = $table;
-      }
-    }
-    else {
-      $tables[] = $table;
-    }
-  }
-
-  return $tables;
+function demo_reset_now_submit($form, &$form_state) {
+  require_once drupal_get_path('module', 'demo') .'/demo.admin.inc';
+  demo_reset_confirm_submit($form, $form_state);
 }
 
 /**
@@ -575,27 +118,12 @@ function demo_enum_tables() {
  */
 function demo_cron() {
   if ($interval = variable_get('demo_reset_interval', 0)) {
-    // See if it's time for another reset
+    // See if it's time for a reset.
     if ((time() - $interval) >= variable_get('demo_reset_last', 0)) {
-      demo_reset(variable_get('demo_dump_cron', 'demo_site'), false);
+      require_once drupal_get_path('module', 'demo') .'/demo.admin.inc';
+      demo_reset(variable_get('demo_dump_cron', 'demo_site'), FALSE);
       variable_set('demo_reset_last', time());
     }
   }
 }
 
-/**
- * Retrieve a pipe delimited string of autocomplete suggestions for existing
- * snapshot names.
- */
-function demo_autocomplete($string = '') {
-  $matches = array();
-  if ($string && $fileconfig = demo_get_fileconfig()) {
-    $string = preg_quote($string);
-    $files = file_scan_directory($fileconfig['dumppath'], $string .'.*\.info$');
-    foreach ($files as $file) {
-      $matches[$file->name] = check_plain($file->name);
-   }
-  }
-  print drupal_to_js($matches);
-  exit();
-}
