1<?php
2/**
3 * @file handlers.inc
4 * Defines the various handler objects to help build and display views.
5 */
6
7/**
8 * Instantiate and construct a new handler
9 */
10function _views_create_handler($definition, $type = 'handler') {
11//  vpr('Instantiating handler ' . $definition['handler']);
12  if (empty($definition['handler'])) {
13    return;
14  }
15
16  if (!class_exists($definition['handler']) && !views_include_handler($definition, $type)) {
17    return;
18  }
19
20  $handler = new $definition['handler'];
21  $handler->set_definition($definition);
22  // let the handler have something like a constructor.
23  $handler->construct();
24
25  return $handler;
26}
27
28/**
29 * Attempt to find the include file for a given handler from its definition.
30 *
31 * This will also attempt to include all parents, though we're maxing the
32 * parent chain to 10 to prevent infinite loops.
33 */
34function views_include_handler($definition, $type, $count = 0) {
35  // Do not proceed if the class already exists.
36  if (isset($definition['handler']) && class_exists($definition['handler'])) {
37    return TRUE;
38  }
39
40  // simple infinite loop prevention.
41  if ($count > 10) {
42    vpr(t('Handler @handler include tried to loop infinitely!', array('@handler' => $definition['handler'])));
43    return FALSE;
44  }
45
46  if (!isset($definition['path'])) {
47    if ($type == 'handler') {
48      $definition += views_fetch_handler_data($definition['handler']);
49    }
50    else {
51      $definition += views_fetch_plugin_data($type, $definition['handler']);
52    }
53  }
54
55  if (!empty($definition['parent'])) {
56    if ($type == 'handler') {
57      $parent = views_fetch_handler_data($definition['parent']);
58    }
59    else {
60      $parent = views_fetch_plugin_data($type, $definition['parent']);
61    }
62
63    if ($parent) {
64      $rc = views_include_handler($parent, $type, $count + 1);
65      // If the parent chain cannot be included, don't try; this will
66      // help alleviate problems with modules with cross dependencies.
67      if (!$rc) {
68        return FALSE;
69      }
70    }
71  }
72
73  if (isset($definition['path']) && $definition['file']) {
74    $filename = './' . $definition['path'] . '/' . $definition['file'];
75    if (file_exists($filename)) {
76      require_once $filename;
77    }
78  }
79
80  return class_exists($definition['handler']);
81}
82
83/**
84 * Prepare a handler's data by checking defaults and such.
85 */
86function _views_prepare_handler($definition, $data, $field) {
87  foreach (array('group', 'title', 'title short', 'help', 'real field') as $key) {
88    if (!isset($definition[$key])) {
89      // First check the field level
90      if (!empty($data[$field][$key])) {
91        $definition[$key] = $data[$field][$key];
92      }
93      // Then if that doesn't work, check the table level
94      else if (!empty($data['table'][$key])) {
95        $definition[$key] = $data['table'][$key];
96      }
97    }
98  }
99
100  return _views_create_handler($definition);
101}
102
103/**
104 * Fetch the handler data from cache.
105 */
106function views_fetch_handler_data($handler = NULL) {
107  static $cache = NULL;
108  if (!isset($cache)) {
109    $start = views_microtime();
110
111    $cache = views_discover_handlers();
112
113    vpr('Views handlers build time: ' . (views_microtime() - $start) * 1000 . ' ms');
114  }
115
116  if (!$handler) {
117    return $cache;
118  }
119  else if (isset($cache[$handler])) {
120    return $cache[$handler];
121  }
122
123  // Return an empty array if there is no match.
124  return array();
125}
126
127/**
128 * Builds and return a list of all handlers available in the system.
129 *
130 * @return Nested array of handlers
131 */
132function views_discover_handlers() {
133  $cache = array();
134  // Get handlers from all modules.
135  foreach (module_implements('views_handlers') as $module) {
136    $function = $module . '_views_handlers';
137    $result = $function();
138    if (!is_array($result)) {
139      continue;
140    }
141
142    $module_dir = isset($result['info']['module']) ? $result['info']['module'] : $module;
143    $path = isset($result['info']['path']) ? $result['info']['path'] : drupal_get_path('module', $module_dir);
144
145    foreach ($result['handlers'] as $handler => $def) {
146      if (!isset($def['module'])) {
147        $def['module'] = $module_dir;
148      }
149      if (!isset($def['path'])) {
150        $def['path'] = $path;
151      }
152      if (!isset($def['file'])) {
153        $def['file'] = "$handler.inc";
154      }
155      if (!isset($def['handler'])) {
156        $def['handler'] = $handler;
157      }
158      // merge the new data in
159      $cache[$handler] = $def;
160    }
161  }
162  return $cache;
163}
164
165/**
166 * Fetch a handler to join one table to a primary table from the data cache
167 */
168function views_get_table_join($table, $base_table) {
169  $data = views_fetch_data($table);
170  if (isset($data['table']['join'][$base_table])) {
171    $h = $data['table']['join'][$base_table];
172    if (!empty($h['handler']) && class_exists($h['handler'])) {
173      $handler = new $h['handler'];
174    }
175    else {
176      $handler = new views_join();
177    }
178
179    // Fill in some easy defaults
180    $handler->definition = $h;
181    if (empty($handler->definition['table'])) {
182      $handler->definition['table'] = $table;
183    }
184    // If this is empty, it's a direct link.
185    if (empty($handler->definition['left_table'])) {
186      $handler->definition['left_table'] = $base_table;
187    }
188
189    if (isset($h['arguments'])) {
190      call_user_func_array(array(&$handler, 'construct'), $h['arguments']);
191    }
192    else {
193      $handler->construct();
194    }
195
196    return $handler;
197  }
198  // DEBUG -- identify missing handlers
199  vpr("Missing join: $table $base_table");
200}
201
202/**
203 * Base handler, from which all the other handlers are derived.
204 * It creates a common interface to create consistency amongst
205 * handlers and data.
206 *
207 * This class would be abstract in PHP5, but PHP4 doesn't understand that.
208 *
209 * Definition terms:
210 * - table: The actual table this uses; only specify if different from
211 *          the table this is attached to.
212 * - real field: The actual field this uses; only specify if different from
213 *               the field this item is attached to.
214 * - group: A text string representing the 'group' this item is attached to,
215 *          for display in the UI. Examples: "Node", "Taxonomy", "Comment",
216 *          "User", etc. This may be inherited from the parent definition or
217 *          the 'table' definition.
218 * - title: The title for this handler in the UI. This may be inherited from
219 *          the parent definition or the 'table' definition.
220 * - help: A more informative string to give to the user to explain what this
221 *         field/handler is or does.
222 * - access callback: If this field should have access control, this could
223 *                    be a function to use. 'user_access' is a common
224 *                    function to use here. If not specified, no access
225 *                    control is provided.
226 * - access arguments: An array of arguments for the access callback.
227 */
228class views_handler extends views_object {
229  /**
230   * init the handler with necessary data.
231   * @param $view
232   *   The $view object this handler is attached to.
233   * @param $options
234   *   The item from the database; the actual contents of this will vary
235   *   based upon the type of handler.
236   */
237  function init(&$view, $options) {
238    $this->view = &$view;
239    $this->unpack_options($this->options, $options);
240
241    // This exist on most handlers, but not all. So they are still optional.
242    if (isset($options['table'])) {
243      $this->table = $options['table'];
244    }
245
246    if (isset($this->definition['real field'])) {
247      $this->real_field = $this->definition['real field'];
248    }
249
250    if (isset($this->definition['field'])) {
251      $this->real_field = $this->definition['field'];
252    }
253
254    if (isset($options['field'])) {
255      $this->field = $options['field'];
256      if (!isset($this->real_field)) {
257        $this->real_field = $options['field'];
258      }
259    }
260
261    $this->query = &$view->query;
262  }
263
264  /**
265   * Return a string representing this handler's name in the UI.
266   */
267  function ui_name($short = FALSE) {
268    $title = ($short && isset($this->definition['title short'])) ? $this->definition['title short'] : $this->definition['title'];
269    return t('!group: !title', array('!group' => $this->definition['group'], '!title' => $title));
270  }
271
272  /**
273   * Provide a form for setting options.
274   */
275  function options_form(&$form, &$form_state) { }
276
277  /**
278   * Validate the options form.
279   */
280  function options_validate($form, &$form_state) { }
281
282  /**
283   * Perform any necessary changes to the form values prior to storage.
284   * There is no need for this function to actually store the data.
285   */
286  function options_submit($form, &$form_state) { }
287
288  /**
289   * If a handler has 'extra options' it will get a little settings widget and
290   * another form called extra_options.
291   */
292  function has_extra_options() { return FALSE; }
293
294  /**
295   * Provide defaults for the handler.
296   */
297  function extra_options(&$option) { }
298
299  /**
300   * Provide a form for setting options.
301   */
302  function extra_options_form(&$form, &$form_state) { }
303
304  /**
305   * Validate the options form.
306   */
307  function extra_options_validate($form, &$form_state) { }
308
309  /**
310   * Perform any necessary changes to the form values prior to storage.
311   * There is no need for this function to actually store the data.
312   */
313  function extra_options_submit($form, &$form_state) { }
314
315  /**
316   * Set new exposed option defaults when exposed setting is flipped
317   * on.
318   */
319  function expose_options() { }
320  /**
321   * Render our chunk of the exposed filter form when selecting
322   */
323  function exposed_form(&$form, &$form_state) { }
324
325  /**
326   * Validate the exposed filter form
327   */
328  function exposed_validate(&$form, &$form_state) { }
329
330  /**
331   * Submit the exposed filter form
332   */
333  function exposed_submit(&$form, &$form_state) { }
334
335  /**
336   * Get information about the exposed form for the form renderer.
337   *
338   * @return
339   *   An array with the following keys:
340   *   - operator: The $form key of the operator. Set to NULL if no operator.
341   *   - value: The $form key of the value. Set to NULL if no value.
342   *   - label: The label to use for this piece.
343   */
344  function exposed_info() { }
345
346  /**
347   * Determine if a handler can be exposed.
348   */
349  function can_expose() { return FALSE; }
350
351 /**
352  * Check whether current user has access to this handler.
353  *
354  * @return boolean
355  */
356  function access() {
357    if (isset($this->definition['access callback']) && function_exists($this->definition['access callback'])) {
358      if (isset($this->definition['access arguments']) && is_array($this->definition['access arguments'])) {
359        return call_user_func_array($this->definition['access callback'], $this->definition['access arguments']);
360      }
361      return $this->definition['access callback']();
362    }
363
364    return TRUE;
365  }
366
367  /**
368   * Run before the view is built.
369   *
370   * This gives all the handlers some time to set up before any handler has
371   * been fully run.
372   */
373  function pre_query() { }
374
375  /**
376   * Called just prior to query(), this lets a handler set up any relationship
377   * it needs.
378   */
379  function set_relationship() {
380    // Ensure this gets set to something.
381    $this->relationship = NULL;
382
383    // Don't process non-existant relationships.
384    if (empty($this->options['relationship']) || $this->options['relationship'] == 'none') {
385      return;
386    }
387
388    $relationship = $this->options['relationship'];
389
390    // Ignore missing/broken relationships.
391    if (empty($this->view->relationship[$relationship])) {
392      return;
393    }
394
395    // Check to see if the relationship has already processed. If not, then we
396    // cannot process it.
397    if (empty($this->view->relationship[$relationship]->alias)) {
398      return;
399    }
400
401    // Finally!
402    $this->relationship = $this->view->relationship[$relationship]->alias;
403  }
404
405  /**
406   * Add this handler into the query.
407   *
408   * If we were using PHP5, this would be abstract.
409   */
410  function query() { }
411
412  /**
413   * Ensure the main table for this handler is in the query. This is used
414   * a lot.
415   */
416  function ensure_my_table() {
417    if (!isset($this->table_alias)) {
418      $this->table_alias = $this->query->ensure_table($this->table, $this->relationship);
419    }
420    return $this->table_alias;
421  }
422
423  /**
424   * Provide text for the administrative summary
425   */
426  function admin_summary() { }
427
428  /**
429   * Determine if the argument needs a style plugin.
430   *
431   * @return TRUE/FALSE
432   */
433  function needs_style_plugin() { return FALSE; }
434
435  /**
436   * Determine if this item is 'exposed', meaning it provides form elements
437   * to let users modify the view.
438   *
439   * @return TRUE/FALSE
440   */
441  function is_exposed() {
442    return !empty($this->options['exposed']);
443  }
444
445  /**
446   * Take input from exposed filters and assign to this handler, if necessary.
447   */
448  function accept_exposed_input($input) { return TRUE; }
449
450  /**
451   * If set to remember exposed input in the session, store it there.
452   */
453  function store_exposed_input($input, $status) { return TRUE; }
454
455  /**
456   * Get the join object that should be used for this handler.
457   *
458   * This method isn't used a great deal, but it's very handy for easily
459   * getting the join if it is necessary to make some changes to it, such
460   * as adding an 'extra'.
461   */
462  function get_join() {
463    // get the join from this table that links back to the base table.
464    // Determine the primary table to seek
465    if (empty($this->query->relationships[$this->relationship])) {
466      $base_table = $this->query->base_table;
467    }
468    else {
469      $base_table = $this->query->relationships[$this->relationship]['base'];
470    }
471
472    $join = views_get_table_join($this->table, $base_table);
473    if ($join) {
474      return drupal_clone($join);
475    }
476  }
477
478  /**
479   * Validates the handler against the complete View.
480   *
481   * This is called when the complete View is being validated. For validating
482   * the handler options form use options_validate().
483   *
484   * @see views_handler::options_validate()
485   *
486   * @return
487   *   Empty array if the handler is valid; an array of error strings if it is not.
488   */
489  function validate() { return array(); }
490
491  /**
492   * Determine if the handler is considered 'broken', meaning it's a
493   * a placeholder used when a handler can't be found.
494   */
495  function broken() { }
496}
497
498/**
499 * This many to one helper object is used on both arguments and filters.
500 *
501 * @todo This requires extensive documentation on how this class is to
502 * be used. For now, look at the arguments and filters that use it. Lots
503 * of stuff is just pass-through but there are definitely some interesting
504 * areas where they interact.
505 *
506 * Any handler that uses this can have the following possibly additional
507 * definition terms:
508 * - numeric: If true, treat this field as numeric, using %d instead of %s in
509 *            queries.
510 *
511 */
512class views_many_to_one_helper {
513  function views_many_to_one_helper(&$handler) {
514    $this->handler = &$handler;
515  }
516
517  public static function option_definition(&$options) {
518    $options['reduce_duplicates'] = array('default' => FALSE);
519  }
520
521  function options_form(&$form, &$form_state) {
522    $form['reduce_duplicates'] = array(
523      '#type' => 'checkbox',
524      '#title' => t('Reduce duplicates'),
525      '#description' => t('This filter can cause items that have more than one of the selected options to appear as duplicate results. If this filter causes duplicate results to occur, this checkbox can reduce those duplicates; however, the more terms it has to search for, the less performant the query will be, so use this with caution. Shouldn\'t be set on single-value fields, as it may cause values to disappear from display, if used on an incompatible field.'),
526      '#default_value' => !empty($this->handler->options['reduce_duplicates']),
527    );
528  }
529
530  /**
531   * Sometimes the handler might want us to use some kind of formula, so give
532   * it that option. If it wants us to do this, it must set $helper->formula = TRUE
533   * and implement handler->get_formula();
534   */
535  function get_field() {
536    if (!empty($this->formula)) {
537      return $this->handler->get_formula();
538    }
539    else {
540      return $this->handler->table_alias . '.' . $this->handler->real_field;
541    }
542  }
543
544  /**
545   * Add a table to the query.
546   *
547   * This is an advanced concept; not only does it add a new instance of the table,
548   * but it follows the relationship path all the way down to the relationship
549   * link point and adds *that* as a new relationship and then adds the table to
550   * the relationship, if necessary.
551   */
552  function add_table($join = NULL, $alias = NULL) {
553    // This is used for lookups in the many_to_one table.
554    $field = $this->handler->table . '.' . $this->handler->field;
555
556    if (empty($join)) {
557      $join = $this->get_join();
558    }
559
560    // See if there's a chain between us and the base relationship. If so, we need
561    // to create a new relationship to use.
562    $relationship = $this->handler->relationship;
563
564    // Determine the primary table to seek
565    if (empty($this->handler->query->relationships[$relationship])) {
566      $base_table = $this->handler->query->base_table;
567    }
568    else {
569      $base_table = $this->handler->query->relationships[$relationship]['base'];
570    }
571
572    // Cycle through the joins. This isn't as error-safe as the normal
573    // ensure_path logic. Perhaps it should be.
574    $r_join = drupal_clone($join);
575    while ($r_join->left_table != $base_table) {
576      $r_join = views_get_table_join($r_join->left_table, $base_table);
577    }
578    // If we found that there are tables in between, add the relationship.
579    if ($r_join->table != $join->table) {
580      $relationship = $this->handler->query->add_relationship($this->handler->table . '_' . $r_join->table, $r_join, $r_join->table, $this->handler->relationship);
581    }
582
583    // And now add our table, using the new relationship if one was used.
584    $alias = $this->handler->query->add_table($this->handler->table, $relationship, $join, $alias);
585
586    // Store what values are used by this table chain so that other chains can
587    // automatically discard those values.
588    if (empty($this->handler->view->many_to_one_tables[$field])) {
589      $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
590    }
591    else {
592      $this->handler->view->many_to_one_tables[$field] = array_merge($this->handler->view->many_to_one_tables[$field], $this->handler->value);
593    }
594
595    return $alias;
596  }
597
598  function get_join() {
599    return $this->handler->get_join();
600  }
601
602  /**
603   * Provide the proper join for summary queries. This is important in part because
604   * it will cooperate with other arguments if possible.
605   */
606  function summary_join() {
607    $field = $this->handler->table . '.' . $this->handler->field;
608    $join = $this->get_join();
609
610    // shortcuts
611    $options = $this->handler->options;
612    $view = &$this->handler->view;
613    $query = &$this->handler->query;
614
615    if (!empty($options['require_value'])) {
616      $join->type = 'INNER';
617    }
618
619    if (empty($options['add_table']) || empty($view->many_to_one_tables[$field])) {
620      return $query->ensure_table($this->handler->table, $this->handler->relationship, $join);
621    }
622    else {
623      if (!empty($view->many_to_one_tables[$field])) {
624        foreach ($view->many_to_one_tables[$field] as $value) {
625          $join->extra = array(
626            array(
627              'field' => $this->handler->real_field,
628              'operator' => '!=',
629              'value' => $value,
630              'numeric' => !empty($this->definition['numeric']),
631            ),
632          );
633        }
634      }
635      return $this->add_table($join);
636    }
637  }
638
639  /**
640   * Override ensure_my_table so we can control how this joins in.
641   * The operator actually has influence over joining.
642   */
643  function ensure_my_table() {
644    if (!isset($this->handler->table_alias)) {
645      // For 'or' if we're not reducing duplicates, we get the absolute simplest:
646      $field = $this->handler->table . '.' . $this->handler->field;
647      if ($this->handler->operator == 'or' && empty($this->handler->options['reduce_duplicates'])) {
648        if (empty($this->handler->options['add_table']) && empty($this->handler->view->many_to_one_tables[$field])) {
649          // query optimization, INNER joins are slightly faster, so use them
650          // when we know we can.
651          $join = $this->get_join();
652          $join->type = 'INNER';
653          $this->handler->table_alias = $this->handler->query->ensure_table($this->handler->table, $this->handler->relationship, $join);
654          $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
655        }
656        else {
657          $join = $this->get_join();
658          $join->type = 'LEFT';
659          if (!empty($this->handler->view->many_to_one_tables[$field])) {
660            foreach ($this->handler->view->many_to_one_tables[$field] as $value) {
661              $join->extra = array(
662                array(
663                  'field' => $this->handler->real_field,
664                  'operator' => '!=',
665                  'value' => $value,
666                  'numeric' => !empty($this->handler->definition['numeric']),
667                ),
668              );
669            }
670          }
671
672          $this->handler->table_alias = $this->add_table($join);
673        }
674
675        return $this->handler->table_alias;
676      }
677
678      if ($this->handler->operator != 'not') {
679        // If it's an and or an or, we do one join per selected value.
680        // Clone the join for each table:
681        $this->handler->table_aliases = array();
682        foreach ($this->handler->value as $value) {
683          $join = $this->get_join();
684          if ($this->handler->operator == 'and') {
685            $join->type = 'INNER';
686          }
687          $join->extra = array(
688            array(
689              'field' => $this->handler->real_field,
690              'value' => $value,
691              'numeric' => !empty($this->handler->definition['numeric']),
692            ),
693          );
694
695          // The table alias needs to be unique to this value across the
696          // multiple times the filter or argument is called by the view.
697          if (!isset($this->handler->view->many_to_one_aliases[$field][$value])) {
698            if (!isset($this->handler->view->many_to_one_count[$this->handler->table])) {
699              $this->handler->view->many_to_one_count[$this->handler->table] = 0;
700            }
701            $this->handler->view->many_to_one_aliases[$field][$value] = $this->handler->table . '_value_' . ($this->handler->view->many_to_one_count[$this->handler->table]++);
702          }
703          $alias = $this->handler->table_aliases[$value] = $this->add_table($join, $this->handler->view->many_to_one_aliases[$field][$value]);
704
705          // and set table_alias to the first of these.
706          if (empty($this->handler->table_alias)) {
707            $this->handler->table_alias = $alias;
708          }
709        }
710      }
711      else {
712        // For not, we just do one join. We'll add a where clause during
713        // the query phase to ensure that $table.$field IS NULL.
714        $join = $this->get_join();
715        $join->type = 'LEFT';
716        $join->extra = array();
717        $join->extra_type = 'OR';
718        foreach ($this->handler->value as $value) {
719          $join->extra[] = array(
720            'field' => $this->handler->real_field,
721            'value' => $value,
722            'numeric' => !empty($this->handler->definition['numeric']),
723          );
724        }
725
726        $this->handler->table_alias = $this->add_table($join);
727      }
728    }
729    return $this->handler->table_alias;
730  }
731
732  function add_filter() {
733    if (empty($this->handler->value)) {
734      return;
735    }
736    $this->handler->ensure_my_table();
737
738    // Shorten some variables:
739    $field = $this->get_field();
740    $options = $this->handler->options;
741    $operator = $this->handler->operator;
742    if (empty($options['group'])) {
743      $options['group'] = 0;
744    }
745
746    $placeholder = !empty($this->handler->definition['numeric']) ? '%d' : "'%s'";
747
748    if ($operator == 'not') {
749      $this->handler->query->add_where($options['group'], "$field IS NULL");
750    }
751    else if ($operator == 'or' && empty($options['reduce_duplicates'])) {
752      if (count($this->handler->value) > 1) {
753        $replace = array_fill(0, sizeof($this->handler->value), $placeholder);
754        $in = '(' . implode(", ", $replace) . ')';
755        $this->handler->query->add_where($options['group'], "$field IN $in", $this->handler->value);
756      }
757      else {
758        $this->handler->query->add_where($options['group'], "$field = $placeholder", $this->handler->value);
759      }
760    }
761    else {
762      $field = $this->handler->real_field;
763      $clauses = array();
764      foreach ($this->handler->table_aliases as $value => $alias) {
765        $clauses[] = "$alias.$field = $placeholder";
766      }
767
768      $group = empty($options['group']) ? 0 : $options['group'];
769
770      // implode on either AND or OR.
771      $this->handler->query->add_where($group, implode(' ' . strtoupper($operator) . ' ', $clauses), $this->handler->value);
772    }
773  }
774}
775
776/*
777 * Break x,y,z and x+y+z into an array. Numeric only.
778 *
779 * @param $str
780 *   The string to parse.
781 * @param $filter
782 *   The filter object to use as a base.
783 *
784 * @return $filter
785 *   The new filter object.
786 */
787function views_break_phrase($str, &$filter) {
788  if (!$filter) {
789    $filter = new stdClass();
790  }
791
792  // Set up defaults:
793  if (!isset($filter->value)) {
794    $filter->value = array();
795  }
796
797  if (!isset($filter->operator)) {
798    $filter->operator = 'or';
799  }
800
801  if ($str == '') {
802    return $filter;
803  }
804
805  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
806    // The '+' character in a query string may be parsed as ' '.
807    $filter->operator = 'or';
808    $filter->value = preg_split('/[+ ]/', $str);
809  }
810  else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
811    $filter->operator = 'and';
812    $filter->value = explode(',', $str);
813  }
814
815  // Keep an 'error' value if invalid strings were given.
816  if (!empty($str) && (empty($filter->value) || !is_array($filter->value))) {
817    $filter->value = array(-1);
818    return $filter;
819  }
820
821  // Doubly ensure that all values are numeric only.
822  foreach ($filter->value as $id => $value) {
823    $filter->value[$id] = intval($value);
824  }
825
826  return $filter;
827}
828
829// --------------------------------------------------------------------------
830// Date helper functions
831
832/**
833 * Figure out what timezone we're in; needed for some date manipulations.
834 */
835function views_get_timezone() {
836  global $user;
837  if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
838    $timezone = $user->timezone;
839  }
840  else {
841    $timezone = variable_get('date_default_timezone', 0);
842  }
843
844  // set up the database timezone
845  if (in_array($GLOBALS['db_type'], array('mysql', 'mysqli', 'pgsql'))) {
846    $offset = '+00:00';
847    static $already_set = false;
848    if (!$already_set) {
849      if ($GLOBALS['db_type'] == 'pgsql') {
850        db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
851      }
852      elseif ($GLOBALS['db_type'] == 'mysqli') {
853        db_query("SET @@session.time_zone = '$offset'");
854      }
855      elseif ($GLOBALS['db_type'] == 'mysql' && version_compare(db_version(), '4.1.3', '>=')) {
856        db_query("SET @@session.time_zone = '$offset'");
857      }
858
859      $already_set = true;
860    }
861  }
862
863  return $timezone;
864}
865
866/**
867 * Helper function to create cross-database SQL dates.
868 *
869 * @param $field
870 *   The real table and field name, like 'tablename.fieldname'.
871 * @param $field_type
872 *  The type of date field, 'int' or 'datetime'.
873 * @param $set_offset
874 *   The name of a field that holds the timezone offset or a fixed timezone
875 *   offset value. If not provided, the normal Drupal timezone handling
876 *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
877 * @return
878 *   An appropriate SQL string for the db type and field type.
879 */
880function views_date_sql_field($field, $field_type = 'int', $set_offset = NULL) {
881  $db_type = $GLOBALS['db_type'];
882  $offset = $set_offset !== NULL ? $set_offset : views_get_timezone();
883  switch ($db_type) {
884    case 'mysql':
885    case 'mysqli':
886      switch ($field_type) {
887        case 'int':
888          $field = "FROM_UNIXTIME($field)";
889          break;
890        case 'datetime':
891          break;
892      }
893      if (!empty($offset)) {
894        $field = "($field + INTERVAL $offset SECOND)";
895      }
896      return $field;
897    case 'pgsql':
898      switch ($field_type) {
899        case 'int':
900          $field = "$field::ABSTIME";
901          break;
902        case 'datetime':
903          break;
904      }
905      if (!empty($offset)) {
906        $field = "($field + INTERVAL '$offset SECONDS')";
907      }
908      return $field;
909  }
910}
911
912/**
913 * Helper function to create cross-database SQL date formatting.
914 *
915 * @param $format
916 *   A format string for the result, like 'Y-m-d H:i:s'.
917 * @param $field
918 *   The real table and field name, like 'tablename.fieldname'.
919 * @param $field_type
920 *   The type of date field, 'int' or 'datetime'.
921 * @param $set_offset
922 *   The name of a field that holds the timezone offset or a fixed timezone
923 *   offset value. If not provided, the normal Drupal timezone handling
924 *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
925 * @return
926 *   An appropriate SQL string for the db type and field type.
927 */
928function views_date_sql_format($format, $field, $field_type = 'int', $set_offset = NULL) {
929  $db_type = $GLOBALS['db_type'];
930  $field = views_date_sql_field($field, $field_type, $set_offset);
931  switch ($db_type) {
932    case 'mysql':
933    case 'mysqli':
934      $replace = array(
935        'Y' => '%Y',
936        'y' => '%y',
937        'M' => '%%b',
938        'm' => '%m',
939        'n' => '%c',
940        'F' => '%M',
941        'D' => '%a',
942        'd' => '%%d',
943        'l' => '%W',
944        'j' => '%e',
945        'W' => '%v',
946        'H' => '%H',
947        'h' => '%h',
948        'i' => '%i',
949        's' => '%%s',
950        'A' => '%p',
951        );
952      $format = strtr($format, $replace);
953      return "DATE_FORMAT($field, '$format')";
954    case 'pgsql':
955      $replace = array(
956        'Y' => 'YYYY',
957        'y' => 'YY',
958        'M' => 'Mon',
959        'm' => 'MM',
960        'n' => 'MM', // no format for Numeric representation of a month, without leading zeros
961        'F' => 'Month',
962        'D' => 'Dy',
963        'd' => 'DD',
964        'l' => 'Day',
965        'j' => 'DD', // no format for Day of the month without leading zeros
966        'W' => 'WW',
967        'H' => 'HH24',
968        'h' => 'HH12',
969        'i' => 'MI',
970        's' => 'SS',
971        'A' => 'AM',
972        );
973      $format = strtr($format, $replace);
974      return "TO_CHAR($field, '$format')";
975  }
976}
977
978/**
979 * Helper function to create cross-database SQL date extraction.
980 *
981 * @param $extract_type
982 *   The type of value to extract from the date, like 'MONTH'.
983 * @param $field
984 *   The real table and field name, like 'tablename.fieldname'.
985 * @param $field_type
986 *   The type of date field, 'int' or 'datetime'.
987 * @param $set_offset
988 *   The name of a field that holds the timezone offset or a fixed timezone
989 *   offset value. If not provided, the normal Drupal timezone handling
990 *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
991 * @return
992 *   An appropriate SQL string for the db type and field type.
993 */
994function views_date_sql_extract($extract_type, $field, $field_type = 'int', $set_offset = NULL) {
995  $db_type = $GLOBALS['db_type'];
996  $field = views_date_sql_field($field, $field_type, $set_offset);
997
998  // Note there is no space after FROM to avoid db_rewrite problems
999  // see http://drupal.org/node/79904.
1000  switch ($extract_type) {
1001  case('DATE'):
1002    return $field;
1003  case('YEAR'):
1004    return "EXTRACT(YEAR FROM($field))";
1005  case('MONTH'):
1006    return "EXTRACT(MONTH FROM($field))";
1007  case('DAY'):
1008    return "EXTRACT(DAY FROM($field))";
1009  case('HOUR'):
1010    return "EXTRACT(HOUR FROM($field))";
1011  case('MINUTE'):
1012    return "EXTRACT(MINUTE FROM($field))";
1013  case('SECOND'):
1014    return "EXTRACT(SECOND FROM($field))";
1015  case('WEEK'):  // ISO week number for date
1016    switch ($db_type) {
1017      case('mysql'):
1018      case('mysqli'):
1019        // WEEK using arg 3 in mysql should return the same value as postgres EXTRACT
1020        return "WEEK($field, 3)";
1021      case('pgsql'):
1022        return "EXTRACT(WEEK FROM($field))";
1023    }
1024  case('DOW'):
1025    switch ($db_type) {
1026      case('mysql'):
1027      case('mysqli'):
1028        // mysql returns 1 for Sunday through 7 for Saturday
1029        // php date functions and postgres use 0 for Sunday and 6 for Saturday
1030        return "INTEGER(DAYOFWEEK($field) - 1)";
1031      case('pgsql'):
1032        return "EXTRACT(DOW FROM($field))";
1033    }
1034  case('DOY'):
1035    switch ($db_type) {
1036      case('mysql'):
1037      case('mysqli'):
1038        return "DAYOFYEAR($field)";
1039      case('pgsql'):
1040        return "EXTRACT(DOY FROM($field))";
1041    }
1042  }
1043}
1044
1045/**
1046 * Implementation of hook_views_handlers() to register all of the basic handlers
1047 * views uses.
1048 */
1049function views_views_handlers() {
1050  return array(
1051    'info' => array(
1052      'path' => drupal_get_path('module', 'views') . '/handlers',
1053    ),
1054    'handlers' => array(
1055      // argument handlers
1056      'views_handler_argument' => array(
1057        'parent' => 'views_handler',
1058      ),
1059      'views_handler_argument_numeric' => array(
1060        'parent' => 'views_handler_argument',
1061      ),
1062      'views_handler_argument_formula' => array(
1063        'parent' => 'views_handler_argument',
1064      ),
1065      'views_handler_argument_date' => array(
1066        'parent' => 'views_handler_argument_formula',
1067      ),
1068      'views_handler_argument_string' => array(
1069        'parent' => 'views_handler_argument',
1070      ),
1071      'views_handler_argument_many_to_one' => array(
1072        'parent' => 'views_handler_argument',
1073      ),
1074      'views_handler_argument_null' => array(
1075        'parent' => 'views_handler_argument',
1076      ),
1077      'views_handler_argument_broken' => array(
1078        'parent' => 'views_handler_argument',
1079      ),
1080
1081      // field handlers
1082      'views_handler_field' => array(
1083        'parent' => 'views_handler',
1084      ),
1085      'views_handler_field_date' => array(
1086        'parent' => 'views_handler_field',
1087      ),
1088      'views_handler_field_boolean' => array(
1089        'parent' => 'views_handler_field',
1090      ),
1091      'views_handler_field_markup' => array(
1092        'parent' => 'views_handler_field',
1093      ),
1094      'views_handler_field_xss' => array(
1095        'parent' => 'views_handler_field',
1096        'file' => 'views_handler_field.inc',
1097      ),
1098      'views_handler_field_url' => array(
1099        'parent' => 'views_handler_field',
1100      ),
1101      'views_handler_field_file_size' => array(
1102        'parent' => 'views_handler_field',
1103        'file' => 'views_handler_field.inc',
1104      ),
1105      'views_handler_field_prerender_list' => array(
1106        'parent' => 'views_handler_field',
1107      ),
1108      'views_handler_field_numeric' => array(
1109        'parent' => 'views_handler_field',
1110      ),
1111      'views_handler_field_custom' => array(
1112        'parent' => 'views_handler_field',
1113      ),
1114      'views_handler_field_counter' => array(
1115        'parent' => 'views_handler_field',
1116      ),
1117      'views_handler_field_math' => array(
1118        'parent' => 'views_handler_field_numeric',
1119      ),
1120      'views_handler_field_broken' => array(
1121        'parent' => 'views_handler_field',
1122      ),
1123
1124      // filter handlers
1125      'views_handler_filter' => array(
1126        'parent' => 'views_handler',
1127      ),
1128      'views_handler_filter_equality' => array(
1129        'parent' => 'views_handler_filter',
1130      ),
1131      'views_handler_filter_string' => array(
1132        'parent' => 'views_handler_filter',
1133      ),
1134      'views_handler_filter_boolean_operator' => array(
1135        'parent' => 'views_handler_filter',
1136      ),
1137      'views_handler_filter_boolean_operator_string' => array(
1138        'parent' => 'views_handler_filter_boolean_operator',
1139      ),
1140      'views_handler_filter_in_operator' => array(
1141        'parent' => 'views_handler_filter',
1142      ),
1143      'views_handler_filter_numeric' => array(
1144        'parent' => 'views_handler_filter',
1145      ),
1146      'views_handler_filter_float' => array(
1147        'parent' => 'views_handler_filter_numeric',
1148      ),
1149      'views_handler_filter_date' => array(
1150        'parent' => 'views_handler_filter_numeric',
1151      ),
1152      'views_handler_filter_many_to_one' => array(
1153        'parent' => 'views_handler_filter_in_operator',
1154      ),
1155      'views_handler_filter_broken' => array(
1156        'parent' => 'views_handler_filter',
1157      ),
1158
1159      // relationship handlers
1160      'views_handler_relationship' => array(
1161        'parent' => 'views_handler',
1162      ),
1163      'views_handler_relationship_broken' => array(
1164        'parent' => 'views_handler_relationship',
1165      ),
1166
1167
1168      // sort handlers
1169      'views_handler_sort' => array(
1170        'parent' => 'views_handler',
1171      ),
1172      'views_handler_sort_formula' => array(
1173        'parent' => 'views_handler_sort',
1174      ),
1175      'views_handler_sort_date' => array(
1176        'parent' => 'views_handler_sort',
1177      ),
1178      'views_handler_sort_menu_hierarchy' => array(
1179        'parent' => 'views_handler_sort',
1180      ),
1181      'views_handler_sort_random' => array(
1182        'parent' => 'views_handler_sort',
1183      ),
1184      'views_handler_sort_broken' => array(
1185        'parent' => 'views_handler_sort',
1186      ),
1187    ),
1188  );
1189}
1190
1191
1192/**
1193 * @}
1194 */
1195
1196/**
1197 * @defgroup views_join_handlers Views' join handlers
1198 * @{
1199 * Handlers to tell Views how to join tables together.
1200
1201 * Here is how you do complex joins:
1202 *
1203 * @code
1204 * class views_join_complex extends views_join {
1205 *   // PHP 4 doesn't call constructors of the base class automatically from a
1206 *   // constructor of a derived class. It is your responsibility to propagate
1207 *   // the call to constructors upstream where appropriate.
1208 *   function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1209 *     parent::construct($table, $left_table, $left_field, $field, $extra, $type);
1210 *   }
1211 *
1212 *   function join($table, &$query) {
1213 *     $output = parent::join($table, $query);
1214 *     $output .= "AND foo.bar = baz.boing";
1215 *     return $output;
1216 *   }
1217 * }
1218 * @endcode
1219 */
1220/**
1221 * A function class to represent a join and create the SQL necessary
1222 * to implement the join.
1223 *
1224 * This is the Delegation pattern. If we had PHP5 exclusively, we would
1225 * declare this an interface.
1226 *
1227 * Extensions of this class can be used to create more interesting joins.
1228 *
1229 * join definition
1230 *   - table: table to join (right table)
1231 *   - field: field to join on (right field)
1232 *   - left_table: The table we join to
1233 *   - left_field: The field we join to
1234 *   - type: either LEFT (default) or INNER
1235 *   - extra: Either a string that's directly added, or an array of items:
1236 *   - - table: if not set, current table; if NULL, no table. This field can't
1237 *       be set in the cached definition because it can't know aliases; this field
1238 *       can only be used by realtime joins.
1239 *   - - field: Field or formula
1240 *   - - operator: defaults to =
1241 *   - - value: Must be set. If an array, operator will be defaulted to IN.
1242 *   - - numeric: If true, the value will not be surrounded in quotes.
1243 *   - - raw: If you specify raw the value will be used as it is, so you can have field to field conditions.
1244 *   - extra type: How all the extras will be combined. Either AND or OR. Defaults to AND.
1245 */
1246class views_join {
1247  /**
1248   * Construct the views_join object.
1249   */
1250  function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1251    $this->extra_type = 'AND';
1252    if (!empty($table)) {
1253      $this->table = $table;
1254      $this->left_table = $left_table;
1255      $this->left_field = $left_field;
1256      $this->field = $field;
1257      $this->extra = $extra;
1258      $this->type = strtoupper($type);
1259    }
1260    else if (!empty($this->definition)) {
1261      // if no arguments, construct from definition.
1262      // These four must exist or it will throw notices.
1263      $this->table = $this->definition['table'];
1264      $this->left_table = $this->definition['left_table'];
1265      $this->left_field = $this->definition['left_field'];
1266      $this->field = $this->definition['field'];
1267      if (!empty($this->definition['extra'])) {
1268        $this->extra = $this->definition['extra'];
1269      }
1270      if (!empty($this->definition['extra type'])) {
1271        $this->extra_type = strtoupper($this->definition['extra type']);
1272      }
1273
1274      $this->type = !empty($this->definition['type']) ? strtoupper($this->definition['type']) : 'LEFT';
1275    }
1276  }
1277
1278  /**
1279   * Build the SQL for the join this object represents.
1280   */
1281  function join($table, &$query) {
1282    if (empty($this->definition['table formula'])) {
1283      $right_table = "{" . $this->table . "}";
1284    }
1285    else {
1286      $right_table = $this->definition['table formula'];
1287    }
1288
1289    if ($this->left_table) {
1290      $left = $query->get_table_info($this->left_table);
1291      $left_field = "$left[alias].$this->left_field";
1292    }
1293    else {
1294      // This can be used if left_field is a formula or something. It should be used only *very* rarely.
1295      $left_field = $this->left_field;
1296    }
1297
1298    $output = " $this->type JOIN $right_table $table[alias] ON $left_field = $table[alias].$this->field";
1299
1300    // Load query tokens replacements.
1301    $view = views_get_current_view();
1302    $replacements = array();
1303    if (!empty($view)) {
1304      $replacements = $view->substitutions();
1305    }
1306    else {
1307      vpr('The current view is not set, maybe some code missed to execute $view->pre_execute()');
1308    }
1309
1310    // Tack on the extra.
1311    if (isset($this->extra)) {
1312      if (is_array($this->extra)) {
1313        $extras = array();
1314        foreach ($this->extra as $info) {
1315          $extra = '';
1316          // Figure out the table name. Remember, only use aliases provided
1317          // if at all possible.
1318          $join_table = '';
1319          if (!array_key_exists('table', $info)) {
1320            $join_table = $table['alias'] . '.';
1321          }
1322          elseif (isset($info['table'])) {
1323            $join_table = $info['table'] . '.';
1324          }
1325
1326          // Apply query token replacements.
1327          $info['value'] = str_replace(array_keys($replacements), $replacements, $info['value']);
1328
1329          // And now deal with the value and the operator.  Set $q to
1330          // a single-quote for non-numeric values and the
1331          // empty-string for numeric values, then wrap all values in $q.
1332          if (empty($info['raw'])) {
1333            $raw_value = $this->db_safe($info['value'], $info);
1334            $q = (empty($info['numeric']) ? "'" : '');
1335          }
1336          else {
1337            $raw_value = $info['value'];
1338            $q = '';
1339          }
1340
1341          if (is_array($raw_value)) {
1342            $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
1343            // Transform from IN() notation to = notation if just one value.
1344            if (count($raw_value) == 1) {
1345              $value = $q . array_shift($raw_value) . $q;
1346              $operator = $operator == 'NOT IN' ? '!=' : '=';
1347            }
1348            else {
1349              $value = "($q" . implode("$q, $q", $raw_value) . "$q)";
1350            }
1351          }
1352          else {
1353            $operator = !empty($info['operator']) ? $info['operator'] : '=';
1354            $value = "$q$raw_value$q";
1355          }
1356          $extras[] = "$join_table$info[field] $operator $value";
1357        }
1358
1359        if ($extras) {
1360          if (count($extras) == 1) {
1361            $output .= ' AND ' . array_shift($extras);
1362          }
1363          else {
1364            $output .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
1365          }
1366        }
1367      }
1368      else if ($this->extra && is_string($this->extra)) {
1369        $output .= " AND ($this->extra)";
1370      }
1371    }
1372    return $output;
1373  }
1374
1375  /**
1376   * Ensure that input is db safe. We only check strings and ints tho
1377   * so something that needs floats in their joins needs to do their
1378   * own type checking.
1379   */
1380  function db_safe($input, $info) {
1381    if (is_array($input)) {
1382      $output = array();
1383      foreach ($input as $value) {
1384        if (empty($info['numeric'])) {
1385          $output[] = db_escape_string($value);
1386        }
1387        else {
1388          $output[] = intval($value);
1389        }
1390      }
1391    }
1392    else if (empty($info['numeric'])) {
1393      $output = db_escape_string($input);
1394    }
1395    else {
1396      $output = intval($input);
1397    }
1398
1399    return $output;
1400  }
1401}
1402
1403/**
1404 * @}
1405 */
1406
1407// Declare API compatibility on behalf of core modules:
1408
1409/**
1410 * Implementation of hook_views_api().
1411 *
1412 * This one is used as the base to reduce errors when updating.
1413 */
1414function views_views_api() {
1415  return array(
1416    'api' => 2,
1417    'path' => drupal_get_path('module', 'views') . '/modules',
1418  );
1419}
1420
1421if (!function_exists('aggregator_views_api')) {
1422  function aggregator_views_api() { return views_views_api(); }
1423}
1424
1425if (!function_exists('book_views_api')) {
1426  function book_views_api() { return views_views_api(); }
1427}
1428
1429if (!function_exists('comment_views_api')) {
1430  function comment_views_api() { return views_views_api(); }
1431}
1432
1433if (!function_exists('locale_views_api')) {
1434  function locale_views_api() { return views_views_api(); }
1435}
1436
1437if (!function_exists('filter_views_api')) {
1438  function filter_views_api() { return views_views_api(); }
1439}
1440
1441if (!function_exists('node_views_api')) {
1442  function node_views_api() { return views_views_api(); }
1443}
1444
1445if (!function_exists('poll_views_api')) {
1446  function poll_views_api() { return views_views_api(); }
1447}
1448
1449if (!function_exists('profile_views_api')) {
1450  function profile_views_api() { return views_views_api(); }
1451}
1452
1453if (!function_exists('search_views_api')) {
1454  function search_views_api() { return views_views_api(); }
1455}
1456
1457if (!function_exists('statistics_views_api')) {
1458  function statistics_views_api() { return views_views_api(); }
1459}
1460
1461if (!function_exists('system_views_api')) {
1462  function system_views_api() { return views_views_api(); }
1463}
1464
1465if (!function_exists('taxonomy_views_api')) {
1466  function taxonomy_views_api() { return views_views_api(); }
1467}
1468
1469if (!function_exists('translation_views_api')) {
1470  function translation_views_api() { return views_views_api(); }
1471}
1472
1473if (!function_exists('upload_views_api')) {
1474  function upload_views_api() { return views_views_api(); }
1475}
1476
1477if (!function_exists('user_views_api')) {
1478  function user_views_api() { return views_views_api(); }
1479}
1480
1481if (!function_exists('contact_views_api')) {
1482  function contact_views_api() { return views_views_api(); }
1483}
1484