21b150a5bf9ece2f12b779bb067810e7dfcf966c
[project/views.git] / includes / handlers.inc
1 <?php
2 /**
3 * @file handlers.inc
4 * Defines the various handler objects to help build and display views.
5 */
6
7 /**
8 * Instantiate and construct a new handler
9 */
10 function _views_create_handler($definition, $type = 'handler', $handler_type = NULL) {
11 // vpr('Instantiating handler ' . $definition['handler']);
12 if (empty($definition['handler'])) {
13 return;
14 }
15
16 if (!empty($definition['override handler']) &&
17 !class_exists($definition['override handler']) &&
18 !views_include_handler($definition['override handler'], $definition, $type)) {
19 return;
20 }
21
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
34 $handler->set_definition($definition);
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
44 // let the handler have something like a constructor.
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 */
56 function views_include_handler($handler, $definition, $type, $count = 0) {
57 // Do not proceed if the class already exists.
58 if (isset($handler) && class_exists($handler)) {
59 return TRUE;
60 }
61
62 // simple infinite loop prevention.
63 if ($count > 10) {
64 vpr(t('Handler @handler include tried to loop infinitely!', array('@handler' => $handler)));
65 return FALSE;
66 }
67
68 if (!isset($definition['path'])) {
69 if ($type == 'handler') {
70 $definition += views_fetch_handler_data($handler);
71 }
72 else {
73 $definition += views_fetch_plugin_data($type, $handler);
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) {
86 $rc = views_include_handler($parent['handler'], $parent, $type, $count + 1);
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
102 return class_exists($handler);
103 }
104
105 /**
106 * Prepare a handler's data by checking defaults and such.
107 */
108 function _views_prepare_handler($definition, $data, $field, $type) {
109 foreach (array('group', 'title', 'title short', 'help', 'real field') as $key) {
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 }
119 }
120 }
121
122 return _views_create_handler($definition, 'handler', $type);
123 }
124
125 /**
126 * Fetch the handler data from cache.
127 */
128 function views_fetch_handler_data($handler = NULL) {
129 static $cache = NULL;
130 if (!isset($cache)) {
131 $start = views_microtime();
132
133 $cache = views_discover_handlers();
134
135 vpr('Views handlers build time: ' . (views_microtime() - $start) * 1000 . ' ms');
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 */
154 function 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 /**
188 * Fetch a handler to join one table to a primary table from the data cache
189 */
190 function views_get_table_join($table, $base_table) {
191 $data = views_fetch_data($table);
192 if (isset($data['table']['join'][$base_table])) {
193 $h = $data['table']['join'][$base_table];
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'])) {
208 $handler->definition['left_table'] = $base_table;
209 }
210
211 if (isset($h['arguments'])) {
212 call_user_func_array(array(&$handler, 'construct'), $h['arguments']);
213 }
214 else {
215 $handler->construct();
216 }
217
218 return $handler;
219 }
220 // DEBUG -- identify missing handlers
221 vpr("Missing join: $table $base_table");
222 }
223
224 /**
225 * Base handler, from which all the other handlers are derived.
226 * It creates a common interface to create consistency amongst
227 * handlers and data.
228 *
229 * This class would be abstract in PHP5, but PHP4 doesn't understand that.
230 *
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.
249 */
250 class views_handler extends views_object {
251 /**
252 * The top object of a view.
253 *
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 /**
266 * The type of the handler, for example filter/footer/field.
267 */
268 var $handler_type = NULL;
269
270 /**
271 * init the handler with necessary data.
272 * @param $view
273 * The $view object this handler is attached to.
274 * @param $options
275 * The item from the database; the actual contents of this will vary
276 * based upon the type of handler.
277 */
278 function init(&$view, $options) {
279 $this->view = &$view;
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['id']
298 );
299
300 $this->unpack_options($this->options, $options);
301
302 // This exist on most handlers, but not all. So they are still optional.
303 if (isset($options['table'])) {
304 $this->table = $options['table'];
305 }
306
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
315 if (isset($options['field'])) {
316 $this->field = $options['field'];
317 if (!isset($this->real_field)) {
318 $this->real_field = $options['field'];
319 }
320 }
321
322 $this->query = &$view->query;
323 }
324
325 function option_definition() {
326 $options = parent::option_definition();
327
328 $options['id'] = array('default' => '');
329 $options['table'] = array('default' => '');
330 $options['field'] = array('default' => '');
331 $options['relationship'] = array('default' => 'none');
332 $options['group_type'] = array('default' => 'group');
333 $options['ui_name'] = array('default' => '');
334
335 return $options;
336 }
337
338 /**
339 * Return a string representing this handler's name in the UI.
340 */
341 function ui_name($short = FALSE) {
342 if (!empty($this->options['ui_name'])) {
343 $title = check_plain($this->options['ui_name']);
344 return $title;
345 }
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));
348 }
349
350 /**
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)
356 */
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 }
377
378 /**
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 /**
417 * Validate the options form.
418 */
419 function options_validate($form, &$form_state) { }
420
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 }
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 /**
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) { }
461
462 /**
463 * Determine if a handler can be exposed.
464 */
465 function can_expose() { return FALSE; }
466
467 /**
468 * Set new exposed option defaults when exposed setting is flipped
469 * on.
470 */
471 function expose_options() { }
472
473 /**
474 * Get information about the exposed form for the form renderer.
475 */
476 function exposed_info() { }
477
478 /**
479 * Render our chunk of the exposed handler form when selecting
480 */
481 function exposed_form(&$form, &$form_state) { }
482
483 /**
484 * Validate the exposed handler form
485 */
486 function exposed_validate(&$form, &$form_state) { }
487
488 /**
489 * Submit the exposed handler form
490 */
491 function exposed_submit(&$form, &$form_state) { }
492
493 /**
494 * Overridable form for exposed handler options.
495 *
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.
501 */
502 function expose_form(&$form, &$form_state) {
503 $form['expose']['start_left'] = array(
504 '#value' => '<div class="views-left-50">',
505 );
506
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) { }
525
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) { }
538
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 }
572
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 }
599
600 /**
601 * Check whether current user has access to this handler.
602 *
603 * @return boolean
604 */
605 function access() {
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
613 return TRUE;
614 }
615
616 /**
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 /**
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.
640 if (empty($this->view->relationship[$relationship])) {
641 return;
642 }
643
644 // Check to see if the relationship has already processed. If not, then we
645 // cannot process it.
646 if (empty($this->view->relationship[$relationship]->alias)) {
647 return;
648 }
649
650 // Finally!
651 $this->relationship = $this->view->relationship[$relationship]->alias;
652 }
653
654 /**
655 * Add this handler into the query.
656 *
657 * If we were using PHP5, this would be abstract.
658 */
659 function query($group_by = FALSE) { }
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() {
666 if (!isset($this->table_alias)) {
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 }
671 $this->table_alias = $this->query->ensure_table($this->table, $this->relationship);
672 }
673 return $this->table_alias;
674 }
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; }
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 /**
699 * Take input from exposed handlers and assign to this handler, if necessary.
700 */
701 function accept_exposed_input($input) { return TRUE; }
702
703 /**
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 /**
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.
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 }
729 }
730
731 /**
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 /**
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() { }
749 }
750
751 /**
752 * This many to one helper object is used on both arguments and filters.
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.
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 *
764 */
765 class views_many_to_one_helper {
766 function views_many_to_one_helper(&$handler) {
767 $this->handler = &$handler;
768 }
769
770 function option_definition(&$options) {
771 $options['reduce_duplicates'] = array('default' => FALSE);
772 }
773
774 function options_form(&$form, &$form_state) {
775 $form['reduce_duplicates'] = array(
776 '#type' => 'checkbox',
777 '#title' => t('Reduce duplicates'),
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.'),
779 '#default_value' => !empty($this->handler->options['reduce_duplicates']),
780 );
781 }
782
783 /**
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();
787 */
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;
794 }
795 }
796
797 /**
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.
807 $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
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])) {
819 $base_table = $this->handler->query->base_table;
820 }
821 else {
822 $base_table = $this->handler->query->relationships[$relationship]['base'];
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);
828 while ($r_join->left_table != $base_table) {
829 $r_join = views_get_table_join($r_join->left_table, $base_table);
830 }
831 // If we found that there are tables in between, add the relationship.
832 if ($r_join->table != $join->table) {
833 $relationship = $this->handler->query->add_relationship($this->handler->table . '_' . $r_join->table, $r_join, $r_join->table, $this->handler->relationship);
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.
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 }
847
848 return $alias;
849 }
850
851 function get_join() {
852 return $this->handler->get_join();
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() {
860 $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
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 /**
893 * Override ensure_my_table so we can control how this joins in.
894 * The operator actually has influence over joining.
895 */
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:
899 $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
900 if ($this->handler->operator == 'or' && empty($this->handler->options['reduce_duplicates'])) {
901 if (empty($this->handler->options['add_table']) && empty($this->handler->view->many_to_one_tables[$field])) {
902 // query optimization, INNER joins are slightly faster, so use them
903 // when we know we can.
904 $join = $this->get_join();
905 if (isset($join)) {
906 $join->type = 'INNER';
907 }
908 $this->handler->table_alias = $this->handler->query->ensure_table($this->handler->table, $this->handler->relationship, $join);
909 $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
910 }
911 else {
912 $join = $this->get_join();
913 $join->type = 'LEFT';
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 }
926
927 $this->handler->table_alias = $this->add_table($join);
928 }
929
930 return $this->handler->table_alias;
931 }
932
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) {
938 $join = $this->get_join();
939 if ($this->handler->operator == 'and') {
940 $join->type = 'INNER';
941 }
942 $join->extra = array(
943 array(
944 'field' => $this->handler->real_field,
945 'value' => $value,
946 'numeric' => !empty($this->handler->definition['numeric']),
947 ),
948 );
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])) {
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;
955 }
956 $this->handler->view->many_to_one_aliases[$field][$value] = $this->handler->table . '_value_' . ($this->handler->view->many_to_one_count[$this->handler->table]++);
957 }
958 $alias = $this->handler->table_aliases[$value] = $this->add_table($join, $this->handler->view->many_to_one_aliases[$field][$value]);
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.
969 $join = $this->get_join();
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 }
980
981 $this->handler->table_alias = $this->add_table($join);
982 }
983 }
984 return $this->handler->table_alias;
985 }
986
987 function add_filter() {
988 if (empty($this->handler->value)) {
989 return;
990 }
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;
999 }
1000
1001 $placeholder = !empty($this->handler->definition['numeric']) ? '%d' : "'%s'";
1002
1003 if ($operator == 'not') {
1004 $this->handler->query->add_where($options['group'], "$field IS NULL");
1005 }
1006 else if ($operator == 'or' && empty($options['reduce_duplicates'])) {
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 }
1015 }
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 }
1022
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);
1027 }
1028 }
1029 }
1030
1031 /*
1032 * Break x,y,z and x+y+z into an array. Numeric only.
1033 *
1034 * @param $str
1035 * The string to parse.
1036 * @param $handler
1037 * The handler object to use as a base.
1038 *
1039 * @return $handler
1040 * The new handler object.
1041 */
1042 function 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 }
1065 else if (preg_match('/^((\w|\s)+,)*(\w|\s)+$/', $str)) {
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 */
1095 function views_break_phrase($str, &$handler) {
1096 if (!$handler) {
1097 $handler = new stdClass();
1098 }
1099
1100 // Set up defaults:
1101 if (!isset($handler->value)) {
1102 $handler->value = array();
1103 }
1104
1105 if (!isset($handler->operator)) {
1106 $handler->operator = 'or';
1107 }
1108
1109 if ($str == '') {
1110 return $handler;
1111 }
1112
1113 if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
1114 // The '+' character in a query string may be parsed as ' '.
1115 $handler->operator = 'or';
1116 $handler->value = preg_split('/[+ ]/', $str);
1117 }
1118 else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
1119 $handler->operator = 'and';
1120 $handler->value = explode(',', $str);
1121 }
1122
1123 // Keep an 'error' value if invalid strings were given.
1124 if (!empty($str) && (empty($handler->value) || !is_array($handler->value))) {
1125 $handler->value = array(-1);
1126 return $handler;
1127 }
1128
1129 // Doubly ensure that all values are numeric only.
1130 foreach ($handler->value as $id => $value) {
1131 $handler->value[$id] = intval($value);
1132 }
1133
1134 return $handler;
1135 }
1136
1137 // --------------------------------------------------------------------------
1138 // Date helper functions
1139
1140 /**
1141 * Figure out what timezone we're in; needed for some date manipulations.
1142 */
1143 function 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
1153 if (in_array($GLOBALS['db_type'], array('mysql', 'mysqli', 'pgsql'))) {
1154 $offset = '+00:00';
1155 static $already_set = false;
1156 if (!$already_set) {
1157 if ($GLOBALS['db_type'] == 'pgsql') {
1158 db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
1159 }
1160 elseif ($GLOBALS['db_type'] == 'mysqli') {
1161 db_query("SET @@session.time_zone = '$offset'");
1162 }
1163 elseif ($GLOBALS['db_type'] == 'mysql' && version_compare(db_version, '4.1.3', '>=')) {
1164 db_query("SET @@session.time_zone = '$offset'");
1165 }
1166
1167 $already_set = true;
1168 }
1169 }
1170
1171 return $timezone;
1172 }
1173
1174 /**
1175 * Helper function to create cross-database SQL dates.
1176 *
1177 * @param $field
1178 * The real table and field name, like 'tablename.fieldname'.
1179 * @param $field_type
1180 * The type of date field, 'int' or 'datetime'.
1181 * @param $set_offset
1182 * The name of a field that holds the timezone offset or a fixed timezone
1183 * offset value. If not provided, the normal Drupal timezone handling
1184 * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1185 * @return
1186 * An appropriate SQL string for the db type and field type.
1187 */
1188 function views_date_sql_field($field, $field_type = 'int', $set_offset = NULL) {
1189 $db_type = $GLOBALS['db_type'];
1190 $offset = $set_offset !== NULL ? $set_offset : views_get_timezone();
1191 switch ($db_type) {
1192 case 'mysql':
1193 case 'mysqli':
1194 switch ($field_type) {
1195 case 'int':
1196 $field = "FROM_UNIXTIME($field)";
1197 break;
1198 case 'datetime':
1199 break;
1200 }
1201 if (!empty($offset)) {
1202 $field = "($field + INTERVAL $offset SECOND)";
1203 }
1204 return $field;
1205 case 'pgsql':
1206 switch ($field_type) {
1207 case 'int':
1208 $field = "$field::ABSTIME";
1209 break;
1210 case 'datetime':
1211 break;
1212 }
1213 if (!empty($offset)) {
1214 $field = "($field + INTERVAL '$offset SECONDS')";
1215 }
1216 return $field;
1217 }
1218 }
1219
1220 /**
1221 * Helper function to create cross-database SQL date formatting.
1222 *
1223 * @param $format
1224 * A format string for the result, like 'Y-m-d H:i:s'.
1225 * @param $field
1226 * The real table and field name, like 'tablename.fieldname'.
1227 * @param $field_type
1228 * The type of date field, 'int' or 'datetime'.
1229 * @param $set_offset
1230 * The name of a field that holds the timezone offset or a fixed timezone
1231 * offset value. If not provided, the normal Drupal timezone handling
1232 * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1233 * @return
1234 * An appropriate SQL string for the db type and field type.
1235 */
1236 function views_date_sql_format($format, $field, $field_type = 'int', $set_offset = NULL) {
1237 $db_type = $GLOBALS['db_type'];
1238 $field = views_date_sql_field($field, $field_type, $set_offset);
1239 switch ($db_type) {
1240 case 'mysql':
1241 case 'mysqli':
1242 $replace = array(
1243 'Y' => '%Y',
1244 'y' => '%y',
1245 'M' => '%%b',
1246 'm' => '%m',
1247 'n' => '%c',
1248 'F' => '%M',
1249 'D' => '%a',
1250 'd' => '%%d',
1251 'l' => '%W',
1252 'j' => '%e',
1253 'W' => '%v',
1254 'H' => '%H',
1255 'h' => '%h',
1256 'i' => '%i',
1257 's' => '%%s',
1258 'A' => '%p',
1259 );
1260 $format = strtr($format, $replace);
1261 return "DATE_FORMAT($field, '$format')";
1262 case 'pgsql':
1263 $replace = array(
1264 'Y' => 'YYYY',
1265 'y' => 'YY',
1266 'M' => 'Mon',
1267 'm' => 'MM',
1268 'n' => 'MM', // no format for Numeric representation of a month, without leading zeros
1269 'F' => 'Month',
1270 'D' => 'Dy',
1271 'd' => 'DD',
1272 'l' => 'Day',
1273 'j' => 'DD', // no format for Day of the month without leading zeros
1274 'W' => 'WW',
1275 'H' => 'HH24',
1276 'h' => 'HH12',
1277 'i' => 'MI',
1278 's' => 'SS',
1279 'A' => 'AM',
1280 );
1281 $format = strtr($format, $replace);
1282 return "TO_CHAR($field, '$format')";
1283 }
1284 }
1285
1286 /**
1287 * Helper function to create cross-database SQL date extraction.
1288 *
1289 * @param $extract_type
1290 * The type of value to extract from the date, like 'MONTH'.
1291 * @param $field
1292 * The real table and field name, like 'tablename.fieldname'.
1293 * @param $field_type
1294 * The type of date field, 'int' or 'datetime'.
1295 * @param $set_offset
1296 * The name of a field that holds the timezone offset or a fixed timezone
1297 * offset value. If not provided, the normal Drupal timezone handling
1298 * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1299 * @return
1300 * An appropriate SQL string for the db type and field type.
1301 */
1302 function views_date_sql_extract($extract_type, $field, $field_type = 'int', $set_offset = NULL) {
1303 $db_type = $GLOBALS['db_type'];
1304 $field = views_date_sql_field($field, $field_type, $set_offset);
1305
1306 // Note there is no space after FROM to avoid db_rewrite problems
1307 // see http://drupal.org/node/79904.
1308 switch ($extract_type) {
1309 case('DATE'):
1310 return $field;
1311 case('YEAR'):
1312 return "EXTRACT(YEAR FROM($field))";
1313 case('MONTH'):
1314 return "EXTRACT(MONTH FROM($field))";
1315 case('DAY'):
1316 return "EXTRACT(DAY FROM($field))";
1317 case('HOUR'):
1318 return "EXTRACT(HOUR FROM($field))";
1319 case('MINUTE'):
1320 return "EXTRACT(MINUTE FROM($field))";
1321 case('SECOND'):
1322 return "EXTRACT(SECOND FROM($field))";
1323 case('WEEK'): // ISO week number for date
1324 switch ($db_type) {
1325 case('mysql'):
1326 case('mysqli'):
1327 // WEEK using arg 3 in mysql should return the same value as postgres EXTRACT
1328 return "WEEK($field, 3)";
1329 case('pgsql'):
1330 return "EXTRACT(WEEK FROM($field))";
1331 }
1332 case('DOW'):
1333 switch ($db_type) {
1334 case('mysql'):
1335 case('mysqli'):
1336 // mysql returns 1 for Sunday through 7 for Saturday
1337 // php date functions and postgres use 0 for Sunday and 6 for Saturday
1338 return "INTEGER(DAYOFWEEK($field) - 1)";
1339 case('pgsql'):
1340 return "EXTRACT(DOW FROM($field))";
1341 }
1342 case('DOY'):
1343 switch ($db_type) {
1344 case('mysql'):
1345 case('mysqli'):
1346 return "DAYOFYEAR($field)";
1347 case('pgsql'):
1348 return "EXTRACT(DOY FROM($field))";
1349 }
1350 }
1351 }
1352
1353 /**
1354 * Implementation of hook_views_handlers() to register all of the basic handlers
1355 * views uses.
1356 */
1357 function views_views_handlers() {
1358 return array(
1359 'info' => array(
1360 'path' => drupal_get_path('module', 'views') . '/handlers',
1361 ),
1362 'handlers' => array(
1363 // argument handlers
1364 'views_handler_argument' => array(
1365 'parent' => 'views_handler',
1366 ),
1367 'views_handler_argument_numeric' => array(
1368 'parent' => 'views_handler_argument',
1369 ),
1370 'views_handler_argument_formula' => array(
1371 'parent' => 'views_handler_argument',
1372 ),
1373 'views_handler_argument_date' => array(
1374 'parent' => 'views_handler_argument_formula',
1375 ),
1376 'views_handler_argument_string' => array(
1377 'parent' => 'views_handler_argument',
1378 ),
1379 'views_handler_argument_many_to_one' => array(
1380 'parent' => 'views_handler_argument',
1381 ),
1382 'views_handler_argument_null' => array(
1383 'parent' => 'views_handler_argument',
1384 ),
1385 'views_handler_argument_broken' => array(
1386 'parent' => 'views_handler_argument',
1387 ),
1388
1389 // field handlers
1390 'views_handler_field' => array(
1391 'parent' => 'views_handler',
1392 ),
1393 'views_handler_field_date' => array(
1394 'parent' => 'views_handler_field',
1395 ),
1396 'views_handler_field_boolean' => array(
1397 'parent' => 'views_handler_field',
1398 ),
1399 'views_handler_field_markup' => array(
1400 'parent' => 'views_handler_field',
1401 ),
1402 'views_handler_field_xss' => array(
1403 'parent' => 'views_handler_field',
1404 'file' => 'views_handler_field.inc',
1405 ),
1406 'views_handler_field_url' => array(
1407 'parent' => 'views_handler_field',
1408 ),
1409 'views_handler_field_file_size' => array(
1410 'parent' => 'views_handler_field',
1411 'file' => 'views_handler_field.inc',
1412 ),
1413 'views_handler_field_prerender_list' => array(
1414 'parent' => 'views_handler_field',
1415 ),
1416 'views_handler_field_numeric' => array(
1417 'parent' => 'views_handler_field',
1418 ),
1419 'views_handler_field_custom' => array(
1420 'parent' => 'views_handler_field',
1421 ),
1422 'views_handler_field_counter' => array(
1423 'parent' => 'views_handler_field',
1424 ),
1425 'views_handler_field_math' => array(
1426 'parent' => 'views_handler_field_numeric',
1427 ),
1428 'views_handler_field_serialized' => array(
1429 'parent' => 'views_handler_field',
1430 ),
1431 'views_handler_field_time_interval' => array(
1432 'parent' => 'views_handler_field',
1433 ),
1434 'views_handler_field_broken' => array(
1435 'parent' => 'views_handler_field',
1436 ),
1437
1438 // filter handlers
1439 'views_handler_filter' => array(
1440 'parent' => 'views_handler',
1441 ),
1442 'views_handler_filter_equality' => array(
1443 'parent' => 'views_handler_filter',
1444 ),
1445 'views_handler_filter_string' => array(
1446 'parent' => 'views_handler_filter',
1447 ),
1448 'views_handler_filter_boolean_operator' => array(
1449 'parent' => 'views_handler_filter',
1450 ),
1451 'views_handler_filter_boolean_operator_string' => array(
1452 'parent' => 'views_handler_filter_boolean_operator',
1453 ),
1454 'views_handler_filter_in_operator' => array(
1455 'parent' => 'views_handler_filter',
1456 ),
1457 'views_handler_filter_numeric' => array(
1458 'parent' => 'views_handler_filter',
1459 ),
1460 'views_handler_filter_float' => array(
1461 'parent' => 'views_handler_filter_numeric',
1462 ),
1463 'views_handler_filter_date' => array(
1464 'parent' => 'views_handler_filter_numeric',
1465 ),
1466 'views_handler_filter_many_to_one' => array(
1467 'parent' => 'views_handler_filter_in_operator',
1468 ),
1469 'views_handler_filter_broken' => array(
1470 'parent' => 'views_handler_filter',
1471 ),
1472
1473 // relationship handlers
1474 'views_handler_relationship' => array(
1475 'parent' => 'views_handler',
1476 ),
1477 'views_handler_relationship_broken' => array(
1478 'parent' => 'views_handler_relationship',
1479 ),
1480
1481
1482 // sort handlers
1483 'views_handler_sort' => array(
1484 'parent' => 'views_handler',
1485 ),
1486 'views_handler_sort_formula' => array(
1487 'parent' => 'views_handler_sort',
1488 ),
1489 'views_handler_sort_date' => array(
1490 'parent' => 'views_handler_sort',
1491 ),
1492 'views_handler_sort_menu_hierarchy' => array(
1493 'parent' => 'views_handler_sort',
1494 ),
1495 'views_handler_sort_random' => array(
1496 'parent' => 'views_handler_sort',
1497 ),
1498 'views_handler_sort_broken' => array(
1499 'parent' => 'views_handler_sort',
1500 ),
1501
1502 // group by handlers
1503 'views_handler_argument_group_by_numeric' => array(
1504 'parent' => 'views_handler_argument',
1505 ),
1506 'views_handler_field_group_by_numeric' => array(
1507 'parent' => 'views_handler_field_numeric',
1508 ),
1509 'views_handler_filter_group_by_numeric' => array(
1510 'parent' => 'views_handler_filter_numeric',
1511 ),
1512 'views_handler_sort_group_by_numeric' => array(
1513 'parent' => 'views_handler_sort',
1514 ),
1515
1516 // area handlers
1517 'views_handler_area' => array(
1518 'parent' => 'views_handler',
1519 ),
1520 'views_handler_area_text' => array(
1521 'parent' => 'views_handler_area',
1522 ),
1523 'views_handler_area_broken' => array(
1524 'parent' => 'views_handler_area',
1525 ),
1526 ),
1527 );
1528 }
1529
1530
1531 /**
1532 * @}
1533 */
1534
1535 /**
1536 * @defgroup views_join_handlers Views' join handlers
1537 * @{
1538 * Handlers to tell Views how to join tables together.
1539
1540 * Here is how you do complex joins:
1541 *
1542 * @code
1543 * class views_join_complex extends views_join {
1544 * // PHP 4 doesn't call constructors of the base class automatically from a
1545 * // constructor of a derived class. It is your responsibility to propagate
1546 * // the call to constructors upstream where appropriate.
1547 * function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1548 * parent::construct($table, $left_table, $left_field, $field, $extra, $type);
1549 * }
1550 *
1551 * function join($table, &$query) {
1552 * $output = parent::join($table, $query);
1553 * $output .= "AND foo.bar = baz.boing";
1554 * return $output;
1555 * }
1556 * }
1557 * @endcode
1558 */
1559 /**
1560 * A function class to represent a join and create the SQL necessary
1561 * to implement the join.
1562 *
1563 * This is the Delegation pattern. If we had PHP5 exclusively, we would
1564 * declare this an interface.
1565 *
1566 * Extensions of this class can be used to create more interesting joins.
1567 *
1568 * join definition
1569 * - table: table to join (right table)
1570 * - field: field to join on (right field)
1571 * - left_table: The table we join to
1572 * - left_field: The field we join to
1573 * - type: either LEFT (default) or INNER
1574 * - extra: Either a string that's directly added, or an array of items:
1575 * - - table: if not set, current table; if NULL, no table. This field can't
1576 * be set in the cached definition because it can't know aliases; this field
1577 * can only be used by realtime joins.
1578 * - - field: Field or formula
1579 * - - operator: defaults to =
1580 * - - value: Must be set. If an array, operator will be defaulted to IN.
1581 * - - numeric: If true, the value will not be surrounded in quotes.
1582 * - extra type: How all the extras will be combined. Either AND or OR. Defaults to AND.
1583 */
1584 class views_join {
1585 /**
1586 * Construct the views_join object.
1587 */
1588 function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1589 $this->extra_type = 'AND';
1590 if (!empty($table)) {
1591 $this->table = $table;
1592 $this->left_table = $left_table;
1593 $this->left_field = $left_field;
1594 $this->field = $field;
1595 $this->extra = $extra;
1596 $this->type = strtoupper($type);
1597 }
1598 else if (!empty($this->definition)) {
1599 // if no arguments, construct from definition.
1600 // These four must exist or it will throw notices.
1601 $this->table = $this->definition['table'];
1602 $this->left_table = $this->definition['left_table'];
1603 $this->left_field = $this->definition['left_field'];
1604 $this->field = $this->definition['field'];
1605 if (!empty($this->definition['extra'])) {
1606 $this->extra = $this->definition['extra'];
1607 }
1608 if (!empty($this->definition['extra type'])) {
1609 $this->extra_type = strtoupper($this->definition['extra type']);
1610 }
1611
1612 $this->type = !empty($this->definition['type']) ? strtoupper($this->definition['type']) : 'LEFT';
1613 }
1614 }
1615
1616 /**
1617 * Build the SQL for the join this object represents.
1618 */
1619 function join($table, &$query) {
1620 if (empty($this->definition['table formula'])) {
1621 $right_table = "{" . $this->table . "}";
1622 }
1623 else {
1624 $right_table = $this->definition['table formula'];
1625 }
1626
1627 if ($this->left_table) {
1628 $left = $query->get_table_info($this->left_table);
1629 $left_field = "$left[alias].$this->left_field";
1630 }
1631 else {
1632 // This can be used if left_field is a formula or something. It should be used only *very* rarely.
1633 $left_field = $this->left_field;
1634 }
1635
1636 $output = " $this->type JOIN $right_table $table[alias] ON $left_field = $table[alias].$this->field";
1637
1638 // Tack on the extra.
1639 if (isset($this->extra)) {
1640 if (is_array($this->extra)) {
1641 $extras = array();
1642 foreach ($this->extra as $info) {
1643 $extra = '';
1644 // Figure out the table name. Remember, only use aliases provided
1645 // if at all possible.
1646 $join_table = '';
1647 if (!array_key_exists('table', $info)) {
1648 $join_table = $table['alias'] . '.';
1649 }
1650 elseif (isset($info['table'])) {
1651 $join_table = $info['table'] . '.';
1652 }
1653
1654 // And now deal with the value and the operator. Set $q to
1655 // a single-quote for non-numeric values and the
1656 // empty-string for numeric values, then wrap all values in $q.
1657 $raw_value = $this->db_safe($info['value']);
1658 $q = (empty($info['numeric']) ? "'" : '');
1659
1660 if (is_array($raw_value)) {
1661 $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
1662 // Transform from IN() notation to = notation if just one value.
1663 if (count($raw_value) == 1) {
1664 $value = $q . array_shift($raw_value) . $q;
1665 $operator = $operator == 'NOT IN' ? '!=' : '=';
1666 }
1667 else {
1668 $value = "($q" . implode("$q, $q", $raw_value) . "$q)";
1669 }
1670 }
1671 else {
1672 $operator = !empty($info['operator']) ? $info['operator'] : '=';
1673 $value = "$q$raw_value$q";
1674 }
1675 $extras[] = "$join_table$info[field] $operator $value";
1676 }
1677
1678 if ($extras) {
1679 if (count($extras) == 1) {
1680 $output .= ' AND ' . array_shift($extras);
1681 }
1682 else {
1683 $output .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
1684 }
1685 }
1686 }
1687 else if ($this->extra && is_string($this->extra)) {
1688 $output .= " AND ($this->extra)";
1689 }
1690 }
1691 return $output;
1692 }
1693
1694 /**
1695 * Ensure that input is db safe. We only check strings and ints tho
1696 * so something that needs floats in their joins needs to do their
1697 * own type checking.
1698 */
1699 function db_safe($input) {
1700 if (is_array($input)) {
1701 $output = array();
1702 foreach ($input as $value) {
1703 if (empty($info['numeric'])) {
1704 $output[] = db_escape_string($value);
1705 }
1706 else {
1707 $output[] = intval($value);
1708 }
1709 }
1710 }
1711 else if (empty($info['numeric'])) {
1712 $output = db_escape_string($input);
1713 }
1714 else {
1715 $output = intval($input);
1716 }
1717
1718 return $output;
1719 }
1720 }
1721
1722 /**
1723 * @}
1724 */
1725
1726 // Declare API compatibility on behalf of core modules:
1727
1728 /**
1729 * Implementation of hook_views_api().
1730 *
1731 * This one is used as the base to reduce errors when updating.
1732 */
1733 function views_views_api() {
1734 return array(
1735 // in your modules do *not* use views_api_version()!!!
1736 'api' => views_api_version(),
1737 'path' => drupal_get_path('module', 'views') . '/modules',
1738 );
1739 }
1740
1741 if (!function_exists('aggregator_views_api')) {
1742 function aggregator_views_api() { return views_views_api(); }
1743 }
1744
1745 if (!function_exists('book_views_api')) {
1746 function book_views_api() { return views_views_api(); }
1747 }
1748
1749 if (!function_exists('comment_views_api')) {
1750 function comment_views_api() { return views_views_api(); }
1751 }
1752
1753 if (!function_exists('locale_views_api')) {
1754 function locale_views_api() { return views_views_api(); }
1755 }
1756
1757 if (!function_exists('filter_views_api')) {
1758 function filter_views_api() { return views_views_api(); }
1759 }
1760
1761 if (!function_exists('node_views_api')) {
1762 function node_views_api() { return views_views_api(); }
1763 }
1764
1765 if (!function_exists('poll_views_api')) {
1766 function poll_views_api() { return views_views_api(); }
1767 }
1768
1769 if (!function_exists('profile_views_api')) {
1770 function profile_views_api() { return views_views_api(); }
1771 }
1772
1773 if (!function_exists('search_views_api')) {
1774 function search_views_api() { return views_views_api(); }
1775 }
1776
1777 if (!function_exists('statistics_views_api')) {
1778 function statistics_views_api() { return views_views_api(); }
1779 }
1780
1781 if (!function_exists('system_views_api')) {
1782 function system_views_api() { return views_views_api(); }
1783 }
1784
1785 if (!function_exists('taxonomy_views_api')) {
1786 function taxonomy_views_api() { return views_views_api(); }
1787 }
1788
1789 if (!function_exists('translation_views_api')) {
1790 function translation_views_api() { return views_views_api(); }
1791 }
1792
1793 if (!function_exists('upload_views_api')) {
1794 function upload_views_api() { return views_views_api(); }
1795 }
1796
1797 if (!function_exists('user_views_api')) {
1798 function user_views_api() { return views_views_api(); }
1799 }
1800
1801 if (!function_exists('contact_views_api')) {
1802 function contact_views_api() { return views_views_api(); }
1803 }