You are here

webform_report.inc in Webform Report 7

Same filename and directory in other branches
  1. 6.2 webform_report.inc
  2. 6 webform_report.inc

This file contains common functions and functions required to ouput a report for the webform report module

File

webform_report.inc
View source
<?php

/**
 * @file
 * This file contains common functions and functions required
 * to ouput a report for the webform report module
 */

/**
 * Search for nodes of type webform and returns their nids and titles
 * in an associative array.
 */
function _webform_report_get_webforms() {
  $webforms = array();
  $webform_types = webform_variable_get('webform_node_types');
  if ($webform_types) {
    $result = db_select('node', 'n')
      ->fields('n')
      ->condition('n.type', $webform_types, 'IN')
      ->execute();
    while ($record = $result
      ->fetchObject()) {
      $webforms[$record->nid] = t($record->title);
    }
  }
  return $webforms;
}

/**
 * Get webform components.
 *
 * @param nid
 *   the node id of a webform
 * @return 
 *   an associative array of webform components
 */
function _webform_report_get_components($nid) {
  $components = array();

  // include meta components
  $components[-1] = array(
    'name' => t('Submitted By User'),
    'type' => 'string',
  );
  $components[-2] = array(
    'name' => t('Submission Date'),
    'type' => 'date',
  );
  $components[-3] = array(
    'name' => t('Submission Time'),
    'type' => 'time',
  );
  $components[-4] = array(
    'name' => t('Submission IP Address'),
    'type' => 'string',
  );
  $components[-5] = array(
    'name' => t('Edit Link'),
    'type' => 'link',
  );
  $components[-6] = array(
    'name' => t('View Link'),
    'type' => 'link',
  );
  $components[-7] = array(
    'name' => t('Delete Link'),
    'type' => 'link',
  );

  // get components from webform
  $result = db_query("SELECT c.cid, c.name, c.type \n                      FROM {webform_component} c \n                      WHERE c.type <> 'fieldset' AND c.nid = :nid \n                      ORDER BY c.weight", array(
    ':nid' => $nid,
  ));
  foreach ($result as $row) {
    $components[$row->cid] = array(
      'name' => filter_xss($row->name),
      'type' => $row->type,
    );
  }
  return $components;
}

/**
 * return an associative array containing the webform report columns
 *
 * @param node
 *   the webform report node
 * @param components
 *   array of webform components
 * @return 
 *   an associative array of webform report columns
 */
function _webform_report_get_columns($node, $components) {

  // build list of columns on the report
  $columns = array();
  if (is_array($node->columns)) {
    for ($i = 0; $i < count($node->columns); $i++) {
      $cid = $node->columns[$i]['cid'];
      if (array_key_exists($cid, $components)) {
        $columns[] = array(
          'name' => $components[$cid]['name'],
          'type' => $components[$cid]['type'],
          'format' => $node->columns[$i]['format'],
          'option' => isset($node->columns[$i]['option']) ? $node->columns[$i]['option'] : '',
          'cid' => $node->columns[$i]['cid'],
          'hidden' => isset($node->columns[$i]['hidden']) ? $node->columns[$i]['hidden'] : FALSE,
          'num' => isset($node->columns[$i]['num']) ? $node->columns[$i]['num'] : FALSE,
          'weight' => isset($node->columns[$i]['weight']) ? $node->columns[$i]['weight'] : 0,
        );
      }
    }
  }
  return $columns;
}

/**
 * return an associative array containing the webform report filters
 *
 * @param node
 *   the webform report node
 * @param components
 *   array of webform components
 * @return 
 *   an associative array of webform report filters
 */
function _webform_report_get_filters($node, $components) {

  // build list of filters
  $filters = array();
  if (is_array($node->filters)) {
    for ($i = 0; $i < count($node->filters); $i++) {
      $cid = $node->filters[$i]['field'];
      if (array_key_exists($cid, $components)) {
        $filters[] = array(
          'name' => $components[$cid]['name'],
          'type' => $components[$cid]['type'],
          'ftype' => $node->filters[$i]['type'],
          'value' => $node->filters[$i]['value'],
          'cid' => $node->filters[$i]['field'],
          'weight' => isset($node->filters[$i]['weight']) ? $node->filters[$i]['weight'] : 0,
        );
      }
    }
  }

  // check for search values - add filters for search
  $search =& $_SESSION['search_form'][$node->nid];
  if (is_array($search)) {
    $col = $search['column'];
    $val = $search['value'];
    if ($val != '') {
      $columns = _webform_report_get_columns($node, $components);

      // if all columns, add search filter for each column
      if ($col == -1) {
        foreach ($columns as $index => $column) {

          // exclude special links (view, edit, etc)
          // and hidden columns
          if ($column['cid'] > -5 && !$column['hidden']) {
            $filters[] = array(
              'name' => '',
              'type' => $column['type'],
              'ftype' => -1,
              'value' => $val,
              'cid' => $column['cid'],
            );
          }
        }
      }
      else {
        if (array_key_exists($col, $columns)) {
          $filters[] = array(
            'name' => '',
            'type' => $columns[$col]['type'],
            'ftype' => -1,
            'value' => $val,
            'cid' => $columns[$col]['cid'],
          );
        }
      }
    }
  }
  return $filters;
}

/**
 * return an array containing the webform report filter options
 *
 * @return 
 *   an array of webform report filter options
 */
