5 * Defines the various handler objects to help build and display views.
9 * Instantiate and construct a new handler
11 function _views_create_handler($definition, $type = 'handler', $handler_type = NULL) {
12 // debug('Instantiating handler ' . $definition['handler']);
13 if (empty($definition['handler'])) {
14 vpr('_views_create_handler - type: @type - failed: handler has not been provided.',
15 array('@type' => isset($handler_type) ? ( $type . '(handler type: ' . $handler_type . ')' ) : $type)
20 // class_exists will automatically load the code file.
21 if (!empty($definition['override handler']) &&
22 !class_exists($definition['override handler'])) {
24 '_views_create_handler - loading override handler @type failed: class @override_handler could not be loaded. ' .
25 'Verify the class file has been registered in the corresponding .info-file (files[]).',
27 '@type' => isset($handler_type) ? ( $type . '(handler type: ' . $handler_type . ')' ) : $type,
28 '@override_handler' => $definition['override handler']
34 if (!class_exists($definition['handler'])) {
36 '_views_create_handler - loading handler @type failed: class @handler could not be loaded. ' .
37 'Verify the class file has been registered in the corresponding .info-file (files[]).',
39 '@type' => isset($handler_type) ? ( $type . '(handler type: ' . $handler_type . ')' ) : $type,
40 '@handler' => $definition['handler']
46 if (!empty($definition['override handler'])) {
47 $handler = new $definition['override handler'];
50 $handler = new $definition['handler'];
53 $handler->set_definition($definition);
54 if ($type == 'handler') {
55 $handler->is_handler = TRUE;
56 $handler->handler_type = $handler_type;
59 $handler->is_plugin = TRUE;
60 $handler->plugin_type = $type;
61 $handler->plugin_name = $definition['name'];
64 // let the handler have something like a constructor.
65 $handler->construct();
71 * Prepare a handler's data by checking defaults and such.
73 function _views_prepare_handler($definition, $data, $field, $type) {
74 foreach (array('group', 'title', 'title short', 'help', 'real field') as $key) {
75 if (!isset($definition[$key])) {
76 // First check the field level
77 if (!empty($data[$field][$key])) {
78 $definition[$key] = $data[$field][$key];
80 // Then if that doesn't work, check the table level
81 elseif (!empty($data['table'][$key])) {
82 $definition[$key] = $data['table'][$key];
87 return _views_create_handler($definition, 'handler', $type);
91 * Fetch a handler to join one table to a primary table from the data cache
93 function views_get_table_join($table, $base_table) {
94 $data = views_fetch_data($table);
95 if (isset($data['table']['join'][$base_table])) {
96 $h = $data['table']['join'][$base_table];
97 if (!empty($h['handler']) && class_exists($h['handler'])) {
98 $handler = new $h['handler'];
101 $handler = new views_join();
104 // Fill in some easy defaults
105 $handler->definition = $h;
106 if (empty($handler->definition['table'])) {
107 $handler->definition['table'] = $table;
109 // If this is empty, it's a direct link.
110 if (empty($handler->definition['left_table'])) {
111 $handler->definition['left_table'] = $base_table;
114 if (isset($h['arguments'])) {
115 call_user_func_array(array(&$handler, 'construct'), $h['arguments']);
118 $handler->construct();
124 // DEBUG -- identify missing handlers
125 vpr("Missing join: @table @base_table", array('@table' => $table, '@base_table' => $base_table));
129 * Base handler, from which all the other handlers are derived.
130 * It creates a common interface to create consistency amongst
133 * This class would be abstract in PHP5, but PHP4 doesn't understand that.
136 * - table: The actual table this uses; only specify if different from
137 * the table this is attached to.
138 * - real field: The actual field this uses; only specify if different from
139 * the field this item is attached to.
140 * - group: A text string representing the 'group' this item is attached to,
141 * for display in the UI. Examples: "Node", "Taxonomy", "Comment",
142 * "User", etc. This may be inherited from the parent definition or
143 * the 'table' definition.
144 * - title: The title for this handler in the UI. This may be inherited from
145 * the parent definition or the 'table' definition.
146 * - help: A more informative string to give to the user to explain what this
147 * field/handler is or does.
148 * - access callback: If this field should have access control, this could
149 * be a function to use. 'user_access' is a common
150 * function to use here. If not specified, no access
151 * control is provided.
152 * - access arguments: An array of arguments for the access callback.
154 class views_handler extends views_object {
156 * The top object of a view.
163 * Where the $query object will reside:
165 * @var views_plugin_query
170 * The type of the handler, for example filter/footer/field.
172 var $handler_type = NULL;
175 * The alias of the table of this handler which is used in the query.
180 * The actual field in the database table, maybe different
181 * on other kind of query plugins/special handlers.
186 * The relationship used for this field.
188 var $relationship = NULL;
191 * init the handler with necessary data.
193 * The $view object this handler is attached to.
195 * The item from the database; the actual contents of this will vary
196 * based upon the type of handler.
198 function init(&$view, &$options) {
199 $this->view = &$view;
200 $display_id = $this->view->current_display;
201 // Check to see if this handler type is defaulted. Note that
202 // we have to do a lookup because the type is singular but the
203 // option is stored as the plural.
205 // If the 'moved to' keyword moved our handler, let's fix that now.
206 if (isset($this->actual_table)) {
207 $options['table'] = $this->actual_table;
210 if (isset($this->actual_field)) {
211 $options['field'] = $this->actual_field;
214 $types = views_object_types();
215 $plural = $this->handler_type;
216 if (isset($types[$this->handler_type]['plural'])) {
217 $plural = $types[$this->handler_type]['plural'];
219 if ($this->view->display_handler->is_defaulted($plural)) {
220 $display_id = 'default';
223 $this->localization_keys = array(
230 $this->unpack_options($this->options, $options);
232 // This exist on most handlers, but not all. So they are still optional.
233 if (isset($options['table'])) {
234 $this->table = $options['table'];
237 if (isset($this->definition['real field'])) {
238 $this->real_field = $this->definition['real field'];
241 if (isset($this->definition['field'])) {
242 $this->real_field = $this->definition['field'];
245 if (isset($options['field'])) {
246 $this->field = $options['field'];
247 if (!isset($this->real_field)) {
248 $this->real_field = $options['field'];
252 $this->query = &$view->query;
255 function option_definition() {
256 $options = parent::option_definition();
258 $options['id'] = array('default' => '');
259 $options['table'] = array('default' => '');
260 $options['field'] = array('default' => '');
261 $options['relationship'] = array('default' => 'none');
262 $options['group_type'] = array('default' => 'group');
263 $options['ui_name'] = array('default' => '');
269 * Return a string representing this handler's name in the UI.
271 function ui_name($short = FALSE) {
272 if (!empty($this->options['ui_name'])) {
273 $title = check_plain($this->options['ui_name']);
276 $title = ($short && isset($this->definition['title short'])) ? $this->definition['title short'] : $this->definition['title'];
277 return t('!group: !title', array('!group' => $this->definition['group'], '!title' => $title));
281 * Shortcut to get a handler's raw field value.
283 * This should be overridden for handlers with formulae or other
284 * non-standard fields. Because this takes an argument, fields
285 * overriding this can just call return parent::get_field($formula)
287 function get_field($field = NULL) {
288 if (!isset($field)) {
289 if (!empty($this->formula)) {
290 $field = $this->get_formula();
293 $field = $this->table_alias . '.' . $this->real_field;
297 // If grouping, check to see if the aggregation method needs to modify the field.
298 if ($this->view->display_handler->use_group_by()) {
299 $this->view->init_query();
301 $info = $this->query->get_aggregation_info();
302 if (!empty($info[$this->options['group_type']]['method']) && function_exists($info[$this->options['group_type']]['method'])) {
303 return $info[$this->options['group_type']]['method']($this->options['group_type'], $field);
312 * Sanitize the value for output.
315 * The value being rendered.
317 * The type of sanitization needed. If not provided, check_plain() is used.
320 * Returns the safe value.
322 function sanitize_value($value, $type = NULL) {
325 $value = filter_xss($value);
328 $value = filter_xss_admin($value);
331 $value = check_url($value);
334 $value = check_plain($value);
341 * Transform a string by a certain method.
344 * The input you want to transform.
346 * How do you want to transform it, possible values:
347 * - upper: Uppercase the string.
348 * - lower: lowercase the string.
349 * - ucfirst: Make the first char uppercase.
350 * - ucwords: Make each word in the string uppercase.
353 * The transformed string.
355 function case_transform($string, $option) {
362 return drupal_strtoupper($string);
364 return drupal_strtolower($string);
366 return drupal_strtoupper(drupal_substr($string, 0, 1)) . drupal_substr($string, 1);
368 if ($multibyte == UNICODE_MULTIBYTE) {
369 return mb_convert_case($string, MB_CASE_TITLE);
372 return ucwords($string);
378 * Validate the options form.
380 function options_validate(&$form, &$form_state) { }
383 * Build the options form.
385 function options_form(&$form, &$form_state) {
386 // Some form elements belong in a fieldset for presentation, but can't
387 // be moved into one because of the form_state['values'] hierarchy. Those
388 // elements can add a #fieldset => 'fieldset_name' property, and they'll
389 // be moved to their fieldset during pre_render.
390 $form['#pre_render'][] = 'views_ui_pre_render_add_fieldset_markup';
392 $form['ui_name'] = array(
393 '#type' => 'textfield',
394 '#title' => t('Administrative title'),
395 '#description' => t('This title will be displayed on the views edit page instead of the default one. This might be useful if you have the same item twice.'),
396 '#default_value' => $this->options['ui_name'],
397 '#fieldset' => 'more',
400 // This form is long and messy enough that the "Administrative title" option
401 // belongs in a "more options" fieldset at the bottom of the form.
402 $form['more'] = array(
403 '#type' => 'fieldset',
404 '#title' => t('More'),
405 '#collapsible' => TRUE,
406 '#collapsed' => TRUE,
409 // Allow to alter the default values brought into the form.
410 drupal_alter('views_handler_options', $this->options, $view);
414 * Perform any necessary changes to the form values prior to storage.
415 * There is no need for this function to actually store the data.
417 function options_submit(&$form, &$form_state) { }
420 * Provides the handler some groupby.
422 function use_group_by() {
426 * Provide a form for aggregation settings.
428 function groupby_form(&$form, &$form_state) {
429 $view = &$form_state['view'];
430 $display_id = $form_state['display_id'];
431 $types = views_object_types();
432 $type = $form_state['type'];
433 $id = $form_state['id'];
435 $form['#title'] = check_plain($view->display[$display_id]->display_title) . ': ';
436 $form['#title'] .= t('Configure aggregation settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $this->ui_name()));
438 $form['#section'] = $display_id . '-' . $type . '-' . $id;
441 $info = $view->query->get_aggregation_info();
442 foreach ($info as $id => $aggregate) {
443 $group_types[$id] = $aggregate['title'];
446 $form['group_type'] = array(
448 '#title' => t('Aggregation type'),
449 '#default_value' => $this->options['group_type'],
450 '#description' => t('Select the aggregation function to use on this field.'),
451 '#options' => $group_types,
456 * Perform any necessary changes to the form values prior to storage.
457 * There is no need for this function to actually store the data.
459 function groupby_form_submit(&$form, &$form_state) {
460 $item =& $form_state['handler']->options;
462 $item['group_type'] = $form_state['values']['options']['group_type'];
466 * If a handler has 'extra options' it will get a little settings widget and
467 * another form called extra_options.
469 function has_extra_options() { return FALSE; }
472 * Provide defaults for the handler.
474 function extra_options(&$option) { }
477 * Provide a form for setting options.
479 function extra_options_form(&$form, &$form_state) { }
482 * Validate the options form.
484 function extra_options_validate($form, &$form_state) { }
487 * Perform any necessary changes to the form values prior to storage.
488 * There is no need for this function to actually store the data.
490 function extra_options_submit($form, &$form_state) { }
493 * Determine if a handler can be exposed.
495 function can_expose() { return FALSE; }
498 * Set new exposed option defaults when exposed setting is flipped
501 function expose_options() { }
504 * Get information about the exposed form for the form renderer.
506 function exposed_info() { }
509 * Render our chunk of the exposed handler form when selecting
511 function exposed_form(&$form, &$form_state) { }
514 * Validate the exposed handler form
516 function exposed_validate(&$form, &$form_state) { }
519 * Submit the exposed handler form
521 function exposed_submit(&$form, &$form_state) { }
524 * Form for exposed handler options.
526 function expose_form(&$form, &$form_state) { }
529 * Validate the options form.
531 function expose_validate($form, &$form_state) { }
534 * Perform any necessary changes to the form exposes prior to storage.
535 * There is no need for this function to actually store the data.
537 function expose_submit($form, &$form_state) { }
540 * Shortcut to display the expose/hide button.
542 function show_expose_button(&$form, &$form_state) { }
545 * Shortcut to display the exposed options form.
547 function show_expose_form(&$form, &$form_state) {
548 if (empty($this->options['exposed'])) {
552 $this->expose_form($form, $form_state);
554 // When we click the expose button, we add new gadgets to the form but they
555 // have no data in $_POST so their defaults get wiped out. This prevents
556 // these defaults from getting wiped out. This setting will only be TRUE
557 // during a 2nd pass rerender.
558 if (!empty($form_state['force_expose_options'])) {
559 foreach (element_children($form['expose']) as $id) {
560 if (isset($form['expose'][$id]['#default_value']) && !isset($form['expose'][$id]['#value'])) {
561 $form['expose'][$id]['#value'] = $form['expose'][$id]['#default_value'];
568 * Check whether current user has access to this handler.
573 if (isset($this->definition['access callback']) && function_exists($this->definition['access callback'])) {
574 if (isset($this->definition['access arguments']) && is_array($this->definition['access arguments'])) {
575 return call_user_func_array($this->definition['access callback'], $this->definition['access arguments']);
577 return $this->definition['access callback']();
584 * Run before the view is built.
586 * This gives all the handlers some time to set up before any handler has
589 function pre_query() { }
592 * Run after the view is executed, before the result is cached.
594 * This gives all the handlers some time to modify values. This is primarily
595 * used so that handlers that pull up secondary data can put it in the
596 * $values so that the raw data can be utilized externally.
598 function post_execute(&$values) { }
601 * Provides a unique placeholders for handlers.
603 function placeholder() {
604 return $this->query->placeholder($this->options['table'] . '_' . $this->options['field']);
608 * Called just prior to query(), this lets a handler set up any relationship
611 function set_relationship() {
612 // Ensure this gets set to something.
613 $this->relationship = NULL;
615 // Don't process non-existant relationships.
616 if (empty($this->options['relationship']) || $this->options['relationship'] == 'none') {
620 $relationship = $this->options['relationship'];
622 // Ignore missing/broken relationships.
623 if (empty($this->view->relationship[$relationship])) {
627 // Check to see if the relationship has already processed. If not, then we
628 // cannot process it.
629 if (empty($this->view->relationship[$relationship]->alias)) {
634 $this->relationship = $this->view->relationship[$relationship]->alias;
638 * Ensure the main table for this handler is in the query. This is used
641 function ensure_my_table() {
642 if (!isset($this->table_alias)) {
643 if (!method_exists($this->query, 'ensure_table')) {
644 vpr(t('Ensure my table called but query has no ensure_table method.'));
647 $this->table_alias = $this->query->ensure_table($this->table, $this->relationship);
649 return $this->table_alias;
653 * Provide text for the administrative summary
655 function admin_summary() { }
658 * Determine if the argument needs a style plugin.
662 function needs_style_plugin() { return FALSE; }
665 * Determine if this item is 'exposed', meaning it provides form elements
666 * to let users modify the view.
670 function is_exposed() {
671 return !empty($this->options['exposed']);
675 * Returns TRUE if the exposed filter works like a grouped filter.
677 function is_a_group() { return FALSE; }
680 * Define if the exposed input has to be submitted multiple times.
681 * This is TRUE when exposed filters grouped are using checkboxes as
684 function multiple_exposed_input() { return FALSE; }
687 * Take input from exposed handlers and assign to this handler, if necessary.
689 function accept_exposed_input($input) { return TRUE; }
692 * If set to remember exposed input in the session, store it there.
694 function store_exposed_input($input, $status) { return TRUE; }
697 * Get the join object that should be used for this handler.
699 * This method isn't used a great deal, but it's very handy for easily
700 * getting the join if it is necessary to make some changes to it, such
701 * as adding an 'extra'.
703 function get_join() {
704 // get the join from this table that links back to the base table.
705 // Determine the primary table to seek
706 if (empty($this->query->relationships[$this->relationship])) {
707 $base_table = $this->query->base_table;
710 $base_table = $this->query->relationships[$this->relationship]['base'];
713 $join = views_get_table_join($this->table, $base_table);
720 * Validates the handler against the complete View.
722 * This is called when the complete View is being validated. For validating
723 * the handler options form use options_validate().
725 * @see views_handler::options_validate()
728 * Empty array if the handler is valid; an array of error strings if it is not.
730 function validate() { return array(); }
733 * Determine if the handler is considered 'broken', meaning it's a
734 * a placeholder used when a handler can't be found.
736 function broken() { }
740 * This many to one helper object is used on both arguments and filters.
742 * @todo This requires extensive documentation on how this class is to
743 * be used. For now, look at the arguments and filters that use it. Lots
744 * of stuff is just pass-through but there are definitely some interesting
745 * areas where they interact.
747 * Any handler that uses this can have the following possibly additional
749 * - numeric: If true, treat this field as numeric, using %d instead of %s in
753 class views_many_to_one_helper {
755 * Contains possible existing placeholders used by the query.
759 public $placeholders = array();
761 function __construct(&$handler) {
762 $this->handler = &$handler;
765 static function option_definition(&$options) {
766 $options['reduce_duplicates'] = array('default' => FALSE, 'bool' => TRUE);
769 function options_form(&$form, &$form_state) {
770 $form['reduce_duplicates'] = array(
771 '#type' => 'checkbox',
772 '#title' => t('Reduce duplicates'),
773 '#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.'),
774 '#default_value' => !empty($this->handler->options['reduce_duplicates']),
780 * Sometimes the handler might want us to use some kind of formula, so give
781 * it that option. If it wants us to do this, it must set $helper->formula = TRUE
782 * and implement handler->get_formula();
784 function get_field() {
785 if (!empty($this->formula)) {
786 return $this->handler->get_formula();
789 return $this->handler->table_alias . '.' . $this->handler->real_field;
794 * Add a table to the query.
796 * This is an advanced concept; not only does it add a new instance of the table,
797 * but it follows the relationship path all the way down to the relationship
798 * link point and adds *that* as a new relationship and then adds the table to
799 * the relationship, if necessary.
801 function add_table($join = NULL, $alias = NULL) {
802 // This is used for lookups in the many_to_one table.
803 $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
806 $join = $this->get_join();
809 // See if there's a chain between us and the base relationship. If so, we need
810 // to create a new relationship to use.
811 $relationship = $this->handler->relationship;
813 // Determine the primary table to seek
814 if (empty($this->handler->query->relationships[$relationship])) {
815 $base_table = $this->handler->query->base_table;
818 $base_table = $this->handler->query->relationships[$relationship]['base'];
821 // Cycle through the joins. This isn't as error-safe as the normal
822 // ensure_path logic. Perhaps it should be.
823 $r_join = clone $join;
824 while ($r_join->left_table != $base_table) {
825 $r_join = views_get_table_join($r_join->left_table, $base_table);
827 // If we found that there are tables in between, add the relationship.
828 if ($r_join->table != $join->table) {
829 $relationship = $this->handler->query->add_relationship($this->handler->table . '_' . $r_join->table, $r_join, $r_join->table, $this->handler->relationship);
832 // And now add our table, using the new relationship if one was used.
833 $alias = $this->handler->query->add_table($this->handler->table, $relationship, $join, $alias);
835 // Store what values are used by this table chain so that other chains can
836 // automatically discard those values.
837 if (empty($this->handler->view->many_to_one_tables[$field])) {
838 $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
841 $this->handler->view->many_to_one_tables[$field] = array_merge($this->handler->view->many_to_one_tables[$field], $this->handler->value);
847 function get_join() {
848 return $this->handler->get_join();
852 * Provide the proper join for summary queries. This is important in part because
853 * it will cooperate with other arguments if possible.
855 function summary_join() {
856 $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
857 $join = $this->get_join();
860 $options = $this->handler->options;
861 $view = &$this->handler->view;
862 $query = &$this->handler->query;
864 if (!empty($options['require_value'])) {
865 $join->type = 'INNER';
868 if (empty($options['add_table']) || empty($view->many_to_one_tables[$field])) {
869 return $query->ensure_table($this->handler->table, $this->handler->relationship, $join);
872 if (!empty($view->many_to_one_tables[$field])) {
873 foreach ($view->many_to_one_tables[$field] as $value) {
874 $join->extra = array(
876 'field' => $this->handler->real_field,
879 'numeric' => !empty($this->definition['numeric']),
884 return $this->add_table($join);
889 * Override ensure_my_table so we can control how this joins in.
890 * The operator actually has influence over joining.
892 function ensure_my_table() {
893 if (!isset($this->handler->table_alias)) {
894 // Case 1: Operator is an 'or' and we're not reducing duplicates.
895 // We hence get the absolute simplest:
896 $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
897 if ($this->handler->operator == 'or' && empty($this->handler->options['reduce_duplicates'])) {
898 if (empty($this->handler->options['add_table']) && empty($this->handler->view->many_to_one_tables[$field])) {
899 // query optimization, INNER joins are slightly faster, so use them
900 // when we know we can.
901 $join = $this->get_join();
903 $join->type = 'INNER';
905 $this->handler->table_alias = $this->handler->query->ensure_table($this->handler->table, $this->handler->relationship, $join);
906 $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
909 $join = $this->get_join();
910 $join->type = 'LEFT';
911 if (!empty($this->handler->view->many_to_one_tables[$field])) {
912 foreach ($this->handler->view->many_to_one_tables[$field] as $value) {
913 $join->extra = array(
915 'field' => $this->handler->real_field,
918 'numeric' => !empty($this->handler->definition['numeric']),
924 $this->handler->table_alias = $this->add_table($join);
927 return $this->handler->table_alias;
930 // Case 2: it's an 'and' or an 'or'.
931 // We do one join per selected value.
932 if ($this->handler->operator != 'not') {
933 // Clone the join for each table:
934 $this->handler->table_aliases = array();
935 foreach ($this->handler->value as $value) {
936 $join = $this->get_join();
937 if ($this->handler->operator == 'and') {
938 $join->type = 'INNER';
940 $join->extra = array(
942 'field' => $this->handler->real_field,
944 'numeric' => !empty($this->handler->definition['numeric']),
948 // The table alias needs to be unique to this value across the
949 // multiple times the filter or argument is called by the view.
950 if (!isset($this->handler->view->many_to_one_aliases[$field][$value])) {
951 if (!isset($this->handler->view->many_to_one_count[$this->handler->table])) {
952 $this->handler->view->many_to_one_count[$this->handler->table] = 0;
954 $this->handler->view->many_to_one_aliases[$field][$value] = $this->handler->table . '_value_' . ($this->handler->view->many_to_one_count[$this->handler->table]++);
956 $alias = $this->handler->table_aliases[$value] = $this->add_table($join, $this->handler->view->many_to_one_aliases[$field][$value]);
958 // and set table_alias to the first of these.
959 if (empty($this->handler->table_alias)) {
960 $this->handler->table_alias = $alias;
964 // Case 3: it's a 'not'.
965 // We just do one join. We'll add a where clause during
966 // the query phase to ensure that $table.$field IS NULL.
968 $join = $this->get_join();
969 $join->type = 'LEFT';
970 $join->extra = array();
971 $join->extra_type = 'OR';
972 foreach ($this->handler->value as $value) {
973 $join->extra[] = array(
974 'field' => $this->handler->real_field,
976 'numeric' => !empty($this->handler->definition['numeric']),
980 $this->handler->table_alias = $this->add_table($join);
983 return $this->handler->table_alias;
987 * Provides a unique placeholders for handlers.
989 function placeholder() {
990 return $this->handler->query->placeholder($this->handler->options['table'] . '_' . $this->handler->options['field']);
993 function add_filter() {
994 if (empty($this->handler->value)) {
997 $this->handler->ensure_my_table();
999 // Shorten some variables:
1000 $field = $this->get_field();
1001 $options = $this->handler->options;
1002 $operator = $this->handler->operator;
1003 $formula = !empty($this->formula);
1004 $value = $this->handler->value;
1005 if (empty($options['group'])) {
1006 $options['group'] = 0;
1009 // add_condition determines whether a single expression is enough(FALSE) or the
1010 // conditions should be added via an db_or()/db_and() (TRUE).
1011 $add_condition = TRUE;
1012 if ($operator == 'not') {
1014 $operator = 'IS NULL';
1015 $add_condition = FALSE;
1017 elseif ($operator == 'or' && empty($options['reduce_duplicates'])) {
1018 if (count($value) > 1) {
1022 $value = is_array($value) ? array_pop($value) : $value;
1025 $add_condition = FALSE;
1028 if (!$add_condition) {
1030 $placeholder = $this->placeholder();
1031 if ($operator == 'IN') {
1032 $operator = "$operator IN($placeholder)";
1035 $operator = "$operator $placeholder";
1037 $placeholders = array(
1038 $placeholder => $value,
1039 ) + $this->placeholders;
1040 $this->handler->query->add_where_expression($options['group'], "$field $operator", $placeholders);
1043 $this->handler->query->add_where($options['group'], $field, $value, $operator);
1047 if ($add_condition) {
1048 $field = $this->handler->real_field;
1049 $clause = $operator == 'or' ? db_or() : db_and();
1050 foreach ($this->handler->table_aliases as $value => $alias) {
1051 $clause->condition("$alias.$field", $value);
1054 // implode on either AND or OR.
1055 $this->handler->query->add_where($options['group'], $clause);
1061 * Break x,y,z and x+y+z into an array. Works for strings.
1064 * The string to parse.
1066 * The object to use as a base. If not specified one will
1070 * An object containing
1071 * - operator: Either 'and' or 'or'
1072 * - value: An array of numeric values.
1074 function views_break_phrase_string($str, &$handler = NULL) {
1076 $handler = new stdClass();
1080 if (!isset($handler->value)) {
1081 $handler->value = array();
1084 if (!isset($handler->operator)) {
1085 $handler->operator = 'or';
1092 // Determine if the string has 'or' operators (plus signs) or 'and' operators
1093 // (commas) and split the string accordingly. If we have an 'and' operator,
1094 // spaces are treated as part of the word being split, but otherwise they are
1095 // treated the same as a plus sign.
1096 $or_wildcard = '[^\s+,]';
1097 $and_wildcard = '[^+,]';
1098 if (preg_match("/^({$or_wildcard}+[+ ])+{$or_wildcard}+$/", $str)) {
1099 $handler->operator = 'or';
1100 $handler->value = preg_split('/[+ ]/', $str);
1102 elseif (preg_match("/^({$and_wildcard}+,)*{$and_wildcard}+$/", $str)) {
1103 $handler->operator = 'and';
1104 $handler->value = explode(',', $str);
1107 // Keep an 'error' value if invalid strings were given.
1108 if (!empty($str) && (empty($handler->value) || !is_array($handler->value))) {
1109 $handler->value = array(-1);
1113 // Doubly ensure that all values are strings only.
1114 foreach ($handler->value as $id => $value) {
1115 $handler->value[$id] = (string) $value;
1122 * Break x,y,z and x+y+z into an array. Numeric only.
1125 * The string to parse.
1127 * The handler object to use as a base. If not specified one will
1131 * The new handler object.
1133 function views_break_phrase($str, &$handler = NULL) {
1135 $handler = new stdClass();
1140 if (!isset($handler->value)) {
1141 $handler->value = array();
1144 if (!isset($handler->operator)) {
1145 $handler->operator = 'or';
1152 if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
1153 // The '+' character in a query string may be parsed as ' '.
1154 $handler->operator = 'or';
1155 $handler->value = preg_split('/[+ ]/', $str);
1157 elseif (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
1158 $handler->operator = 'and';
1159 $handler->value = explode(',', $str);
1162 // Keep an 'error' value if invalid strings were given.
1163 if (!empty($str) && (empty($handler->value) || !is_array($handler->value))) {
1164 $handler->value = array(-1);
1168 // Doubly ensure that all values are numeric only.
1169 foreach ($handler->value as $id => $value) {
1170 $handler->value[$id] = intval($value);
1176 // --------------------------------------------------------------------------
1177 // Date helper functions
1180 * Figure out what timezone we're in; needed for some date manipulations.
1182 function views_get_timezone() {
1184 if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
1185 $timezone = $user->timezone;
1188 $timezone = variable_get('date_default_timezone', 0);
1191 // set up the database timezone
1192 $db_type = Database::getConnection()->databaseType();
1193 if (in_array($db_type, array('mysql', 'pgsql'))) {
1195 static $already_set = FALSE;
1196 if (!$already_set) {
1197 if ($db_type == 'pgsql') {
1198 db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
1200 elseif ($db_type == 'mysql') {
1201 db_query("SET @@session.time_zone = '$offset'");
1204 $already_set = true;
1212 * Helper function to create cross-database SQL dates.
1215 * The real table and field name, like 'tablename.fieldname'.
1216 * @param $field_type
1217 * The type of date field, 'int' or 'datetime'.
1218 * @param $set_offset
1219 * The name of a field that holds the timezone offset or a fixed timezone
1220 * offset value. If not provided, the normal Drupal timezone handling
1221 * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1223 * An appropriate SQL string for the db type and field type.
1225 function views_date_sql_field($field, $field_type = 'int', $set_offset = NULL) {
1226 $db_type = Database::getConnection()->databaseType();
1227 $offset = $set_offset !== NULL ? $set_offset : views_get_timezone();
1228 if (isset($offset) && !is_numeric($offset)) {
1229 $dtz = new DateTimeZone($offset);
1230 $dt = new DateTime("now", $dtz);
1231 $offset_seconds = $dtz->getOffset($dt);
1236 switch ($field_type) {
1238 $field = "DATE_ADD('19700101', INTERVAL $field SECOND)";
1243 if (!empty($offset)) {
1244 $field = "($field + INTERVAL $offset_seconds SECOND)";
1248 switch ($field_type) {
1250 $field = "TO_TIMESTAMP($field)";
1255 if (!empty($offset)) {
1256 $field = "($field + INTERVAL '$offset_seconds SECONDS')";
1260 if (!empty($offset)) {
1261 $field = "($field + '$offset_seconds')";
1268 * Helper function to create cross-database SQL date formatting.
1271 * A format string for the result, like 'Y-m-d H:i:s'.
1273 * The real table and field name, like 'tablename.fieldname'.
1274 * @param $field_type
1275 * The type of date field, 'int' or 'datetime'.
1276 * @param $set_offset
1277 * The name of a field that holds the timezone offset or a fixed timezone
1278 * offset value. If not provided, the normal Drupal timezone handling
1279 * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1281 * An appropriate SQL string for the db type and field type.
1283 function views_date_sql_format($format, $field, $field_type = 'int', $set_offset = NULL) {
1284 $db_type = Database::getConnection()->databaseType();
1285 $field = views_date_sql_field($field, $field_type, $set_offset);
1306 $format = strtr($format, $replace);
1307 return "DATE_FORMAT($field, '$format')";
1314 'n' => 'MM', // no format for Numeric representation of a month, without leading zeros
1319 'j' => 'DD', // no format for Day of the month without leading zeros
1327 $format = strtr($format, $replace);
1328 return "TO_CHAR($field, '$format')";
1331 'Y' => '%Y', // 4 digit year number
1332 'y' => '%Y', // no format for 2 digit year number
1333 'M' => '%m', // no format for 3 letter month name
1334 'm' => '%m', // month number with leading zeros
1335 'n' => '%m', // no format for month number without leading zeros
1336 'F' => '%m', // no format for full month name
1337 'D' => '%d', // no format for 3 letter day name
1338 'd' => '%d', // day of month number with leading zeros
1339 'l' => '%d', // no format for full day name
1340 'j' => '%d', // no format for day of month number without leading zeros
1341 'W' => '%W', // ISO week number
1342 'H' => '%H', // 24 hour hour with leading zeros
1343 'h' => '%H', // no format for 12 hour hour with leading zeros
1344 'i' => '%M', // minutes with leading zeros
1345 's' => '%S', // seconds with leading zeros
1346 'A' => '', // no format for AM/PM
1348 $format = strtr($format, $replace);
1349 return "strftime('$format', $field, 'unixepoch')";
1354 * Helper function to create cross-database SQL date extraction.
1356 * @param $extract_type
1357 * The type of value to extract from the date, like 'MONTH'.
1359 * The real table and field name, like 'tablename.fieldname'.
1360 * @param $field_type
1361 * The type of date field, 'int' or 'datetime'.
1362 * @param $set_offset
1363 * The name of a field that holds the timezone offset or a fixed timezone
1364 * offset value. If not provided, the normal Drupal timezone handling
1365 * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1367 * An appropriate SQL string for the db type and field type.
1369 function views_date_sql_extract($extract_type, $field, $field_type = 'int', $set_offset = NULL) {
1370 $db_type = Database::getConnection()->databaseType();
1371 $field = views_date_sql_field($field, $field_type, $set_offset);
1373 // Note there is no space after FROM to avoid db_rewrite problems
1374 // see http://drupal.org/node/79904.
1375 switch ($extract_type) {
1379 return "EXTRACT(YEAR FROM($field))";
1381 return "EXTRACT(MONTH FROM($field))";
1383 return "EXTRACT(DAY FROM($field))";
1385 return "EXTRACT(HOUR FROM($field))";
1387 return "EXTRACT(MINUTE FROM($field))";
1389 return "EXTRACT(SECOND FROM($field))";
1390 case('WEEK'): // ISO week number for date
1393 // WEEK using arg 3 in mysql should return the same value as postgres EXTRACT
1394 return "WEEK($field, 3)";
1396 return "EXTRACT(WEEK FROM($field))";
1401 // mysql returns 1 for Sunday through 7 for Saturday
1402 // php date functions and postgres use 0 for Sunday and 6 for Saturday
1403 return "INTEGER(DAYOFWEEK($field) - 1)";
1405 return "EXTRACT(DOW FROM($field))";
1410 return "DAYOFYEAR($field)";
1412 return "EXTRACT(DOY FROM($field))";
1422 * @defgroup views_join_handlers Views join handlers
1424 * Handlers to tell Views how to join tables together.
1426 * Here is how you do complex joins:
1429 * class views_join_complex extends views_join {
1430 * // PHP 4 doesn't call constructors of the base class automatically from a
1431 * // constructor of a derived class. It is your responsibility to propagate
1432 * // the call to constructors upstream where appropriate.
1433 * function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1434 * parent::construct($table, $left_table, $left_field, $field, $extra, $type);
1437 * function build_join($select_query, $table, $view_query) {
1438 * $this->extra = 'foo.bar = baz.boing';
1439 * parent::build_join($select_query, $table, $view_query);
1446 * A function class to represent a join and create the SQL necessary
1447 * to implement the join.
1449 * This is the Delegation pattern. If we had PHP5 exclusively, we would
1450 * declare this an interface.
1452 * Extensions of this class can be used to create more interesting joins.
1455 * - table: table to join (right table)
1456 * - field: field to join on (right field)
1457 * - left_table: The table we join to
1458 * - left_field: The field we join to
1459 * - type: either LEFT (default) or INNER
1460 * - extra: An array of extra conditions on the join. Each condition is
1461 * either a string that's directly added, or an array of items:
1462 * - - table: If not set, current table; if NULL, no table. If you specify a
1463 * table in cached definition, Views will try to load from an existing
1464 * alias. If you use realtime joins, it works better.
1465 * - - field: Field or formula
1466 * in formulas we can reference the right table by using %alias
1467 * @see SelectQueryInterface::addJoin()
1468 * - - operator: defaults to =
1469 * - - value: Must be set. If an array, operator will be defaulted to IN.
1470 * - - numeric: If true, the value will not be surrounded in quotes.
1471 * - - extra type: How all the extras will be combined. Either AND or OR. Defaults to AND.
1475 var $left_table = NULL;
1476 var $left_field = NULL;
1480 var $definition = array();
1483 * Construct the views_join object.
1485 function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1486 $this->extra_type = 'AND';
1487 if (!empty($table)) {
1488 $this->table = $table;
1489 $this->left_table = $left_table;
1490 $this->left_field = $left_field;
1491 $this->field = $field;
1492 $this->extra = $extra;
1493 $this->type = strtoupper($type);
1495 elseif (!empty($this->definition)) {
1496 // if no arguments, construct from definition.
1497 // These four must exist or it will throw notices.
1498 $this->table = $this->definition['table'];
1499 $this->left_table = $this->definition['left_table'];
1500 $this->left_field = $this->definition['left_field'];
1501 $this->field = $this->definition['field'];
1502 if (!empty($this->definition['extra'])) {
1503 $this->extra = $this->definition['extra'];
1505 if (!empty($this->definition['extra type'])) {
1506 $this->extra_type = strtoupper($this->definition['extra type']);
1509 $this->type = !empty($this->definition['type']) ? strtoupper($this->definition['type']) : 'LEFT';
1514 * Build the SQL for the join this object represents.
1516 * When possible, try to use table alias instead of table names.
1518 * @param $select_query
1519 * An implementation of SelectQueryInterface.
1521 * The base table to join.
1522 * @param $view_query
1523 * The source query, implementation of views_plugin_query.
1525 function build_join($select_query, $table, $view_query) {
1526 if (empty($this->definition['table formula'])) {
1527 $right_table = $this->table;
1530 $right_table = $this->definition['table formula'];
1533 if ($this->left_table) {
1534 $left = $view_query->get_table_info($this->left_table);
1535 $left_field = "$left[alias].$this->left_field";
1538 // This can be used if left_field is a formula or something. It should be used only *very* rarely.
1539 $left_field = $this->left_field;
1542 $condition = "$left_field = $table[alias].$this->field";
1543 $arguments = array();
1545 // Tack on the extra.
1546 if (isset($this->extra)) {
1547 if (is_array($this->extra)) {
1549 foreach ($this->extra as $info) {
1550 // Figure out the table name. Remember, only use aliases provided
1551 // if at all possible.
1553 if (!array_key_exists('table', $info)) {
1554 $join_table = $table['alias'] . '.';
1556 elseif (isset($info['table'])) {
1557 // If we're aware of a table alias for this table, use the table
1558 // alias instead of the table name.
1559 if (isset($left) && $left['table'] == $info['table']) {
1560 $join_table = $left['alias'] . '.';
1563 $join_table = $info['table'] . '.';
1567 // If left_field is set use it for a field-to-field condition.
1568 if (!empty($info['left_field'])) {
1569 $operator = !empty($info['operator']) ? $info['operator'] : '=';
1570 $left_table = (isset($info['left_table'])) ? $info['left_table'] : $left['alias'];
1571 $extras[] = "$join_table$info[field] $operator $left_table.$info[left_field]";
1573 // Else if formula is set, us it for a flexible on clause.
1574 elseif (!empty($info['formula'])) {
1575 // If a field is given, we build a "$field $op $formula".
1576 // Without it would only be "$formula".
1578 if (isset($info['field'])) {
1579 // With a single value, the '=' operator is implicit.
1580 $operator = !empty($info['operator']) ? $info['operator'] : '=';
1581 $extra .= "$join_table$info[field] $operator ";
1583 $extra .= $info['formula'];
1584 // Add placeholder arguments.
1585 if (isset($info['formula_arguments']) && is_array($info['formula_arguments'])) {
1586 $arguments = array_merge($arguments, $info['formula_arguments']);
1590 // Otherwise - and if we have a value - use it for a field-to-value condition.
1591 elseif (isset($info['value'])) {
1592 // Convert a single-valued array of values to the single-value case,
1593 // and transform from IN() notation to = notation
1594 if (is_array($info['value']) && count($info['value']) == 1) {
1595 if (empty($info['operator'])) {
1599 $operator = $info['operator'] == 'NOT IN' ? '!=' : '=';
1601 $info['value'] = array_shift($info['value']);
1604 if (is_array($info['value'])) {
1605 // With an array of values, we need multiple placeholders and the
1606 // 'IN' operator is implicit.
1607 foreach ($info['value'] as $value) {
1608 $placeholder_i = ':views_join_condition_' . $select_query->nextPlaceholder();
1609 $arguments[$placeholder_i] = $value;
1612 $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
1613 $placeholder = '( ' . implode(', ', array_keys($arguments)) . ' )';
1616 // With a single value, the '=' operator is implicit.
1617 $operator = !empty($info['operator']) ? $info['operator'] : '=';
1618 $placeholder = ':views_join_condition_' . $select_query->nextPlaceholder();
1619 $arguments[$placeholder] = $info['value'];
1622 $extras[] = "$join_table$info[field] $operator $placeholder";
1627 if (count($extras) == 1) {
1628 $condition .= ' AND ' . array_shift($extras);
1631 $condition .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
1635 elseif ($this->extra && is_string($this->extra)) {
1636 $condition .= " AND ($this->extra)";
1640 $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
1645 * Join handler for relationships that join with a subquery as the left field.
1647 * LEFT JOIN node node_term_data ON ([YOUR SUBQUERY HERE]) = node_term_data.nid
1650 * same as views_join class above, except:
1651 * - left_query: The subquery to use in the left side of the join clause.
1653 class views_join_subquery extends views_join {
1654 function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1655 parent::construct($table, $left_table, $left_field, $field, $extra, $type);
1656 $this->left_query = $this->definition['left_query'];
1660 * Build the SQL for the join this object represents.
1662 * @param $select_query
1663 * An implementation of SelectQueryInterface.
1665 * The base table to join.
1666 * @param $view_query
1667 * The source query, implementation of views_plugin_query.
1671 function build_join($select_query, $table, $view_query) {
1672 if (empty($this->definition['table formula'])) {
1673 $right_table = "{" . $this->table . "}";
1676 $right_table = $this->definition['table formula'];
1679 // Add our join condition, using a subquery on the left instead of a field.
1680 $condition = "($this->left_query) = $table[alias].$this->field";
1681 $arguments = array();
1683 // Tack on the extra.
1684 // This is just copied verbatim from the parent class, which itself has a bug: http://drupal.org/node/1118100
1685 if (isset($this->extra)) {
1686 if (is_array($this->extra)) {
1688 foreach ($this->extra as $info) {
1689 // Figure out the table name. Remember, only use aliases provided
1690 // if at all possible.
1692 if (!array_key_exists('table', $info)) {
1693 $join_table = $table['alias'] . '.';
1695 elseif (isset($info['table'])) {
1696 $join_table = $info['table'] . '.';
1699 $placeholder = ':views_join_condition_' . $select_query->nextPlaceholder();
1701 if (is_array($info['value'])) {
1702 $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
1703 // Transform from IN() notation to = notation if just one value.
1704 if (count($info['value']) == 1) {
1705 $info['value'] = array_shift($info['value']);
1706 $operator = $operator == 'NOT IN' ? '!=' : '=';
1710 $operator = !empty($info['operator']) ? $info['operator'] : '=';
1713 $extras[] = "$join_table$info[field] $operator $placeholder";
1714 $arguments[$placeholder] = $info['value'];
1718 if (count($extras) == 1) {
1719 $condition .= ' AND ' . array_shift($extras);
1722 $condition .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
1726 elseif ($this->extra && is_string($this->extra)) {
1727 $condition .= " AND ($this->extra)";
1731 $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);