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