function _webform_report_get_filter_options($datetimeonly = FALSE) {
  $opt = array();
  $opt[0] = t('none');
  if (!$datetimeonly) {
    $opt[1] = t('begins with');
    $opt[2] = t('does not begin with');
    $opt[3] = t('contains');
    $opt[4] = t('does not contain');
  }
  $opt[5] = t('equals');
  $opt[6] = t('does not equal');
  $opt[7] = t('is empty');
  $opt[8] = t('is not empty');
  $opt[9] = t('greater than');
  $opt[10] = t('less than');
  $opt[11] = t('greater than or equal');
  $opt[12] = t('less than or equal');
  $opt[13] = t('between');
  return $opt;
}

/**
 * return an associative array containing the webform report sort criteria
 *
 * @param node
 *   the webform report node
 * @param components
 *   array of webform components
 * @return 
 *   an associative array of webform report sort criteria
 */
function _webform_report_get_sorton($node, $components) {

  // build list of sort parameters
  $sorton = array();
  if (is_array($node->sorton)) {
    for ($i = 0; $i < count($node->sorton); $i++) {
      $cid = $node->sorton[$i]['cid'];
      if (array_key_exists($cid, $components)) {
        $sorton[] = array(
          'name' => $components[$cid]['name'],
          'type' => $components[$cid]['type'],
          'order' => $node->sorton[$i]['order'],
          'cid' => $node->sorton[$i]['cid'],
          'weight' => isset($node->filters[$i]['weight']) ? $node->filters[$i]['weight'] : 0,
        );
      }
    }
  }
  return $sorton;
}

/**
 * Get submission data for the specified webform.
 *
 * @param node 
 *   the current node object
 * @return 
 *   a database query result set
 */
function _webform_report_get_submissions($node) {
  if (isset($node->wnid)) {
    return db_query("\n      SELECT w.nid, c.name, c.cid, d.nid, d.sid, d.data, s.uid, u.name as user, s.submitted, s.remote_addr\n      FROM {webform} w\n      LEFT JOIN {webform_submitted_data} d ON w.nid = d.nid\n      LEFT JOIN {webform_component} c ON d.cid = c.cid\n      LEFT JOIN {webform_submissions} s  ON d.sid = s.sid\n      LEFT JOIN {users} u ON s.uid = u.uid\n      WHERE d.nid = c.nid \n      AND c.nid = s.nid\n      AND s.nid = :nid\n      ORDER BY d.sid, c.cid, d.no", array(
      ':nid' => $node->wnid,
    ));
  }
  else {
    return NULL;
  }
}

/**
 * Get node body content for the specified webform report.
 *
 * @param node 
 *   the current node object
 * @return 
 *   a string of text or a themed table
 */
