diff sites/all/modules/webform/webform.api.php @ 0:ff03f76ab3fe

initial version
author danieleb <danielebarchiesi@me.com>
date Wed, 21 Aug 2013 18:51:11 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sites/all/modules/webform/webform.api.php	Wed Aug 21 18:51:11 2013 +0100
@@ -0,0 +1,923 @@
+<?php
+
+/**
+ * @file
+ * Sample hooks demonstrating usage in Webform.
+ */
+
+/**
+ * @defgroup webform_hooks Webform Module Hooks
+ * @{
+ * Webform's hooks enable other modules to intercept events within Webform, such
+ * as the completion of a submission or adding validation. Webform's hooks also
+ * allow other modules to provide additional components for use within forms.
+ */
+
+/**
+ * Define callbacks that can be used as select list options.
+ *
+ * When users create a select component, they may select a pre-built list of
+ * certain options. Webform core provides a few of these lists such as the
+ * United States, countries of the world, and days of the week. This hook
+ * provides additional lists that may be utilized.
+ *
+ * @see webform_options_example()
+ * @see hook_webform_select_options_info_alter()
+ *
+ * @return
+ *   An array of callbacks that can be used for select list options. This array
+ *   should be keyed by the "name" of the pre-defined list. The values should
+ *   be an array with the following additional keys:
+ *     - title: The translated title for this list.
+ *     - options callback: The name of the function that will return the list.
+ *     - options arguments: Any additional arguments to send to the callback.
+ *     - file: Optional. The file containing the options callback, relative to
+ *       the module root.
+ */
+function hook_webform_select_options_info() {
+  $items = array();
+
+  $items['days'] = array(
+    'title' => t('Days of the week'),
+    'options callback' => 'webform_options_days',
+    'file' => 'includes/webform.options.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Alter the list of select list options provided by Webform and other modules.
+ *
+ * @see hook_webform_select_options_info().
+ */
+function hook_webform_select_options_info_alter(&$items) {
+  // Remove the days of the week options.
+  unset($items['days']);
+}
+
+/**
+ * This is an example function to demonstrate a webform options callback.
+ *
+ * This function returns a list of options that Webform may use in a select
+ * component. In order to be called, the function name
+ * ("webform_options_example" in this case), needs to be specified as a callback
+ * in hook_webform_select_options_info().
+ *
+ * @param $component
+ *   The Webform component array for the select component being displayed.
+ * @param $flat
+ *   Boolean value indicating whether the returned list needs to be a flat array
+ *   of key => value pairs. Select components support up to one level of
+ *   nesting, but when results are displayed, the list needs to be returned
+ *   without the nesting.
+ * @param $filter
+ *   Boolean value indicating whether the included options should be passed
+ *   through the _webform_filter_values() function for token replacement (only)
+ *   needed if your list contains tokens).
+ * @param $arguments
+ *   The "options arguments" specified in hook_webform_select_options_info().
+ * @return
+ *   An array of key => value pairs suitable for a select list's #options
+ *   FormAPI property.
+ */
+function webform_options_example($component, $flat, $filter, $arguments) {
+  $options = array(
+    'one' => t('Pre-built option one'),
+    'two' => t('Pre-built option two'),
+    'three' => t('Pre-built option three'),
+  );
+
+  return $options;
+}
+
+/**
+ * Respond to the loading of Webform submissions.
+ *
+ * @param $submissions
+ *   An array of Webform submissions that are being loaded, keyed by the
+ *   submission ID. Modifications to the submissions are done by reference.
+ */
+function hook_webform_submission_load(&$submissions) {
+  foreach ($submissions as $sid => $submission) {
+    $submissions[$sid]->new_property = 'foo';
+  }
+}
+
+/**
+ * Modify a Webform submission, prior to saving it in the database.
+ *
+ * @param $node
+ *   The Webform node on which this submission was made.
+ * @param $submission
+ *   The Webform submission that is about to be saved to the database.
+ */
+function hook_webform_submission_presave($node, &$submission) {
+  // Update some component's value before it is saved.
+  $component_id = 4;
+  $submission->data[$component_id]['value'][0] = 'foo';
+}
+
+/**
+ * Respond to a Webform submission being inserted.
+ *
+ * Note that this hook is called after a submission has already been saved to
+ * the database. If needing to modify the submission prior to insertion, use
+ * hook_webform_submission_presave().
+ *
+ * @param $node
+ *   The Webform node on which this submission was made.
+ * @param $submission
+ *   The Webform submission that was just inserted into the database.
+ */
+function hook_webform_submission_insert($node, $submission) {
+  // Insert a record into a 3rd-party module table when a submission is added.
+  db_insert('mymodule_table')
+    ->fields(array(
+      'nid' => $node->nid,
+      'sid' => $submission->sid,
+      'foo' => 'foo_data',
+    ))
+    ->execute();
+}
+
+/**
+ * Respond to a Webform submission being updated.
+ *
+ * Note that this hook is called after a submission has already been saved to
+ * the database. If needing to modify the submission prior to updating, use
+ * hook_webform_submission_presave().
+ *
+ * @param $node
+ *   The Webform node on which this submission was made.
+ * @param $submission
+ *   The Webform submission that was just updated in the database.
+ */
+function hook_webform_submission_update($node, $submission) {
+  // Update a record in a 3rd-party module table when a submission is updated.
+  db_update('mymodule_table')
+    ->fields(array(
+      'foo' => 'foo_data',
+    ))
+    ->condition('nid', $node->nid)
+    ->condition('sid', $submission->sid)
+    ->execute();
+}
+
+/**
+ * Respond to a Webform submission being deleted.
+ *
+ * @param $node
+ *   The Webform node on which this submission was made.
+ * @param $submission
+ *   The Webform submission that was just deleted from the database.
+ */
+function hook_webform_submission_delete($node, $submission) {
+  // Delete a record from a 3rd-party module table when a submission is deleted.
+  db_delete('mymodule_table')
+    ->condition('nid', $node->nid)
+    ->condition('sid', $submission->sid)
+    ->execute();
+}
+
+/**
+ * Provide a list of actions that can be executed on a submission.
+ *
+ * Some actions are displayed in the list of submissions such as edit, view, and
+ * delete. All other actions are displayed only when viewing the submission.
+ * These additional actions may be specified in this hook. Examples included
+ * directly in the Webform module include PDF, print, and resend e-mails. Other
+ * modules may extend this list by using this hook.
+ *
+ * @param $node
+ *   The Webform node on which this submission was made.
+ * @param $submission
+ *   The Webform submission on which the actions may be performed.
+ */
+function hook_webform_submission_actions($node, $submission) {
+  if (webform_results_access($node)) {
+    $actions['myaction'] = array(
+      'title' => t('Do my action'),
+      'href' => 'node/' . $node->nid . '/submission/' . $submission->sid . '/myaction',
+      'query' => drupal_get_destination(),
+    );
+  }
+
+  return $actions;
+}
+
+/**
+ * Alter the display of a Webform submission.
+ *
+ * This function applies to both e-mails sent by Webform and normal display of
+ * submissions when viewing through the adminsitrative interface.
+ *
+ * @param $renderable
+ *   The Webform submission in a renderable array, similar to FormAPI's
+ *   structure. This variable must be passed in by-reference. Important
+ *   properties of this array include #node, #submission, #email, and #format,
+ *   which can be used to find the context of the submission that is being
+ *   rendered.
+ */
+function hook_webform_submission_render_alter(&$renderable) {
+  // Remove page breaks from sent e-mails.
+  if (isset($renderable['#email'])) {
+    foreach (element_children($renderable) as $key) {
+      if ($renderable[$key]['#component']['type'] == 'pagebreak') {
+        unset($renderable[$key]);
+      }
+    }
+  }
+}
+
+/**
+ * Modify a loaded Webform component.
+ *
+ * IMPORTANT: This hook does not actually exist because components are loaded
+ * in bulk as part of webform_node_load(). Use hook_node_load() to modify loaded
+ * components when the node is loaded. This example is provided merely to point
+ * to hook_node_load().
+ *
+ * @see hook_nodeapi()
+ * @see webform_node_load()
+ */
+function hook_webform_component_load() {
+  // This hook does not exist. Instead use hook_node_load().
+}
+
+/**
+ * Modify a Webform component before it is saved to the database.
+ *
+ * Note that most of the time this hook is not necessary, because Webform will
+ * automatically add data to the component based on the component form. Using
+ * hook_form_alter() will be sufficient in most cases.
+ *
+ * @see hook_form_alter()
+ * @see webform_component_edit_form()
+ *
+ * @param $component
+ *   The Webform component being saved.
+ */
+function hook_webform_component_presave(&$component) {
+  $component['extra']['new_option'] = 'foo';
+}
+
+/**
+ * Respond to a Webform component being inserted into the database.
+ */
+function hook_webform_component_insert($component) {
+  // Insert a record into a 3rd-party module table when a component is inserted.
+  db_insert('mymodule_table')
+    ->fields(array(
+      'nid' => $component['nid'],
+      'cid' => $component['cid'],
+      'foo' => 'foo_data',
+    ))
+    ->execute();
+}
+
+/**
+ * Respond to a Webform component being updated in the database.
+ */
+function hook_webform_component_update($component) {
+  // Update a record in a 3rd-party module table when a component is updated.
+  db_update('mymodule_table')
+    ->fields(array(
+      'foo' => 'foo_data',
+    ))
+    ->condition('nid', $component['nid'])
+    ->condition('cid', $component['cid'])
+    ->execute();
+}
+
+/**
+ * Respond to a Webform component being deleted.
+ */
+function hook_webform_component_delete($component) {
+  // Delete a record in a 3rd-party module table when a component is deleted.
+  db_delete('mymodule_table')
+    ->condition('nid', $component['nid'])
+    ->condition('cid', $component['cid'])
+    ->execute();
+}
+
+/**
+ * Define components to Webform.
+ *
+ * @return
+ *   An array of components, keyed by machine name. Required properties are
+ *   "label" and "description". The "features" array defines which capabilities
+ *   the component has, such as being displayed in e-mails or csv downloads.
+ *   A component like "markup" for example would not show in these locations.
+ *   The possible features of a component include:
+ *
+ *     - csv
+ *     - email
+ *     - email_address
+ *     - email_name
+ *     - required
+ *     - conditional
+ *     - spam_analysis
+ *     - group
+ *
+ *   Note that most of these features do not indicate the default state, but
+ *   determine if the component can have this property at all. Setting
+ *   "required" to TRUE does not mean that a component's fields will always be
+ *   required, but instead give the option to the administrator to choose the
+ *   requiredness. See the example implementation for details on how these
+ *   features may be set.
+ *
+ *   An optional "file" may be specified to be loaded when the component is
+ *   needed. A set of callbacks will be established based on the name of the
+ *   component. All components follow the pattern:
+ *
+ *   _webform_[callback]_[component]
+ *
+ *   Where [component] is the name of the key of the component and [callback] is
+ *   any of the following:
+ *
+ *     - defaults
+ *     - edit
+ *     - render
+ *     - display
+ *     - submit
+ *     - delete
+ *     - help
+ *     - theme
+ *     - analysis
+ *     - table
+ *     - csv_headers
+ *     - csv_data
+ *
+ * See the sample component implementation for details on each one of these
+ * callbacks.
+ *
+ * @see webform_components()
+ */
+function hook_webform_component_info() {
+  $components = array();
+
+  $components['textfield'] = array(
+    'label' => t('Textfield'),
+    'description' => t('Basic textfield type.'),
+    'features' => array(
+      // Add content to CSV downloads. Defaults to TRUE.
+      'csv' => TRUE,
+
+      // This component supports default values. Defaults to TRUE.
+      'default_value' => FALSE,
+
+      // This component supports a description field. Defaults to TRUE.
+      'description' => FALSE,
+
+      // Show this component in e-mailed submissions. Defaults to TRUE.
+      'email' => TRUE,
+
+      // Allow this component to be used as an e-mail FROM or TO address.
+      // Defaults to FALSE.
+      'email_address' => FALSE,
+
+      // Allow this component to be used as an e-mail SUBJECT or FROM name.
+      // Defaults to FALSE.
+      'email_name' => TRUE,
+
+      // This component may be toggled as required or not. Defaults to TRUE.
+      'required' => TRUE,
+
+      // This component supports a title attribute. Defaults to TRUE.
+      'title' => FALSE,
+
+      // This component has a title that can be toggled as displayed or not.
+      'title_display' => TRUE,
+
+      // This component has a title that can be displayed inline.
+      'title_inline' => TRUE,
+
+      // If this component can be used as a conditional SOURCE. All components
+      // may always be displayed conditionally, regardless of this setting.
+      // Defaults to TRUE.
+      'conditional' => TRUE,
+
+      // If this component allows other components to be grouped within it
+      // (like a fieldset or tabs). Defaults to FALSE.
+      'group' => FALSE,
+
+      // If this component can be used for SPAM analysis, usually with Mollom.
+      'spam_analysis' => FALSE,
+
+      // If this component saves a file that can be used as an e-mail
+      // attachment. Defaults to FALSE.
+      'attachment' => FALSE,
+    ),
+    'file' => 'components/textfield.inc',
+  );
+
+  return $components;
+}
+
+/**
+ * Alter the list of available Webform components.
+ *
+ * @param $components
+ *   A list of existing components as defined by hook_webform_component_info().
+ *
+ * @see hook_webform_component_info()
+ */
+function hook_webform_component_info_alter(&$components) {
+  // Completely remove a component.
+  unset($components['grid']);
+
+  // Change the name of a component.
+  $components['textarea']['label'] = t('Text box');
+}
+
+/**
+ * Alter access to a Webform submission.
+ *
+ * @param $node
+ *   The Webform node on which this submission was made.
+ * @param $submission
+ *   The Webform submission.
+ * @param $op
+ *   The operation to be performed on the submission. Possible values are:
+ *   - "view"
+ *   - "edit"
+ *   - "delete"
+ *   - "list"
+ * @param $account
+ *   A user account object.
+ * @return
+ *   TRUE if the current user has access to submission,
+ *   or FALSE otherwise.
+ */
+function hook_webform_submission_access($node, $submission, $op = 'view', $account = NULL) {
+  switch ($op) {
+    case 'view':
+      return TRUE;
+      break;
+    case 'edit':
+      return FALSE;
+      break;
+    case 'delete':
+      return TRUE;
+      break;
+    case 'list':
+      return TRUE;
+      break;
+  }
+}
+
+/**
+ * Determine if a user has access to see the results of a webform.
+ *
+ * Note in addition to the view access to the results granted here, the $account
+ * must also have view access to the Webform node in order to see results.
+ *
+ * @see webform_results_access().
+ *
+ * @param $node
+ *   The Webform node to check access on.
+ * @param $account
+ *   The user account to check access on.
+ * @return
+ *   TRUE or FALSE if the user can access the webform results.
+ */
+function hook_webform_results_access($node, $account) {
+  // Let editors view results of unpublished webforms.
+  if ($node->status == 0 && in_array('editor', $account->roles)) {
+    return TRUE;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Return an array of files associated with the component.
+ *
+ * The output of this function will be used to attach files to e-mail messages.
+ *
+ * @param $component
+ *   A Webform component array.
+ * @param $value
+ *   An array of information containing the submission result, directly
+ *   correlating to the webform_submitted_data database schema.
+ * @return
+ *   An array of files, each file is an array with following keys:
+ *     - filepath: The relative path to the file.
+ *     - filename: The name of the file including the extension.
+ *     - filemime: The mimetype of the file.
+ *   This will result in an array looking something like this:
+ *   @code
+ *   array[0] => array(
+ *     'filepath' => '/sites/default/files/attachment.txt',
+ *     'filename' => 'attachment.txt',
+ *     'filemime' => 'text/plain',
+ *   );
+ *   @endcode
+ */
+function _webform_attachments_component($component, $value) {
+  $files = array();
+  $files[] = (array) file_load($value[0]);
+  return $files;
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup webform_component Sample Webform Component
+ * @{
+ * In each of these examples, the word "component" should be replaced with the,
+ * name of the component type (such as textfield or select). These are not
+ * actual hooks, but instead samples of how Webform integrates with its own
+ * built-in components.
+ */
+
+/**
+ * Specify the default properties of a component.
+ *
+ * @return
+ *   An array defining the default structure of a component.
+ */
+function _webform_defaults_component() {
+  return array(
+    'name' => '',
+    'form_key' => NULL,
+    'mandatory' => 0,
+    'pid' => 0,
+    'weight' => 0,
+    'extra' => array(
+      'options' => '',
+      'questions' => '',
+      'optrand' => 0,
+      'qrand' => 0,
+      'description' => '',
+    ),
+  );
+}
+
+/**
+ * Generate the form for editing a component.
+ *
+ * Create a set of form elements to be displayed on the form for editing this
+ * component. Use care naming the form items, as this correlates directly to the
+ * database schema. The component "Name" and "Description" fields are added to
+ * every component type and are not necessary to specify here (although they
+ * may be overridden if desired).
+ *
+ * @param $component
+ *   A Webform component array.
+ * @return
+ *   An array of form items to be displayed on the edit component page
+ */
+function _webform_edit_component($component) {
+  $form = array();
+
+  // Disabling the description if not wanted.
+  $form['description'] = array();
+
+  // Most options are stored in the "extra" array, which stores any settings
+  // unique to a particular component type.
+  $form['extra']['options'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Options'),
+    '#default_value' => $component['extra']['options'],
+    '#description' => t('Key-value pairs may be entered separated by pipes. i.e. safe_key|Some readable option') . theme('webform_token_help'),
+    '#cols' => 60,
+    '#rows' => 5,
+    '#weight' => -3,
+    '#required' => TRUE,
+  );
+  return $form;
+}
+
+/**
+ * Render a Webform component to be part of a form.
+ *
+ * @param $component
+ *   A Webform component array.
+ * @param $value
+ *   If editing an existing submission or resuming a draft, this will contain
+ *   an array of values to be shown instead of the default in the component
+ *   configuration. This value will always be an array, keyed numerically for
+ *   each value saved in this field.
+ * @param $filter
+ *   Whether or not to filter the contents of descriptions and values when
+ *   rendering the component. Values need to be unfiltered to be editable by
+ *   Form Builder.
+ *
+ * @see _webform_client_form_add_component()
+ */
+function _webform_render_component($component, $value = NULL, $filter = TRUE) {
+  $form_item = array(
+    '#type' => 'textfield',
+    '#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
+    '#required' => $component['mandatory'],
+    '#weight' => $component['weight'],
+    '#description'   => $filter ? _webform_filter_descriptions($component['extra']['description']) : $component['extra']['description'],
+    '#default_value' => $filter ? _webform_filter_values($component['value']) : $component['value'],
+    '#prefix' => '<div class="webform-component-textfield" id="webform-component-' . $component['form_key'] . '">',
+    '#suffix' => '</div>',
+  );
+
+  if (isset($value)) {
+    $form_item['#default_value'] = $value[0];
+  }
+
+  return $form_item;
+}
+
+/**
+ * Display the result of a submission for a component.
+ *
+ * The output of this function will be displayed under the "Results" tab then
+ * "Submissions". This should output the saved data in some reasonable manner.
+ *
+ * @param $component
+ *   A Webform component array.
+ * @param $value
+ *   An array of information containing the submission result, directly
+ *   correlating to the webform_submitted_data database table schema.
+ * @param $format
+ *   Either 'html' or 'text'. Defines the format that the content should be
+ *   returned as. Make sure that returned content is run through check_plain()
+ *   or other filtering functions when returning HTML.
+ * @return
+ *   A renderable element containing at the very least these properties:
+ *    - #title
+ *    - #weight
+ *    - #component
+ *    - #format
+ *    - #value
+ *   Webform also uses #theme_wrappers to output the end result to the user,
+ *   which will properly format the label and content for use within an e-mail
+ *   (such as wrapping the text) or as HTML (ensuring consistent output).
+ */
+function _webform_display_component($component, $value, $format = 'html') {
+  return array(
+    '#title' => $component['name'],
+    '#weight' => $component['weight'],
+    '#theme' => 'webform_display_textfield',
+    '#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
+    '#post_render' => array('webform_element_wrapper'),
+    '#field_prefix' => $component['extra']['field_prefix'],
+    '#field_suffix' => $component['extra']['field_suffix'],
+    '#component' => $component,
+    '#format' => $format,
+    '#value' => isset($value[0]) ? $value[0] : '',
+  );
+}
+
+/**
+ * A hook for changing the input values before saving to the database.
+ *
+ * Webform expects a component to consist of a single field, or a single array
+ * of fields. If you have a component that requires a deeper form tree
+ * you must flatten the data into a single array using this callback
+ * or by setting #parents on each field to avoid data loss and/or unexpected
+ * behavior.
+ *
+ * Note that Webform will save the result of this function directly into the
+ * database.
+ *
+ * @param $component
+ *   A Webform component array.
+ * @param $value
+ *   The POST data associated with the user input.
+ * @return
+ *   An array of values to be saved into the database. Note that this should be
+ *   a numerically keyed array.
+ */
+function _webform_submit_component($component, $value) {
+  // Clean up a phone number into 123-456-7890 format.
+  if ($component['extra']['phone_number']) {
+    $matches = array();
+    $number = preg_replace('[^0-9]', $value[0]);
+    if (strlen($number) == 7) {
+      $number = substr($number, 0, 3) . '-' . substr($number, 3, 4);
+    }
+    else {
+      $number = substr($number, 0, 3) . '-' . substr($number, 3, 3) . '-' . substr($number, 6, 4);
+    }
+  }
+
+  $value[0] = $number;
+  return $value;
+}
+
+/**
+ * Delete operation for a component or submission.
+ *
+ * @param $component
+ *   A Webform component array.
+ * @param $value
+ *   An array of information containing the submission result, directly
+ *   correlating to the webform_submitted_data database schema.
+ */
+function _webform_delete_component($component, $value) {
+  // Delete corresponding files when a submission is deleted.
+  $filedata = unserialize($value['0']);
+  if (isset($filedata['filepath']) && is_file($filedata['filepath'])) {
+    unlink($filedata['filepath']);
+    db_query("DELETE FROM {files} WHERE filepath = '%s'", $filedata['filepath']);
+  }
+}
+
+/**
+ * Module specific instance of hook_help().
+ *
+ * This allows each Webform component to add information into hook_help().
+ */
+function _webform_help_component($section) {
+  switch ($section) {
+    case 'admin/config/content/webform#grid_description':
+      return t('Allows creation of grid questions, denoted by radio buttons.');
+  }
+}
+
+/**
+ * Module specific instance of hook_theme().
+ *
+ * This allows each Webform component to add information into hook_theme(). If
+ * you specify a file to include, you must define the path to the module that
+ * this file belongs to.
+ */
+function _webform_theme_component() {
+  return array(
+    'webform_grid' => array(
+      'render element' => 'element',
+      'file' => 'components/grid.inc',
+      'path' => drupal_get_path('module', 'webform'),
+    ),
+    'webform_display_grid' => array(
+      'render element' => 'element',
+      'file' => 'components/grid.inc',
+      'path' => drupal_get_path('module', 'webform'),
+    ),
+  );
+}
+
+/**
+ * Calculate and returns statistics about results for this component.
+ *
+ * This takes into account all submissions to this webform. The output of this
+ * function will be displayed under the "Results" tab then "Analysis".
+ *
+ * @param $component
+ *   An array of information describing the component, directly correlating to
+ *   the webform_component database schema.
+ * @param $sids
+ *   An optional array of submission IDs (sid). If supplied, the analysis will
+ *   be limited to these sids.
+ * @param $single
+ *   Boolean flag determining if the details about a single component are being
+ *   shown. May be used to provided detailed information about a single
+ *   component's analysis, such as showing "Other" options within a select list.
+ * @return
+ *   An array of data rows, each containing a statistic for this component's
+ *   submissions.
+ */
+function _webform_analysis_component($component, $sids = array(), $single = FALSE) {
+  // Generate the list of options and questions.
+  $options = _webform_select_options_from_text($component['extra']['options'], TRUE);
+  $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
+
+  // Generate a lookup table of results.
+  $query = db_select('webform_submitted_data', 'wsd')
+    ->fields('wsd', array('no', 'data'))
+    ->condition('nid', $component['nid'])
+    ->condition('cid', $component['cid'])
+    ->condition('data', '', '<>')
+    ->groupBy('no')
+    ->groupBy('data');
+  $query->addExpression('COUNT(sid)', 'datacount');
+
+  if (count($sids)) {
+    $query->condition('sid', $sids, 'IN');
+  }
+
+  $result = $query->execute();
+  $counts = array();
+  foreach ($result as $data) {
+    $counts[$data->no][$data->data] = $data->datacount;
+  }
+
+  // Create an entire table to be put into the returned row.
+  $rows = array();
+  $header = array('');
+
+  // Add options as a header row.
+  foreach ($options as $option) {
+    $header[] = $option;
+  }
+
+  // Add questions as each row.
+  foreach ($questions as $qkey => $question) {
+    $row = array($question);
+    foreach ($options as $okey => $option) {
+      $row[] = !empty($counts[$qkey][$okey]) ? $counts[$qkey][$okey] : 0;
+    }
+    $rows[] = $row;
+  }
+  $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('webform-grid'))));
+
+  return array(array(array('data' => $output, 'colspan' => 2)));
+}
+
+/**
+ * Return the result of a component value for display in a table.
+ *
+ * The output of this function will be displayed under the "Results" tab then
+ * "Table".
+ *
+ * @param $component
+ *   A Webform component array.
+ * @param $value
+ *   An array of information containing the submission result, directly
+ *   correlating to the webform_submitted_data database schema.
+ * @return
+ *   Textual output formatted for human reading.
+ */
+function _webform_table_component($component, $value) {
+  $questions = array_values(_webform_component_options($component['extra']['questions']));
+  $output = '';
+  // Set the value as a single string.
+  if (is_array($value)) {
+    foreach ($value as $item => $value) {
+      if ($value !== '') {
+        $output .= $questions[$item] . ': ' . check_plain($value) . '<br />';
+      }
+    }
+  }
+  else {
+    $output = check_plain(!isset($value['0']) ? '' : $value['0']);
+  }
+  return $output;
+}
+
+/**
+ * Return the header for this component to be displayed in a CSV file.
+ *
+ * The output of this function will be displayed under the "Results" tab then
+ * "Download".
+ *
+ * @param $component
+ *   A Webform component array.
+ * @param $export_options
+ *   An array of options that may configure export of this field.
+ * @return
+ *   An array of data to be displayed in the first three rows of a CSV file, not
+ *   including either prefixed or trailing commas.
+ */
+function _webform_csv_headers_component($component, $export_options) {
+  $header = array();
+  $header[0] = array('');
+  $header[1] = array($component['name']);
+  $items = _webform_component_options($component['extra']['questions']);
+  $count = 0;
+  foreach ($items as $key => $item) {
+    // Empty column per sub-field in main header.
+    if ($count != 0) {
+      $header[0][] = '';
+      $header[1][] = '';
+    }
+    // The value for this option.
+    $header[2][] = $item;
+    $count++;
+  }
+
+  return $header;
+}
+
+/**
+ * Format the submitted data of a component for CSV downloading.
+ *
+ * The output of this function will be displayed under the "Results" tab then
+ * "Download".
+ *
+ * @param $component
+ *   A Webform component array.
+ * @param $export_options
+ *   An array of options that may configure export of this field.
+ * @param $value
+ *   An array of information containing the submission result, directly
+ *   correlating to the webform_submitted_data database schema.
+ * @return
+ *   An array of items to be added to the CSV file. Each value within the array
+ *   will be another column within the file. This function is called once for
+ *   every row of data.
+ */
+function _webform_csv_data_component($component, $export_options, $value) {
+  $questions = array_keys(_webform_select_options($component['extra']['questions']));
+  $return = array();
+  foreach ($questions as $key => $question) {
+    $return[] = isset($value[$key]) ? $value[$key] : '';
+  }
+  return $return;
+}
+
+/**
+ * @}
+ */