<?php
// $Id: dba.module,v 1.59 2007/12/15 02:03:46 dww Exp $

/**
 * @file
 * Allows administrators direct access to their Drupal database.
 * Written by Jeremy Andrews <jeremy@kerneltrap.org>, June 2004.
 * PostgreSQL functionality provided by AAM <aam@ugpl.de>
 * Major security audit, porting, and maintenance by 
 *  Derek "dww" Wright (http://drupal.org/user/46549)
 */

define('DBA_BACKUP_EXCLUDE', 'accesslog, cache, search_index, search_total, watchdog');


// Standard Drupal functions.
function dba_perm() {
  return array('dba view database', 'dba administer database');
}

function dba_help($section = '') {
  switch ($section) {
    case 'admin/help#dba':
      $output .= t('The dba module allows site administrators a method for direct database administration. This is a dangerous module, in that it gives unlimited access and control over the active database. With this module, it is possible to corrupt or delete an entire drupal database. Use at your own risk.');
      break;
  }
  return $output;
}

function dba_menu($may_cache) {
  $items = array();
  $admin_access = user_access('dba administer database');
  $access = user_access('dba view database') || $admin_access;
  if ($may_cache) {
    // Provide menus to dbas with view permissions.
    $items[] = array(
      'path' => 'admin/build/database',
      'title' => t('Database'),
      'description' => t("View and edit your site's database directly."),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'dba_database_overview_form',
      'access' => $access,
    );
    // Tabs.
    $items[] = array(
      'path' => 'admin/build/database/table',
      'title' => t('Tables'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'dba_database_overview_form',
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/build/database/query',
      'title' => t('Query database'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('dba_query_form'),
      'access' => $admin_access,
      'type' => MENU_LOCAL_TASK,
      'weight' => 8,
    );
    $items[] = array(
      'path' => 'admin/build/database/script',
      'title' => t('Run script'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('dba_run_script'),
      'access' => $admin_access,
      'type' => MENU_LOCAL_TASK,
      'weight' => 10,
    );
    $items[] = array(
      'path' => 'admin/settings/dba',
      'title' => t('Database administration'),
      'description' => t('Control automatic backups and other settings.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('dba_settings_form'),
      'access' => $admin_access,
    );
  }
  elseif (strstr(drupal_get_path_alias($_GET['q']), 'admin/build/database')) {
    $tables = dba_get_active_tables(0);
    if (!empty($tables) && count($tables) == 1) {
      // You can only view or describe one table at a time.
      $table = reset($tables);

      // Regular subtabs.
      $items[] = array(
        'path' => "admin/build/database/table/$table/view",
        'title' => t('View'),
        'callback' => 'dba_admin_tables_view',
        'access' => $access,
        'type' => MENU_LOCAL_TASK,
        'weight' => 0,
      );
      $items[] = array(
        'path' => "admin/build/database/table/$table/describe",
        'title' => t('Describe'),
        'callback' => 'dba_admin_tables_describe',
        'access' => $access,
        'type' => MENU_LOCAL_TASK,
        'weight' => 2,
      );
      if (_is_mysql()) {
        $items[] = array(
          'path' => "admin/build/database/table/$table/check",
          'title' => t('Check'),
          'callback' => 'dba_admin_tables_check',
          'access' => $access,
          'type' => MENU_LOCAL_TASK,
          'weight' => 4,
        );
        $items[] = array(
          'path' => "admin/build/database/table/$table/optimize",
          'title' => t('Optimize'),
          'callback' => 'dba_admin_tables_optimize',
          'access' => $access,
          'type' => MENU_LOCAL_TASK,
          'weight' => 4,
        );
      }
      // Subtabs for dbas with administer permissions.
      $items[] = array(
        'path' => "admin/build/database/table/$table/backup",
        'title' => t('Backup'),
        'callback' => 'dba_admin_tables_verify_op',
        'callback arguments' => array('backup'),
        'access' => $admin_access,
        'type' => MENU_LOCAL_TASK,
        'weight' => 8,
      );
      $items[] = array(
        'path' => "admin/build/database/table/$table/empty",
        'title' => t('Empty'),
        'callback' => 'dba_admin_tables_verify_op',
        'callback arguments' => array('empty'),
        'access' => $admin_access,
        'type' => MENU_LOCAL_TASK,
        'weight' => 8,
      );
      $items[] = array(
        'path' => "admin/build/database/table/$table/drop",
        'title' => t('Drop'),
        'callback' => 'dba_admin_tables_verify_op',
        'callback arguments' => array('drop'),
        'access' => $admin_access,
        'type' => MENU_LOCAL_TASK,
        'weight' => 10,
      );
    }
    // Administrative callbacks.
    $items[] = array(
      'path' => "admin/build/database/backup",
      'title' => t('Backup'),
      'callback' => 'dba_admin_tables_verify_op',
      'callback arguments' => array('backup'),
      'access' => $admin_access,
      'weight' => 15,
      'type' => (arg(3) == 'backup' ? MENU_LOCAL_TASK : MENU_CALLBACK),
    );
    $items[] = array(
      'path' => "admin/build/database/empty",
      'title' => t('Empty'),
      'callback' => 'dba_admin_tables_verify_op',
      'callback arguments' => array('empty'),
      'access' => $admin_access,
      'weight' => 15,
      'type' => (arg(3) == 'empty' ? MENU_LOCAL_TASK : MENU_CALLBACK),
    );
    $items[] = array(
      'path' => "admin/build/database/drop",
      'title' => t('Drop'),
      'callback' => 'dba_admin_tables_verify_op',
      'callback arguments' => array('drop'),
      'access' => $admin_access,
      'weight' => 15,
      'type' => (arg(3) == 'drop' ? MENU_LOCAL_TASK : MENU_CALLBACK),
    );
    if (_is_mysql()) {
      $items[] = array(
        'path' => "admin/build/database/check",
        'title' => t('Check'),
        'callback' => 'dba_admin_tables_check',
        'access' => $admin_access,
        'weight' => 15,
        'type' => (arg(3) == 'check' ? MENU_LOCAL_TASK : MENU_CALLBACK),
      );
      $items[] = array(
        'path' => "admin/build/database/optimize",
        'title' => t('Optimize'),
        'callback' => 'dba_admin_tables_optimize',
        'access' => $admin_access,
        'weight' => 15,
        'type' => (arg(3) == 'optimize' ? MENU_LOCAL_TASK : MENU_CALLBACK),
      );
    }
  }
  return $items;
}

function dba_settings_form() {
  if (!user_access('dba administer database')) {
    drupal_access_denied();
    module_invoke_all('exit');
    exit;
  }
  // Backups
  $form['backup'] = array(
    '#type' => 'fieldset',
    '#title' => t('Database backups'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['backup']['dba_default_filename'] = array(
    '#type' => 'textfield',
    '#title' => t('Default backup filename'),
    '#description' => t('Default filename to use when backing up multiple tables. If backing up only one table, the filename will default to the name of the table. You will have an opportunity to modify this filename when you actually perform the backup. If automatically backing up tables, the name will be prepended with the current date and time.'),
    '#default_value' => variable_get('dba_default_filename', 'backup.sql'),
    '#size' => 30,
    '#maxlength' => 64,
  );

  $period = drupal_map_assoc(array(0, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
  $period[0] = t('disabled');
  $form['backup']['dba_auto_backup_interval'] = array(
    '#type' => 'select',
    '#title' => t('Automatically backup database every'),
    '#default_value' => variable_get('dba_auto_backup_interval', 0),
    '#options' => $period,
    '#description' => t('Select how often you wish to have your database automatically backed up. Requires crontab.'),
  );
  $backup_interval = variable_get('dba_auto_backup_interval', 0);
  $backup_path = variable_get('dba_auto_backup_path', file_directory_temp());
  $backup_exclude = variable_get('dba_auto_backup_exclude_tables', DBA_BACKUP_EXCLUDE);

  if ($backup_interval) {
    $attributes = array('enabled' => 'enabled');
  }
  else {
    $attributes = array('disabled' => 'disabled');
  }
  $form['backup']['dba_auto_backup_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Automatic backup path'),
    '#default_value' => $backup_path,
    '#size' => 30,
    '#maxlength' => 255,
    '#description' => t('If automatic backups are enabled, you must specify a directory where you would like to store the backup files.  The path must be absolute and for security reasons should not be accesible to the web.'),
    '#attributes' => $attributes,
  );

  if (function_exists('bzcompress')) {
    $form['backup']['dba_auto_backup_bzip2'] = array(
      '#type' => 'checkbox',
      '#title' => t('Compress automatic backups'),
      '#return_value' => 1,
      '#default_value' => variable_get('dba_auto_backup_bzip2', 0),
      '#description' => t('Enable this option to compress automatic backups with <a href="http://sources.redhat.com/bzip2/">bzip2</a>.'),
      '#attributes' => $attributes,
    );
  }
  else if (function_exists('gzencode')) {
    $form['backup']['dba_auto_backup_gzip'] = array(
      '#type' => 'checkbox',
      '#title' => t('Compress automatic backups'),
      '#return_value' => 1,
      '#default_value' => variable_get('dba_auto_backup_gzip', 0),
      '#description' => t('Enable this option to compress automatic backups with <a href="http://www.gzip.org/zlib/">zlib</a>.'),
      '#attributes' => $attributes,
    );
  }
  $form['backup']['dba_auto_backup_mail'] = array(
    '#type' => 'checkbox',
    '#title' => t('Mail backup to administrator'),
    '#return_value' => 1,
    '#default_value' => variable_get('dba_auto_backup_mail', 0),
    '#description' => t("Enable this option to have a copy of the database backup files mailed to your administrator's email address."),
    '#attributes' => $attributes,
  );
  $form['backup']['dba_auto_backup_exclude_tables'] = array(
    '#type' => 'textfield',
    '#title' => t('Automatic backup excluded tables'),
    '#default_value' => $backup_exclude,
    '#description' => t("If automatic backups are enabled, you can specify a space-separated list of table names where you only want the table definition (schema) backed up, but not the actual data.  This is useful for tables that can be rebuilt (such as the tables related to search indexing) or the watchdog table, which holds log events but no actual site content.  Only saving the schema and not the data for these tables can greatly reduce the size of the backups, without losing real content."),
    '#attributes' => $attributes,
  );
  
  // MySQL
  if (_is_mysql()) {
    $form['mysql_options'] = array(
      '#type' => 'fieldset',
      '#title' => t('MySQL options'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );
    $form['mysql_options']['dba_default_check_type'] = array(
      '#type' => 'radios',
      '#title' => t('Default check type'),
      '#description' => t('MySQL databases support many types of database integrity checks. Select your preferred default type from the list above. Medium is the MySQL recommended default type.'),
      '#default_value' => variable_get('dba_default_check_type', 'MEDIUM'),
      '#options' => array('QUICK' => t('Quick'), 'FAST' => t('Fast'), 'CHANGED' => t('Changed'), 'MEDIUM' => t('Medium'), 'EXTENDED' => t('Extended'))
    );
    $form['mysql_options']['dba_repair'] = array(
      '#type' => 'radios',
      '#title' => t('Repair option'),
      '#description' => t('By default, the dba module will only display a repair button if a table has been determined to need a repair. Alternatively, you can make the module always display a repair button, or never display a repair button.'),
      '#default_value' => variable_get('dba_repair', 0),
      '#options' => array(
        '0' => t('Automatic'),
        '1' => t('Always'),
        '2' => t('Never'),
      ),
    );
  }
  // Add a validation callback to make sure the backup path is writable.
  $setting_valid = array('dba_settings_validate' => array());
  $form['#validate'] = isset($form['#validate']) ? array_merge($form['#validate'], $setting_valid) : $setting_valid;
  return system_settings_form($form);
}

function dba_settings_validate($form_id, $form_values, $form) {
  if (!file_check_directory($form_values['dba_auto_backup_path'])) {
    form_set_error('dba_auto_backup_path', t('The automatic backup path does not exist, or is not writeable. Automatic backups will not begin until you fix this error.'));
  }
  elseif ($test = tempnam($form_values['dba_auto_backup_path'], 'dba.')) {
    file_delete($test);
  }
  else {
    form_set_error('dba_auto_backup_path', t('The automatic backup path exists, but is not writeable. Automatic backups will not begin until you fix this error.'));
  }
}

function dba_cron() {
  if ($interval = variable_get('dba_auto_backup_interval', 0)) {
    // See if it's time for another auto-backup.
    if ((time() - $interval) >= variable_get('dba_auto_backup_last', 0)) {
      dba_auto_backup();
    }
  }
}

function dba_auto_backup() {
  $backup_started = time();
  $path = variable_get('dba_auto_backup_path', file_directory_temp());
  // See what tables (if any) the admin wants us to only backup
  // the schema, not the actual data.  we need it as an array, so we
  // lookup the setting as a string, then split() it into an array.
  $exclude_tables_str = variable_get('dba_auto_backup_exclude_tables', DBA_BACKUP_EXCLUDE);
  $exclude_tables = split('[ ,]', $exclude_tables_str);
  // Make sure we have permission to save our backup file.
  if (file_check_directory($path, FILE_CREATE_DIRECTORY)) {
    $database = dba_get_database();
    $filename = format_date(time(), 'custom', 'Y-md-Hi_') . variable_get('dba_default_filename', 'backup.sql');

    $backup  = "-- Drupal dba.module database dump\n";
    $backup .= "--\n";
    $backup .= "-- Database: $database\n";
    $backup .= "-- Date: ". format_date(time(), 'large') ."\n\n";

    $tables = dba_get_tables();
    foreach ($tables as $table) {
      $backup .= dba_backup_table($table, TRUE, FALSE, in_array($table, $exclude_tables) ? FALSE : TRUE);
    }

    // Optionally bzip2 compress auto-backup file.
    if (variable_get('dba_auto_backup_bzip2', 0)) {
      $backup = bzcompress($backup, 9);
      $filename = $filename .'.bz2';
    }
    // Otherwise, optionally gzip compress auto-backup file.
    else if (variable_get('dba_auto_backup_gzip', 0)) {
      if (version_compare(phpversion(), '4.2', '>=')) {
        $backup = gzencode($backup, 9, FORCE_GZIP);
      }
      else {
        $backup = gzencode($backup, FORCE_GZIP);
      }
      $filename = $filename .'.gz';
    }

    if ($fp = fopen($path ."/$filename", 'wb')) {
      fwrite($fp, $backup);
      fclose($fp);
      variable_set('dba_auto_backup_last', $backup_started);

      // If enabled, email a copy of the backup to the site administrator.
      if (variable_get('dba_auto_backup_mail', 0)) {
        $attachment = new stdClass();
        $attachment->path = $path ."/$filename";
        $attachment->filename = $filename;
        dba_mail_backup($attachment);
      }
    }
  }
}

/**
 * Display the contents of the selected table.
 */
function dba_admin_tables_view() {
  if (user_access('dba administer database') && arg(6) && arg(7) && arg(8)) {
    switch (arg(6)) {
      case 'delete':
        return drupal_get_form('dba_delete_row', arg(4), arg(7), arg(8));
      case 'edit':
        return drupal_get_form('dba_edit_row', arg(4), arg(7), arg(8));
    }
  }
  else {
    return dba_table_overview(arg(4));
  }
}

/**
 * Describe the schema of the selected table.
 */
function dba_admin_tables_describe() {
  $output = '';
  if (user_access('dba view database')) {
    $output = dba_table_describe(arg(4));
  }
  print theme('page', $output);
}

/**
 * MySQL only: check the selected table(s).
 */
function dba_admin_tables_check() {
  return drupal_get_form('dba_check_tables');
}

/**
 * MySQL only: optimize the selected table(s).
 */
function dba_admin_tables_optimize() {
  $output = '';
  if (user_access('dba administer database')) {
    $output = dba_tables_optimize();
    unset($_SESSION['dba_tables']);
  }
  print theme('page', $output);
}

/**
 * Menu callback to verify the administrator wants to backup, empty or
 * drop the selected table(s) by means of a confirm form.
 */
function dba_admin_tables_verify_op($op) {
  $tables = dba_get_active_tables(0);
  unset($_SESSION['dba_tables']);
  return drupal_get_form('dba_verify', $tables, $op);
}

function theme_dba_database_overview_form($form) {
  $output = '';
  $rows = array();
  $database = dba_get_database();
  drupal_set_title(t('View database %database', array('%database' => $database)));

  // It'd be great to use the pager and tablesort, but doesn't appear possible.
  $header = array('', t('Tables'), t('Rows'));
  $tables = dba_get_tables();
  foreach ($tables as $table) {
    $count = dba_get_row_count($table);
    $checkbox = drupal_render($form['tables'][$table]);
    $rows[] = array($checkbox, l($table, "admin/build/database/table/$table/view"), $count);
  }
  $output .= dba_select_all_js();
  $output .= theme('table', $header, $rows);
  $output .= dba_select_all_js();
  $output .= drupal_render($form);
  drupal_set_html_head(checkoff_head());
  return $output;
}

function dba_database_overview_form() {
  $tables = dba_get_tables();
  $form = array();
  $form['#tree'] = TRUE;
  $form['tables'] = array();
  foreach ($tables as $table) {
    $form['tables'][$table] = array(
      '#type' => 'checkbox',
      '#title' => '',
      '#default_value' => 0,
    );
  }
  if (_is_mysql()) {
    $form['check'] = array('#type' => 'submit', '#value' => t('Check'));
    $form['optimize'] = array('#type' => 'submit', '#value' => t('Optimize'));
  }
  if (user_access('dba administer database')) {
    $form['backup'] = array('#type' => 'submit', '#value' => t('Backup'));
    $form['empty'] = array('#type' => 'submit', '#value' => t('Empty'));
    $form['drop'] = array('#type' => 'submit', '#value' => t('Drop'));
  }
  return $form;
}

function dba_database_overview_form_validate($form_id, $form_values) {
  if (!array_filter($form_values['tables'])) {
    $op = isset($_POST['op']) ? $_POST['op'] : '';
    form_set_error('tables', t('You must select the table(s) to %op.', array('%op' => theme('placeholder', $op))));
  }
}

function dba_database_overview_form_submit($form_id, $form_values) {
  $op = isset($_POST['op']) ? $_POST['op'] : '';
  // We already validated the overview form, so we know we have tables.
  $form_tables = array_keys(array_filter($form_values['tables']));
  $_SESSION['dba_tables'] = dba_get_active_tables(0, $form_tables);

  switch ($op) {
    case t('Check'):
      return 'admin/build/database/check';

    case t('Optimize'):
      return 'admin/build/database/optimize';

    case t('Backup'):
      return 'admin/build/database/backup';

    case t('Empty'):
      return 'admin/build/database/empty';

    case t('Drop'):
      return 'admin/build/database/drop';
  }
}

function dba_select_all_js() {
  $output = "<a href=\"javascript:checkoff('dba-database-overview-form',1)\">";
  $output .= t('select all');
  $output .= "</a>&nbsp;&nbsp;|&nbsp;&nbsp;";
  $output .= "<a href=\"javascript:checkoff('dba-database-overview-form',0)\">";
  $output .= t('clear all');
  $output .= "</a><br>";
  return $output;
}

function dba_delete_row($table, $key, $keyid) {
  $rows = array();
  $keyid = str_replace('__2F_', '/', $keyid);
  $result = db_query("SELECT * FROM %s WHERE %s = '%s'", $table, $key, $keyid);
  $row = db_fetch_array($result);
  $rows[] = array_map('check_plain', (array)$row);
  $header = array_map('check_plain', array_keys($row));
  $form = array();
  $form['row'] = array('#value' => theme('table', $header, $rows));
  $form['table'] = array('#type' => 'hidden', '#value' => $table);
  $form['key'] = array('#type' => 'hidden', '#value' => $key);
  $form['keyid'] = array('#type' => 'hidden', '#value' => $keyid);
  $form = confirm_form($form,
    t('Are you sure you want to delete this row from the "%table" table?', array('%table' => $table)),
    "admin/build/database/table/$table/view",
    t('By clicking "Delete row" you will permanently remove this row from the %table table.  This action cannot be undone.', array('%table' => $table)),
    t('Delete row'),
    t('Cancel')
  );
  return $form;
}

function dba_edit_row($table, $key, $keyid) {
  $rows = array();
  $keyid = str_replace('__2F_', '/', $keyid);
  $result = db_query("SELECT * FROM %s WHERE %s = '%s'", $table, $key, $keyid);
  $row = db_fetch_array($result);
  $header = array_keys($row);
  foreach ($row as $k => $value) {
    if ($k == $key) {
      $form['key'] = array('#type' => 'markup', '#value' => $value);
    }
    else {
      // We store all fields in sub-array 'fields' to avoid naming collisions.
      $size = strlen($value);
      if ($size > 255) {
        $form['field'][$k] = array(
          '#type' => 'textarea',
          '#default_value' => $value,
          '#cols' => 70, '#rows' => 10,
        );
      }
      else {
        $form['field'][$k] = array(
          '#type' => 'textfield',
          '#default_value' => $value,
          '#size' => $size, '#maxlength' => 255,
        );
      }
    }
  }
  $form['header'] = array(
    '#type' => 'hidden',
    '#value' => implode(',', $header),
  );
  $form['table'] = array('#type' => 'hidden', '#value' => $table);
  $form['key'] = array('#type' => 'hidden', '#value' => $key);
  $form['keyid'] = array('#type' => 'hidden', '#value' => $keyid);
  return confirm_form($form,
    t('Edit row from the "%table" table', array('%table' => $table)),
    "admin/build/database/table/$table/view",
    t('By clicking "Edit row" you will save any changes you make to this row of the %table table.  This action cannot be undone.', array('%table' => $table)),
    t('Edit row'),
    t('Cancel')
  );
}

function theme_dba_edit_row($form) {
  $header = explode(',', $form['header']['#value']);
  $key = $form['key']['#value'];
  $keyid = $form['keyid']['#value'];
  $rows = array();
  $row = array();
  foreach ($header as $k => $name) {
    if ($name == $key) {
      $row[] = $keyid;
    }
    else {
      $row[] = drupal_render($form['field'][$name]);
    }
  }
  $rows[] = $row;
  $output = theme('table', $header, $rows);
  $output .= drupal_render($form);
  return $output;
}

function dba_edit_row_submit($form_id, $form_values) {
  if (user_access('dba administer database')) {
    $key = $form_values['key'];
    $keyid = $form_values['keyid'];
    $table = $form_values['table'];
    $fields = dba_get_fields($table);
    foreach ($fields as $field) {
      if ($field != $key) {
        $value = "{$form_values[$field]}";
        if (isset($query)) {
          $query .= ", $field = '". db_escape_string($value) ."'";
        }
        else {
          $query = "$field = '". db_escape_string($value) ."'";
        }
      }
    }
    // @todo Manual prefixing
    $query = "UPDATE $table SET $query WHERE $key = '$keyid'";
    drupal_set_message(check_plain($query));
    // Use _db_query so we preserve {}'s.
    _db_query($query);
  }
  return "admin/build/database/table/$table/view";
}

function dba_delete_row_submit($form_id, $form_values) {
  if (user_access('dba administer database')) {
    $key = $form_values['key'];
    $keyid = $form_values['keyid'];
    $table = $form_values['table'];
    $query = "DELETE FROM $table WHERE $key = '$keyid'";
    drupal_set_message(check_plain($query));
    $query = "DELETE FROM %s WHERE %s = '%s'";
    db_query($query, $table, $key, $keyid);
  }
  return "admin/build/database/table/$table/view";
}

function dba_table_overview($table) {
  $rows = array();
  $tables = dba_get_active_tables();
  $quantity = count($tables);
  if ($quantity == 1) {
    drupal_set_title(t('View table %table', array('%table' => $table)));

    if (user_access('dba administer database')) {
      $primary = dba_get_primary_key($table);
    }
    else {
      $primary = NULL;
    }

    $fields = dba_get_fields($table);
    foreach ($fields as $field) {
      $header[] = array('data' => "$field", 'field' => "$field");
    }

    $sql = "SELECT * FROM {$table}";
    $sql .= tablesort_sql($header);
    $result = pager_query($sql, 20);

    if (!is_null($primary)) {
      $header[] = t('actions');
    }

    if (db_num_rows($result)) {
      while ($row = db_fetch_array($result)) {
        $line = array_map('check_plain', array_values($row));
        if (!is_null($primary)) {
          $id = "{$row[$primary]}";
          $id = str_replace('/', '__2F_', $id);
          $actions = '['. l(t('edit'), "admin/build/database/table/$table/view/edit/$primary/$id") .']';
          $actions .= ' ['. l(t('delete'), "admin/build/database/table/$table/view/delete/$primary/$id") .']';
          $line[] = $actions;
        }
        $rows[] = $line;
        unset($line);
      }
      if ($pager = theme('pager', NULL, 20, 0)) {
        $rows[] = array(array('data' => $pager, 'colspan' => sizeof($fields)));
      }
      $output = theme('table', $header, $rows);
    }
    else {
      $output = t('The table is empty.');
    }
  }
  else {
    drupal_set_message(t('Unable to view more than one table at a time.'), 'error');
    $output .= dba_database_overview_form();
  }
  return $output;
}

function dba_get_primary_key($table) {
  if (_is_mysql()) {
    $rows = array();
    $tables = dba_get_active_tables();
    $quantity = count($tables);
    if ($quantity == 1) {
      $result = dba_describe_table($table, FALSE);
      while ($row = db_fetch_array($result)) {
        if ($row['Key'] == "PRI") {
          return ($row['Field']);
        }
      }
    }
    else {
      drupal_set_message(t('Unable to return the primary key for more than one table at a time.'), 'error');
    }
  }
  else {
    // Not MySQL, so currently unsupported.
    return;
  }
  return;
}

function dba_table_describe($table) {
  $rows = array();
  $tables = dba_get_active_tables();
  $quantity = count($tables);
  if ($quantity == 1) {
    drupal_set_title(t('Describe table %table', array('%table' => $table)));
    $result = dba_describe_table($table);
    while ($row = db_fetch_array($result)) {
      if (!$header) {
        $header = array_keys($row);
      }
      $rows[] = (array)($row);
    }
    return (theme('table', $header, $rows));
  }
  else {
    drupal_set_message(t('Unable to describe more than one table at a time.'), 'error');
    $output .= dba_database_overview();
  }
  return $output;
}

function dba_query_form() {
  // Now, add a text area for the admin to enter a query.
  $form['query'] = array('#type' => 'fieldset', '#title' => t('Query'));
  $form['query']['dba_query'] = array(
    '#type' => 'textarea',
    '#title' => t('Database query'),
    '#cols' => 70, '#rows' => 10,
    '#description' => t('Enter the text of your database query.  This will be executed directly in your database, so the action can not be undone.  Do not wrap your tables in {}, as direct database queries do not support Drupal\'s database prefixing.  If you are using a database prefix, you will need to manually include the prefix in your table name.  Separate multiple queries with a ";".  A sample query: \'SELECT COUNT(*) FROM accesslog;\''),
  );
  $form['query']['actions'] = array(
    '#prefix' => '<div class="container-inline">',
    '#suffix' => '</div>',
  );
  $form['query']['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Execute query'),
  );
  $form['query']['actions']['cancel'] = array(
    '#type' => 'markup',
    '#value' => l(t('Cancel'), 'admin/database'),
  );

  // We use #pre_render, so that we can safely use validated form values
  // to see if a query has been entered, and if so, run the query and
  // dynamically generate other form elements to display the results.
  $form['#pre_render'][] = 'dba_query_form_pre_render';

  // We don't want to get redirected, which would run the queries twice.
  $form['#redirect'] = false;

  return $form;
}

function dba_query_form_pre_render($form_id, &$form) {
  // If there are no validation errors and there's already a query,
  // run it and display the results.
  $dba_query = $form['query']['dba_query']['#value'];
  if (!form_get_errors() && !empty($dba_query)) {
    // Execute each sql statement individually.
    $i = 0;
    foreach (explode(';', $dba_query) as $sql) {
      $header = NULL;
      if (trim($sql) == '') {
        break;
      }
      $result = dba_execute_query($sql);
      if ($result && $result != 1 && db_num_rows($result)) {
        while ($row = db_fetch_array($result)) {
          if (!$header) {
            $header = array_map('check_plain', array_keys($row));
          }
          $rows[] = array_map('check_plain', array_values($row));
        }
      }
      if (!empty($rows)) {
        $form['results'][$i] = array(
          '#type' => 'fieldset',
          '#title' => t('Result') .': '. theme_placeholder($sql),
        );
        $form['results'][$i]['result'] = array(
          '#value' => theme('table', $header, $rows),
        );
        unset($rows);
      }
      $i++;
    }
    $form['results'] = form_builder('dba_query_form', $form['results']);
  }
}

function dba_execute_query($sql) {
  if (user_access('dba administer database')) {
    return _db_query($sql);
  }
}

function dba_run_script() {
  if (user_access('dba administer database')) {
    $form['script'] = array('#type' => 'fieldset', '#title' => 'Script');
    $form['script']['script_filename'] = array(
      '#type' => 'file', '#title' => t('Select a script'),
      '#description' => t('Click the "browse" button to select a database script from your local computer.')
    );
    $form['script']['verbose'] = array(
      '#type' => 'checkbox',
      '#title' => t('Verbose'),
      '#return_value' => 1,
      '#default_value' => 0,
      '#description' => t('Check this box if you wish to see all queries that are run.'),
    );
    $form['script']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Run script'),
    );
    $form['#attributes'] = array('enctype' => 'multipart/form-data');
  }
  return $form;
}

function dba_run_script_submit($form_id, $form_values) {
  if ($file = file_save_upload('script_filename')) {
    // File is now in temporary directory.
    if (file_exists($file->filepath)) {
      if ($fp = fopen($file->filepath, 'r')) {
        $query = NULL;
        $count = 0;
        while (!feof($fp)) {
          $line = fgets($fp, 8192);
          if ($line && strncmp($line, '--', 2) && strncmp($line, '#', 1)) {
            $query .= $line;
            if (strpos($line, ';')) {
              if (db_query($query, FALSE)) {
                if ($form_values['verbose']) {
                  drupal_set_message(check_plain($query));
                }
                $count++;
              }
              else {
                drupal_set_message(t('Query failed: %query', array('%query' => $query)), 'error');
              }
              $query = NULL;
            }
          }
        }
        fclose ($fp);
        drupal_set_message(t('Succesfully ran !query from script %filename.', array('!query' => format_plural($count, '1 query', '@count queries'), '%filename' => $file->filename)));
      }
      else {
        drupal_set_message(t('Unable to open script %filename.', array('%filename' => $file->filename)), 'error');
      }
      file_delete($file->filepath);
    }
    else {
      drupal_set_message(t('Script %filename does not exist.', array('%filename' => $file->filename)), 'error');
    }
    // Cleanup session.
    unset($_SESSION['file_uploads'][$file->source]);
  }
}

function dba_check_tables_form() {

  // Setup a form value to remember what table(s) we're operating on.
  $form['check_tables']['tables']['#type'] = 'hidden';
  // First, see if we what the active table is, based solely on
  // $_SESSION and the URL.
  $tables = dba_get_active_tables(0);
  unset($_SESSION['dba_tables']);
  if (!empty($tables)) {
    // We already know, so this is easy...
    $form['check_tables']['tables']['#default_value'] = implode(',', $tables);
  }
  else {
    // It must be in the form values, then.  In this case, we need to
    // call form_builder() to safely grab the data out of $_POST.
    $form['check_tables'] = form_builder('dba_check_tables', $form['check_tables']);
    if (!empty($form['check_tables']['tables']['#value'])) {
      $form_tables = explode(',', $form['check_tables']['tables']['#value']);
      $tables = dba_get_active_tables(0, $form_tables);
    }
  }
  // Make sure we have something to do.
  if (empty($tables)) {
    drupal_set_message(t('You must select the tables to check.'), 'error');
    drupal_goto('admin/build/database');
  }

  $form['check_options'] = array(
    '#type' => 'fieldset',
    '#title' => t('Actions'),
  );
  $form['check_options']['check_type'] = array(
    '#type' => 'radios',
    '#title' => t('Check type'),
    '#default_value' => variable_get('dba_default_check_type', 'MEDIUM'),
    '#options' => array(
      'QUICK' => t('Quick'),
      'FAST' => t('Fast'),
      'CHANGED' => t('Changed'),
      'MEDIUM' => t('Medium'),
      'EXTENDED' => t('Extended')
    ),
  );
  $form['check_options']['check'] = array(
    '#type' => 'submit',
    '#value' => t('Check again')
  );

  // Most of the interesting stuff in this form has to be added via
  // #pre_render, so that we can safely use validated form values to
  // dynamically generate other form elements.  In particular, we add
  // a fieldset with the results of whatever operation we perform, and
  // depending on the admin settings and the state of the tables, we
  // might need to add a 'Repair' button, too.
  $form['#pre_render'][] = 'dba_check_table_form_pre_render';

  // We don't want to get redirected, which would run the queries twice.
  $form['#redirect'] = false;

  return drupal_get_form('dba_check_tables', $form);
}

function dba_check_table_form_pre_render($form_id, &$form) {
  global $form_values;
  if (form_get_errors()) {
    // If there's a validation error (e.g. #token is wrong), return
    // immediately since we can't trust $form_values.
    return;
  }

  $action = isset($_POST['op']) ? $_POST['op'] : 'check';
  $type = $form_values['check_type'];
  $tables = explode(',', $form_values['tables']);

  if ($action == t('Repair')) {
    drupal_set_title(t('Performing table repair.'));
    $result = dba_repair_tables($tables);
  }
  else {
    drupal_set_title(t('Performing %type table check', array('%type' => check_plain($type))));
    $result = dba_check_tables($tables, $type);
  }

  // Construct the output of the operation as a table, and see if any
  // tables need to be repaired.
  $repair = array();
  $header = array(
    t('Table'),
    t('Operation'),
    t('Message type'),
    t('Message text'),
  );
  while ($row = db_fetch_object($result)) {
    $rows[] = (array)($row);
    if ($row->Msg_type == 'status') {
      $status = $row->Msg_text;
      if ($status != 'OK' && $status != 'Table is already up to date') {
        // An error message will result if we use the database name when trying
        // to repair a table and the database has '-' in the name, so to be
        // safe we strip off the database name.
        $repair_table = explode('.', $row->Table);
        $repair[] = $repair_table[1];
      }
    }
  }
  $output .= theme('table', $header, $rows);

  if ($repair) {
    $output .= '<h3>'. t('One or more tables need repairs.') .'</h3>';
    $to_repair = 1;
  }
  else {
    $output .= '<h3>'. t('No repairs are required.') .'</h3>';
    $to_repair = 0;
  }

  $form['check_results'] = array(
   '#type' => 'fieldset',
   '#title' => t('Result'),
   '#weight' => -5,
  );
  $form['check_results']['result'] = array(
    '#type' => 'markup',
    '#value' => $output,
  );
  // Since we just added these form elements, but we're already at the
  // pre_render stage, we need to manually invoke form_builder() so that
  // these elements are rendered in the final form show to the user.
  $form['check_results'] = form_builder('dba_check_tables', $form['check_results']);

  if (user_access('dba administer database')) {
    $repair_option = variable_get('dba_repair', 0);
    if (($repair_option == 0 && $to_repair) || $repair_option == 1) {
      $form['check_options']['repair'] = array(
        '#type' => 'submit',
        '#value' => t('Repair'),
        '#weight' => 20,
      );
      if (!$repair_option) {
        $form['check_options']['repair_tables'] = array(
          '#type' => 'hidden',
          '#value' => implode(',', $repair_tables),
        );
      }
    }
  }
  // Now that we know what type of check we used, we can set the default.
  $form['check_options']['check_type']['#default_value'] = $type;

  // Rebuild the form elements we've modified here, so they're
  // displayed on when the page is rendered.
  $form['check_options'] = form_builder('dba_check_tables', $form['check_options']);
}

function dba_tables_optimize() {
  $tables = dba_get_active_tables(0);
  $quantity = (empty($tables) ? 0 : count($tables));

  if (!$quantity) {
    drupal_set_message(t('You must select the tables to optimize.'), 'error');
    drupal_goto('admin/build/database');
  }
  else {
    drupal_set_title(t('Optimizing %table', array('%table' => format_plural($quantity, 'table', 'tables'))));

    $query = 'OPTIMIZE TABLE '. implode(', ', $tables) .';';
    drupal_set_message(check_plain($query));
    $result = db_query($query);

    $header = array(t('Table'), t('Operation'), t('Message type'), t('Message text'));
    $rows = array();
    while ($row = db_fetch_object($result)) {
      $rows[] = array_map('check_plain', (array)($row));
    }
    $output = theme('table', $header, $rows);
  }
  return $output;
}

/**
 * Figure out what table(s) are currently selected.
 *
 * There are various ways in the dba UI to select tables for each of the
 * possible operations. The table name could be imbedded in the menu path, a
 * set of tables could be selected via the checkboxes on the overview page, or
 * a set of tables might be saved in a special form value to remember
 * previously selected tables.
 *
 * In all cases, this function will validate the table names by comparing them
 * against the actual names of the tables in the DB, so it is essential to
 * always use this function, even if you already have a list of tables.
 *
 * @param $use_all
 *   Boolean to specify that if no active tables are found, return all tables.
 *
 * @param $form_tables
 *   Array of active table names that we got from a form element.
 *
 */
function dba_get_active_tables($use_all = TRUE, $form_tables = array()) {
  static $all_tables = array();
  if (empty($all_tables)) {
    $all_tables = dba_get_tables();
  }

  $tables = array();
  if (!empty($form_tables)) {
    $tables = $form_tables;
  }
  elseif (arg(5) && arg(4)) {
    $tables[] = arg(4);
  }
  elseif ($use_all) {
    // No tables were set, by default we will return a list of all tables.
    return $all_tables;
  }
  elseif (isset($_SESSION['dba_tables'])) {
    $tables = $_SESSION['dba_tables'];
  }

  // Final sanity/security check to prevent malicious table names.
  $safe_tables = array();
  foreach ($tables as $table) {
    if (isset($all_tables[$table])) {
      $safe_tables[] = $table;
    }
  }
  return $safe_tables;
}

function dba_verify($tables, $action) {
  $quantity = count($tables);
  if ($quantity) {
    $form = array();
    $table_list = theme('placeholder', implode(', ', $tables));
    $table_id = format_plural($quantity, 'table', 'tables');
    $substitutions = array(
      '!tables' => $table_list,
      '!table' => $table_id,
      '!this' => format_plural($quantity, 'this', 'these'),
      '!itself' => format_plural($quantity, 'itself', 'themselves'),
      '!its' => format_plural($quantity, 'its', 'their'),
    );
    $form['tables'] = array(
      '#type' => 'value',
      '#value' => implode(',', $tables),
    );
    switch ($action) {
      case 'backup':
        if ($quantity == 1) {
          $filename = reset($tables) .'.sql';
        }
        else {
          $filename = variable_get('dba_default_filename', 'backup.sql');
        }
        $form['file_name'] = array(
          '#type' => 'textfield',
          '#title' => t('Backup filename'),
          '#default_value' => $filename,
          '#size' => 40,
          '#maxlength' => 255,
          '#description' => t("Please specify the filename you wish to give your database backup.  Once you click 'Backup !table' below your web browser will allow you to save the database backup to your local computer.", array('!table' => format_plural($quantity, 'table', 'tables'))),
        );
        $form['add_drop_table'] = array(
          '#type' => 'checkbox',
          '#title' => t('Add DROP TABLE'),
          '#default_value' => 0,
          '#description' => t('Check this box if you wish to add DROP TABLE IF EXISTS before each table schema.  This will allow you to quickly restore from a backup without having to manually drop all tables first.'),
        );
        $form = confirm_form($form,
          t('Backup !table to local computer?', array('!table' => $table_id)),
          'admin/build/database',
          t('By clicking "Backup !table" you will be prompted to save the following !table to your local computer: !tables', array('!tables' => $table_list, '!table' => $table_id)),
          t('Backup !table', array('!table' => $table_id)),
          t('Cancel')
        );
        $form['#base'] = 'dba_verify_backup_form';
        break;

      case 'empty':
        $form = confirm_form($form,
          t('Are you sure you want to delete all rows from the "!tables" !table?', $substitutions),
          'admin/build/database',
          t('By clicking "Empty !table" you will completely remove all data from !this !table, though the !table !itself will not be dropped. This action cannot be undone.', $substitutions),
          t('Empty !table', array('!table' => $table_id)),
          t('Cancel')
        );
        $form['#base'] = 'dba_verify_empty_form';
        break;

      case 'drop':
        $form = confirm_form($form,
          t('Are you sure you want to drop the "!tables" !table?', $substitutions),
          'admin/build/database',
          t('By clicking "Drop !table" you will be completely removing !this !table and all !its data from the database. This action cannot be undone.', $substitutions),
          t('Drop !table', array('!table' => $table_id)),
          t('Cancel')
        );
        $form['#base'] = 'dba_verify_drop_form';
        break;
    }
  }
  return $form;
}

function dba_verify_backup_form_submit($form_id, $form_values) {
  if (is_array($form_values['tables'])) {
    $tables = $form_values['tables'];
  }
  else {
    $tables = explode(',', $form_values['tables']);
  }
  $file_name = $form_values['file_name'];
  if (user_access('dba administer database')) {
    $database = dba_get_database();
    Header("Content-type: application/octet-stream");
    Header("Content-Disposition: attachment; filename=". $file_name);
    echo "-- Drupal dba.module database dump\n";
    echo "--\n";
    echo "-- Database: $database\n";
    echo "-- Date: ". format_date(time(), 'large') ."\n\n";
    foreach ($tables as $table) {
      dba_backup_table($table, $form_values['add_drop_table']);
    }
    $quantity = count($tables);
    $display = implode(', ', $tables);
    drupal_set_message(t("Saved %tables to %filename.", array('%filename' => $file_name, '%tables' => $display)));
    exit(0);
  }
}

function dba_verify_empty_form_submit($form_id, $form_values) {
  if (is_array($form_values['tables'])) {
    $tables = $form_values['tables'];
  }
  else {
    $tables = explode(',', $form_values['tables']);
  }
  if (user_access('dba administer database')) {
    foreach ($tables as $table) {
      dba_delete_table($table);
    }
  }
  if (count($tables) > 1) {
    return 'admin/build/database';
  }
  return "admin/build/database/table/$table/view";
}

function dba_verify_drop_form_submit($form_id, $form_values) {
  if (is_array($form_values['tables'])) {
    $tables = $form_values['tables'];
  }
  else {
    $tables = explode(',', $form_values['tables']);
  }
  if (user_access('dba administer database')) {
    foreach ($tables as $table) {
      dba_drop_table($table);
    }
  }
  return 'admin/build/database';
}


/**
 * @defgroup dba_api Database Administrator API
 * @{
 */

/**
 * Return all tables in active database as array.
 */
function dba_get_tables() {
  global $db_prefix;
  static $table_list = array();
  if ($table_list) {
    // Cache copy so function can be called multiple times efficiently.
    return $table_list;
  }

  if (_is_mysql()) {
    $result = db_query('show tables');
  }
  else {
    $result = db_query('SELECT DISTINCT tabname as Table FROM {drupal_system_catalog}');
  }

  while ($tables = db_fetch_object($result)) {
    foreach ($tables as $db => $table) {
      if (!$db_prefix) {
        $table_list[$table] = $table;
      }
      elseif (is_array($db_prefix)) {
        foreach ($db_prefix as $prefix) {
          $prefix = isset($db_prefix[$table]) ? $db_prefix[$table] : $db_prefix['default'];
          if (preg_match("/^($prefix)/", $table)) {
            $table_list[$table] = $table;
            break;
          }
        }
      }  
      elseif (preg_match("/^($db_prefix)/", $table)) {
          $table_list[$table] = $table;
      }
    }
  }

  return $table_list;
}

/**
 * Return name of active database.
 */
function dba_get_database() {
  static $database = array();
  if ($database) {
    // Cache copy so function can be called multiple times efficiently.
    return $database;
  }

  if (_is_mysql()) {
    $database = array_keys(db_fetch_array(db_query('show tables')));
    $database = preg_replace('/^Tables_in_/', '', $database[0]);
  }
  else {
    $result = db_fetch_object(db_query('SELECT DISTINCT dbname FROM {drupal_system_catalog} LIMIT 1'));
    $database = $result->dbname;
  }

  return $database;
}

/**
 * Return all fields in specified table as array.
 */
function dba_get_fields($table) {
  $fields = array();

  if (_is_mysql()) {
    $result = db_query("DESCRIBE $table");
    while ($row = db_fetch_object($result)) {
      $fields[] = $row->Field;
    }
  }
  else {
    // Lowercase of names of resulting columns seems to be important for postgresql.
    $result = db_query("SELECT colname as field FROM {drupal_system_catalog} WHERE tabname = '". $table ."'");
    while ($row = db_fetch_object($result)) {
      $fields[] = $row->field;
    }
  }

  return $fields;
}

/**
 * Return the number of rows in the specified table.
 */
function dba_get_row_count($table) {
  $rows = db_fetch_object(db_query("SELECT COUNT(*) as count FROM {$table}"));
  return $rows->count;
}

/**
 * Perform specified check type on specified table(s) (MySQL specific).
 */
function dba_check_tables($tables, $type = 'MEDIUM') {
  $query = 'CHECK TABLE '. implode(', ', $tables) .' '. db_escape_string($type) . ';';
  drupal_set_message(check_plain($query));
  return db_query($query);
}

/**
 * Repair specified table(s) (MySQL specific).
 */
function dba_repair_tables($tables) {
  $query = 'REPAIR TABLE '. implode(', ', $tables) .';';
  drupal_set_message(check_plain($query));
  return db_query($query);
}

/**
 * Describe table.
 */
function dba_describe_table($table, $verbose = TRUE) {
  if (_is_mysql()) {
    $query = 'DESCRIBE '. db_escape_table($table) .';';
  }
  else {
    // Lowercase names of resulting colums are important for PostgreSQL.
    $query = "SELECT tabname as table, colname as field, coltype as type, colnull as null, coldefault as default, colextra as extra FROM {drupal_system_catalog} WHERE tabname = '{". db_escape_table($table) ."}' ORDER BY colextra;";
  }
  if ($verbose) {
    drupal_set_message($query);
  }
  return db_query($query);
}

/**
 * Backup table to file.
 */
function dba_backup_table($table, $add_drop_table, $verbose = TRUE, $data = TRUE) {
  $output = "--\n";
  $output .= "-- Table structure for table '$table'\n";
  $output .= "--\n\n";

  if ($add_drop_table) {
    $output .= "DROP TABLE IF EXISTS $table;\n";
  }

  $create = dba_show_create_table($table);
  $output .= $create['Create Table'];
  $output .= ";\n\n";

  if (!$data) {
    // Backup schema only for this table.
    return $output;
  }

  $output .= "--\n";
  $output .= "-- Dumping data for table '$table'\n";
  $output .= "--\n\n";
  
  if ($verbose) {
    echo $output;
    $output = NULL;
  }

  $result = db_query("select * from $table");
  $numrow = db_num_rows($result);
  $fields = dba_get_fields($table);
  $num_fields = sizeof($fields);

  while ($row = db_fetch_array($result)) {
    $line = "INSERT INTO $table VALUES(";
    $i = 0;
    foreach ($row as $value) {
      $value = db_escape_string($value);
      $value = ereg_replace("\n","\\n",$value);
      $line .= (isset($value)) ? "\"$value\"" : "\"\"";
      $line .= (++$i < $num_fields) ? ',' : ");\n";
    }
    $output .= $line;
    if ($verbose) {
      echo $output;
      $output = NULL;
    }
  }
  return $output;
}

function dba_show_create_table($table) {
  if (_is_mysql()) {
    $query = "SHOW CREATE TABLE $table;";
  }
  else {
    drupal_set_message(t('Support for showing the command used to create a table is not currently available in this module for PostgreSQL.'), 'error');
  }
  drupal_set_message(check_plain($query));
  return db_fetch_array(db_query($query));
}

/**
 * Delete table contents.
 */
function dba_delete_table($table) {
  if (_is_mysql()) {
    $query = "DELETE FROM $table;";
  }
  else {
    drupal_set_message(t('Support for deleting the contents of tables is not currently available in PostgreSQL.'), 'error');
    return;
  }
  drupal_set_message(check_plain($query));
  return db_query($query);
}

/**
 * Drop table and all contents from current database.
 */
function dba_drop_table($table) {
  $query = "DROP TABLE $table;";
  drupal_set_message(check_plain($query));
  return db_query($query);
}

function _is_mysql() {
  switch($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      return 1;
    default:
      return 0;
  }
}

/**
 * Adds the javascript for selecting all tables.
 * 
 * @todo Can be replaced by Checkall module.
 */
function checkoff_head() {
  return "<script type=\"text/javascript\">function checkoff(form,toggle){ var i=0;	frm=document.forms[form];	len = frm.elements.length;	for( i=0 ; i<len ; i++) {	if (frm.elements[i].type=='checkbox') {	frm.elements[i].checked=toggle;	}	}} </script>";
}

/**
 * Temporary mail handler class.
 * 
 * Define a mail class to send a message with an attachment. Eventually Drupal
 * core should provide this functionality, at which time this code will be
 * removed.
 * 
 * More info on sending email at <http://php.net/function.mail>.
 */
function dba_mail_backup($attachment) {
  class mime_mail {
    var $parts;
    var $to;
    var $from;
    var $headers;
    var $subject;
    var $body;

    function mime_mail() {
      $this->parts = array();
      $this->to = "";
      $this->from = "";
      $this->headers = "";
      $this->subject = "";
      $this->body = "";
    }

    function add_attachment($message, $name = "", $ctype = "application/octet-stream") {
      $this->parts[] = array(
        "ctype" => $ctype,
        "message" => $message,
        "encode" => $encode,
        "name" => $name,
      );
    }

    function build_message($part) {
      $message = $part["message"];
      $message = chunk_split(base64_encode($message));
      $encoding = "base64";
      return "Content-Type: ". $part["ctype"] . ($part["name"] ? "; name = \"". $part["name"] ."\"" : "") ."\nContent-Transfer-Encoding: $encoding\n\n$message\n";
    }

    function build_multipart() {
      $boundary = "b". md5(uniqid(time()));
      $multipart = "Content-Type: multipart/mixed; boundary = $boundary\n\nThis is a MIME encoded message.\n\n--$boundary";
      for ($i = sizeof($this->parts) - 1; $i >= 0; $i--) {
        $multipart .= "\n". $this->build_message($this->parts[$i]) ."--$boundary";
      }
      return $multipart .= "--\n";
    }

    function send() {
      $mime = "";
      if (!empty($this->from)) $mime .= "From: ". $this->from ."\n";
      if (!empty($this->headers)) $mime .= $this->headers ."\n";
      if (!empty($this->body)) $this->add_attachment($this->body, "", "text/plain");
      $mime .= "MIME-Version: 1.0\n". $this->build_multipart();
      mail($this->to, $this->subject, "", $mime);
    }
  }

  // Send mail
  $attach = fread(fopen($attachment->path, "r"), filesize($attachment->path));
  $mail = new mime_mail();
  $mail->from = variable_get('site_mail', ini_get('sendmail_from'));
  $mail->headers = 'Errors-To: [EMAIL='. $mail->from .']'. $mail->from .'[/EMAIL]';
  $admin = user_load(array('uid' => 1));
  $mail->to = $admin->mail;
  $mail->subject = t('Database backup from !site: !file', array('!site' => variable_get('site_name', 'drupal'), '!file' => $attachment->filename));
  $mail->body = t('Database backup attached');
  
  $mail->add_attachment("$attach", $attachment->filename, "Content-Transfer-Encoding: base64 /9j/4AAQSkZJRgABAgEASABIAAD/7QT+UGhvdG9zaG");
  $mail->send();
}

/**
 *
 * @} End of "defgroup dba_api".
 */

function _dba_ops($op) {
  $ops = array(
    'drop' => t('drop'),
    'describe' => t('describe'),
    'optimize' => t('optimize'),
    'check' => t('check'),
    'backup' => t('backup'),
    'empty' => t('empty'),
    'view' => t('view'),
  );
  return $op ? $ops[$op] : $ops;
}
