<?php
// $Id: webform.install,v 1.40.2.22 2010/10/18 08:08:59 quicksketch Exp $

/**
 * @file
 *   Webform module install/schema hooks.
 */

/**
 * Implementation of hook_schema().
 */
function webform_schema() {
  $schema = array();

  $schema['webform'] = array(
    'description' => 'Table for storing additional properties for webform nodes.',
    'fields' => array(
      'nid' => array(
        'description' => 'The node identifier of a webform.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'confirmation' => array(
        'description' => 'The confirmation message or URL displayed to the user after submitting a form.',
        'type' => 'text',
        'not null' => TRUE,
      ),
      'confirmation_format' => array(
        'description' => 'The input format used by the confirmation message.',
        'type' => 'int',
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 0,
      ),
      'redirect_url' => array(
        'description' => 'The URL a user is redirected to after submitting a form.',
        'type' => 'varchar',
        'length' => 255,
        'default' => '<confirmation>',
      ),
      'block' => array(
         'description' => 'Boolean value for whether this form be available as a block.',
         'type' => 'int',
         'size' => 'tiny',
         'not null' => TRUE,
         'default' => 0,
      ),
      'teaser' => array(
        'description' => 'Boolean value for whether the entire form should be displayed on the teaser.',
        'type' => 'int',
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 0,
      ),
      'allow_draft' => array(
         'description' => 'Boolean value for whether submissions to this form be saved as a draft.',
         'type' => 'int',
         'size' => 'tiny',
         'not null' => TRUE,
         'default' => 0,
      ),
      'submit_notice' => array(
        'description' => 'Boolean value for whether to show or hide the previous submissions notification.',
        'type' => 'int',
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 1,
      ),
      'submit_text' => array(
        'description' => 'The title of the submit button on the form.',
        'type' => 'varchar',
        'length' => 255,
      ),
      'submit_limit' => array(
        'description' => 'The number of submissions a single user is allowed to submit within an interval. -1 is unlimited.',
        'type' => 'int',
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => -1,
      ),
      'submit_interval' => array(
        'description' => 'The amount of time in seconds that must pass before a user can submit another submission within the set limit.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => -1,
      ),
    ),
    'primary key' => array('nid'),
  );

  $schema['webform_component'] = array(
    'description' => 'Stores information about components for webform nodes.',
    'fields' => array(
      'nid' => array(
        'description' => 'The node identifier of a webform.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'cid' => array(
        'description' => 'The identifier for this component within this node, starts at 0 for each node.',
        'type' => 'int',
        'size' => 'small',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'pid' => array(
        'description' => 'If this component has a parent fieldset, the cid of that component.',
        'type' => 'int',
        'size' => 'small',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'form_key' => array(
        'description' => 'When the form is displayed and processed, this key can be used to reference the results.',
        'type' => 'varchar',
        'length' => 128,
      ),
      'name' => array(
        'description' => 'The label for this component.',
        'type' => 'varchar',
        'length' => 255,
      ),
      'type' => array(
        'description' => 'The field type of this component (textfield, select, hidden, etc.).',
        'type' => 'varchar',
        'length' => 16,
      ),
      'value' => array(
        'description' => 'The default value of the component when displayed to the end-user.',
        'type' => 'text',
        'not null' => TRUE,
      ),
      'extra' => array(
        'description' => 'Additional information unique to the display or processing of this component.',
        'type' => 'text',
        'not null' => TRUE,
      ),
      'mandatory' => array(
        'description' => 'Boolean flag for if this component is required.',
        'type' => 'int',
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 0,
      ),
      'weight' => array(
        'description' => 'Determines the position of this component in the form.',
        'type' => 'int',
        'size' => 'small',
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
    'primary key' => array('nid', 'cid'),
  );

  $schema['webform_emails'] = array(
    'description' => 'Holds information regarding e-mails that should be sent upon submitting a webform',
    'fields' => array(
      'nid' => array(
        'description' => 'The node identifier of a webform.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'eid' => array(
        'description' => 'The e-mail identifier for this row\'s settings.',
        'type' => 'int',
        'unsigned' => TRUE,
        'size' => 'small',
        'not null' => TRUE,
        'default' => 0,
      ),
      'email' => array(
        'description' => 'The e-mail address that will be sent to upon submission. This may be an e-mail address, the special key "default" or a numeric value. If a numeric value is used, the value of a component will be substituted on submission.',
        'type' => 'text',
        'not null' => FALSE,
      ),
      'subject' => array(
        'description' => 'The e-mail subject that will be used. This may be a string, the special key "default" or a numeric value. If a numeric value is used, the value of a component will be substituted on submission.',
        'type' => 'varchar',
        'length' => '255',
        'not null' => FALSE,
      ),
      'from_name' => array(
        'description' => 'The e-mail "from" name that will be used. This may be a string, the special key "default" or a numeric value. If a numeric value is used, the value of a component will be substituted on submission.',
        'type' => 'varchar',
        'length' => '255',
        'not null' => FALSE,
      ),
      'from_address' => array(
        'description' => 'The e-mail "from" e-mail address that will be used. This may be a string, the special key "default" or a numeric value. If a numeric value is used, the value of a component will be substituted on submission.',
        'type' => 'varchar',
        'length' => '255',
        'not null' => FALSE,
      ),
      'template' => array(
        'description' => 'A template that will be used for the sent e-mail. This may be a string or the special key "default", which will use the template provided by the theming layer.',
        'type' => 'text',
        'not null' => FALSE,
      ),
      'excluded_components' => array(
        'description' => 'A list of components that will not be included in the %email_values token. A list of CIDs separated by commas.',
        'type' => 'text',
        'not null' => TRUE,
      ),
      'html' => array(
        'description' => 'Determines if the e-mail will be sent in an HTML format. Requires Mime Mail module.',
        'type' => 'int',
        'unsigned' => TRUE,
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 0,
      ),
      'attachments' => array(
        'description' => 'Determines if the e-mail will include file attachments. Requires Mime Mail module.',
        'type' => 'int',
        'unsigned' => TRUE,
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
    'primary key' => array('nid', 'eid'),
  );

  $schema['webform_roles'] = array(
    'description' => 'Holds access information regarding which roles are allowed to submit which webform nodes. Does not prevent access to the webform node entirely, use the {node_access} table for that purpose.',
    'fields' => array(
      'nid' => array(
        'description' => 'The node identifier of a webform.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'rid' => array(
        'description' => 'The role identifier.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
    'primary key' => array('nid', 'rid'),
  );

  $schema['webform_submissions'] = array(
    'description' => 'Holds general information about submissions outside of field values.',
    'fields' => array(
      'sid' => array(
        'description' => 'The unique identifier for this submission.',
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'nid' => array(
        'description' => 'The node identifier of a webform.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'uid' => array(
        'description' => 'The id of the user that completed this submission.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'is_draft' => array(
         'description' => 'Is this a draft of the submission?',
         'type' => 'int',
         'size' => 'tiny',
         'not null' => TRUE,
         'default' => 0,
      ),
      'submitted' => array(
        'description' => 'Timestamp of when the form was submitted.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'remote_addr' => array(
        'description' => 'The IP address of the user that submitted the form.',
        'type' => 'varchar',
        'length' => 128,
      ),
    ),
    'unique keys' => array(
      'sid_nid' => array('sid', 'nid'),
    ),
    'primary key' => array('sid'),
  );

  $schema['webform_submitted_data'] = array(
    'description' => 'Stores all submitted field data for webform submissions.',
    'fields' => array(
      'nid' => array(
        'description' => 'The node identifier of a webform.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'sid' => array(
        'description' => 'The unique identifier for this submission.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'cid' => array(
        'description' => 'The identifier for this component within this node, starts at 0 for each node.',
        'type' => 'int',
        'size' => 'small',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'no' => array(
        'description' => 'Usually this value is 0, but if a field has multiple values (such as a time or date), it may require multiple rows in the database.',
        'type' => 'varchar',
        'length' => 128,
        'not null' => TRUE,
        'default' => '0',
      ),
      'data' => array(
        'description' => 'The submitted value of this field, may be serialized for some components.',
        'type' => 'text',
        'size' => 'medium',
        'not null' => TRUE,
      ),
    ),
    'indexes' => array(
      'nid' => array('nid'),
      'sid_nid' => array('sid', 'nid'),
    ),
    'primary key' => array('nid', 'sid', 'cid', 'no'),
  );

  return $schema;
}

/**
 * Implementation of hook_install().
 */
function webform_install() {
  module_load_include('inc', 'node', 'content_types');
  db_query("UPDATE {system} SET weight = -1 WHERE name='webform' AND type='module'");
  drupal_install_schema('webform');

  // Create the default webform type.
  $webform_type = array(
    'type' => 'webform',
    'name' => st('Webform'),
    'module' => 'node',
    'description' => st('Create a new form or questionnaire accessible to users. Submission results and statistics are recorded and accessible to privileged users.'),
    'custom' => TRUE,
    'modified' => TRUE,
    'locked' => FALSE,
  );
  $webform_type = (object) _node_type_set_defaults($webform_type);
  node_type_save($webform_type);
}

/**
 * Implementation of hook_uninstall().
 */
function webform_uninstall() {
  // Unset webform variables.
  variable_del('webform_node_types');
  variable_del('webform_node_types_redirect');
  variable_del('webform_use_cookies');
  variable_del('webform_enable_fieldset');
  variable_del('webform_default_from_address');
  variable_del('webform_default_from_name');
  variable_del('webform_default_subject');
  variable_del('webform_default_format');
  variable_del('webform_format_override');
  variable_del('webform_csv_delimiter');
  variable_del('webform_allowed_tags');
  variable_del('webform_blocks');

  $component_list = array();
  $path = drupal_get_path('module', 'webform') . '/components';
  $files = file_scan_directory($path, '^.*\.inc$');
  foreach ($files as $filename => $file) {
    variable_del('webform_enable_' . $file->name, 1);
  }

  // Delete uploaded files.
  $filepath = file_create_path('webform');
  _webform_recursive_delete($filepath);

  // Drop tables.
  drupal_uninstall_schema('webform');
}

/**
 * Set the minimum upgrade version.
 *
 * This should allow updates from any 2.x version of Webform (for D5 or D6),
 * but prevent updates from any version prior to Webform 1.10.
 */
function webform_update_last_removed() {
  return 20;
}

/**
 * Upgrade to Drupal 6. Convert submissions sid column to auto-increment.
 */
function webform_update_6001() {
  $ret = array();
  // Keys must be dropped before altering the column.
  db_drop_primary_key($ret, 'webform_submissions');
  db_drop_unique_key($ret, 'webform_submissions', 'sid_nid');

  // Alter to a primary key and add the unique key back.
  db_change_field($ret, 'webform_submissions', 'sid', 'sid', array('type' => 'serial', 'not null' => TRUE), array('primary key' => array('sid')));
  db_add_unique_key($ret, 'webform_submissions', 'sid_nid', array('sid', 'nid'));
  return $ret;
}

/**
 * Increase the size of the component instance name.
 */
function webform_update_6200() {
  $ret = array();
  db_change_field($ret, 'webform_component', 'name', 'name', array('type' => 'varchar', 'length' => 255, 'default' => 'NULL'));
  return $ret;
}

/**
 * Add a column for email to the webform_component table.
 */
function webform_update_6201() {
  $ret = array();

  // This update will already be run as webform_update_5201 on Drupal 5.
  if (db_column_exists('webform_component', 'email')) {
    return $ret;
  }

  db_add_field($ret, 'webform_component', 'email', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
  $ret[] = update_sql('UPDATE {webform_component} SET email = 1');

  return $ret;
}

/**
 * Per-webform submission access control based on roles.
 */
function webform_update_6202() {
  $ret = array();

  // This update will already be run as webform_update_5202 on Drupal 5.
  if (db_table_exists('webform_roles')) {
    return $ret;
  }

  db_create_table($ret, 'webform_roles', array(
    'description' => 'Holds access information regarding which roles are allowed to submit which webform nodes. Does not prevent access to the webform node entirely, use the {node_access} table for that purpose.',
    'fields' => array(
      'nid' => array(
        'description' => 'The node identifier of a webform.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'rid' => array(
        'description' => 'The role identifier.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
    'primary key' => array('nid', 'rid'),
  ));

  $result = db_query("SELECT nid FROM {node} WHERE type = 'webform'");
  while ($node = db_fetch_object($result)) {
    db_query("INSERT INTO {webform_roles} (nid, rid) VALUES (%d, 1)", $node->nid);
    db_query("INSERT INTO {webform_roles} (nid, rid) VALUES (%d, 2)", $node->nid);
  }

  return $ret;
}

/**
 * Cleanup filtering values used by the file component.
 *
 * Previously, file extensions were saved by category, exactly as the FormAPI
 * returned to the submit handler. All extensions are now stored in a single
 * array, including only valid extensions.
 */
function webform_update_6203() {
  $ret = array();

  // This update will already be run as webform_update_5203 on Drupal 5.

  $result = db_query("SELECT nid, cid, extra FROM {webform_component} WHERE type = 'file'");
  while ($component = db_fetch_object($result)) {
    $extra = unserialize($component->extra);
    $extensions = array();

    // Sanity check, set some defaults if no filtering is in place.
    if (!isset($extra['filtering']['types'])) {
      $extra['filtering']['types'] = array(
        'webimages' => drupal_map_assoc(array('png', 'gif', 'jpg')),
      );
    }
    // Or if filtering has already been updated, skip this update.
    elseif (!isset($extra['filtering']['types']['webimages'])) {
      continue;
    }

    // Defined types.
    foreach ($extra['filtering']['types'] as $category => $category_extensions) {
      foreach ((array)$category_extensions as $extension) {
        if (!is_numeric($extension)) {
          $extensions[] = $extension;
        }
      }
    }

    // Additional types.
    $additional_extensions = explode(',', $extra['filtering']['addextensions']);
    foreach ($additional_extensions as $extension) {
      $clean_extension = drupal_strtolower(trim($extension));
      if (!empty($clean_extension) && !in_array($clean_extension, $extensions)) {
        $extensions[] = $clean_extension;
      }
    }

    $extra['filtering']['types'] = $extensions;
    db_query("UPDATE {webform_component} SET extra = '%s' WHERE nid = %d AND cid = %d", serialize($extra), $component->nid, $component->cid);
  }

  return $ret;
}

/**
 * Set all files to permanent status uploaded by Webform.
 */
function webform_update_6204() {
  $ret = array();
  $ret[] = update_sql("UPDATE {files} SET status = 1 WHERE filepath LIKE '" . file_directory_path() . "/webform/%'");
  return $ret;
}

/**
 * Schema fixes to make Drupal 5 upgrades identical to clean Drupal 6 installs.
 */
function webform_update_6205() {
  $ret = array();

  // Remove disp-width and default from webform.nid.
  db_drop_primary_key($ret, 'webform');
  db_change_field($ret, 'webform', 'nid', 'nid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE), array('primary key' => array('nid')));

  // Set not null property on webform.confirmation.
  db_change_field($ret, 'webform', 'confirmation', 'confirmation', array('type' => 'text', 'not null' => TRUE));

  // Set size to tiny, remove disp-width on webform.submit_limit.
  db_change_field($ret, 'webform', 'submit_limit', 'submit_limit', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => -1));

  // Set default value to -1, remove disp-width on webform.submit_interval.
  db_change_field($ret, 'webform', 'submit_interval', 'submit_interval', array('type' => 'int', 'not null' => TRUE, 'default' => -1));

  // Set not null property on webform.additional_validate.
  db_change_field($ret, 'webform', 'additional_validate', 'additional_validate', array('type' => 'text', 'not null' => TRUE));

  // Set not null property on webform.additional_submit.
  db_change_field($ret, 'webform', 'additional_submit', 'additional_submit', array('type' => 'text', 'not null' => TRUE));

  // Set not null property, default on webform_component.name.
  db_change_field($ret, 'webform_component', 'name', 'name', array('type' => 'varchar', 'length' => 255));

  // Set not null property on webform_component.value.
  db_change_field($ret, 'webform_component', 'value', 'value', array('type' => 'text', 'not null' => TRUE));
 
  // Set not null property on webform_component.extra.
  db_change_field($ret, 'webform_component', 'extra', 'extra', array('type' => 'text', 'not null' => TRUE));

  // Set column size, disp-width on webform_component.mandatory.
  db_change_field($ret, 'webform_component', 'mandatory', 'mandatory', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));

  // Set column size, disp-width, not null property on webform_component.weight.
  db_change_field($ret, 'webform_component', 'weight', 'weight', array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0));

  // Set unsigned, not null property on webform_submissions.sid.
  db_drop_unique_key($ret, 'webform_submissions', 'sid_nid');
  db_change_field($ret, 'webform_submissions', 'sid', 'sid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE));
  db_drop_primary_key($ret, 'webform_submissions');
  db_change_field($ret, 'webform_submissions', 'sid', 'sid', array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), array('primary key' => array('sid'), 'unique keys' => array('sid_nid' => array('sid', 'nid'))));

  // Temporarily drop all keys from the webform_submitted_data table for changes.
  db_drop_primary_key($ret, 'webform_submitted_data');
  db_drop_index($ret, 'webform_submitted_data', 'nid');
  db_drop_index($ret, 'webform_submitted_data', 'sid_nid');

  // Set unsigned, size on webform_submitted_data.no.
  db_change_field($ret, 'webform_submitted_data', 'no', 'no', array('type' => 'int', 'unsigned' => TRUE, 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));

  // Set size, not null property on webform_submitted_data.data.
  db_change_field($ret, 'webform_submitted_data', 'data', 'data', array('type' => 'text', 'size' => 'medium', 'not null' => TRUE));

  // Set correct keys.
  db_add_primary_key($ret, 'webform_submitted_data', array('nid', 'sid', 'cid', 'no'));
  db_add_index($ret, 'webform_submitted_data', 'nid', array('nid'));
  db_add_index($ret, 'webform_submitted_data', 'sid_nid', array('sid', 'nid'));

  return $ret;
}

/**
 * Add a separate column for confirmation message input format.
 */
function webform_update_6301() {
  $ret = array();

  // Safety check to prevent re-adding existing column.
  if (db_column_exists('webform', 'confirmation_format')) {
    return $ret;
  }

  db_add_field($ret, 'webform', 'confirmation_format', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
  $result = db_query("SELECT n.nid, nr.format FROM {node} n INNER JOIN {node_revisions} nr ON n.vid = nr.vid WHERE n.type = 'webform'");
  while ($node = db_fetch_object($result)) {
    db_query('UPDATE {webform} SET confirmation_format = %d WHERE nid = %d', $node->format, $node->nid);
  }

  return $ret;
}

/**
 * Convert node-level e-mail settings to new webform_emails table.
 */
function webform_update_6302() {
  $ret = array();

  // Safety check to prevent recreating the webform_emails table.
  if (db_table_exists('webform_emails')) {
    return $ret;
  }

  $table = array(
    'fields' => array(
      'nid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'eid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'size' => 'small',
        'not null' => TRUE,
        'default' => 0,
      ),
      'email' => array(
        'type' => 'text',
        'not null' => FALSE,
      ),
      'subject' => array(
        'type' => 'varchar',
        'length' => '255',
        'not null' => FALSE,
      ),
      'from_name' => array(
        'type' => 'varchar',
        'length' => '255',
        'not null' => FALSE,
      ),
      'from_address' => array(
        'type' => 'varchar',
        'length' => '255',
        'not null' => FALSE,
      ),
      'template' => array(
        'type' => 'text',
        'not null' => FALSE,
      )
    ),
    'primary key' => array('nid', 'eid'),
  );

  db_create_table($ret, 'webform_emails', $table);

  // Move over data from the webform table.
  $result = db_query("SELECT w.nid, w.email, w.email_from_name, w.email_from_address, w.email_subject, wc.cid, wc.extra FROM {webform} w LEFT JOIN {webform_component} wc ON w.nid = wc.nid AND type IN ('select', 'hidden', 'email') ORDER BY nid");
  $nid = 0;
  while ($row = db_fetch_object($result)) {
    // Insert an e-mail settings row for the default e-mail.
    if ($row->nid != $nid) {
      $nid = $row->nid;
      $eid = 0;
      if (!empty($row->email)) {
        $eid++;
        db_query("INSERT INTO {webform_emails} (nid, eid, email, subject, from_name, from_address, template) VALUES (%d, %d, '%s', '%s', '%s', '%s', 'default')", $nid, $eid, $row->email, $row->email_subject, $row->email_from_name, $row->email_from_address);
      }
    }

    // Check for an e-mail based on a component.
    if ($row->extra) {
      $extra = unserialize($row->extra);
      if ($extra['email']) {
        $eid++;
        unset($extra['email']);
        db_query("INSERT INTO {webform_emails} (nid, eid, email, subject, from_name, from_address, template) VALUES (%d, %d, '%s', '%s', '%s', '%s', 'default')", $nid, $eid, $row->cid, $row->email_subject, $row->email_from_name, $row->email_from_address);
        db_query("UPDATE {webform_component} SET extra = '%s' WHERE nid = %d AND cid = %d", serialize($extra), $row->nid, $row->cid);
      }
    }
  }

  // Remove columns from webform table.
  db_drop_field($ret, 'webform', 'email');
  db_drop_field($ret, 'webform', 'email_from_name');
  db_drop_field($ret, 'webform', 'email_from_address');
  db_drop_field($ret, 'webform', 'email_subject');

  return $ret;
}

/**
 * Add the submit_notice field and update all existing webforms to the 2.x previous submissions notice default.
 */
function webform_update_6303() {
  $ret = array();
  db_add_field($ret, 'webform', 'submit_notice', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
  $ret[] = update_sql("UPDATE {webform} SET submit_notice = 1 WHERE submit_notice = 0");
  return $ret;
}

/**
 * Convert the webform content type to be owned by Node module.
 */
function webform_update_6304() {
  $ret = array();
  $ret[] = update_sql("UPDATE {node_type} SET module = 'node', custom = 1, modified = 1, locked = 0 WHERE type = 'webform'");
  if (!db_affected_rows()) {
    $ret[] = update_sql("INSERT INTO {node_type} (type, name, module, description, help, has_title, title_label, has_body, body_label, min_word_count, custom, modified, locked, orig_type) VALUES ('webform', 'Webform', 'node', 'Create a new form or questionnaire accessible to users. Submission results and statistics are recorded and accessible to privileged users.', '', 1, 'Title', 1, 'Body', 0, 1, 1, 0, 'webform')");
  }

  // This variable for some reason must be set manually.
  variable_set('webform_node_types', array('webform'));

  return $ret;
}

/**
 * Migrate the renamed permissions. Add separate permissions for delete.
 */
function webform_update_6305() {
  $ret = array();

  $updated_permissions = array(
    'create webforms' => array('add webform content'),
    'edit own webforms' => array('edit own webform content', 'delete own webform content'),
    'edit webforms' => array('edit any webform content', 'delete any webform content'),
    'access webform results' => array('access all webform results'),
    'edit webform submissions' => array('edit all webform submissions'),
    'edit own webform submissions' => array('edit own webform submissions', 'delete own webform submissions'),
    'clear webform results' => array('delete all webform submissions'),
  );

  $result = db_query("SELECT * FROM {role} r INNER JOIN {permission} p ON p.rid = r.rid");
  while ($role = db_fetch_object($result)) {
    $role->perm = drupal_map_assoc(explode(', ', $role->perm));
    foreach ($updated_permissions as $old => $new) {
      if (isset($role->perm[$old])) {
        unset($role->perm[$old]);
        foreach ($new as $perm) {
          $role->perm[$perm] = $perm;
        }
      }
    }
    $ret[] = update_sql("UPDATE {permission} SET perm = '" . implode(', ', $role->perm) . "' WHERE rid = " . $role->rid);
  }

  return $ret;
}

/**
 * Add the ability to save as draft.
 */
function webform_update_6306() {
  $ret = array();
  db_add_field($ret, 'webform', 'allow_draft', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
  db_add_field($ret, 'webform_submissions', 'is_draft', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
  return $ret;
}

/**
 * Convert the file component to use only FIDs instead of serialized arrays.
 */
function webform_update_6307() {
  $ret = array();

  $result = db_query("SELECT d.*, s.uid, s.submitted FROM {webform_submitted_data} d INNER JOIN {webform_component} c ON d.cid = c.cid AND d.nid = c.nid AND c.type = 'file' INNER JOIN {webform_submissions} s ON d.sid = s.sid");
  while ($row = db_fetch_object($result)) {
    $file = @unserialize($row->data);
    // File name should always exist, even when upgrading from Drupal 5.
    if ($file && isset($file['filename'])) {
      // Create an entry in the files table if needed.
      if (!isset($file['fid'])) {
        db_query("INSERT INTO {files} (uid, filename, filepath, filemime, filesize, status, timestamp) VALUES (%d, '%s', '%s', '%s', %d, 1, %d)", $row->uid, $file['filename'], $file['filepath'], $file['filemime'], $file['filesize'], $row->submitted);
        $fid = db_last_insert_id('files', 'fid');
      }
      else {
        $fid = $file['fid'];
      }
      // Update the submitted data with the FID.
      db_query("UPDATE {webform_submitted_data} SET data = '%d' WHERE nid = %d AND sid = %d AND cid = %d", $fid, $row->nid, $row->sid, $row->cid);
    }
    // Insert an empty entry, now just an empty string.
    else {
      db_query("UPDATE {webform_submitted_data} SET data = '' WHERE nid = %d AND sid = %d AND cid = %d", $row->nid, $row->sid, $row->cid);
    }
  }

  $ret[] = array('success' => TRUE, 'query' => t('Updated file components to use numeric file IDs in the submitted values.'));

  return $ret;
}

/**
 * Convert "Include in e-mail" from the component-level to a per e-mail setting.
 */
function webform_update_6308() {
  $ret = array();

  // Add the new column to the e-mail table.
  db_add_field($ret, 'webform_emails', 'excluded_components', array('type' => 'text', 'not null' => TRUE, 'initial' => ''));

  // Build up our EXCLUSION lists, finding all components not in e-mails.
  $result = db_query("SELECT nid, cid FROM {webform_component} WHERE email = 0 ORDER BY nid");
  $nid = 0;
  $excluded_components = array();
  while ($row = db_fetch_object($result)) {
    if ($nid != $row->nid) {
      if (!empty($excluded_components)) {
        db_query("UPDATE {webform_emails} SET excluded_components = '%s' WHERE nid = %d", implode(',', $excluded_components), $nid);
      }
      $nid = $row->nid;
      $excluded_components = array();
    }
    $excluded_components[] = $row->cid;
  }

  // One last query for the last form in the list.
  if (!empty($excluded_components)) {
    db_query("UPDATE {webform_emails} SET excluded_components = '%s' WHERE nid = %d", implode(',', $excluded_components), $nid);
  }

  db_drop_field($ret, 'webform_component', 'email');

  return $ret;
}

/**
 * Fix permissions for all roles by adding an additional space after comma.
 */
function webform_update_6309() {
  $ret = array();

  $result = db_query("SELECT r.rid, p.perm FROM {role} r INNER JOIN {permission} p ON p.rid = r.rid");
  while ($role = db_fetch_object($result)) {
    $perms = explode(',', $role->perm);
    foreach ($perms as $id => $perm) {
      $perms[$id] = trim($perm);
    }
    $ret[] = update_sql("UPDATE {permission} SET perm = '" . implode(', ', $perms) . "' WHERE rid = " . $role->rid);
  }

  return $ret;
}

/**
 * Add the redirect_url field and update existing webforms to use it.
 */
function webform_update_6310() {
  $ret = array();

  // Safety check to prevent re-adding existing column.
  if (db_column_exists('webform', 'redirect_url')) {
    return $ret;
  }

  // Add the new redirect_url column.
  db_add_field($ret, 'webform', 'redirect_url', array('type' => 'varchar', 'length' => '255'));

  // If the webform is using the confirmation field as a redirect then move it
  // to the new redirect_url field.
  $result = db_query("SELECT nid, confirmation FROM {webform}");
  while ($row = db_fetch_object($result)) {
    $confirmation = trim(strip_tags($row->confirmation, '<front>'));
    if (valid_url($confirmation, TRUE) || preg_match('/^internal:/', $confirmation)) {
      $redirect_url = preg_replace('/^internal:/', '', $confirmation);
      db_query("UPDATE {webform} SET redirect_url = '%s' WHERE nid = %d", $redirect_url, $row->nid);
      db_query("UPDATE {webform} SET confirmation = '' WHERE nid = %d", $row->nid);
    }
    elseif (preg_match('/^message:/', $confirmation)) {
      $message = preg_replace('/^message:/', '', $confirmation);
      db_query("UPDATE {webform} SET redirect_url = '%s' WHERE nid = %d", 'node/' . $row->nid, $row->nid);
      db_query("UPDATE {webform} SET confirmation = '%s' WHERE nid = %d", $message, $row->nid);
    }
  }

  return $ret;
}

/**
 * Convert the "no" column to be a varchar column instead of an integer.
 */
function webform_update_6311() {
  $ret = array();

  // Change the column.
  db_drop_primary_key($ret, 'webform_submitted_data');
  db_change_field($ret, 'webform_submitted_data', 'no', 'no', array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => '0'), array());
  db_add_primary_key($ret, 'webform_submitted_data', array('nid', 'sid', 'cid', 'no'));

  // Update date components.
  $ret[] = update_sql("UPDATE {webform_submitted_data} SET no = 'month' WHERE no = '0' AND (nid, cid) IN (SELECT nid, cid FROM {webform_component} WHERE type = 'date')");
  $ret[] = update_sql("UPDATE {webform_submitted_data} SET no = 'day' WHERE no = '1' AND (nid, cid) IN (SELECT nid, cid FROM {webform_component} WHERE type = 'date')");
  $ret[] = update_sql("UPDATE {webform_submitted_data} SET no = 'year' WHERE no = '2' AND (nid, cid) IN (SELECT nid, cid FROM {webform_component} WHERE type = 'date')");

  // Update time components.
  $ret[] = update_sql("UPDATE {webform_submitted_data} SET no = 'hour' WHERE no = '0' AND (nid, cid) IN (SELECT nid, cid FROM {webform_component} WHERE type = 'time')");
  $ret[] = update_sql("UPDATE {webform_submitted_data} SET no = 'minute' WHERE no = '1' AND (nid, cid) IN (SELECT nid, cid FROM {webform_component} WHERE type = 'time')");
  $ret[] = update_sql("UPDATE {webform_submitted_data} SET no = 'ampm' WHERE no = '2' AND (nid, cid) IN (SELECT nid, cid FROM {webform_component} WHERE type = 'time')");

  // Updating of select and grid components is done in following updates.

  return $ret;
}

/**
 * Convert select options to use numeric keys if none are manually specified.
 */
function webform_update_6312() {
  $ret = array();

  $result = db_query("SELECT * FROM {webform_component} WHERE type = 'select'");
  while ($row = db_fetch_object($result)) {
    $extra = unserialize($row->extra);
    $lines = explode("\n", $extra['items']);

    // Get a list of items that have manual keys or no keys at all.
    $keys = array();
    $values = array();
    foreach ($lines as $line) {
      $line = rtrim($line, "\r\n");
      if (strlen($line) == 0) {
        continue;
      }
      $matches = array();
      if (preg_match('/^\<([^>]*)\>$/', $line, $matches)) {
        $keys[] = '<group>';
        $values[] = $line;
      }
      elseif (preg_match('/^([^|]+)\|(.*)$/', $line, $matches)) {
        $keys[] = $matches[1];
        $values[] = $matches[2];
      }
      else {
        $keys[] = '<unknown>';
        $values[] = $line;
      }
    }

    // Assign new keys to items that have none.
    $new_key = 0;
    $new_keys = array();
    $items = '';
    foreach ($keys as $n => $key) {
      if ($key == '<group>') {
        $items .= $values[$n] . "\n";
      }
      elseif ($key == '<unknown>') {
        while (in_array($new_key, $keys, TRUE) || in_array($new_key, $new_keys, TRUE)) {
          $new_key++;
        }
        $new_keys[$n] = $new_key;
        $items .= $new_key . '|' . $values[$n] . "\n";
      }
      else {
        $items .= $key . '|' . $values[$n] . "\n";
      }
    }

    // While we're here, get rid of the 'Y' value for options.
    foreach ($extra as $key => $value) {
      if ($value === 'Y') {
        $extra[$key] = '1';
      }
      elseif ($value === 'N') {
        $extra[$key] = 0;
      }
    }

    // Update the component.
    $extra['items'] = $items;
    db_query("UPDATE {webform_component} SET extra = '%s' WHERE nid = %d AND cid = %d", serialize($extra), $row->nid, $row->cid);

    // Update the saved results.
    foreach ($new_keys as $delta => $new_key) {
      db_query("UPDATE {webform_submitted_data} SET data = '%s' WHERE nid = %d AND cid = %d AND data = '%s'", $new_key, $row->nid, $row->cid, $values[$delta]);
    }

    // Delete empty rows, which are no longer stored for select lists.
    db_query("DELETE FROM {webform_submitted_data} WHERE data = '' AND nid = %d AND cid = %d");
  }

  return $ret;
}

/**
 * Create keys for all questions (which don't currently have keys at all),
 * create keys for all options that don't yet have any, and then convert the
 * existing data to use these keys.
 */
function webform_update_6313() {
  $ret = array();

  $result = db_query("SELECT * FROM {webform_component} WHERE type = 'grid'");
  while ($row = db_fetch_object($result)) {
    $extra = unserialize($row->extra);
    $lines = explode("\n", $extra['options']);

    // Get a list of items that have manual keys or no keys at all.
    $keys = array();
    $values = array();
    foreach ($lines as $line) {
      $line = rtrim($line, "\r\n");
      if (strlen($line) == 0) {
        continue;
      }
      $matches = array();
      if (preg_match('/^([^|]+)\|(.*)$/', $line, $matches)) {
        $keys[] = $matches[1];
        $values[] = $matches[2];
      }
      else {
        $keys[] = '<unknown>';
        $values[] = $line;
      }
    }

    // Assign new keys to options that have none.
    $new_key = 0;
    $new_keys = array();
    $options = '';
    foreach ($keys as $n => $key) {
      if ($key == '<unknown>') {
        while (in_array($new_key, $keys, TRUE) || in_array($new_key, $new_keys, TRUE)) {
          $new_key++;
        }
        $new_keys[$n] = $new_key;
        $options .= $new_key . '|' . $values[$n] . "\n";
      }
      else {
        $options .= $key . '|' . $values[$n] . "\n";
      }
    }
    $extra['options'] = $options;

    // Assign question keys. This is easier since they don't have keys at all.
    $lines = explode("\n", $extra['questions']);
    $questions = array();
    foreach ($lines as $delta => $line) {
      $line = rtrim($line, "\r\n");
      if (strlen($line) == 0) {
        continue;
      }
      $questions[$delta] = $delta . '|' . $line;
    }
    $extra['questions'] = implode("\n", $questions);

    // While we're here, get rid of the 'Y' value for options.
    foreach ($extra as $key => $value) {
      if ($value === 'Y') {
        $extra[$key] = '1';
      }
      elseif ($value === 'N') {
        $extra[$key] = 0;
      }
    }

    // Update the component.
    db_query("UPDATE {webform_component} SET extra = '%s' WHERE nid = %d AND cid = %d", serialize($extra), $row->nid, $row->cid);

    // Convert the option values into keys if new ones were created.
    foreach ($new_keys as $delta => $new_key) {
      db_query("UPDATE {webform_submitted_data} SET data = '%s' WHERE nid = %d AND cid = %d AND data = '%s'", $new_key, $row->nid, $row->cid, $values[$delta]);
    }

    // Note: Converting the question values into keys is not necessary because
    // data was already stored based on the question position. Since we assigned
    // permanent keys based on position, all our keys are already accurate.

    // Delete empty rows, which are no longer stored for grids.
    db_query("DELETE FROM {webform_submitted_data} WHERE data = '' AND nid = %d AND cid = %d");
  }

  return $ret;
}

/**
 * Convert Dates and Times into using ISO 8601 strings instead of 3 rows.
 */
function webform_update_6314() {
  $ret = array();

  // Note that we can't use webform_component_include(), because calls to
  // hook_webform_component_info() will fail if webform.module is disabled.
  drupal_load('module', 'webform');
  module_load_include('inc', 'webform', 'components/time');
  module_load_include('inc', 'webform', 'components/date');

  $result = db_query("SELECT c.type, c.extra, d.* FROM {webform_component} c INNER JOIN {webform_submitted_data} d ON c.nid = d.nid AND c.cid = d.cid WHERE c.type = 'time' OR c.type = 'date' ORDER BY d.sid, d.cid");
  $current_cid = NULL;
  $current_sid = NULL;
  while ($row = db_fetch_object($result)) {
    if ($current_cid != $row->cid || $current_sid != $row->sid) {
      $current_cid = $row->cid;
      $current_sid = $row->sid;
      $extra = unserialize($row->extra);
      $value = array();
    }

    $value[$row->no] = $row->data;

    // Update a complete date.
    if ($row->type == 'date' && array_key_exists('day', $value) && array_key_exists('month', $value) && array_key_exists('year', $value)) {
      $value = ($value['day'] && $value['month'] && $value['year']) ? webform_date_string($value, 'date') : '';
      db_query("UPDATE {webform_submitted_data} SET no = '0', data = '%s' WHERE sid = %d AND cid = %d AND no = 'day'", $value, $current_sid, $current_cid);
    }

    // Update a complete time.
    if ($row->type == 'time' && array_key_exists('hour', $value) && array_key_exists('minute', $value) && ($extra['hourformat'] == '24-hour' || array_key_exists('ampm', $value))) {
      if ($extra['hourformat'] == '12-hour') {
        $value = webform_time_convert($value, '24-hour');
      }
      $value = ($value['hour']) ? webform_date_string($value, 'time') : '';
      db_query("UPDATE {webform_submitted_data} SET no = '0', data = '%s' WHERE sid = %d AND cid = %d AND no = 'hour'", $value, $current_sid, $current_cid);
    }
  }

  // Remove all extra rows (which should just be day, minute, and ampm values).
  db_query("DELETE FROM {webform_submitted_data} WHERE no IN ('day', 'month', 'year', 'hour', 'minute', 'second', 'ampm')");

  $ret[] = array('success' => TRUE, 'query' => t('Updated date and time components to use ISO 8601 formatted strings in the submitted values.'));

  return $ret;
}

/**
 * Remove "daylight_savings" and the GMT option in "timezone".
 */
function webform_update_6315() {
  $ret = array();

  $result = db_query("SELECT * FROM {webform_component} WHERE type IN ('date', 'time')");
  while ($row = db_fetch_object($result)) {
    $extra = unserialize($row->extra);
    if ($extra['timezone'] == 'gmt') {
      $extra['timezone'] = 'site';
    }
    unset($extra['check_daylight_savings']);
    db_query("UPDATE {webform_component} SET extra = '%s' WHERE nid = %d AND cid = %d", serialize($extra), $row->nid, $row->cid);
  }

  $ret[] = array('success' => TRUE, 'query' => t('Removed Webform-specific daylight savings handling, now provided by Date API module if available.'));

  return $ret;
}

/**
 * Remove the Webform Debug variable.
 */
function webform_update_6316() {
  $ret = array();
  variable_del('webform_debug');
  $ret[] = array('success' => TRUE, 'query' => t('Removed the webform_debug variable which is no longer used.'));
  return $ret;
}

/**
 * Remove orphaned e-mail settings of nodes that have been deleted.
 */
function webform_update_6317() {
  $ret = array();
  $ret[] = update_sql("DELETE FROM {webform_emails} WHERE nid NOT IN (SELECT nid FROM {node})");
  return $ret;
}

/**
 * Ensure that the confirmation format column is correctly using size = 'tiny'.
 */
function webform_update_6318() {
  $ret = array();
  db_change_field($ret, 'webform', 'confirmation_format', 'confirmation_format', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
  return $ret;
}

/**
 * Add columns for e-mail HTML and attachment settings.
 */
function webform_update_6319() {
  $ret = array();
  if (!db_column_exists('webform_emails', 'html')) {
    db_add_field($ret, 'webform_emails', 'html', array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'default' => 0, 'not null' => TRUE));
    db_add_field($ret, 'webform_emails', 'attachments', array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'default' => 0, 'not null' => TRUE));
  }
  return $ret;
}

/**
 * Set the default for the "submit_notice" column to 1.
 */
function webform_update_6320() {
  $ret = array();
  db_change_field($ret, 'webform', 'submit_notice', 'submit_notice', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 1));
  return $ret;
}

/**
 * Add field for block feature and redirection setting.
 */
function webform_update_6321() {
  $ret = array();
  db_add_field($ret, 'webform', 'block', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
  db_change_field($ret, 'webform', 'redirect_url', 'redirect_url', array('type' => 'varchar', 'length' => 255, 'default' => '<confirmation>'));
  update_sql("UPDATE {webform} SET redirect_url = '<confirmation>' WHERE redirect_url = ''");
  return $ret;
}

/**
 * Set additional_validate and additional_submit columns to allow NULL.
 */
function webform_update_6322() {
  $ret = array();
  if (db_column_exists('webform', 'additional_validate')) {
    db_change_field($ret, 'webform', 'additional_validate', 'additional_validate', array('type' => 'text', 'not null' => FALSE));
    db_change_field($ret, 'webform', 'additional_submit', 'additional_submit', array('type' => 'text', 'not null' => FALSE));
  }
  return $ret;
}

/**
 * Recursively delete all files and folders in the specified filepath, then
 * delete the containing folder.
 *
 * Note that this only deletes visible files with write permission
 *
 * @param string $path
 *   A filepath relative to file_directory_path
 */
function _webform_recursive_delete($path) {
  if ($path && is_dir($path)) {
    $listing = $path . '/*';
    foreach (glob($listing) as $file) {
      if (is_file($file) === TRUE) {
        @unlink($file);
      }
      elseif (is_dir($file) === TRUE) {
        _webform_recursive_delete($file);
      }
    }
    @rmdir($path);
  }
}