function _webform_report_get_body_content($node, $view_mode) {
  global $user;

  // get report data
  $report = _webform_report_get_report_data($node);

  // webform report does not have any criteria
  if (!isset($report['headers'])) {
    $output = t('It appears that no criteria have been specified for this report.
      Please click on Report Criteria under Edit to add webform data to your report.');
  }
  elseif (!isset($report['rows'])) {
    $output = t('There are no submissions for the selected webform. Either the form
       has not yet been completed by anyone, or the results have been cleared. This will not
       prevent you from creating this report, but this message will be displayed on the report
       page until someone submits the selected webform.');
  }
  else {
    $output = '';

    // display search form if desired
    if (isset($node->options['search_form']) && $node->options['search_form'] && $view_mode == 'full') {

      // do not show search form to anonymous users if caching is enabled
      if ($user->uid > 0 || !$GLOBALS['conf']['cache']) {
        $output .= drupal_render(drupal_get_form('webform_report_search_form', $node));
      }
    }

    // Display number of rows after description.
    $output .= filter_xss_admin($node->description) . ' (' . count($report['rows']) . ' ' . t('results') . ') ';
    if (!$node->options['hide_csv']) {
      $output .= l(t('Download as CSV'), 'node/' . arg(1) . '/csv') . '</p>';
    }

    // output current page
    $output .= _webform_report_pager($report['headers'], $report['rows'], $node);

    // no submissions met criteria
    if (count($report['rows']) == 0) {
      $output .= t('There are no submissions that match the criteria for the selected webform.');
    }
  }
  return $output;
}

/**
 * Get webform report data
 *
 * @param node 
 *   the current node object
 * @return 
 *   report array - keys 'headers' = report headers, 'rows' = report rows
 */
function _webform_report_get_report_data($node) {
  $report = array();

  // get report criteria
  $components = _webform_report_get_components($node->wnid);
  $columns = _webform_report_get_columns($node, $components);

  // if any columns
  if (count($columns) > 0) {
    $headers = array();
    $fields = array();

    // set column headers and field info
    foreach ($columns as $index => $col) {
      if ($col['hidden'] != TRUE) {

        // set report header - also save type for later
        $headers[] = array(
          'data' => $col['name'],
          'field' => $col['cid'],
          'type' => $col['type'],
        );
      }

      // fields by cid for quick lookup
      $fields[$col['cid']] = $col['name'];

      // Get mapping for select lists (Start)
      if ($col['type'] == 'select') {

        // get component info
        $result = db_query("SELECT c.extra FROM {webform_component} c WHERE c.nid = :nid AND c.cid = :cid", array(
          ':nid' => $node->wnid,
          ':cid' => $col['cid'],
        ));
        $r = $result
          ->fetchObject();
        $extra = unserialize($r->extra);

        // add missing key to fix php notice
        if (!array_key_exists('options_source', $extra)) {
          $extra['options_source'] = '';
        }

        // load the webform select component handler
        module_load_include('inc', 'webform', 'components/select');

        // detect webforms 3.x and handle
        if (function_exists('_webform_select_options_callback')) {

          // make a webform "component"
          $wfcomp['extra'] = $extra;

          // get select options
          $columns[$index]['pairs'] = _webform_select_options($wfcomp);
        }
        else {
          if (strpos($extra['items'], '|')) {
            $columns[$index]['pairs'] = array();
            foreach (explode("\n", $extra['items']) as $pair) {
              list($k, $v) = explode('|', $pair);
              $columns[$index]['pairs'][$k] = $v;
            }
          }
        }
      }

      // end - if ($col['type'] == 'select')...
      // Get mapping for select lists (End)
    }

    // end - foreach ($columns as $index => $col)...
    // query submissions
    $rs = _webform_report_get_submissions($node);
    if ($rs) {

      // get other report criteria
      $filters = _webform_report_get_filters($node, $components);
      $sorton = _webform_report_get_sorton($node, $components);

      // init values
      $rows = array();
      $csid = 0;

      // add filter fields to lookup
      foreach ($filters as $index => $filter) {

        // fields by cid for quick lookup
        $fields[$filter['cid']] = $filter['name'];
      }

      // submission counter
      $sc = 0;

      // get column data
      while (TRUE) {

        // get next submission data
        $row = $rs
          ->fetchObject();

        // check for end of data or end of submission
        if (!$row || $row->sid != $csid) {

          // process data for this submission, if any
          if ($csid != 0) {

            // test submission against filters
            if (_webform_report_test_filters($data, $filters, $columns)) {

              // output row if filters pass
              $rows[] = _webform_report_output($data, $columns);
            }
          }

          // if end of submission data, exit
          if (!$row) {
            break;
          }

          // count submission
          $sc++;

          // set current submission id
          $csid = $row->sid;

          // empty raw data row
          $data = array();

          // save submitter uid and node nid
          $data['uid'] = $row->uid;
          $data['nid'] = $row->nid;

          // fill in meta fields
          if (array_key_exists(-1, $fields)) {
            $data[-1] = array(
              l($row->user, 'user/' . $row->uid),
            );
          }
          if (array_key_exists(-2, $fields)) {
            $data[-2] = array(
              $row->submitted,
            );
          }
          if (array_key_exists(-3, $fields)) {
            $data[-3] = array(
              $row->submitted,
            );
          }
          if (array_key_exists(-4, $fields)) {
            $data[-4] = array(
              $row->remote_addr,
            );
          }
          if (array_key_exists(-5, $fields)) {
            $data[-5] = array(
              l(t('edit'), 'node/' . $row->nid . '/submission/' . $row->sid . '/edit'),
            );
          }
          if (array_key_exists(-6, $fields)) {
            $data[-6] = array(
              l(t('view'), 'node/' . $row->nid . '/submission/' . $row->sid),
            );
          }
          if (array_key_exists(-7, $fields)) {
            $data[-7] = array(
              l(t('delete'), 'node/' . $row->nid . '/submission/' . $row->sid . '/delete'),
            );
          }
        }

        // if component is on report
        if (array_key_exists($row->cid, $fields)) {

          // add raw data
          $data[$row->cid][] = $row->data;
        }
      }

      // end - while (TRUE)...
      // see if any rows are available
      if (count($rows) > 0) {

        // sort
        _webform_report_sort($headers, $rows, $sorton, $columns);

        // execute php code, if given
        if (isset($node->options['php_code'])) {
          eval('?>' . $node->options['php_code']);
        }
      }

      // save rows if any submissions were returned
      if ($sc > 0) {
        $report['rows'] = $rows;
      }
    }

    // end - if (!empty($rs))...
    $report['headers'] = $headers;
  }

  // end -   if (count($columns) > 0)...
  return $report;
}

/**
 * test data against filters
 *
 * @param data
 *   array of raw submission data
 * @param filters
 *   list of filters to test against data
 * @return 
 *   TRUE if filters passed, otherwise FALSE
 */
function _webform_report_test_filters($data, $filters, $columns) {

  // filter result, return true if no filters
  $ok = TRUE;

  // if any filters
  if (count($filters) > 0) {

    // search flag
    $hit = FALSE;

    // loop through all filters
    foreach ($filters as $index => $filter) {

      // reset result for each filter
      $ok = FALSE;

      // check that cid is in data
      if (array_key_exists($filter['cid'], $data)) {

        // get raw data
        $raw = $data[$filter['cid']];

        // format value
        foreach ($columns as $col) {
          if ($col['cid'] == $filter['cid']) {
            break;
          }
        }
        $value = _webform_report_format_data($raw, $col);

        // prepare filter values
        $filter_data = strip_tags(strtolower(trim($value['data'])));
        $filter_value = $filter['value'];

        // Do token substitution - after checking whether the
        // value might even contain a token
        if (strpos($filter_value, '[') !== FALSE) {

          // load submitter user and webform node if not done
          if (!isset($submitter)) {
            $submitter = user_load($data['uid']);
          }
          if (!isset($wfnode)) {
            $wfnode = node_load($data['nid']);
          }

          // replace globals
          $filter_value = token_replace($filter_value);

          // replace submitter user values and webform node values
          $filter_value = token_replace($filter_value, array(
            'user' => $submitter,
            'node' => $wfnode,
          ));
        }

        // normalize
        $filter_value = strtolower(trim($filter_value));

        // extract filter values for between
        if ($filter['ftype'] == 13) {
          $filter_value = explode('|', $filter_value);

          // should be two values
          if (count($filter_value) == 1) {
            $filter_value[] = '';
          }
        }

        // flag eq, ne, gt, lt, ge, le, between compares
        $cmpflag = $filter['ftype'] == 5 || $filter['ftype'] == 6 || $filter['ftype'] == 9 || $filter['ftype'] == 10 || $filter['ftype'] == 11 || $filter['ftype'] == 12 || $filter['ftype'] == 13;

        // see if special date and time processing is required
        if ($filter['type'] == 'date' && $cmpflag) {

          // user entered date
          if ($filter['cid'] > 0) {

            // dates are stored as yyyy-mm-dd
            $filter_data = new DateTime($raw[0]);
          }
          else {
            $filter_data = new DateTime();
            $filter_data
              ->setTimeStamp($raw[0]);
          }
        }
        if ($filter['type'] == 'time' && $cmpflag) {

          // user entered time - stored as hh:mm:ss
          if ($filter['cid'] > 0) {
            $filter_data = new DateTime($raw[0]);
          }
          else {
            $filter_data = new DateTime();
            $filter_data
              ->setTimeStamp($raw[0]);
          }
        }

        // convert filter value to timestamp if necessary
        if (($filter['type'] == 'date' || $filter['type'] == 'time') && $cmpflag) {
          if (is_array($filter_value)) {
            $filter_value[0] = new DateTime($filter_value[0]);
            $filter_value[1] = new DateTime($filter_value[1]);
          }
          else {
            $filter_value = new DateTime($filter_value);
          }
        }

        // extract filename for file types
        if ($filter['type'] == 'file') {
          $filter_data = '';
          if (is_numeric($raw[0])) {

            // must load the webform file component handler
            module_load_include('inc', 'webform', 'components/file');
            $file = webform_get_file($raw[0]);
            if (!empty($file->fid)) {
              $filter_data = webform_file_name($file->filename);
            }
          }
        }

        // convert to numeric if flagged and a non-string comparison
        if ($col['num'] && $cmpflag) {
          $filter_data = $filter_data + 0;
          if ($filter['ftype'] == 13) {
            $filter_value[0] = $filter_value[0] + 0;
            $filter_value[1] = $filter_value[1] + 0;
          }
          else {
            $filter_value = $filter_value + 0;
          }
        }

        // apply filter
        switch ($filter['ftype']) {

          // search for x
          case -1:

            // convert submission date and time to strings
            if ($filter['cid'] == -2 || $filter['cid'] == -3) {
              $filter_data = new DateTime();
              $filter_data
                ->setTimeStamp($raw[0]);
              $filter_value = new DateTime($filter_value);
              if ($filter_data && $filter_value) {
                if ($filter['cid'] == -2) {
                  $filter_data = $filter_data
                    ->setTime(0, 0, 0);
                  $filter_value = $filter_value
                    ->setTime(0, 0, 0);
                  if ($filter_data == $filter_value) {
                    $hit = TRUE;
                    $ok = TRUE;
                  }
                }
                if ($filter['cid'] == -3) {
                  $dh = (int) $filter_data
                    ->format('G');
                  $vh = (int) $filter_value
                    ->format('G');
                  $dm = (int) $filter_data
                    ->format('i');
                  $vm = (int) $filter_value
                    ->format('i');
                  if ($dh == $vh && $dm == $vm) {
                    $hit = TRUE;
                    $ok = TRUE;
                  }
                }
              }
            }
            else {
              if ($filter_data != '' && strpos($filter_data, $filter_value) !== FALSE) {
                $hit = TRUE;
                $ok = TRUE;
              }
            }
            break;

          // none
          case 0:
            $ok = TRUE;

          // Begins with x.
          case 1:
            if (substr($filter_data, 0, drupal_strlen($filter_value)) == $filter_value) {
              $ok = TRUE;
            }
            break;

          // Does not begin with x.
          case 2:
            if (substr($filter_data, 0, drupal_strlen($filter_value)) != $filter_value) {
              $ok = TRUE;
            }
            break;

          // Contains x.
          case 3:
            if (!$filter_value || strpos($filter_data, $filter_value) !== FALSE) {
              $ok = TRUE;
            }
            break;

          // Does not contain x.
          case 4:
            if (!$filter_value || strpos($filter_data, $filter_value) === FALSE) {
              $ok = TRUE;
            }
            break;

          // Equals x.
          case 5:
            if ($filter_data == $filter_value) {
              $ok = TRUE;
            }
            break;

          // Does not equal x.
          case 6:
            if ($filter_data != $filter_value) {
              $ok = TRUE;
            }
            break;

          // is empty
          case 7:
            if (drupal_strlen($filter_data) == 0) {
              $ok = TRUE;
            }
            break;

          // is not empty
          case 8:
            if (drupal_strlen($filter_data) != 0) {
              $ok = TRUE;
            }
            break;

          // greater than
          case 9:
            if ($filter_data > $filter_value) {
              $ok = TRUE;
            }
            break;

          // less than
          case 10:
            if ($filter_data < $filter_value) {
              $ok = TRUE;
            }
            break;

          // greater than or equal
          case 11:
            if ($filter_data >= $filter_value) {
              $ok = TRUE;
            }
            break;

          // less than or equal
          case 12:
            if ($filter_data <= $filter_value) {
              $ok = TRUE;
            }
            break;

          // between
          case 13:

            // negate filter if both values are empty
            if (!$filter_value[0] && !$filter_value[1] || $filter_data >= $filter_value[0] && $filter_data <= $filter_value[1]) {
              $ok = TRUE;
            }
            break;
        }

        // end - switch($filter['type'])...
      }
      else {
        if ($filter['ftype'] == 0) {

          // pass it
          $ok = TRUE;
        }
      }

      // if hit on search, quit - search filters are last,
      // so any report filters have already been applied
      if ($hit) {
        break;
      }

      // if last filter was search, keep going
      if ($filter['ftype'] == -1) {
        continue;
      }

      // if filter did not pass, don't check any further
      if (!$ok) {
        break;
      }
    }

    // end - foreach($filters as $index => $filter)...
  }

  // end - if (count($filters) > 0)...
  // return filter result
  return $ok;
}

/**
 * format output table row for report
 *
 * @param data 
 *   raw submission data - array with key of cid, value an array of raw data
 * @param columns
 *   list of report columns
 * @return 
 *   table row suitable for theme_table
 */
function _webform_report_output($data, $columns) {
  $row = array();

  // add each column to output row, applying formatting
  foreach ($columns as $index => $col) {

    // check if data exists
    if (array_key_exists($col['cid'], $data)) {

      // get raw data
      $raw = $data[$col['cid']];

      // add column to output row
      $row[] = _webform_report_format_data($raw, $col);
    }
    else {

      // no data, add dummy data
      $row[] = array(
        'data' => '',
        'field' => $col['cid'],
        'hidden' => $col['hidden'],
        'sort' => '',
      );
    }
  }
  return $row;
}

/**
 * format raw submission data
 *
 * @param raw
 *   array of raw submission data
 * @param column
 *   the report column
 * @return 
 *   array of formatted table data
 */
function _webform_report_format_data($raw, $column) {
  $out = '';

  // handle file type
  if ($column['type'] == 'file') {

    // init variables
    $link = '';
    $text = '';
    $path = '';
    $text = '';

    // new file handling in webforms 7.x-3.x - data is an id
    if (is_numeric($raw[0])) {

      // must load the webform file component handler
      module_load_include('inc', 'webform', 'components/file');
      $file = webform_get_file($raw[0]);
      if (!empty($file->fid)) {
        $text = webform_file_name(check_plain(webform_file_name($file->uri)));
        $path = webform_file_url($file->uri);
        $link = '<a href="' . $path . '">' . $text . '</a>' . ' (' . (int) ($file->filesize / 1024) . ' KB)';
      }
    }
    else {
      $tmp = unserialize($raw[0]);
      if (!empty($tmp['filename'])) {
        $text = $tmp['filename'];
        $path = base_path() . $tmp['filepath'];
        $link = '<a href="' . $path . '">' . $text . '</a>' . ' (' . (int) ($tmp['filesize'] / 1024) . ' KB)';
      }
    }

    // output file column - save path for later
    $out = array(
      'data' => $link,
      'field' => $column['cid'],
      'sort' => $text,
      'path' => $path,
    );
  }
  elseif ($column['type'] == 'date') {
    $ts = FALSE;

    // if report date
    if ($column['cid'] > 0) {

      // date is stored as yyyy-mm-dd
      $ts = new DateTime($raw[0]);
    }
    else {
      $ts = new DateTime();
      $ts
        ->setTimeStamp($raw[0]);
    }

    // if valid date
    if ($ts) {
      $format = $column['format'];

      // set default format if none given
      if (empty($format)) {
        $format = 'Y-m-d';
      }

      // format the date
      $out = array(
        'data' => $ts
          ->format($format),
        'field' => $column['cid'],
        'sort' => $ts,
      );
    }
  }
  elseif ($column['type'] == 'time') {
    $ts = FALSE;
    if ($column['cid'] > 0) {

      // date is stored as hh:mm:ss
      $ts = new DateTime($raw[0]);
    }
    else {

      // otherwise get submission timestamp
      $ts = new DateTime();
      $ts
        ->setTimeStamp($raw[0]);
    }

    // if valid timestamp
    if ($ts) {
      $format = $column['format'];

      // default format if none given
      if (empty($format)) {
        $format = 'H:i';
      }

      // format the time
      $out = array(
        'data' => $ts
          ->format($format),
        'field' => $column['cid'],
        'sort' => $ts,
      );
    }
  }
  elseif ($column['type'] == 'select') {
    $data = '';
    $sort = '';

    // process each key in case of multi-selects
    for ($i = 0; $i < count($raw); $i++) {
      if ($i > 0) {
        $data .= ',';
        $sort .= ',';
      }

      // save key value
      $key = $raw[$i];

      // display key if wanted or if no key/value pairs exist
      if ($column['format'] == 'KEY' || !array_key_exists('pairs', $column)) {
        $data .= $key;
      }
      else {

        // set select value to friendly format (default)
        $data .= isset($column['pairs'][$key]) ? $column['pairs'][$key] : '';
      }

      // set sort key - use key if selected
      if ($column['option'] == 'KEY') {
        $sort .= $key;
      }
      else {
        if (array_key_exists($key, $column['pairs'])) {
          $sort .= $column['pairs'][$key];
        }
      }
    }

    // save the value
    $out = array(
      'data' => $data,
      'field' => $column['cid'],
      'sort' => $sort,
    );
  }
  else {

    // get raw data
    $tmp = filter_xss_admin(implode(',', $raw));

    // build sort key
    $sort = strip_tags($tmp);

    // allow for accents if module is present
    if (function_exists('_accents_remove_accents')) {
      $sort = _accents_remove_accents($sort);
    }
    $out = array(
      'data' => $tmp,
      'field' => $column['cid'],
      'sort' => $sort,
    );

    // add links
    if (valid_email_address($out['data'])) {
      $out['data'] = l($out['data'], 'mailto:' . $out['data']);
    }
    else {
      if (valid_url($out['data'], TRUE)) {
        $out['data'] = l($out['data'], $out['data']);
      }
    }
  }

  // pass hidden attribute to output
  if ($column['hidden'] == TRUE) {
    $out['hidden'] = TRUE;
  }

  // return formatted data
  return $out;
}

/**
 * sort row data
 *
 * anonymous sort comparison function creation adapted from:
 * http://www.the-art-of-web.com/php/sortarray/
 *
 * @param headers
 *   array of report headers
 * @param rows
 *   array of report rows
 * @param sorton
 *   list of default sort parameters
 */
function _webform_report_sort(&$headers, &$rows, $sorton, $columns) {

  // get current sort - 'name' key is column name, 'sql' is cid
  $ts = tablesort_get_order($headers);

  // get sort order
  $ts_order = tablesort_get_sort($headers);

  // if inital display (no column header has been clicked)
  if (!isset($_GET['order'])) {

    // if sort parameters given
    if (count($sorton) > 0) {

      // init variables
      $a = array();
      $cid = 0;
      $ord = '';

      // loop through sorts
      foreach ($sorton as $sort) {

        // find column to be sorted
        foreach ($columns as $index => $col) {
          if ($sort['cid'] == $col['cid']) {

            // save first sort column
            if ($cid == 0) {
              $cid = $sort['cid'];
              if ($sort['order'] == SORT_ASC) {
                $ord = 'asc';
              }
              else {
                $ord = 'desc';
              }
            }

            // add the sort function
            $a[] = _webform_report_sort_column($index, $sort['order'] == SORT_DESC, $sort['type'], $col['num']);
            break;
          }
        }
      }

      // end - foreach ($sorton...
      // flag the header with the first sort column
      foreach ($headers as $index => $header) {

        // mark the first sort header
        if ($header['field'] == $cid) {
          $headers[$index]['sort'] = $ord;
          break;
        }

        // end - if ($header['field'])...
      }

      // end - foreach ($headers as...
      if (count($a) == 0) {

        // probably no columns
        $code = 'return 0;';
      }
      elseif (count($a) == 1) {

        // only one clause, return it
        $code = 'return ' . $a[0] . ';';
      }
      else {

        // multiple clauses, build descending compare function
        $code = '';
        for ($i = 0; $i < count($a); $i++) {
          if ($i < count($a) - 1) {
            $code .= 'if (($tmp = ' . $a[$i] . ') == 0) { ';
          }
          else {
            $code .= '$tmp = ' . $a[$i] . '; ';
          }
        }
        for ($i = 0; $i < count($a) - 1; $i++) {
          $code .= ' } ';
        }
        $code .= 'return $tmp;';
      }
    }

    // end - if (count($sorton) > 0)...
  }

  // end - if ($init)...
  // user has clicked on a column header or no sort criteria specified
  // must sort on current table sort column
  if (!isset($code)) {

    // find header to be sorted
    foreach ($headers as $index => $header) {
      if ($header['field'] == $ts['sql']) {
        $headers[$index]['sort'] = $ts_order;
        break;
      }
    }

    // find column to be sorted
    foreach ($columns as $index => $col) {
      if ($col['cid'] == $ts['sql']) {
        $code = 'return ' . _webform_report_sort_column($index, $ts_order == 'desc', $col['type'], $col['num']) . ';';
        break;
      }
    }
  }

  // sort the rows
  usort($rows, create_function('$a,$b', $code));
}

/**
 * Generate a sort function for a column
 *
 * @param index
 *   the index of the field to sort
 * @param desc 
 *   if true sort is descending
 * @param type
 *   the field type 
 * @return 
 *   the sort function for the given column
 */
function _webform_report_sort_column($index, $desc = FALSE, $type = '', $num = FALSE) {

  // create comparison based on type
  // for dates and times compare timestamps
  if ($type == 'date' || $type == 'time') {
    $code = "(\$a[{$index}]['sort']>\$b[{$index}]['sort'])";
  }
  else {
    if ($num) {
      $code = "((\$a[{$index}]['sort']+0)-(\$b[{$index}]['sort']+0))";
    }
    else {
      $code = "strcasecmp(\$a[{$index}]['sort'],\$b[{$index}]['sort'])";
    }
  }

  // invert output for descending sort
  if ($desc) {
    $code = $code . '*-1';
  }
  $code = '(' . $code . ')';
  return $code;
}

/**
 * Display a list of webform reports in the admin area.
 *
 * @return 
 *   a themed table
 */
function _webform_report_page() {
  $output = NULL;
  if (user_access('access webform reports')) {
    $headers = array(
      t('View'),
      t('Edit'),
      t('Delete'),
    );
    $rows = array();
    $query = db_select('node', 'n');
    $query
      ->fields('n');
    $query
      ->condition('n.type', 'webform_report', '=');
    $result = $query
      ->execute();
    foreach ($result as $node) {
      $rows[] = array(
        l($node->title, 'node/' . $node->nid),
        l(t('edit'), 'node/' . $node->nid . '/edit'),
        l(t('delete'), 'node/' . $node->nid . '/delete'),
      );
    }
    $output = theme('table', array(
      'header' => $headers,
      'rows' => $rows,
      'attributes' => array(
        'class' => array(
          'webform',
        ),
      ),
    ));
  }
  return $output;
}

/**
 * Paginate the webform report.
 *
 * @return 
 *   a themed table with page links
 */
function _webform_report_pager($headers, $rows, $node) {
  global $pager_page_array, $pager_total;

  // get results per page setting - start with default
  $results_per_page = 20;

  // if value set, use that
  if (array_key_exists('results_per_page', $node->options)) {
    $results_per_page = $node->options['results_per_page'];
  }

  // Add the css file for form display.
  drupal_add_css(drupal_get_path('module', 'webform_report') . '/webform_report.css');

  // Break the array into chunks for pagination.
  if ($results_per_page != 0) {
    $pages = array_chunk($rows, $results_per_page, TRUE);

    // get the current page
    $page = pager_find_page();
  }
  else {
    $pages[0] = $rows;
    $page = 0;
  }

  // set report layout
  $layout = 'REPORT';
  if (array_key_exists('layout', $node->options)) {
    $layout = $node->options['layout'];
  }

  // filter out hidden columns
  $out = array();
  if (count($rows) > 0 && is_array($pages[$page])) {
    foreach ($pages[$page] as $row) {
      $outrow = array();
      foreach ($row as $cell) {
        $hide = isset($cell['hidden']) ? $cell['hidden'] : FALSE;
        if (!$hide) {

          // remove cell attributes
          if (is_array($cell)) {
            unset($cell['hidden']);
            unset($cell['sort']);
            unset($cell['field']);
          }
          $outrow[] = $cell;
        }
      }
      $out[] = $outrow;
    }
  }
  if ($layout == 'REPORT') {

    // format the table with the current page
    $output = theme('table', array(
      'header' => $headers,
      'rows' => $out,
      'attributes' => array(
        'class' => array(
          'webform_report',
        ),
      ),
    ));
  }
  else {
    $output = webform_report_format_submissions($headers, $out);
  }

  // Add the pager to the output if needed
  if ($results_per_page != 0) {
    pager_default_initialize(count($rows), $results_per_page);
    $output .= theme('pager');
  }
  return $output;
}

/**
 * Output webform report submissions
 *
 * @param headers
 *   the field headers
 * @param rows
 *   the fields
 */
function webform_report_format_submissions($headers, $rows) {
  $out = array();
  foreach ($rows as $row) {
    $output = '';
    for ($i = 0; $i < count($row); $i++) {
      $output .= '<div class="webform-report-submission-cell">';
      $output .= '<div class="webform-report-submission-header">';
      if (is_array($headers[$i])) {
        $output .= $headers[$i]['data'];
      }
      else {
        $output .= $headers[$i];
      }
      $output .= '</div>';
      $output .= '<div class="webform-report-submission-data">';
      if (is_array($row[$i])) {
        $output .= $row[$i]['data'];
      }
      else {
        $output .= $row[$i];
      }
      $output .= '</div>';
      $output .= '</div>';
    }
    $out[] = array(
      $output,
    );
  }
  return theme('table', array(
    'rows' => $out,
    'attributes' => array(
      'class' => array(
        'webform_report webform-report-submission',
      ),
    ),
  ));
}

/**
 * Output a webform report in CSV format
 *
 * code adapted from Webform module
 *
 * @param node
 *   the current node
 */
function webform_report_csv($node) {
  return drupal_get_form('webform_report_export_form', $node);
}

/**
 * Generate a form for exporting report data
 *
 * @param form_state
 *   drupal form state
 * @param node
 *   current node object
 * @return 
 *   an array of form elements
 */
function webform_report_export_form($form, $form_state, $node) {
  $form = array();

  // export fieldset
  $form['export'] = array(
    '#type' => 'fieldset',
    '#title' => t('Export Options'),
  );
  $form['export']['nid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  $format = array(
    'text' => t('Text'),
    'excel' => t('Excel'),
  );
  $form['export']['format'] = array(
    '#type' => 'radios',
    '#title' => t('Export format'),
    '#options' => $format,
    '#default_value' => 'text',
  );
  $delim = array(
    ',' => t('Comma (,)'),
    '\\t' => t('Tab (\\t)'),
    ';' => t('Semicolon (;)'),
    ':' => t('Colon (:)'),
    '|' => t('Pipe (|)'),
    '.' => t('Period (.)'),
    ' ' => t('Space ( )'),
  );
  $form['export']['delim'] = array(
    '#type' => 'select',
    '#title' => t('Delimiter'),
    '#options' => $delim,
    '#required' => TRUE,
  );
  $form['export']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Export'),
    '#weight' => 1,
  );
  $form['return'] = array(
    '#markup' => l(t('Return to Report'), 'node/' . $node->nid),
  );
  return $form;
}

/**
 * Implementation of hook_submit for the report export form
 *
 * @param form_id
 *   drupal form id
 * @param form_state
 *   drupal form state and values
 */
function webform_report_export_form_submit($form_id, $form_state) {
  $nid = $form_state['values']['nid'];
  $format = $form_state['values']['format'];
  $delim = $form_state['values']['delim'];
  $node = node_load($nid);
  $report = _webform_report_get_report_data($node);
  if (isset($report['headers']) && isset($report['rows'])) {
    $output = _webform_report_output_csv($report['headers'], $report['rows'], $format, $delim);
    if ($format == 'excel') {
      $ftype = 'xls';
      $ctype = 'application/x-msexcel';
    }
    else {
      if ($delim == '\\t') {
        $ftype = 'tsv';
        $ctype = 'text/tab-separated-values';
      }
      else {
        $ftype = 'csv';
        $ctype = 'text/plain';
      }
    }
    $fname = 'wfr_export.' . $ftype;
    drupal_add_http_header('Content-Type', $ctype);
    drupal_add_http_header('Content-Length', strlen($output));
    drupal_add_http_header('Content-Disposition', 'attachment; filename="' . $fname . '"');

    // Allow Internet Explorer to download Excel files over HTTPS
    drupal_add_http_header('Cache-Control', 'private, must-revalidate');
    echo $output;
    die;
  }
  drupal_goto('/node/' . $nid);
}

/**
 * Format webform report data as a CSV
 *
 * @param headers
 *   report headers
 * @param rows
 *   report rows
 * @param format
 *   report format
 * @param delim
 *   report delimiter
 * @return 
 *   CSV output
 */
function _webform_report_output_csv($headers, $rows, $format = 'text', $delim = ',') {
  $output = '';

  // set delimiter to tab and output bof for excel
  if ($format == 'excel') {
    $delim = '\\t';
    if (function_exists('mb_convert_encoding')) {
      $output .= chr(255) . chr(254);
    }
  }

  // set tab delimiter
  if ($delim == '\\t') {
    $delim = "\t";
  }

  // output headers
  $tmp = array();
  foreach ($headers as $header) {
    $tmp[] .= _webform_report_format_csv_column($header['data'], $delim);
  }
  $output .= _webform_report_format_csv_row($tmp, $format, $delim);

  // output rows
  foreach ($rows as $row) {
    $tmp = array();
    foreach ($row as $cell) {

      // output cell if not hidden
      if (!array_key_exists('hidden', $cell) || $cell['hidden'] == FALSE) {

        // special handling for file types - use entire path
        if (array_key_exists('path', $cell)) {
          $data = $cell['path'];
        }
        else {
          $data = strip_tags($cell['data']);
        }
        $tmp[] = _webform_report_format_csv_column($data, $delim);
      }
    }
    $output .= _webform_report_format_csv_row($tmp, $format, $delim);
  }
  return $output;
}

/**
 * Format a CSV column value
 *
 * @param value
 *   the value to format for CSV
 * @param delim
 *   the CSV delimiter
 * @return 
 *   CSV column
 */
function _webform_report_format_csv_column($value, $delim) {

  // Replace single quotes with double quotes, replace CRLF with LF
  $value = str_replace(array(
    '"',
    "\r\n",
  ), array(
    '""',
    "\n",
  ), $value);
  return '"' . $value . '"';
}

/**
 * Format a CSV row
 *
 * @param row
 *   an array of row values
 * @param format
 *   the CSV file format
 * @param delim
 *   the CSV delimiter
 * @return 
 *   the CSV row
 */
function _webform_report_format_csv_row($row, $format, $delim) {
  $txt = implode($delim, $row) . "\n";
  if ($format == 'excel' && function_exists('mb_convert_encoding')) {
    $txt = mb_convert_encoding($txt, 'UTF-16LE', 'UTF-8');
  }
  return $txt;
}

Functions

Namesort descending Description
webform_report_csv Output a webform report in CSV format
webform_report_export_form Generate a form for exporting report data
webform_report_export_form_submit Implementation of hook_submit for the report export form
webform_report_format_submissions Output webform report submissions
_webform_report_format_csv_column Format a CSV column value
_webform_report_format_csv_row Format a CSV row
_webform_report_format_data format raw submission data
_webform_report_get_body_content Get node body content for the specified webform report.
_webform_report_get_columns return an associative array containing the webform report columns
_webform_report_get_components Get webform components.
_webform_report_get_filters return an associative array containing the webform report filters
_webform_report_get_filter_options return an array containing the webform report filter options
_webform_report_get_report_data Get webform report data
_webform_report_get_sorton return an associative array containing the webform report sort criteria
_webform_report_get_submissions Get submission data for the specified webform.
_webform_report_get_webforms Search for nodes of type webform and returns their nids and titles in an associative array.
_webform_report_output format output table row for report
_webform_report_output_csv Format webform report data as a CSV
_webform_report_page Display a list of webform reports in the admin area.
_webform_report_pager Paginate the webform report.
_webform_report_sort sort row data
_webform_report_sort_column Generate a sort function for a column
_webform_report_test_filters test data against filters