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