=== modified file 'sites/all/modules/webform_report/webform_report.inc'
--- webform_report.inc	2011-04-25 22:20:12 +0000
+++ webform_report.inc	2011-04-26 00:12:15 +0000
@@ -219,29 +219,406 @@ function _webform_report_get_sorton($nod
 /**
  * Get submission data for the specified webform.
  *
- * @param node 
- *   the current node object
+ * @param nid
+ *   the current webform node nid
+ * @param filters
+ *   the set of filters to possibly apply to the submission data.
+ *   Filters which are applied will be marked as such.
  * @return 
  *   a database query result set
  */
-function _webform_report_get_submissions($node) {
+function _webform_report_get_submissions($nid, &$filters) {
+
+  // Determine all filters that can be applied to SQL statement, and build
+  // SQL 'where expressions' from them & signify that those filters are applied.
+  // This is based on the code in _webform_report_test_filters().
+  // (If the foreach loop below was deleted, reports would still work because
+  // _webform_report_test_filters() would still filter all database rows
+  // correctly. It would just take a lot more time.)
+
+  $query_where = array();
+  $query_args = array($nid);
+  $data_where = array();
+  $data_args = array();
+
+  // Preparation: see if there is a search going on one (not more) field
+  $search_ok = FALSE;
+  foreach ($filters as $filter) {
+    if ($filter['ftype'] == -1) {
+      if ($search_ok) {
+        // search on multiple fields
+        $search_ok = FALSE;
+        break;
+      }
+      else {
+        $search_ok = TRUE;
+      }
+    }
+  }
+
+  // Process all filters
+
+  foreach ($filters as &$filter) {
+
+    // If $filter['value'] contains tokens, filtering might be on a 'global' or
+    // node property. We can replace those already.
+    if (module_exists('token') && strpos($filter['value'], TOKEN_PREFIX) !== FALSE) {
+      if (!isset($wfnode)) {
+        $wfnode = node_load($data['nid']);
+      }
+      // replace globals
+      $filter['value'] = token_replace($filter['value']);
+      // replace webform node values
+      $filter['value'] = token_replace($filter['value'], 'node', $wfnode);
+    }
+    // Now if there still is a token present, it's probably a user token, for
+    // the submitter of the data. This is different for every submission so we
+    // cannot simply include it in the SQL. Skip it.
+    if (strpos($filter['value'], TOKEN_PREFIX) === FALSE) {
+
+      $sql_field = '';
+      $sql_expr = '';
+      $filter_value = '';
+
+      // Determine field.
+      switch ($filter['cid']) {
+        case -1:
+          // Submitted by user
+          //$sql_field = 'u.name';
+          // ... _webform_report_get_report_data() actually fills $data[-1]
+          // with a link (a href) to the user, not with the user name.
+          // Let's not touch it.
+          break;
+
+        case -2:
+          // Submission date
+          $sql_field = 's.submitted';
+          break;
+
+        case -3:
+          // Submission time. We don't want to add an extra field, but
+          // rather want to refine the $filter_value connected to the
+          // submission date (-2), if it's there. We could make code to do
+          // this, but that would require reorganisation. Let's handle this in
+          // _webform_report_test_filters(). The big benefit in SQL filtering
+          // is in the date anyway, not in 'finetuning' the time.
+          break;
+
+        case -4:
+          // Submission IP address
+          $sql_field = 's.remote_addr';
+          break;
+
+        default:
+          $sql_field = 'd.data';
+      }
+
+      // Determine (validity of) value.
+      if ($sql_field) {
+        // ($filter['value'] is a (empty) string also for filter types
+        // 'empty / not empty', so we can safely use string operations on it.)
+
+        // Normalize value.
+        $filter_value = strtolower(trim($filter['value']));
+
+        // Extract filter values for 'between'. Correct values will be arrays
+        //  from this point(to make the rest of the code more uniform).
+        if ($filter['ftype'] == 13) {
+          $filter_value = explode('|', $filter_value);
+          // Only do SQL filtering if both values are filled; the logic
+          // is too brittle if $filter_value[1] is unfilled, so we'll leave that
+          // up to the code in _webform_report_test_filters() in that case.
+          if (count($filter_value) == 1 || $filter_value[0] === '') {
+            $filter_value = '';
+          }
+        }
+        else {
+          $filter_value = array($filter_value);
+        }
+
+        // Reformat value / determine if we can't use it.
+        switch ($filter['type']) {
+
+          // Here's what we will NOT handle:
+          case 'pagebreak':
+          case 'markup':
+            // These are webform types which Webform Report allows to select in
+            // filter criteria ('by mistake'), although they probably will never
+            // be in submission data. Never include them in SQL filter.
+            //$filter['applied'] = TRUE; <- I feel like this, but let's give
+            // control to _webform_report_test_filters() over how to handle it.
+
+          case 'link':
+            // Apparently not a real webform component type. It is a
+            // 'report component type' (see _webform_report_get_components())
+            // and we cannot filter the SQL statement on them.
+
+          case 'file':
+            // Filtering will be performed on file contents, instead on what is
+            // in the submission data (the filename). So we can't do that here.
+
+          case 'select':
+          case 'grid':
+            // For types 'select' and 'grid', our SQL filter amounts to _one_
+            // of the selected (key) values in a multi-select element matching
+            // all filter criteria. That is: if we had two report filter criteria
+            // 'multi-select-element > 1' and 'multi-select-element < 3', then
+            // we would have a hit if the submission values contained 2 and 4,
+            // because one of the submitted values would match both criteria.
+            // Submission values 1 and 4 would _not_ generate a hit.
+            //
+            // In _webform_report_test_filters(),
+            // - for a 'select' element, the submitted values are concatenated
+            //   with commas, which yields one value ("1,4") that is matched.
+            // - with a 'grid' element (which has no special handling), _all_
+            //   submitted values would need to match the criteria (i.e. if
+            //   _one_ of the submitted values was <> 2 in our example, a hit
+            //   would not be generated.)
+            // We cannot do this with our current SQL structure, so fall through.
+            //
+            // (If you want the behavior described above, just comment out the
+            // two 'case' statements -- and test :) )
+            $filter_value = '';
+            break;
+
+          case 'date':
+            if ($sql_field == 's.submitted') {
+              // The database field we will compare against is numeric
+              // (a timestamp), so turn the date value(s) into one too.
+              // (We assume it's a valid date value, as enforced by the report
+              // filters entry screen)
+              foreach ($filter_value as &$val) {
+                $val = strtotime($val);
+              }
+            }
+            elseif (_webform_report_wf_version() < 3) {
+              // We don't support webform 2's date values in SQL filter.
+              $filter_value = '';
+            }
+            break;
+            
+          case 'time':
+            // The database field is a string formatted as HH:MM:SS.
+            // .......too lazy to think of a cross-db SQL comparison for this...
+            $filter_value = '';
+            break;
+        }
+      }   // end - if ($sql_field => determine $filter_value)
+
+      // Construct SQL expression.
+      if (is_array($filter_value)) {
+
+        // Reformat operand if necessary.
+        if ($filter['ftype'] > 8) {
+          // Comparison is numeric; the database field may not be numeric.
+          $placeholder = '%d';
+          if ($filter['type'] != 'date') {
+            // Cast database field to numeric format first - necessary for a.o.
+            // PostgreSQL. DECIMAL is the only type I found in both MySQL & Pg.
+            $sql_field = 'CAST(' . $sql_field . ' AS DECIMAL)';
+          }
+          elseif ($sql_field != 's.submitted') {
+            // The database field we will compare against is a string
+            // containing a date field, formatted YYYY-MM-DD.
+            // This is a valid value for SQL date comparisons.
+            // Cast the field itself to a date, so it will work on non-mysql.
+            $sql_field = 'CAST(' . $sql_field . ' AS DATE)';
+            // placeholder for date value
+            $placeholder = "'%s'";
+          }
+        }
+
+        // Construct expression.
+        switch ($filter['ftype']) {
+
+          // none. Do not filter.
+          case 0:
+            // No need to run through the filter next time, either.
+            $filter['applied'] = TRUE;
+            break;
+
+          // search for x by user
+          case -1:
+            if (!$search_ok) {
+              // Skip; let _webform_report_test_filters() take care of search
+              break;
+            }
+            // no break, on purpose!
+
+          // Contains x.
+          case 3:
+            $filter_value[0] = '%' . str_replace('%', '%%', $filter_value[0]) . '%';
+            $sql_expr = $sql_field . " LIKE '%s'";
+            break;
+
+
+          // Begins with x.
+          case 1:
+            $filter_value[0] = str_replace('%', '%%', $filter_value[0]) . '%';
+            $sql_expr = $sql_field . " LIKE '%s'";
+            break;
+
+          // Does not begin with x.
+          case 2:
+            $filter_value[0] = str_replace('%', '%%', $filter_value[0]) . '%';
+            $sql_expr = $sql_field . " NOT LIKE '%s'";
+            break;
+
+          // Does not contain x.
+          case 4:
+            $filter_value[0] = '%' . str_replace('%', '%%', $filter_value[0]) . '%';
+            $sql_expr = $sql_field . " NOT LIKE '%s'";
+            break;
+
+          // Equals x.
+          case 5:
+            // all database fields are text (not number)
+            $sql_expr = $sql_field . " = '%s'";
+            break;
 
-  if (isset($node->wnid)) {
-    return db_query("
-      SELECT w.nid, c.name, c.cid, d.nid, d.sid, d.data, s.uid, u.name as user, s.submitted, s.remote_addr
-      FROM {webform} w
-      LEFT JOIN {webform_submitted_data} d ON w.nid = d.nid
-      LEFT JOIN {webform_component} c ON d.cid = c.cid
-      LEFT JOIN {webform_submissions} s  ON d.sid = s.sid
-      LEFT JOIN {users} u ON s.uid = u.uid
-      WHERE d.nid = c.nid 
-      AND c.nid = s.nid
-      AND s.nid = %d
-      ORDER BY d.sid, c.cid, d.no", $node->wnid);
+          // Does not equal x.
+          case 6:
+            $sql_expr = $sql_field . " <> '%s'";
+            break;
+
+          // is empty
+          case 7:
+            $filter_value = array();
+            $sql_expr = '(' . $sql_field . ' IS NULL OR ' . $sql_expr . " = '')";
+            break;
+
+          // is not empty
+          case 8:
+            $filter_value = array();
+            $sql_expr = $sql_field . " <> ''";
+            break;
+
+          // greater than
+          case 9:
+            $sql_expr = $sql_field . ' > ' . $placeholder;
+            break;
+
+          // less than
+          case 10:
+            $sql_expr = $sql_field . ' < ' . $placeholder;
+            break;
+
+          // greater than or equal
+          case 11:
+            $sql_expr = $sql_field . ' >= ' . $placeholder;
+            break;
+
+          // less than or equal
+          case 12:
+            $sql_expr = $sql_field . ' <= ' . $placeholder;
+            break;
+
+          // between
+          case 13:
+            // Both values are filled (see above)
+            $sql_expr = $sql_field . ' >= ' . $placeholder . ' AND '
+              . $sql_field . ' <= ' . $placeholder;
+            break;
+        }
+      }   // end - if ($filter_value is valid => determine $sql_expr)
+
+      // Now store what we got to apply to the SQL later, if that's possible.
+      if ($sql_expr) {
+
+        // See where to apply:
+        if ($filter['cid'] < 0) {
+          // Add a straightforward filter (on e.g. webform_submissions)
+          // and 0, 1 or 2 query arguments
+          $query_where[] = $sql_expr;
+          $query_args = array_merge($query_args, $filter_value);
+        }
+        else {
+          // Add a filter on webform_submitted_data - this is just one of the
+          // criteria that should be met for a submission.
+          if (!isset($data_where[$filter['cid']])) {
+            $data_where[$filter['cid']] = 'd.cid = %d AND ' . $sql_expr;
+            array_unshift($filter_value, $filter['cid']);
+            $data_args[$filter['cid']] = $filter_value;
+          }
+          else {
+            $data_where[$filter['cid']] .= ' AND ' . $sql_expr;
+            $data_args[$filter['cid']] = array_merge($data_args[$filter['cid']], $filter_value);
+          }
+        }
+
+        $filter['applied'] = TRUE;
+      }
+    }   // end - if $filter['value'] contains no tokens...
+  }   // end - foreach ($filters)
+
+  // Construct query, based on the filters
+  
+  $fields = "s.sid, s.uid, u.name as user, s.submitted, s.remote_addr";
+  $fields_outer = "s.sid, s.uid, s.user, s.submitted, s.remote_addr";
+  $fields_groupby = "s.sid, s.uid, u.name, s.submitted, s.remote_addr";
+  $query = "
+    FROM {webform_submitted_data} d
+    INNER JOIN {webform_submissions} s ON d.sid = s.sid
+    INNER JOIN {users} u ON s.uid = u.uid
+    WHERE d.nid = %d";
+
+  if (!empty($query_where)) {
+    // Add 'normal' filters (to anything but webform_submitted_data);
+    // arguments are in
+    $query .= ' AND ' . implode(' AND ', $query_where);
+  }
+
+  if (empty($data_where)) {
+    // no grouped subquery necessary.
+    $query = 'SELECT ' . $fields . ', d.cid, d.data' . $query;
   }
   else {
-    return NULL;
+    // We need a grouped subquery to properly filter submissions
+    // on criteria for the submitted data.
+    $query = "SELECT ' . $fields_outer . ', d.cid, d.data
+      FROM (SELECT " . $fields . $query;
+
+    // Add filters on data fields.
+    $query_where = array();
+    foreach ($data_args as $cid => $args) {
+      $query_args = array_merge($query_args, $args);
+      // Probably a redundant rebuild from $data_where into $query_where,
+      // to be sure we're adding $data_where values in exactly the same order as
+      // $data_args values.
+      $query_where[] = $data_where[$cid];
+    }
+    $query .= ' AND ((' . implode(') OR (', $query_where) . ')) GROUP BY ' . $fields_groupby;
+
+    if (count($data_where) > 1) {
+      // Add another 'having' filter on the grouped result, specifying that _all_ 
+      // fields must be matched.
+      $query .= ' HAVING count(d.cid) = %d';
+      $query_args[] = count($query_where);
+    }
+
+    // Finish off the outer query, joining submitted data against the filtered
+    // submissions again
+    $query .= ') s INNER JOIN {webform_submitted_data} d ON s.sid = d.sid';
+  }
+
+  return db_query($query, $query_args);
+}
+
+/**
+ * Returns webform version. Only possible return values are 2 and 3
+ */
+function _webform_report_wf_version() {
+  static $version = NULL;
+
+  if (!isset($version)) {
+    if (db_result(db_query("SELECT schema_version FROM {system} where name='webform'")) >= 6300) {
+      $version = 3;
+    }
+    else {
+      $version = 2;
+    }
   }
+  return $version;
 }
 
 /**
@@ -270,15 +647,6 @@ function _webform_report_get_body_conten
       Please click on Report Criteria under Edit to add webform data to your report.');
   }
   
-  // the selected webform has no submissions.
-  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.');
-  }
-  
   // output report
   else {
 
@@ -300,7 +668,9 @@ function _webform_report_get_body_conten
     }
     
     // output current page
-    $output .= _webform_report_pager($report['headers'], $report['rows'], $node);
+    if (isset($report['rows'])) {
+      $output .= _webform_report_pager($report['headers'], $report['rows'], $node);
+    }
 
     // no submissions met criteria
     if (count($report['rows']) == 0) {
@@ -374,24 +744,24 @@ function _webform_report_get_report_data
       }   // end - if ($col['type'] == 'select')...
       // Get mapping for select lists (End)
     }   // end - foreach ($columns as $index => $col)...
-    
+
+    $filters = _webform_report_get_filters($node, $components);
+    // add filter fields to lookup
+    foreach ($filters as $index => $filter) {
+      // fields by cid for quick lookup
+      $fields[$filter['cid']] = $filter['name'];
+    }
+
     // query submissions  
-    $rs = _webform_report_get_submissions($node);  
+    $rs = _webform_report_get_submissions($node->wnid, $filters);
     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;      
@@ -430,7 +800,7 @@ function _webform_report_get_report_data
           
           // save submitter uid and node nid
           $data['uid'] = $row->uid;
-          $data['nid'] = $row->nid;
+          $data['nid'] = $node->wnid;
             
           // fill in meta fields
           if (array_key_exists(-1, $fields)) {
@@ -504,17 +874,16 @@ function _webform_report_test_filters($d
 
   // filter result, return true if no filters
   $ok = TRUE;
+  // search flag
+  $hit = FALSE;
 
-  // if any filters
-  if (count($filters) > 0) {
-  
-    // search flag
-    $hit = FALSE;
+  // loop through all filters.
+  foreach ($filters as $index => $filter) {
 
-    // loop through all filters  
-    foreach ($filters as $index => $filter) {
+    // check that the filter hasn't already been applied (in the SQL statement).
+    if (empty($filter['applied'])) {
 
-      // reset result for each filter  
+      // reset result for each filter
       $ok = FALSE;
         
       // check that cid is in data
@@ -534,7 +903,7 @@ function _webform_report_test_filters($d
         // prepare filter values
         $filter_data = strip_tags(strtolower(trim($value['data'])));
         $filter_value = $filter['value'];
-        
+
         // Do token substitution, if installed - AFTER checking whether the
         // value might even contain a token (because token_replace is really
         // resource intensive. Look for presence of TOKEN_PREFIX first.)
@@ -749,28 +1118,28 @@ function _webform_report_test_filters($d
             }
             break;
           }   // end - switch($filter['type'])...
-          
+
         }   // end - if (array_key_exists($filter['cid'], $data)...
-        
+
         // 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)...
+
+    }   // end - if (empty($filter['applied']))...
+
+  }   // end - foreach($filters as $index => $filter)...
   
   // return filter result
   return $ok;

