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