Restore missing, lost object
[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
EM
10 */
11function _views_create_handler($definition) {
238f7347 12// vpr('Instantiating handler ' . $definition['handler']);
021885c3 13 $handler = new $definition['handler'];
c60d618c 14 $handler->set_definition($definition);
725bd2c9 15 // let the handler have something like a constructor.
021885c3 16 if (isset($definition['arguments'])) {
b207ba28 17 call_user_func_array(array(&$handler, 'construct'), $definition['arguments']);
021885c3 18 }
725bd2c9
EM
19 else {
20 $handler->construct();
21 }
021885c3 22
021885c3
EM
23 return $handler;
24}
25
26/**
27 * Prepare a handler's data by checking defaults and such.
021885c3
EM
28 */
29function _views_prepare_handler($definition, $data, $field) {
30 foreach (array('group', 'title', 'help') as $key) {
31 // First check the field level
32 if (!isset($definition[$key]) && !empty($data[$field][$key])) {
33 $definition[$key] = $data[$field][$key];
34 }
35 // Then if that doesn't work, check the table level
36 if (!isset($definition['table'][$key]) && !empty($data['table'][$key])) {
37 $definition[$key] = $data['table'][$key];
38 }
39 }
40
41 return _views_create_handler($definition);
42}
43
44/**
45 * Fetch a handler to join one table to a primary table from the data cache
021885c3
EM
46 */
47function views_get_table_join($table, $primary_table) {
48 $data = views_fetch_data($table);
49 if (isset($data['table']['join'][$primary_table])) {
50 $h = $data['table']['join'][$primary_table];
51 $handler = new $h['handler'];
52 if (isset($h['arguments'])) {
b207ba28 53 call_user_func_array(array(&$handler, 'construct'), $h['arguments']);
021885c3
EM
54 }
55 return $handler;
56 }
57 // DEBUG -- identify missing handlers
d3b06159 58 vpr("Missing join: $table $primary_table");
021885c3
EM
59}
60
61/**
013538bb
EM
62 * @defgroup views_join_handlers Views' join handlers
63 * @{
64 * Handlers to tell Views how to join tables together.
65
66 * Here is how you do complex joins:
67 *
68 * @code
69 * class views_join_complex extends views_join {
86ed07d8
EM
70 * // PHP 4 doesn't call constructors of the base class automatically from a
71 * // constructor of a derived class. It is your responsibility to propagate
013538bb 72 * // the call to constructors upstream where appropriate.
cffc1056
EM
73 * function construct($left_table, $left_field, $field, $extra = array(), $type = 'LEFT') {
74 * parent::construct($left_table, $left_field, $field, $extra, $type);
013538bb
EM
75 * }
76 *
77 * function join($table, &$query) {
78 * $output = parent::join($table, $query);
79 * }
80 * $output .= "AND foo.bar = baz.boing";
81 * return $output;
82 * }
83 * @endcode
84 */
85/**
86 * A function class to represent a join and create the SQL necessary
87 * to implement the join.
86ed07d8 88 *
013538bb
EM
89 * This is the Delegation pattern. If we had PHP5 exclusively, we would
90 * declare this an interface.
91 *
92 * Extensions of this class can be used to create more interesting joins.
93 */
94class views_join {
95 /**
96 * Construct the views_join object.
97 */
cffc1056 98 function construct($table, $left_table, $left_field, $field, $extra = array(), $type = 'LEFT') {
013538bb
EM
99 $this->table = $table;
100 $this->left_table = $left_table;
101 $this->left_field = $left_field;
102 $this->field = $field;
103 $this->extra = $extra;
104 $this->type = strtoupper($type);
105 }
106
107 /**
108 * Build the SQL for the join this object represents.
109 */
110 function join($table, &$query) {
111 $left = $query->get_table_info($this->left_table);
112 $output = " $this->type JOIN {" . $this->table . "} $table[alias] ON $left[alias].$this->left_field = $table[alias].$this->field";
113
114 // Tack on the extra.
115 if (isset($extra)) {
116 foreach ($extra as $field => $value) {
117 $output .= " AND $table[alias].$this->field";
118 if (is_array($value) && !empty($value)) {
119 $output .= " IN ('". implode("','", $value) ."')";
120 }
121 else if ($value !== NULL) {
122 $output .= " = '$value'";
123 }
124 }
125 }
86ed07d8 126 return $output;
013538bb
EM
127 }
128}
129
130/**
131 * @}
132 */
133
134/**
86ed07d8 135 * Base handler, from which all the other handlers are derived.
013538bb
EM
136 * It creates a common interface to create consistency amongst
137 * handlers and data.
138 *
139 * The default handler has no constructor, so there's no need to jank with
140 * parent::views_handler() here.
141 *
142 * This class would be abstract in PHP5, but PHP4 doesn't understand that.
86ed07d8 143 *
013538bb 144 */
cffc1056 145class views_handler extends views_object {
013538bb 146 /**
725bd2c9
EM
147 * A constructor for the handler base object
148 *
149 * This should be overridden to provide for a consistent constructor
150 * mechanism.
151 */
152 function construct() { }
c60d618c 153
725bd2c9 154 /**
86ed07d8 155 * init the handler with necessary data.
013538bb
EM
156 * @param $view
157 * The $view object this handler is attached to.
5ad6fb04 158 * @param $options
013538bb
EM
159 * The item from the database; the actual contents of this will vary
160 * based upon the type of handler.
161 */
1008b7ff 162 function init(&$view, $options) {
013538bb 163 $this->view = &$view;
1008b7ff 164 $this->options = &$options;
013538bb 165
013538bb 166 // This exist on most handlers, but not all. So they are still optional.
1008b7ff
EM
167 if (isset($options['table'])) {
168 $this->table = $options['table'];
013538bb
EM
169 }
170
1008b7ff
EM
171 if (isset($options['field'])) {
172 $this->field = $options['field'];
cffc1056 173 if (!isset($this->real_field)) {
1008b7ff 174 $this->real_field = $options['field'];
cffc1056 175 }
013538bb
EM
176 }
177
013538bb
EM
178 if (!empty($view->query)) {
179 $this->query = &$view->query;
180 }
181 }
182
183 /**
4191b1df
EM
184 * Return a string representing this handler's name in the UI.
185 */
186 function ui_name() {
187 return t('@group: @title', array('@group' => $this->definition['group'], '@title' => $this->definition['title']));
188 }
189
190 /**
3a69a04c
EM
191 * Provide defaults for the handler.
192 */
193 function options(&$option) { }
194
195 /**
013538bb
EM
196 * Provide a form for setting options.
197 */
3a69a04c 198 function options_form(&$form, &$form_state) { }
86ed07d8 199
013538bb
EM
200 /**
201 * Validate the options form.
202 */
203 function options_validate($form, &$form_state) { }
204
205 /**
206 * Perform any necessary changes to the form values prior to storage.
207 * There is no need for this function to actually store the data.
208 */
209 function options_submit($form, &$form_state) { }
210
211 /**
d3887131
EM
212 * Set new exposed option defaults when exposed setting is flipped
213 * on.
214 */
215 function expose_options() { }
216 /**
217 * Render our chunk of the exposed filter form when selecting
70b45fc4
EM
218 */
219 function exposed_form(&$form, &$form_state) { }
220
221 /**
222 * Validate the exposed filter form
223 */
224 function exposed_validate(&$form, &$form_state) { }
225
226 /**
227 * Submit the exposed filter form
228 */
229 function exposed_submit(&$form, &$form_state) { }
230
231 /**
d3887131
EM
232 * Get information about the exposed form for the form renderer.
233 *
234 * @return
235 * An array with the following keys:
236 * - operator: The $form key of the operator. Set to NULL if no operator.
237 * - value: The $form key of the value. Set to NULL if no value.
238 * - label: The label to use for this piece.
239 */
240 function exposed_info() { }
4191b1df 241
7ae4dc41
EM
242 /**
243 * Check whether current user has access to this handler.
244 *
245 * @return boolean
4191b1df 246 */
7ae4dc41
EM
247 function access() {
248 return TRUE;
249 }
d3887131
EM
250
251 /**
0a5243db
EM
252 * Run before the view is built.
253 *
254 * This gives all the handlers some time to set up before any handler has
255 * been fully run.
256 */
257 function pre_query() { }
258
259 /**
4191b1df
EM
260 * Called just prior to query(), this lets a handler set up any relationship
261 * it needs.
262 */
263 function set_relationship() {
264 // Ensure this gets set to something.
265 $this->relationship = NULL;
266
267 // Don't process non-existant relationships.
268 if (empty($this->options['relationship']) || $this->options['relationship'] == 'none') {
269 return;
270 }
271
272 $relationship = $this->options['relationship'];
273
274 // Ignore missing/broken relationships.
275 if (empty($this->view->relationship[$relationship]) || empty($this->view->relationship[$relationship]['handler'])) {
276 return;
277 }
278
279 // Check to see if the relationship has already processed. If not, then we
280 // cannot process it.
281 if (empty($this->view->relationship[$relationship]['handler']->alias)) {
282 return;
283 }
284
285 // Finally!
286 $this->relationship = $this->view->relationship[$relationship]['handler']->alias;
287 }
288
289 /**
013538bb
EM
290 * Add this handler into the query.
291 *
292 * If we were using PHP5, this would be abstract.
293 */
294 function query() { }
cffc1056
EM
295
296 /**
297 * Ensure the main table for this handler is in the query. This is used
298 * a lot.
299 */
300 function ensure_my_table() {
1146ee98 301 if (!isset($this->table_alias)) {
4191b1df
EM
302 $value = $this->query->ensure_table($this->table, $this->relationship);
303 $this->table_alias = $value;
cffc1056
EM
304 }
305 return $this->table_alias;
306 }
3a69a04c
EM
307
308 /**
309 * Provide text for the administrative summary
310 */
311 function admin_summary() { }
312
313 /**
314 * Determine if the argument needs a style plugin.
315 *
316 * @return TRUE/FALSE
317 */
318 function needs_style_plugin() { return FALSE; }
bb770a1b
EM
319
320 /**
321 * Determine if this item is 'exposed', meaning it provides form elements
322 * to let users modify the view.
323 *
324 * @return TRUE/FALSE
325 */
326 function is_exposed() {
327 return !empty($this->options['exposed']);
328 }
329
330 /**
331 * Take input from exposed filters and assign to this handler, if necessary.
332 */
618449fb 333 function accept_exposed_input($input) { return TRUE; }
013538bb
EM
334}
335
336/**
337 * @defgroup views_relationship_handlers Views' relationship handlers
338 * @{
339 * Handlers to tell Views how to create alternate relationships.
340 */
341
342/**
343 * Simple relationship handler that allows a new version of the primary table
344 * to be linked in.
345 */
346class views_handler_relationship extends views_handler {
347 /**
4191b1df
EM
348 * Get this field's label.
349 */
350 function label() {
351 if (!isset($this->options['label'])) {
352 return $this->ui_name();
353 }
354 return $this->options['label'];
355 }
356
357 /**
358 * Provide a default label
359 */
360 function options(&$options) {
361 parent::options($options);
362 $options['label'] = !empty($this->definition['label']) ? $this->definition['label'] : $this->definition['field'];
363 }
364
365 /**
366 * Default options form that provides the label widget that all fields
367 * should have.
368 */
369 function options_form(&$form, &$form_state) {
370 $form['label'] = array(
371 '#type' => 'textfield',
372 '#title' => t('Label'),
373 '#default_value' => isset($this->options['label']) ? $this->options['label'] : '',
374 '#description' => t('The label for this relationship that will be displayed only administratively.'),
375 );
376 }
377
378 /**
013538bb
EM
379 * Called to implement a relationship in a query.
380 */
381 function query() {
4191b1df
EM
382 // Figure out what base table this relationship brings to the party.
383 $table_data = views_fetch_data($this->definition['base']);
384 $base_field = $table_data['table']['base']['field'];
385
386 $join = new views_join;
387 $join->construct($this->definition['base'], $this->table, $this->real_field, $base_field);
388
389 $this->ensure_my_table();
390
391 // use a short alias for this:
392 $alias = $this->options['id'];
393
394 $this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $this->relationship);
013538bb
EM
395 }
396}
397
398/**
399 * @}
400 */
401
402/**
403 * @defgroup views_field_handlers Views' field handlers
404 * @{
405 * Handlers to tell Views how to build and display fields.
cffc1056 406 *
013538bb
EM
407 */
408
409/**
410 * Base field handler that has no options and renders an unformatted field.
411 */
412class views_handler_field extends views_handler {
b06399e3 413 var $field_alias = 'unknown';
013538bb
EM
414
415 /**
416 * Construct a new field handler.
417 */
c60d618c 418 function construct() {
725bd2c9
EM
419 $this->additional_fields = array();
420 if (!empty($this->definition['additional fields'])) {
421 $this->additional_fields = $this->definition['additional fields'];
422 }
013538bb
EM
423 }
424
425 /**
426 * Called to add the field to a query.
427 */
428 function query() {
cffc1056 429 $this->ensure_my_table();
013538bb 430 // Add the field.
cffc1056 431 $this->field_alias = $this->query->add_field($this->table_alias, $this->real_field);
b06399e3 432
4191b1df
EM
433 $this->add_additional_fields();
434 }
435
436 /**
437 * Add 'additional' fields to the query.
438 *
439 * @param $fields
440 * An array of fields. The key is an identifier used to later find the
441 * field alias used. The value is either a string in which case it's
442 * assumed to be a field on this handler's table; or it's an array in the
443 * form of
444 * @code array('table' => $tablename, 'field' => $fieldname) @endcode
445 */
446 function add_additional_fields($fields = NULL) {
447 if (!isset($fields)) {
448 // notice check
449 if (empty($this->additional_fields)) {
450 return;
451 }
452 $fields = $this->additional_fields;
453 }
454 if (!empty($fields) && is_array($fields)) {
455 foreach ($fields as $identifier => $info) {
456 if (is_array($info)) {
457 if (isset($info['table'])) {
458 $table_alias = $this->query->ensure_table($info['table'], $this->relationship);
459 }
460 else {
461 $table_alias = $this->table_alias;
462 }
463 $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field']);
464 }
465 else {
466 $this->aliases[$info] = $this->query->add_field($this->table_alias, $info);
467 }
013538bb
EM
468 }
469 }
470 }
471
472 /**
473 * Called to determine what to tell the clicksorter.
474 */
6b92ee49
EM
475 function click_sort($order) {
476 $this->query->add_orderby($this->table, $this->field, $order, $this->field_alias);
477 }
478
479 /**
480 * Determine if this field is click sortable.
481 */
482 function click_sortable() {
483 return !empty($this->definition['click sortable']);
484 }
485
486 /**
487 * Get this field's label.
488 */
489 function label() {
490 if (!isset($this->options['label'])) {
491 return '';
492 }
493 return $this->options['label'];
494 }
495
496 /**
497 * Provide a default label
498 */
499 function options(&$options) {
500 parent::options($options);
4191b1df 501 $options['label'] = $this->ui_name();
6b92ee49
EM
502 }
503
504 /**
505 * Default options form that provides the label widget that all fields
506 * should have.
507 */
508 function options_form(&$form, &$form_state) {
509 $form['label'] = array(
510 '#type' => 'textfield',
511 '#title' => t('Label'),
512 '#default_value' => isset($this->options['label']) ? $this->options['label'] : '',
513 '#description' => t('The label for this field that will be displayed to end users if the style requires it.'),
514 );
013538bb
EM
515 }
516
517 /**
4191b1df
EM
518 * Provide extra data to the administration form
519 */
520 function admin_summary() {
521 return $this->label();
522 }
523
524 /**
0a5243db
EM
525 * Run before any fields are rendered.
526 *
527 * This gives the handlers some time to set up before any handler has
528 * been rendered.
529 *
5ad6fb04 530 * @param $values
0a5243db
EM
531 * An array of all objects returned from the query.
532 */
533 function pre_render($values) { }
534
535 /**
013538bb
EM
536 * Render the field.
537 *
538 * @param $values
539 * The values retrieved from the database.
540 */
541 function render($values) {
542 $value = $values->{$this->field_alias};
543 return check_plain($value);
544 }
545}
546
547/**
548 * A handler to provide proper displays for dates.
549 */
550class views_handler_field_date extends views_handler_field {
551 /**
725bd2c9 552 * Fill in default options.
013538bb 553 */
1008b7ff
EM
554 function options(&$options) {
555 parent::options($options);
556 $options['date_format'] = 'small';
557 $options['custom_date_format'] = '';
3a69a04c
EM
558 }
559
560 function options_form(&$form, &$form_state) {
6b92ee49 561 parent::options_form($form, $form_state);
59a7762e
EM
562 $time = time();
563
013538bb
EM
564 $form['date_format'] = array(
565 '#type' => 'select',
566 '#title' => t('Date format'),
567 '#options' => array(
59a7762e
EM
568 'small' => format_date($time, 'small'),
569 'medium' => format_date($time, 'medium'),
570 'large' => format_date($time, 'large'),
013538bb
EM
571 'custom' => t('Custom'),
572 'time ago' => t('Time ago'),
573 ),
1008b7ff 574 '#default_value' => isset($this->options['date_format']) ? $this->options['date_format'] : 'small',
013538bb
EM
575 );
576 $form['custom_date_format'] = array(
577 '#type' => 'textfield',
578 '#title' => t('Custom date format'),
579 '#description' => t('If "Custom", see <a href="http://us.php.net/manual/en/function.date.php">the PHP docs</a> for date formats. If "Time ago" this is the the number of different units to display, which defaults to two.'),
1008b7ff 580 '#default_value' => isset($this->options['custom_date_format']) ? $this->options['custom_date_format'] : '',
01c6ed1e
EM
581 '#process' => array('views_process_dependency'),
582 '#dependency' => array('edit-options-date-format' => array('custom', 'time ago')),
013538bb
EM
583 );
584 }
585
586 function render($values) {
587 $value = $values->{$this->field_alias};
1008b7ff 588 $format = $this->options['date_format'];
cffc1056 589 if ($format == 'custom') {
3a69a04c 590 $custom_format = $this->custom_date_format;
cffc1056 591 }
013538bb
EM
592
593 switch ($format) {
594 case 'time ago':
595 return $value ? t('%time ago', array('%time' => format_interval(time() - $value, is_numeric($custom_format) ? $custom_format : 2))) : theme('views_nodate');
596 case 'custom':
597 return $value ? format_date($value, $format, $custom_format) : theme('views_nodate');
598 default:
599 return $value ? format_date($value, $format) : theme('views_nodate');
600 }
601 }
602}
603
604/**
0225248a
EM
605 * A handler to provide proper displays for dates.
606 *
607 * Allows for display of true/false, yes/no, on/off.
608 */
609class views_handler_field_boolean extends views_handler_field {
1008b7ff
EM
610 function options(&$options) {
611 parent::options($options);
612 $options['type'] = 'yes-no';
613 $options['not'] = FALSE;
3a69a04c
EM
614 }
615
616 function options_form(&$form, &$form_state) {
6b92ee49 617 parent::options_form($form, $form_state);
0225248a
EM
618 $form['type'] = array(
619 '#type' => 'select',
3a69a04c 620 '#title' => t('Output format'),
0225248a
EM
621 '#options' => array(
622 'yes-no' => t('Yes/No'),
623 'true-false' => t('True/False'),
624 'on-off' => t('On/Off'),
625 ),
1008b7ff 626 '#default_value' => $this->options['type'],
0225248a
EM
627 );
628 $form['not'] = array(
629 '#type' => 'checkbox',
630 '#title' => t('Reverse'),
631 '#description' => t('If checked, true will be displayed as false.'),
1008b7ff 632 '#default_value' => $this->options['not'],
0225248a
EM
633 );
634 }
635
636 function render($values) {
637 $value = $values->{$this->field_alias};
1008b7ff 638 if (!empty($this->options['not'])) {
0225248a
EM
639 $value = !$value;
640 }
641
1008b7ff 642 switch ($this->options['type']) {
0225248a
EM
643 case 'yes-no':
644 default:
645 return $value ? t('Yes') : t('No');
646 case 'true-false':
647 return $value ? t('True') : t('False');
3a69a04c 648 case 'on-off':
0225248a
EM
649 return $value ? t('On') : t('Off');
650 }
651 }
652}
653
654/**
2bd5f0a0
EM
655 * A handler to run a field through check_markup, using a companion
656 * format field.
2bd5f0a0
EM
657 */
658class views_handler_field_markup extends views_handler_field {
659 /**
660 * Constructor; calls to base object constructor.
661 */
725bd2c9
EM
662 function construct() {
663 $this->format = $this->definition['format'];
2bd5f0a0 664
725bd2c9
EM
665 $this->additional_fields = array();
666 if (!is_numeric($this->format)) {
c7819f0c 667 $this->additional_fields['format'] = $this->format;
2bd5f0a0 668 }
2bd5f0a0
EM
669 }
670
671 function render($values) {
672 $value = $values->{$this->field_alias};
c7819f0c 673 $format = is_numeric($this->format) ? $this->format : $values->{$this->aliases['format']};
2bd5f0a0
EM
674 return check_markup($value, $format, FALSE);
675 }
676}
677
678/**
c7819f0c
EM
679 * A handler to run a field through simple XSS filtering
680 */
681class views_handler_field_xss extends views_handler_field {
682 function render($values) {
683 $value = $values->{$this->field_alias};
684 return filter_xss($value);
685 }
686}
687
688/**
0a5243db
EM
689 * Field handler to provide simple renderer that turns a URL into a clickable link.
690 */
691class views_handler_field_url extends views_handler_field {
692 /**
693 * Provide link to the page being visited.
694 */
695 function options_form(&$form, &$form_state) {
6b92ee49 696 parent::options_form($form, $form_state);
0a5243db
EM
697 $form['display_as_link'] = array(
698 '#title' => t('Display as link'),
699 '#type' => 'checkbox',
700 '#default_value' => !empty($this->options['display_as_link']),
701 );
702 }
703
704 function render($values) {
705 $value = $values->{$this->field_alias};
706 if (!empty($this->options['display_as_link'])) {
707 return l(check_plain($value), $value, array('html' => TRUE));
708 }
709 else {
710 return $value;
711 }
712 }
713}
714
715/**
013538bb
EM
716 * @}
717 */
718
719/**
720 * @defgroup views_sort_handlers Views' sort handlers
721 * @{
722 * Handlers to tell Views how to sort queries
723 */
724/**
725 * Base sort handler that has no options and performs a simple sort
726 */
727class views_handler_sort extends views_handler {
728 /**
729 * Called to add the sort to a query.
730 */
731 function query() {
cffc1056 732 $this->ensure_my_table();
013538bb 733 // Add the field.
1008b7ff 734 $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']);
013538bb 735 }
3a69a04c
EM
736
737 /**
738 * Set defaults for new handler
739 */
740 function options(&$options) {
1008b7ff
EM
741 parent::options($options);
742 $options['order'] = 'ASC';
3a69a04c
EM
743 }
744
745 /**
746 * Display whether or not the sort order is ascending or descending
747 */
748 function admin_summary() {
1008b7ff 749 switch ($this->options['order']) {
3a69a04c
EM
750 case 'ASC':
751 case 'asc':
752 default:
753 $type = t('asc');
754 break;
755 case 'DESC';
756 case 'desc';
757 $type = t('desc');
758 break;
759 }
760 return '<span class="views-ascending"><span>' . $type . '</span></span>';
761 }
762
763 /**
764 * Basic options for all sort criteria
765 */
766 function options_form(&$form, &$form_state) {
767 $form['order'] = array(
768 '#type' => 'radios',
769 '#title' => t('Sort order'),
770 '#options' => array('ASC' => t('Ascending'), 'DESC' => t('Descending')),
1008b7ff 771 '#default_value' => $this->options['order'],
3a69a04c
EM
772 );
773 }
013538bb
EM
774}
775
776/**
777 * Base sort handler that has no options and performs a simple sort
778 */
779class views_handler_sort_formula extends views_handler_sort {
780 /**
781 * Constructor to take the formula this sorts on.
013538bb 782 */
725bd2c9
EM
783 function construct() {
784 $this->formula = $this->definition['formula'];
785 if (is_array($this->formula) && !isset($this->formula['default'])) {
786 $this->error = t('views_handler_sort_formula missing default: @formula', array('@formula' => var_export($this->formula, TRUE)));
013538bb 787 }
725bd2c9 788 parent::construct();
013538bb
EM
789 }
790 /**
791 * Called to add the sort to a query.
792 */
793 function query() {
794 if (is_array($this->formula)) {
795 global $db_type;
796 if (isset($this->formula[$db_type])) {
797 $formula = $this->formula[$db_type];
798 }
799 else {
800 $formula = $this->formula['default'];
801 }
802 }
803 else {
804 $formula = $this->formula;
805 }
cffc1056 806 $this->ensure_my_table();
013538bb 807 // Add the field.
01c6ed1e 808 $this->query->add_orderby(NULL, $formula, $this->options['order'], $this->table_alias . '_' . $this->field);
013538bb
EM
809 }
810}
811
01c6ed1e
EM
812class views_handler_sort_date extends views_handler_sort {
813 function options(&$options) {
814 parent::options($options);
815 $options['granularity'] = 'second';
816 }
817
818 function options_form(&$form, &$form_state) {
819 parent::options_form($form, $form_state);
820
821 $form['granularity'] = array(
822 '#type' => 'radios',
823 '#title' => t('Granularity'),
824 '#options' => array(
825 'second' => t('Second'),
826 'minute' => t('Minute'),
827 'hour' => t('Hour'),
828 'day' => t('Day'),
829 'month' => t('Month'),
830 'year' => t('Year'),
831 ),
832 '#description' => t('The granularity is the smallest unit to use when determining whether two dates are the same; for example, if the granularity is "Year" then all dates in 1999, regardless of when they fall in 1999, will be considered the same date.'),
833 '#default_value' => $this->options['granularity'],
834 );
835 }
836
837 /**
838 * Called to add the sort to a query.
839 */
840 function query() {
841 $timezone = views_get_timezone();
7c9d559e 842 $timezone = ($timezone) ? sprintf('%+d', $timezone) : '';
01c6ed1e
EM
843
844 $this->ensure_my_table();
845 switch($this->options['granularity']) {
846 case 'second':
847 default:
848 $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']);
849 return;
850 case 'minute':
851 $formula = "DATE_FORMAT(FROM_UNIXTIME($this->table_alias.$this->real_field), '%Y%m%%d%H%i')";
852 break;
853 case 'hour':
854 $formula = "DATE_FORMAT(FROM_UNIXTIME($this->table_alias.$this->real_field), '%Y%m%%d%H')";
855 break;
856 case 'day':
7c9d559e 857 $formula = "DATE_FORMAT(FROM_UNIXTIME($this->table_alias.$this->real_field $timezone), '%Y%m%%d')";
01c6ed1e
EM
858 break;
859 case 'month':
7c9d559e 860 $formula = "DATE_FORMAT(FROM_UNIXTIME($this->table_alias.$this->real_field $timezone), '%Y%m')";
01c6ed1e
EM
861 break;
862 case 'year':
7c9d559e 863 $formula = "DATE_FORMAT(FROM_UNIXTIME($this->table_alias.$this->real_field $timezone), '%Y')";
01c6ed1e
EM
864 break;
865 }
866
867 // Add the field.
868 $this->query->add_orderby(NULL, $formula, $this->options['order'], $this->table_alias . '_' . $this->field . '_' . $this->options['granularity']);
869 }
870}
871
872
013538bb
EM
873/**
874 * @}
875 */
876
877/**
878 * @defgroup views_filter_handlers Views' filter handlers
879 * @{
880 * Handlers to tell Views how to filter queries.
881 */
882
883/**
884 * Base class for filters.
885 */
886class views_handler_filter extends views_handler {
887 /**
07bd18b0 888 * Provide some extra help to get the operator/value easier to use.
bb770a1b
EM
889 *
890 * This likely has to be overridden by filters which are more complex
891 * than simple operator/value.
07bd18b0
EM
892 */
893 function init(&$view, $options) {
894 parent::init($view, $options);
895
6c88512c
EM
896 $this->operator = $options['operator'];
897 $this->value = $options['value'];
07bd18b0
EM
898 }
899
900 /**
3a69a04c
EM
901 * Provide a simple default initializer -- should be overridden.
902 */
1008b7ff
EM
903 function options(&$options) {
904 parent::options($options);
07bd18b0
EM
905 $options['operator'] = '=';
906 $options['value'] = '';
1008b7ff 907 $options['group'] = 0;
bb770a1b
EM
908 $options['exposed'] = FALSE;
909 $options['expose'] = array(
910 'operator' => FALSE,
911 'label' => '',
912 );
3a69a04c
EM
913 }
914
07bd18b0
EM
915 /**
916 * Display the filter on the administrative summary
917 */
3a69a04c 918 function admin_summary() {
07bd18b0 919 return check_plain($this->operator) . ' ' . check_plain($this->value);
3a69a04c
EM
920 }
921
922 /**
923 * Provide the basic form which calls through to subforms.
bb770a1b
EM
924 * If overridden, it is best to call through to the parent,
925 * or to at least make sure all of the functions in this form
926 * are called.
3a69a04c
EM
927 */
928 function options_form(&$form, &$form_state) {
bb770a1b
EM
929 $this->show_expose_button($form, $form_state);
930 $this->show_operator_form($form, $form_state);
931 $this->show_value_form($form, $form_state);
932 $this->show_expose_form($form, $form_state);
3a69a04c
EM
933 }
934
935 /**
936 * Simple validate handler
937 */
938 function options_validate(&$form, &$form_state) {
bb770a1b
EM
939 $this->operator_validate($form, $form_state);
940 $this->value_validate($form, $form_state);
941 if (!empty($this->options['exposed'])) {
942 $this->expose_validate($form, $form_state);
943 }
944
3a69a04c
EM
945 }
946
947 /**
948 * Simple submit handler
949 */
950 function options_submit(&$form, &$form_state) {
bb770a1b
EM
951 $this->operator_submit($form, $form_state);
952 $this->value_submit($form, $form_state);
953 if (!empty($this->options['exposed'])) {
954 $this->expose_submit($form, $form_state);
955 }
956 }
957
958 /**
959 * Shortcut to display the operator form.
960 */
961 function show_operator_form(&$form, &$form_state) {
962 $this->operator_form($form, $form_state);
963 $form['operator']['#prefix'] = '<div class="views-left-30">';
964 $form['operator']['#suffix'] = '</div>';
3a69a04c
EM
965 }
966
967 /**
013538bb 968 * Provide a form for setting the operator.
07bd18b0 969 *
52208794 970 * This may be overridden by child classes, and it must
07bd18b0 971 * define $form['operator'];
013538bb 972 */
52208794
EM
973 function operator_form(&$form, &$form_state) {
974 $options = $this->operator_options();
975 $form['operator'] = array(
976 '#type' => count($options) < 10 ? 'radios' : 'select',
977 '#title' => t('Operator'),
978 '#default_value' => $this->operator,
979 '#options' => $options,
980 );
981 }
982
983 /**
984 * Provide a list of options for the default operator form.
985 * Should be overridden by classes that don't override operator_form
986 */
987 function operator_options() { return array(); }
86ed07d8 988
013538bb
EM
989 /**
990 * Validate the operator form.
991 */
992 function operator_validate($form, &$form_state) { }
993
994 /**
995 * Perform any necessary changes to the form values prior to storage.
996 * There is no need for this function to actually store the data.
997 */
998 function operator_submit($form, &$form_state) { }
999
1000 /**
bb770a1b
EM
1001 * Shortcut to display the value form.
1002 */
1003 function show_value_form(&$form, &$form_state) {
1004 $this->value_form($form, $form_state);
52208794 1005 $form['value']['#prefix'] = '<div class="views-left-60">';
bb770a1b
EM
1006 $form['value']['#suffix'] = '</div>';
1007 }
1008
1009 /**
013538bb 1010 * Provide a form for setting options.
07bd18b0
EM
1011 *
1012 * This should be overridden by all child classes and it must
1013 * define $form['value']
013538bb 1014 */
07bd18b0 1015 function value_form(&$form, &$form_state) { $form['value'] = array(); }
86ed07d8 1016
013538bb
EM
1017 /**
1018 * Validate the options form.
1019 */
1020 function value_validate($form, &$form_state) { }
1021
1022 /**
1023 * Perform any necessary changes to the form values prior to storage.
1024 * There is no need for this function to actually store the data.
1025 */
1026 function value_submit($form, &$form_state) { }
1027
1028 /**
bb770a1b
EM
1029 * Shortcut to display the expose/hide button.
1030 */
1031 function show_expose_button(&$form, &$form_state) {
1032 $form['expose_button'] = array(
1033 '#prefix' => '<div class="views-expose clear-block">',
1034 '#suffix' => '</div>',
1035 );
1036 if (empty($this->options['exposed'])) {
1037 $form['expose_button']['button'] = array(
1038 '#type' => 'submit',
1039 '#value' => t('Expose'),
1040 '#submit' => array('views_ui_config_item_form_expose'),
1041 );
1042 $form['expose_button']['markup'] = array(
1043 '#prefix' => '<div class="description">',
1044 '#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.'),
1045 '#suffix' => '</div>',
1046 );
1047 }
1048 else {
1049 $form['expose_button']['button'] = array(
1050 '#type' => 'submit',
1051 '#value' => t('Hide'),
1052 '#submit' => array('views_ui_config_item_form_expose'),
1053 );
1054 $form['expose_button']['markup'] = array(
1055 '#prefix' => '<div class="description">',
1056 '#value' => t('This item is currently exposed. If you <strong>hide</strong> it, users will not able to change the filter as they view it.'),
1057 '#suffix' => '</div>',
1058 );
1059 }
1060 }
1061
1062 /**
1063 * Shortcut to display the exposed options form.
1064 */
1065 function show_expose_form(&$form, &$form_state) {
1066 if (empty($this->options['exposed'])) {
1067 return;
1068 }
1069
1070 $form['expose'] = array(
1071 '#prefix' => '<div class="views-expose-options clear">',
1072 '#suffix' => '</div>',
1073 );
1074 $this->expose_form($form, $form_state);
1075 }
1076
1077 /**
1078 * Overridable form for exposed filter options.
1079 *
1080 * If overridden, it is best to call the parent or re-implement
1081 * the stuff here.
1082 *
1083 * Many filters will need to override this in order to provide options
1084 * that are nicely tailored to the given filter.
1085 */
1086 function expose_form(&$form, &$form_state) {
6c88512c
EM
1087 // @todo we should break this up into two functions to make it easier
1088 // for child objects to put options in the left or right side without
1089 // having to override this whole thing.
d3887131
EM
1090 $form['expose']['start_left'] = array(
1091 '#value' => '<div class="views-left-50">',
bb770a1b 1092 );
d3887131
EM
1093
1094 if (!empty($form['operator']['#type'])) {
1095 $form['expose']['operator'] = array(
1096 '#type' => 'textfield',
1097 '#default_value' => $this->options['expose']['operator'],
1098 '#title' => t('Operator identifier'),
1099 '#size' => 40,
1100 '#description' => t('This will appear in the URL after the ? to identify this operator. Leave blank to not expose the operator.'),
1101 );
1102 }
1103 else {
1104 $form['expose']['operator'] = array(
1105 '#type' => 'value',
1106 '#value' => '',
1107 );
1108 }
bb770a1b
EM
1109 $form['expose']['identifier'] = array(
1110 '#type' => 'textfield',
1111 '#default_value' => $this->options['expose']['identifier'],
1112 '#title' => t('Filter identifier'),
d3887131 1113 '#size' => 40,
bb770a1b
EM
1114 '#description' => t('This will appear in the URL after the ? to identify this filter. Cannot be blank.'),
1115 );
1116 $form['expose']['label'] = array(
1117 '#type' => 'textfield',
1118 '#default_value' => $this->options['expose']['label'],
1119 '#title' => t('Label'),
d3887131
EM
1120 '#size' => 40,
1121 );
1122
1123 $form['expose']['end_left'] = array(
1124 '#value' => '</div>',
1125 );
1126
1127 $form['expose']['start_checkboxes'] = array(
1128 '#value' => '<div class="form-checkboxes views-left-40 clear-block">',
1129 );
1130 $form['expose']['optional'] = array(
1131 '#type' => 'checkbox',
1132 '#title' => t('Optional'),
1133 '#description' => t('This exposed filter is optional and will have added options to allow it not to be set.'),
1134 '#default_value' => $this->options['expose']['optional'],
1135 );
1136 if (empty($this->no_single)) {
1137 $form['expose']['single'] = array(
1138 '#type' => 'checkbox',
1139 '#title' => t('Force single'),
1140 '#description' => t('Force this exposed filter to accept only one option.'),
1141 '#default_value' => $this->options['expose']['single'],
1142 );
1143 }
1144 $form['expose']['remember'] = array(
1145 '#type' => 'checkbox',
1146 '#title' => t('Remember'),
1147 '#description' => t('Remember the last setting the user gave this filter.'),
1148 '#default_value' => $this->options['expose']['remember'],
1149 );
1150 $form['expose']['end_checkboxes'] = array(
1151 '#value' => '</div>',
bb770a1b
EM
1152 );
1153 }
1154
1155 /**
1156 * Validate the options form.
1157 */
1158 function expose_validate($form, &$form_state) {
1159 if (empty($this->options['expose']['identifier'])) {
1160 if (empty($form_state['values']['options']['expose']['identifier'])) {
1161 form_error($form['expose']['identifier'], t('The identifier is required if the filter is
1162 exposed.'));
1163 }
1164 }
1165 }
1166
1167 /**
1168 * Perform any necessary changes to the form exposes prior to storage.
1169 * There is no need for this function to actually store the data.
1170 */
1171 function expose_submit($form, &$form_state) { }
1172
d3887131
EM
1173 function expose_options() {
1174 $this->options['expose'] = array(
1175 'operator' => $this->options['id'] . '_oper',
1176 'identifier' => $this->options['id'],
4191b1df 1177 'label' => $this->ui_name(),
d3887131
EM
1178 'remember' => FALSE,
1179 'single' => TRUE,
1180 'optional' => TRUE,
1181 );
1182 }
bb770a1b 1183 /**
d3887131
EM
1184 * Render our chunk of the exposed filter form when selecting
1185 *
1186 * You can override this if it doesn't do what you expect.
bb770a1b
EM
1187 */
1188 function exposed_form(&$form, &$form_state) {
1189 if (empty($this->options['exposed'])) {
1190 return;
1191 }
1192
1193 if (!empty($this->options['expose']['operator'])) {
d3887131 1194 $operator = $this->options['expose']['operator'];
bb770a1b 1195 $this->operator_form($form, $form_state);
d3887131
EM
1196 $form[$operator] = $form['operator'];
1197
1198 if (isset($form[$operator]['#title'])) {
1199 unset($form[$operator]['#title']);
1200 }
1201
1202 $this->exposed_translate($form[$operator], 'operator');
1203
bb770a1b
EM
1204 unset($form['operator']);
1205 }
1206
1207 if (!empty($this->options['expose']['identifier'])) {
d3887131 1208 $value = $this->options['expose']['identifier'];
bb770a1b 1209 $this->value_form($form, $form_state);
d3887131
EM
1210 $form[$value] = $form['value'];
1211
1212 if (isset($form[$value]['#title']) && !empty($form[$value]['#type']) && $form[$value]['#type'] != 'checkbox') {
1213 unset($form[$value]['#title']);
1214 }
1215
1216 $this->exposed_translate($form[$value], 'value');
1217
1218 if (!empty($form['#type']) && ($form['#type'] == 'checkboxes' || ($form['#type'] == 'select' && !empty($form['#multiple'])))) {
1219 unset($form[$value]['#default_value']);
1220 }
1221
1222 if (!empty($form['#type']) && $form['#type'] == 'select' && empty($form['#multiple'])) {
1223 $form[$value]['#default_value'] = 'All';
1224 }
1225
bb770a1b
EM
1226 unset($form['value']);
1227 }
bb770a1b
EM
1228 }
1229
1230 /**
d3887131
EM
1231 * Make some translations to a form item to make it more suitable to
1232 * exposing.
1233 */
1234 function exposed_translate(&$form, $type) {
1235 if (!isset($form['#type'])) {
1236 return;
1237 }
1238
1239 if ($form['#type'] == 'radios') {
1240 $form['#type'] = 'select';
1241 }
1242 if ($form['#type'] == 'checkboxes' && !empty($this->options['expose']['single'])) {
1243 $form['#type'] = 'select';
1244 }
1245 if (!empty($this->options['expose']['single']) && isset($form['#multiple'])) {
1246 unset($form['#multiple']);
1247 }
1248
1249 if ($type == 'value' && !empty($this->options['expose']['optional']) && $form['#type'] == 'select' && empty($form['#multiple'])) {
1250 $form['#options'] = array('All' => t('<Any>')) + $form['#options'];
1251 $form['#default_value'] = 'All';
1252 }
1253 }
1254
1255 /**
1256 * Tell the renderer about our exposed form. This only needs to be
1257 * overridden for particularly complex forms. And maybe not even then.
1258 */
1259 function exposed_info() {
1260 if (empty($this->options['exposed'])) {
1261 return;
1262 }
1263
1264 return array(
1265 'operator' => $this->options['expose']['operator'],
1266 'value' => $this->options['expose']['identifier'],
1267 'label' => $this->options['expose']['label'],
1268 );
1269 }
1270
1271 /**
bb770a1b
EM
1272 * Check to see if input from the exposed filters should change
1273 * the behavior if this filter.
1274 */
1275 function accept_exposed_input($input) {
1276 if (empty($this->options['exposed'])) {
4c3f2152 1277 return TRUE;
bb770a1b
EM
1278 }
1279
d3887131 1280 if (!empty($this->options['expose']['operator']) && isset($input[$this->options['expose']['operator']])) {
bb770a1b 1281 $this->operator = $input[$this->options['expose']['operator']];
001ff7f4
EM
1282 if ($this->options['expose']['remember']) {
1283 $_SESSION['views'][$this->view->name][$this->view->current_display][$this->options['expose']['operator']] = $input[$this->options['expose']['operator']];
1284 }
bb770a1b
EM
1285 }
1286
d3887131
EM
1287 if (!empty($this->options['expose']['identifier'])) {
1288 $value = $input[$this->options['expose']['identifier']];
001ff7f4
EM
1289 if ($this->options['expose']['remember']) {
1290 $_SESSION['views'][$this->view->name][$this->view->current_display][$this->options['expose']['identifier']] = $value;
1291 }
1292
d3887131
EM
1293 // Various ways to check for the absence of optional input.
1294 if (!empty($this->options['expose']['optional'])) {
1295 if ($value == 'All' || $value === array()) {
1296 return FALSE;
1297 }
1298
1299 if (!empty($this->no_single) && $value === '') {
1300 return FALSE;
1301 }
1302 }
1303
1304
1305 if (isset($value)) {
1306 $this->value = $value;
1307 }
1308 else {
1309 return FALSE;
1310 }
bb770a1b 1311 }
d3887131
EM
1312
1313 return TRUE;
bb770a1b
EM
1314 }
1315
1316 /**
013538bb 1317 * Add this filter to the query.
3a69a04c
EM
1318 *
1319 * Due to the nature of fapi, the value and the operator have an unintended
07bd18b0
EM
1320 * level of indirection. You will find them in $this->operator
1321 * and $this->value respectively.
013538bb
EM
1322 */
1323 function query() {
cffc1056 1324 $this->ensure_my_table();
07bd18b0 1325 $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . $this->operator . " '%s'", $this->value);
3a69a04c
EM
1326 }
1327}
1328
1329/**
1330 * Simple filter to handle equal to / not equal to filters
1331 */
1332class views_handler_filter_equality extends views_handler_filter {
d3887131
EM
1333 // exposed filter options
1334 var $no_single = TRUE;
1335
3a69a04c 1336 /**
5f1c9af8
EM
1337 * Provide basic defaults for the equality operator
1338 */
1008b7ff
EM
1339 function options(&$options) {
1340 parent::options($options);
07bd18b0
EM
1341 $options['operator'] = '=';
1342 $options['value'] = '';
5f1c9af8
EM
1343 }
1344
1345 /**
3a69a04c
EM
1346 * Provide simple equality operator
1347 */
52208794
EM
1348 function operator_options() {
1349 return array(
1350 '=' => t('Is equal to'),
1351 '!=' => t('Is not equal to'),
1352 );
1353 }
1354
1355 /**
1356 * Provide a simple textfield for equality
1357 */
1358 function value_form(&$form, &$form_state) {
1359 $form['value'] = array(
1360 '#type' => 'textfield',
1361 '#title' => t('Value'),
1362 '#size' => 30,
1363 '#default_value' => $this->value,
1364 );
1365 }
1366}
1367
1368/**
1369 * Basic textfield filter to handle string filtering commands
1370 * including equality, like, not like, etc.
1371 */
1372class views_handler_filter_string extends views_handler_filter {
1373 // exposed filter options
1374 var $no_single = TRUE;
1375
1376 /**
1377 * Provide basic defaults for the equality operator
1378 */
1379 function options(&$options) {
1380 parent::options($options);
1381 $options['operator'] = '=';
1382 $options['value'] = '';
1383 $options['case'] = TRUE;
1384 }
1385
1386 /**
1387 * This kind of construct makes it relatively easy for a child class
1388 * to add or remove functionality by overriding this function and
1389 * adding/removing items from this array.
1390 */
6c88512c 1391 function operators() {
2368b68d 1392 $operators = array(
52208794
EM
1393 '=' => array(
1394 'title' => t('Is equal to'),
1395 'short' => t('='),
1396 'method' => 'op_equal',
46e6f196 1397 'values' => 1,
52208794
EM
1398 ),
1399 '!=' => array(
1400 'title' => t('Is not equal to'),
1401 'short' => t('='),
1402 'method' => 'op_equal',
46e6f196 1403 'values' => 1,
52208794
EM
1404 ),
1405 'contains' => array(
1406 'title' => t('Contains'),
1407 'short' => t('contains'),
1408 'method' => 'op_contains',
46e6f196 1409 'values' => 1,
52208794
EM
1410 ),
1411 'word' => array(
1412 'title' => t('Contains any word'),
1413 'short' => t('has word'),
1414 'method' => 'op_word',
46e6f196 1415 'values' => 1,
52208794
EM
1416 ),
1417 'allwords' => array(
1418 'title' => t('Contains all words'),
1419 'short' => t('has all'),
1420 'method' => 'op_word',
46e6f196 1421 'values' => 1,
52208794
EM
1422 ),
1423 'starts' => array(
1424 'title' => t('Starts with'),
1425 'short' => t('begins'),
1426 'method' => 'op_starts',
46e6f196 1427 'values' => 1,
52208794
EM
1428 ),
1429 'ends' => array(
1430 'title' => t('Ends with'),
1431 'short' => t('ends'),
1432 'method' => 'op_ends',
46e6f196 1433 'values' => 1,
52208794
EM
1434 ),
1435 'not' => array(
1436 'title' => t('Does not contain'),
1437 'short' => t('!has'),
1438 'method' => 'op_not',
46e6f196 1439 'values' => 1,
3a69a04c
EM
1440 ),
1441 );
2368b68d
EM
1442 // if the definition allows for the empty operator, add it.
1443 if (!empty($this->definition['allow empty'])) {
1444 $operators += array(
1445 'empty' => array(
1446 'title' => t('Is empty (NULL)'),
1447 'method' => 'op_empty',
1448 'short' => t('empty'),
1449 'values' => 0,
1450 ),
1451 'not empty' => array(
1452 'title' => t('Is not empty (NULL)'),
1453 'method' => 'op_empty',
1454 'short' => t('not empty'),
1455 'values' => 0,
1456 ),
1457 );
1458 }
1459
1460 return $operators;
3a69a04c
EM
1461 }
1462
725bd2c9 1463 /**
6c88512c 1464 * Build strings from the operators() for 'select' options
52208794 1465 */
6c88512c 1466 function operator_options($which = 'title') {
52208794 1467 $options = array();
6c88512c 1468 foreach ($this->operators() as $id => $info) {
52208794
EM
1469 $options[$id] = $info[$which];
1470 }
1471
1472 return $options;
1473 }
1474
52208794 1475 function admin_summary() {
6c88512c 1476 $options = $this->operator_options('short');
52208794
EM
1477 return (!empty($this->options['exposed']) ? t('exposed<br />') : '') . $options[$this->operator];
1478 }
1479
1480 function options_form(&$form, &$form_state) {
1481 parent::options_form($form, $form_state);
1482 $form['case'] = array(
1483 '#type' => 'checkbox',
1484 '#title' => t('Case sensitive'),
1485 '#default_value' => $this->options['case'],
1486 '#description' => t('Case sensitive filters may be faster; MySQL might ignore case sensitivity.'),
1487 );
1488 }
1489
67d330e5
EM
1490 function operator_values($values = 1) {
1491 $options = array();
1492 foreach ($this->operators() as $id => $info) {
1493 if (isset($info['values']) && $info['values'] == $values) {
1494 $options[] = $id;
1495 }
1496 }
1497
1498 return $options;
1499 }
1500
52208794 1501 /**
725bd2c9
EM
1502 * Provide a simple textfield for equality
1503 */
3a69a04c 1504 function value_form(&$form, &$form_state) {
67d330e5
EM
1505 // We have to make some choices when creating this as an exposed
1506 // filter form. For example, if the operator is locked and thus
1507 // not rendered, we can't render dependencies; instead we only
1508 // render the form items we need.
1509 $which = 'all';
1510 $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
1511 if (!empty($form_state['exposed'])) {
1512 if (empty($this->options['expose']['operator'])) {
1513 // exposed and locked.
1514 $which = in_array($this->operator, $this->operator_values(1)) ? 'value' : 'none';
1515 }
1516 else {
1517 $source = 'edit-' . form_clean_id($this->options['expose']['operator']);
1518 }
1519 }
1520
1521 if ($which == 'all' || $which == 'value') {
1522 $form['value'] = array(
1523 '#type' => 'textfield',
1524 '#title' => t('Value'),
1525 '#size' => 30,
1526 '#default_value' => $this->value,
1527 );
1528 if ($which == 'all') {
1529 $form['value'] += array(
1530 '#process' => array('views_process_dependency'),
1531 '#dependency' => array($source => $this->operator_values(1)),
1532 );
1533 }
1534 }
013538bb 1535 }
52208794
EM
1536
1537 function case_transform() {
1538 return empty($this->options['case']) ? '' : 'UPPER';
1539 }
1540
1541 /**
1542 * Add this filter to the query.
1543 *
1544 * Due to the nature of fapi, the value and the operator have an unintended
1545 * level of indirection. You will find them in $this->operator
1546 * and $this->value respectively.
1547 */
1548 function query() {
1549 $this->ensure_my_table();
1550 $field = "$this->table_alias.$this->real_field";
1551 $upper = $this->case_transform();
1552
6c88512c 1553 $info = $this->operators();
52208794
EM
1554 if (!empty($info[$this->operator]['method'])) {
1555 $this->{$info[$this->operator]['method']}($field, $upper);
1556 }
1557 }
1558
1559 function op_equal($field, $upper) {
1560 // operator is either = or !=
1561 $this->query->add_where($this->options['group'], "$upper(%s) $this->operator $upper('%s')", $field, $this->value);
1562 }
1563
1564 function op_contains($field, $upper) {
1565 $this->query->add_where($this->options['group'], "$upper(%s) LIKE $upper('%%%s%%')", $field, $this->value);
1566 }
1567
1568 function op_word($field, $upper) {
1569 preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' '. $this->value, $matches, PREG_SET_ORDER);
1570 foreach ($matches as $match) {
1571 $phrase = false;
1572 // Strip off phrase quotes
1573 if ($match[2]{0} == '"') {
1574 $match[2] = substr($match[2], 1, -1);
1575 $phrase = true;
1576 }
1577 $words = trim($match[2], ',?!();:-');
1578 $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
1579 foreach ($words as $word) {
1580 $where[] = "$upper(%s) LIKE $upper('%%%s%%')";
1581 $values[] = $field;
1582 $values[] = trim($word, " ,!?");
1583 }
1584 }
1585 if ($this->operator == 'word') {
1586 $where = '('. implode(' OR ', $where) .')';
1587 }
1588 else {
1589 $where = implode(' AND ', $where);
1590 }
1591 // previously this was a call_user_func_array but that's unnecessary
1592 // as views will unpack an array that is a single arg.
1593 $this->query->add_where($this->options['group'], $where, $values);
1594 }
1595
1596 function op_starts($field, $upper) {
1597 $this->query->add_where($this->options['group'], "$upper(%s) LIKE $upper('%s%%')", $field, $this->value);
1598 }
1599
1600 function op_ends($field, $upper) {
1601 $this->query->add_where($this->options['group'], "$upper(%s) LIKE $upper('%%%s')", $field, $this->value);
1602 }
1603
1604 function op_not($field, $upper) {
1605 $this->query->add_where($this->options['group'], "$upper(%s) NOT LIKE $upper('%%%s%%')", $field, $this->value);
1606 }
1607
2368b68d
EM
1608 function op_empty($field) {
1609 if ($this->operator == 'empty') {
1610 $operator = "IS NULL";
1611 }
1612 else {
1613 $operator = "IS NOT NULL";
1614 }
1615
1616 $this->query->add_where($this->options['group'], "$field $operator");
1617 }
1618
013538bb
EM
1619}
1620
52208794 1621
013538bb 1622/**
725bd2c9
EM
1623 * Simple filter to handle matching of boolean values
1624 */
1625class views_handler_filter_boolean_operator extends views_handler_filter {
d3887131
EM
1626 // exposed filter options
1627 var $no_single = TRUE;
1628
57e4427a 1629 function construct() {
725bd2c9
EM
1630 $this->value_value = t('True');
1631 if (isset($this->definition['label'])) {
1632 $this->value_value = $this->definition['label'];
1633 }
1634 parent::construct();
1635 }
c60d618c 1636
1008b7ff
EM
1637 function options(&$options) {
1638 parent::options($options);
d3887131 1639 $options['value'] = FALSE;
5f1c9af8
EM
1640 }
1641
725bd2c9 1642 function operator_form(&$form, &$form_state) {
d3887131 1643 $form['operator'] = array();
725bd2c9 1644 }
d3887131 1645
725bd2c9 1646 function value_form(&$form, &$form_state) {
d3887131
EM
1647 if (empty($this->options['exposed'])) {
1648 $form['value'] = array(
1649 '#type' => 'checkbox',
1650 '#title' => $this->value_value,
1651 '#default_value' => $this->value,
1652 );
1653 }
1654 else {
1655 $form['value'] = array(
1656 '#type' => 'select',
1657 '#title' => $this->value_value,
1658 '#options' => array(1 => t('Yes'), 0 => t('No')),
1659 '#default_value' => $this->value,
1660 );
1661 }
725bd2c9 1662 }
725bd2c9 1663 function admin_summary() {
d3887131
EM
1664 if (!empty($this->options['exposed'])) {
1665 return t('exposed');
1666 }
1667
1668 return (empty($this->value) ? t("False") : t('True'));
1669 }
1670
1671 function expose_options() {
1672 $this->options['expose'] = array(
1673 'operator' => '',
1674 'identifier' => $this->options['id'],
1675 'label' => $this->value_value,
1676 'remember' => FALSE,
1677 'single' => TRUE,
1678 'optional' => FALSE,
1679 );
725bd2c9
EM
1680 }
1681
1682 function query() {
57e4427a 1683 $this->ensure_my_table();
d3887131 1684 $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . (empty($this->value) ? '=' : '<>') . " 0");
725bd2c9
EM
1685 }
1686
1687}
1688
1689/**
1690 * Simple filter to handle matching of multiple options selectable via checkboxes
1691 */
1692class views_handler_filter_in_operator extends views_handler_filter {
1693 function construct() {
1694 parent::construct();
1695 $this->value_title = t('Options');
1696 $this->value_options = array(t('Yes'), t('No'));
1697 }
1698
1008b7ff
EM
1699 function options(&$options) {
1700 parent::options($options);
07bd18b0
EM
1701 $options['operator'] = 'in';
1702 $options['value'] = 0;
5f1c9af8
EM
1703 }
1704
725bd2c9
EM
1705 /**
1706 * Provide inclusive/exclusive matching
1707 */
52208794
EM
1708 function operator_options() {
1709 return array(
1710 'in' => t('Is one of'),
1711 'not in' => t('Is not one of'),
725bd2c9
EM
1712 );
1713 }
1714
1715 function value_form(&$form, &$form_state) {
1716 $form['value'] = array(
1717 '#type' => 'checkboxes',
725bd2c9
EM
1718 '#title' => $this->value_title,
1719 '#options' => $this->value_options,
07bd18b0 1720 '#default_value' => (array) $this->value,
725bd2c9
EM
1721 );
1722 }
c60d618c 1723
725bd2c9
EM
1724 function value_submit($form, &$form_state) {
1725 // This is so deeply deeply deeply nested due to the way the form is layered.
07bd18b0 1726 $form_state['values']['options']['value'] = array_filter($form_state['values']['options']['value']);
725bd2c9 1727 }
c60d618c 1728
725bd2c9 1729 function admin_summary() {
d3887131
EM
1730 if (!empty($this->options['exposed'])) {
1731 return t('exposed');
1732 }
1733
07bd18b0 1734 if (count($this->value) == 1) {
ab848f62 1735 // If there is only one, show it as an =.
07bd18b0 1736 $keys = array_keys($this->value);
bb770a1b
EM
1737 $key = array_shift($keys);
1738 if (!empty($this->value_options[$key])) {
1739 $value = check_plain($this->value_options[$key]);
1740 }
1741 else {
1742 $value = t('Unknown');
1743 }
ab848f62 1744
07bd18b0 1745 return ($this->operator == 'in' ? '=' : '<>') . ' ' . $value;
ab848f62
EM
1746 }
1747 $output = '';
07bd18b0 1748 foreach ($this->value as $value) {
ab848f62
EM
1749 if ($output) {
1750 $output .= ', ';
1751 }
1752 if (strlen($output) > 8) {
1753 $output .= '...';
1754 break;
1755 }
1756 $output .= check_plain($this->value_options[$value]);
1757 }
1758
07bd18b0 1759 return check_plain($this->operator) . ' ' . $output;
725bd2c9 1760 }
c60d618c 1761
725bd2c9 1762 function query() {
d3887131
EM
1763 if (empty($this->value)) {
1764 return;
1765 }
725bd2c9 1766 $this->ensure_my_table();
07bd18b0 1767 $replace = array_fill(0, sizeof($this->value), "'%s'");
725bd2c9 1768 $in = ' (' . implode(", ", $replace) . ')';
07bd18b0 1769 $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . $this->operator . $in, $this->value);
725bd2c9
EM
1770 }
1771}
1772
1773/**
0a5243db
EM
1774 * Simple filter to handle greater than/less than filters
1775 */
1776class views_handler_filter_numeric extends views_handler_filter {
6c88512c 1777 var $no_single = TRUE;
0a5243db
EM
1778 /**
1779 * Provide basic defaults for the filter
1780 */
1781 function options(&$options) {
1782 parent::options($options);
1783 $options['operator'] = '=';
1784 $options['value']['min'] = '';
1785 $options['value']['max'] = '';
6c88512c
EM
1786 $options['value']['value'] = '';
1787 }
1788
1789 function operators() {
2368b68d 1790 $operators = array(
6c88512c
EM
1791 '<' => array(
1792 'title' => t('Is less than'),
1793 'method' => 'op_simple',
1794 'short' => t('<'),
1795 'values' => 1,
1796 ),
1797 '<=' => array(
1798 'title' => t('Is less than or equal to'),
1799 'method' => 'op_simple',
1800 'short' => t('<='),
1801 'values' => 1,
1802 ),
1803 '=' => array(
1804 'title' => t('Is equal to'),
1805 'method' => 'op_simple',
1806 'short' => t('='),
1807 'values' => 1,
1808 ),
1809 '!=' => array(
1810 'title' => t('Is not equal to'),
1811 'method' => 'op_simple',
1812 'short' => t('!='),
1813 'values' => 1,
1814 ),
1815 '>=' => array(
1816 'title' => t('Is greater than or equal to'),
1817 'method' => 'op_simple',
1818 'short' => t('>='),
1819 'values' => 1,
1820 ),
1821 '>' => array(
1822 'title' => t('Is greater than'),
1823 'method' => 'op_simple',
1824 'short' => t('>'),
1825 'values' => 1,
1826 ),
1827 'between' => array(
1828 'title' => t('Is between'),
1829 'method' => 'op_between',
1830 'short' => t('between'),
1831 'values' => 2,
1832 ),
1833 'not between' => array(
1834 'title' => t('Is not between'),
1835 'method' => 'op_between',
1836 'short' => t('not between'),
1837 'values' => 2,
1838 ),
1839 );
2368b68d
EM
1840
1841 // if the definition allows for the empty operator, add it.
1842 if (!empty($this->definition['allow empty'])) {
1843 $operators += array(
1844 'empty' => array(
1845 'title' => t('Is empty (NULL)'),
1846 'method' => 'op_empty',
1847 'short' => t('empty'),
1848 'values' => 0,
1849 ),
1850 'not empty' => array(
1851 'title' => t('Is not empty (NULL)'),
1852 'method' => 'op_empty',
1853 'short' => t('not empty'),
1854 'values' => 0,
1855 ),
1856 );
1857 }
1858
1859 return $operators;
0a5243db
EM
1860 }
1861
1862 /**
1863 * Provide a list of all the numeric operators
1864 */
6c88512c
EM
1865 function operator_options($which = 'title') {
1866 $options = array();
1867 foreach ($this->operators() as $id => $info) {
1868 $options[$id] = $info[$which];
1869 }
1870
1871 return $options;
0a5243db
EM
1872 }
1873
6c88512c
EM
1874 function operator_values($values = 1) {
1875 $options = array();
1876 foreach ($this->operators() as $id => $info) {
1877 if ($info['values'] == $values) {
1878 $options[] = $id;
1879 }
1880 }
1881
1882 return $options;
1883 }
0a5243db
EM
1884 /**
1885 * Provide a simple textfield for equality
1886 */
1887 function value_form(&$form, &$form_state) {
1888 $form['value']['#tree'] = TRUE;
6c88512c
EM
1889
1890 // We have to make some choices when creating this as an exposed
1891 // filter form. For example, if the operator is locked and thus
1892 // not rendered, we can't render dependencies; instead we only
1893 // render the form items we need.
1894 $which = 'all';
01c6ed1e
EM
1895 if (!empty($form['operator'])) {
1896 $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
1897 }
1898
6c88512c
EM
1899 if (!empty($form_state['exposed'])) {
1900 if (empty($this->options['expose']['operator'])) {
1901 // exposed and locked.
1902 $which = in_array($this->operator, $this->operator_values(2)) ? 'minmax' : 'value';
1903 }
1904 else {
1905 $source = 'edit-' . form_clean_id($this->options['expose']['operator']);
1906 }
1907 }
1908
1909 if ($which == 'all' || $which == 'value') {
1910 $form['value']['value'] = array(
1911 '#type' => 'textfield',
1912 '#title' => t('Value'),
1913 '#size' => 30,
1914 '#default_value' => $this->value['value'],
6c88512c
EM
1915 );
1916 if ($which == 'all') {
1917 $form['value']['value'] += array(
1918 '#process' => array('views_process_dependency'),
1919 '#dependency' => array($source => $this->operator_values(1)),
1920 );
1921 }
1922 }
1923
1924 if ($which == 'all' || $which == 'minmax') {
1925 $form['value']['min'] = array(
1926 '#type' => 'textfield',
4c3f2152 1927 '#title' => t('Min'),
6c88512c
EM
1928 '#size' => 30,
1929 '#default_value' => $this->value['min'],
6c88512c
EM
1930 );
1931 $form['value']['max'] = array(
1932 '#type' => 'textfield',
4c3f2152 1933 '#title' => t('And max'),
6c88512c
EM
1934 '#size' => 30,
1935 '#default_value' => $this->value['max'],
6c88512c
EM
1936 );
1937 if ($which == 'all') {
1938 $dependency = array(
1939 '#process' => array('views_process_dependency'),
1940 '#dependency' => array($source => $this->operator_values(2)),
1941 );
1942 $form['value']['min'] += $dependency;
1943 $form['value']['max'] += $dependency;
1944 }
1945 }
0a5243db
EM
1946 }
1947
0a5243db
EM
1948 function query() {
1949 $this->ensure_my_table();
6c88512c
EM
1950 $field = "$this->table_alias.$this->real_field";
1951
1952 $info = $this->operators();
1953 if (!empty($info[$this->operator]['method'])) {
1954 $this->{$info[$this->operator]['method']}($field);
0a5243db
EM
1955 }
1956 }
1957
6c88512c
EM
1958 function op_between($field) {
1959 if ($this->operator == 'between') {
1960 $a = $this->value['min'];
1961 $b = $this->value['max'];
1962 }
1963 else {
1964 $a = $this->value['max'];
1965 $b = $this->value['min'];
1966 }
1967
1968 $this->query->add_where($this->options['group'], "$field >= %d", $a);
1969 $this->query->add_where($this->options['group'], "$field <= %d", $b);
1970 }
1971
1972 function op_simple($field) {
1973 $this->query->add_where($this->options['group'], "$field $this->operator %d", $this->value['value']);
1974 }
1975
2368b68d
EM
1976 function op_empty($field) {
1977 if ($this->operator == 'empty') {
1978 $operator = "IS NULL";
1979 }
1980 else {
1981 $operator = "IS NOT NULL";
1982 }
1983
1984 $this->query->add_where($this->options['group'], "$field $operator");
1985 }
1986
0a5243db 1987 function admin_summary() {
6c88512c
EM
1988 $output = check_plain($this->operator) . ' ';
1989 if (in_array($this->operator, $this->operator_values(2))) {
1990 $output .= t('@min and @max', array('@min' => $this->value['min'], '@max' => $this->value['max']));
1991 }
1992 else {
1993 $output .= check_plain($this->value['value']);
0a5243db
EM
1994 }
1995 return $output;
1996 }
1997}
1998
4c3f2152
EM
1999class views_handler_filter_date extends views_handler_filter_numeric {
2000 function options(&$options) {
2001 parent::options($options);
2002 $options['value']['type'] = 'date';
2003 }
2004
2005 /**
2006 * Add a type selector to the value form
2007 */
2008 function value_form(&$form, &$form_state) {
2009 $form['value']['type'] = array(
2010 '#type' => 'radios',
2011 '#title' => t('Value type'),
2012 '#options' => array(
2013 'date' => t('A date in any machine readable format. CCYY-MM-DD HH:MM:SS is preferred.'),
2014 'offset' => t('An offset from the current time such as "+1 day" or "-2 hours and 30 minutes"'),
2015 ),
2016 '#default_value' => $this->value['type'],
2017 );
2018 parent::value_form($form, $form_state);
2019 }
2020
2021 function options_validate(&$form, &$form_state) {
2022 parent::options_validate($form, $form_state);
2023 $operators = $this->operators();
2024 if ($operators[$form_state['values']['options']['operator']]['values'] == 1) {
2025 $convert = strtotime($form_state['values']['options']['value']['value']);
2026 if ($convert == -1 || $convert === FALSE) {
2027 form_error($form['value']['value'], t('Invalid date format.'));
2028 }
2029 }
2030 else {
2031 $min = strtotime($form_state['values']['options']['value']['min']);
2032 if ($min == -1 || $min === FALSE) {
2033 form_error($form['value']['min'], t('Invalid date format.'));
2034 }
2035 $max = strtotime($form_state['values']['options']['value']['max']);
2036 if ($max == -1 || $max === FALSE) {
2037 form_error($form['value']['max'], t('Invalid date format.'));
2038 }
2039 }
2040 }
2041
2042 function op_between($field) {
2043 if ($this->operator == 'between') {
2044 $a = strtotime($this->value['min'], 0);
2045 $b = strtotime($this->value['max'], 0);
2046 }
2047 else {
2048 $a = strtotime($this->value['max'], 0);
2049 $b = strtotime($this->value['min'], 0);
2050 }
2051
2052 if ($this->value['type'] == 'offset') {
2053 $a = '***CURRENT_TIME***' . sprintf('%+d', $a); // keep sign
2054 $b = '***CURRENT_TIME***' . sprintf('%+d', $b); // keep sign
2055 }
2056 // %s is safe here because strtotime scrubbed the input and we might
2057 // have a string if using offset.
2058 $this->query->add_where($this->options['group'], "$field >= %s", $a);
2059 $this->query->add_where($this->options['group'], "$field <= %s", $b);
2060 }
2061
2062 function op_simple($field) {
2063 $value = strtotime($this->value['value'], 0);
2064 if ($this->value['type'] == 'offset') {
2065 $value = '***CURRENT_TIME***' . sprintf('%+d', $value); // keep sign
2066 }
2067 $this->query->add_where($this->options['group'], "$field $this->operator %s", $value);
2068 }
2069}
2070
0a5243db 2071/**
013538bb
EM
2072 * @}
2073 */
2074
2075/**
2076 * @defgroup views_argument_handlers Handlers for arguments
2077 * @{
2078 */
2079/**
86ed07d8 2080 * Base class for arguments.
013538bb
EM
2081 *
2082 * The basic argument works for very simple arguments such as nid and uid
2083 */
2084class views_handler_argument extends views_handler {
0225248a 2085 var $name_field = NULL;
013538bb
EM
2086 /**
2087 * Constructor
2088 */
725bd2c9
EM
2089 function construct() {
2090 if (!empty($this->definition['name field'])) {
2091 $this->name_field = $this->definition['name field'];
2092 }
013538bb
EM
2093 }
2094
2095 /**
c60d618c
EM
2096 * Give an argument the opportunity to modify the breadcrumb, if it wants.
2097 * This only gets called on displays where a breadcrumb is actually used.
2098 *
2099 * The breadcrumb will be in the form of an array, with the keys being
2100 * the path and the value being the already sanitized title of the path.
2101 */
2102 function set_breadcrumb(&$breadcrumb) { }
2103
2104 /**
3a69a04c
EM
2105 * Provide defaults for the argument when a new one is created.
2106 */
1008b7ff
EM
2107 function options(&$options) {
2108 parent::options($options);
2109 $options['default_action'] = 'ignore';
2110 $options['style_plugin'] = 'default_summary';
2111 $options['style_options'] = array();
2112 $options['wildcard'] = 'all';
2113 $options['wildcard_substitution'] = t('All');
2114 $options['title'] = '';
3a69a04c
EM
2115 }
2116
2117 /**
2118 * Provide a default options form for the argument.
2119 */
2120 function options_form(&$form, &$form_state) {
2121 $defaults = $this->default_actions();
2122 foreach ($defaults as $id => $info) {
2123 $options[$id] = $info['title'];
2124 }
2125 $form['default_action'] = array(
2126 '#prefix' => '<div class="views-left-50">',
2127 '#suffix' => '</div>',
2128 '#type' => 'radios',
2129 '#title' => t('Action to take if argument is not present'),
2130 '#options' => $options,
1008b7ff 2131 '#default_value' => $this->options['default_action'],
3a69a04c
EM
2132 );
2133 $form['wildcard'] = array(
2134 '#prefix' => '<div class="views-left-40">',
2135 // prefix and no suffix means these two items will be grouped together.
2136 '#type' => 'textfield',
2137 '#title' => t('Wildcard'),
2138 '#size' => 20,
1008b7ff 2139 '#default_value' => $this->options['wildcard'],
3a69a04c
EM
2140 '#description' => t('If this value is received as an argument, the argument will be ignored; i.e, "all values"'),
2141 );
2142 $form['wildcard_substitution'] = array(
2143 '#suffix' => '</div>',
2144 '#type' => 'textfield',
2145 '#title' => t('Wildcard title'),
2146 '#size' => 20,
1008b7ff 2147 '#default_value' => $this->options['wildcard_substitution'],
3a69a04c
EM
2148 '#description' => t('The title to use for the wildcard in substitutions elsewhere.'),
2149 );
c60d618c 2150 $form['title'] = array(
fbb2a020
EM
2151 '#prefix' => '<div class="clear">',
2152 '#suffix' => '</div>',
c60d618c
EM
2153 '#type' => 'textfield',
2154 '#title' => t('Title'),
1008b7ff 2155 '#default_value' => $this->options['title'],
c60d618c
EM
2156 '#description' => t('The title to use when this argument is present; it will override the title of the view and titles from previous arguments.'),
2157 );
3a69a04c
EM
2158 }
2159
2160 /**
2161 * Provide a list of default behaviors for this argument if the argument
2162 * is not present.
2163 *
2164 * Override this method to provide additional (or fewer) default behaviors.
2165 */
2166 function default_actions($which = NULL) {
2167 $defaults = array(
2168 'ignore' => array(
2169 'title' => t('Display all values'),
2170 'method' => 'default_ignore',
c60d618c 2171 'breadcrumb' => TRUE, // generate a breadcrumb to here
3a69a04c
EM
2172 ),
2173 'not found' => array(
2174 'title' => t('Display page not found'),
2175 'method' => 'default_not_found',
2176 ),
2177 'empty' => array(
2178 'title' => t('Display empty text'),
2179 'method' => 'default_empty',
c60d618c 2180 'breadcrumb' => TRUE, // generate a breadcrumb to here
3a69a04c
EM
2181 ),
2182 'summary asc' => array(
2183 'title' => t('Summary, sorted ascending'),
2184 'method' => 'default_summary',
2185 'method args' => array('asc'),
2186 'style plugin' => TRUE,
c60d618c 2187 'breadcrumb' => TRUE, // generate a breadcrumb to here
3a69a04c
EM
2188 ),
2189 'summary desc' => array(
2190 'title' => t('Summary, sorted descending'),
2191 'method' => 'default_summary',
2192 'method args' => array('desc'),
2193 'style plugin' => TRUE,
c60d618c 2194 'breadcrumb' => TRUE, // generate a breadcrumb to here
3a69a04c
EM
2195 ),
2196 );
2197
2198 if ($which) {
2199 if (!empty($defaults[$which])) {
2200 return $defaults[$which];
2201 }
2202 }
2203 else {
2204 return $defaults;
2205 }
2206 }
2207
2208 /**
c60d618c
EM
2209 * Determine if the can generate a breadcrumb
2210 *
2211 * @return TRUE/FALSE
2212 */
2213 function uses_breadcrumb() {
1008b7ff 2214 $info = $this->default_actions($this->options['default_action']);
c60d618c
EM
2215 return !empty($info['breadcrumb']);
2216 }
2217
2218 /**
3a69a04c
EM
2219 * Determine if the argument needs a style plugin.
2220 *
2221 * @return TRUE/FALSE
2222 */
2223 function needs_style_plugin() {
1008b7ff 2224 $info = $this->default_actions($this->options['default_action']);
3a69a04c
EM
2225 return !empty($info['style plugin']);
2226 }
2227
2228 /**
2229 * Handle the default action, which means our argument wasn't present.
2230 *
2231 * Override this method only with extreme care.
2232 *
2233 * @return
2234 * A boolean value; if TRUE, continue building this view. If FALSE,
2235 * building the view will be aborted here.
2236 */
2237 function default_action() {
1008b7ff 2238 $info = $this->default_actions($this->options['default_action']);
3a69a04c
EM
2239 if (!$info) {
2240 return FALSE;
2241 }
2242
2243 if (!empty($info['method args'])) {
2244 return call_user_func_array(array($this, $info['method']), $info['method args']);
2245 }
2246 else {
d670ec30 2247 return $this->{$info['method']}();
3a69a04c
EM
2248 }
2249 }
2250
2251 /**
2252 * Default action: ignore.
2253 *
2254 * If an argument was expected and was not given, in this case, simply
2255 * ignore the argument entirely.
2256 */
2257 function default_ignore() {
2258 return TRUE;
2259 }
2260
2261 /**
2262 * Default action: not found.
2263 *
2264 * If an argument was expected and was not given, in this case, report
2265 * the view as 'not found' or hide it.
2266 */
2267 function default_not_found() {
2268 // Set a failure condition and let the display manager handle it.
2269 $this->view->build_info['fail'] = TRUE;
2270 return FALSE;
2271 }
2272
2273 /**
2274 * Default action: empty
2275 *
2276 * If an argument was expected and was not given, in this case, display
2277 * the view's empty text
2278 */
2279 function default_empty() {
2280 // We return with no query; this will force the empty text.
2281 $this->view->built = TRUE;
cbb3e649
EM
2282 $this->view->executed = TRUE;
2283 $this->view->result = array();
3a69a04c
EM
2284 return FALSE;
2285 }
2286
2287 /**
2288 * Default action: summary.
2289 *
2290 * If an argument was expected and was not given, in this case, display
2291 * a summary query.
2292 */
2293 function default_summary($order) {
2294 $this->view->build_info['summary'] = TRUE;
1008b7ff 2295 $this->view->build_info['summary_level'] = $this->options['id'];
3a69a04c
EM
2296
2297 // Change the display style to the summary style for this
2298 // argument.
1008b7ff
EM
2299 $this->view->style_plugin = $this->options['style_plugin'];
2300 $this->view->style_options = $this->options['style_options'];
3a69a04c
EM
2301
2302 // Clear out the normal primary field and whatever else may have
2303 // been added and let the summary do the work.
2304 $this->query->clear_fields();
2305 $this->summary_query();
2306
d69aa078 2307 $this->summary_sort($order);
3a69a04c
EM
2308
2309 // Summaries have their own sorting and fields, so tell the View not
2310 // to build these.
2311 $this->view->build_sort = $this->view->build_fields = FALSE;
2312 return TRUE;
2313 }
2314
2315 /**
013538bb
EM
2316 * Build the info for the summary query.
2317 *
2318 * This must:
2319 * - add_groupby: group on this field in order to create summaries.
2320 * - add_field: add a 'num_nodes' field for the count. Usually it will
2321 * be a count on $view->primary_field
2322 * - set_count_field: Reset the count field so we get the right paging.
2323 *
86ed07d8 2324 * @return
013538bb
EM
2325 * The alias used to get the number of records (count) for this entry.
2326 */
2327 function summary_query() {
cffc1056 2328 $this->ensure_my_table();
013538bb 2329 // Add the field.
86ed07d8
EM
2330 $this->base_alias = $this->query->add_field($this->table_alias, $this->real_field);
2331
013538bb
EM
2332 // Add the 'name' field. For example, if this is a uid argument, the
2333 // name field would be 'name' (i.e, the username).
2334 if (isset($this->name_field)) {
cffc1056 2335 $this->name_alias = $this->query->add_field($this->table_alias, $this->name_field);
013538bb
EM
2336 }
2337 else {
2338 $this->name_alias = $this->base_alias;
2339 }
86ed07d8 2340
cffc1056
EM
2341 return $this->summary_basics();
2342 }
013538bb 2343
cffc1056
EM
2344 /**
2345 * Some basic summary behavior that doesn't need to be repeated as much as
2346 * code that goes into summary_query()
2347 */
2348 function summary_basics($count_field = TRUE) {
013538bb 2349 // Add the number of nodes counter
5cad536a 2350 $count_alias = $this->query->add_field(NULL, 'COUNT('. $this->query->primary_table .'.'. $this->query->primary_field . ')', 'num_records');
013538bb
EM
2351 $this->query->add_groupby($this->base_alias);
2352
cffc1056
EM
2353 if ($count_field) {
2354 $this->query->set_count_field($this->table_alias, $this->real_field);
2355 }
013538bb 2356
86ed07d8 2357 $this->count_alias = $count_alias;
013538bb
EM
2358 }
2359
2360 /**
2361 * Sorts the summary based upon the user's selection. The base variant of
2362 * this is usually adequte.
86ed07d8 2363 *
013538bb
EM
2364 * @param $order
2365 * The order selected in the UI.
2366 */
2367 function summary_sort($order) {
d69aa078 2368 $this->query->add_orderby(NULL, NULL, $order, $this->base_alias);
013538bb
EM
2369 }
2370
2371 /**
2372 * Provides a link from the summary to the next level; this will be called
2373 * once per row of a summary.
2374 *
2375 * @param $data
2376 * The query results for the row.
2377 * @param $url
2378 * The base URL to use.
2379 */
2380 function summary_link($data, $url) {
cffc1056 2381 $value = $data->{$this->base_alias};
86ed07d8 2382 return url("$url/$value");
013538bb
EM
2383 }
2384
2385 /**
0225248a
EM
2386 * Provides the name to use for the summary. By default this is just
2387 * the name field.
2388 *
2389 * @param $data
2390 * The query results for the row.
2391 */
2392 function summary_name($data) {
2393 return check_plain($data->{$this->name_alias});
2394 }
013538bb
EM
2395
2396 /**
2397 * Set up the query for this argument.
2398 *
2399 * The argument sent may be found at $this->argument.
2400 */
2401 function query() {
cffc1056
EM
2402 $this->ensure_my_table();
2403 $this->query->add_where(0, "$this->table_alias.$this->real_field = '%s'", $this->argument);
013538bb
EM
2404 }
2405
2406 /**
2407 * Get the title this argument will assign the view, given the argument.
2408 *
2409 * This usually needs to be overridden to provide a proper title.
2410 */
cffc1056
EM
2411 function title() {
2412 return check_plain($this->argument);
013538bb 2413 }
b06399e3
EM
2414
2415 /**
2416 * Validate that this argument works. By default, all arguments are valid.
2417 */
2418 function validate($arg) {
2419 return TRUE;
2420 }
013538bb
EM
2421}
2422
2423/**
cffc1056
EM
2424 * Abstract argument handler for simple formulae.
2425 *
2426 * Child classes of this object should implement summary_link, at least.
013538bb 2427 */
cffc1056 2428class views_handler_argument_formula extends views_handler_argument {
725bd2c9 2429 var $formula = NULL;
cffc1056
EM
2430 /**
2431 * Constructor
2432 */
725bd2c9
EM
2433 function construct() {
2434 if (!empty($this->definition['formula'])) {
2435 $this->formula = $this->definition['formula'];
2436 }
cffc1056
EM
2437 }
2438
f9f3c621
EM
2439 function get_formula() {
2440 return str_replace('***table***', $this->table_alias, $this->formula);
2441 }
2442
cffc1056
EM
2443 /**
2444 * Build the summary query based on a formula
2445 */
2446 function summary_query() {
2447 $this->ensure_my_table();
f9f3c621
EM
2448 // Now that our table is secure, get our formula.
2449 $formula = $this->get_formula();
2450
cffc1056 2451 // Add the field.
f9f3c621
EM
2452 $this->base_alias = $this->name_alias = $this->query->add_field(NULL, $formula, $this->field);
2453 $this->query->set_count_field(NULL, $formula, $this->field);
86ed07d8 2454
cffc1056
EM
2455 return $this->summary_basics(FALSE);
2456 }
2457
2458 /**
2459 * Build the query based upon the formula
2460 */
2461 function query() {
2462 $this->ensure_my_table();
f9f3c621
EM
2463 // Now that our table is secure, get our formula.
2464 $formula = $this->get_formula();
2465
2466 $this->query->add_where(0, "$formula = '%s'", $this->argument);
cffc1056
EM
2467 }
2468}
2469
2470/**
b19d32fb
EM
2471 * Basic argument handler to implement string arguments that may have length
2472 * limits.
2473 */
2474class views_handler_argument_string extends views_handler_argument {
2475 function options(&$options) {
2476 parent::options($options);
2477 $options['glossary'] = FALSE;
2478 $options['limit'] = 0;
2479 $options['case'] = 'none';
2480 $options['path_case'] = 'none';
2481 $options['transform_dash'] = FALSE;
2482 }
2483
2484 function options_form(&$form, &$form_state) {
2485 parent::options_form($form, $form_state);
2486
2487 $form['glossary'] = array(
2488 '#type' => 'checkbox',
2489 '#title' => t('Glossary mode'),
2490 '#description' => t('Glossary mode applies a limit to the number of characters used in the argument, which allows the summary view to act as a glossary.'),
2491 '#default_value' => $this->options['glossary'],
2492 );
2493
2494 $form['limit'] = array(
2495 '#type' => 'textfield',
2496 '#title' => t('Character limit'),
2497 '#description' => t('How many characters of the argument to filter against. If set to 1, all fields starting with the letter in the argument would be matched.'),
2498 '#default_value' => $this->options['limit'],
2499 '#process' => array('views_process_dependency'),
2500 '#dependency' => array('edit-options-glossary' => array(TRUE)),
2501 );
2502
2503 $form['case'] = array(
2504 '#type' => 'select',
2505 '#title' => t('Case'),
2506 '#description' => t('When printing the argument result, how to transform the case.'),
2507 '#options' => array(
2508 'none' => t('No transform'),
2509 'upper' => t('Upper case'),
2510 'lower' => t('Lower case'),
2511 'ucfirst' => t('Capitalize first letter'),
2512 'ucwords' => t('Capitalize each word'),
2513 ),
2514 '#default_value' => $this->options['case'],
2515 );
2516
2517 $form['path_case'] = array(
2518 '#type' => 'select',
2519 '#title' => t('Case in path'),
2520 '#description' => t('When printing url paths, how to transform the of the argument. Do not use this unless with Postgres as it uses case sensitive comparisons.'),
2521 '#options' => array(
2522 'none' => t('No transform'),
2523 'upper' => t('Upper case'),
2524 'lower' => t('Lower case'),
2525 'ucfirst' => t('Capitalize first letter'),
2526 'ucwords' => t('Capitalize each word'),
2527 ),
2528 '#default_value' => $this->options['path_case'],
2529 );
2530
2531 $form['transform_dash'] = array(
2532 '#type' => 'checkbox',
2533 '#title' => t('Transform spaces to dashes in URL'),
2534 '#description' => t('Glossary mode applies a limit to the number of characters used in the argument, which allows the summary view to act as a glossary.'),
2535 '#default_value' => $this->options['transform_dash'],
2536 );
2537 }
2538
2539 /**
2540 * Build the summary query based on a formula
2541 */
2542 function summary_query() {
2543 $this->ensure_my_table();
2544 if (empty($this->options['glossary'])) {
2545 // Add the field.
2546 $this->base_alias = $this->name_alias = $this->query->add_field($this->table_alias, $this->real_field);
2547 $this->query->set_count_field($this->table_alias, $this->real_field);
2548 }
2549 else {
2550 // Add the field.
2551 $this->base_alias = $this->name_alias = $this->query->add_field(NULL, $this->get_formula(), $this->field . '_truncated');
2552 $this->query->set_count_field(NULL, $formula, $this->field, $this->field . '_truncated');
2553 }
2554
2555 return $this->summary_basics(FALSE);
2556 }
2557
2558 /**
2559 * Get the formula for this argument.
2560 *
2561 * $this->ensure_my_table() MUST have been called prior to this.
2562 */
2563 function get_formula() {
2564 return "LEFT($this->table_alias.$this->real_field, " . intval($this->options['limit']) . ")";
2565 }
2566
2567 /**
2568 * Build the query based upon the formula
2569 */
2570 function query() {
2571 $this->ensure_my_table();
2572
2573 $argument = $this->argument;
2574 if (!empty($this->options['transform_dash'])) {
2575 $argument = strtr($argument, '-', ' ');
2576 }
2577
2578 if (empty($this->options['glossary'])) {
2579 $field = "$this->table_alias.$this->real_field";
2580 }
2581 else {
2582 $field = $this->get_formula();
2583 }
2584
2585 $this->query->add_where(0, "$field = '%s'", $argument);
2586 }
2587
2588 /**
2589 * Provides a link from the summary to the next level; this will be called
2590 * once per row of a summary.
2591 *
2592 * @param $data
2593 * The query results for the row.
2594 * @param $url
2595 * The base URL to use.
2596 */
2597 function summary_link($data, $url) {
2598 $value = $this->case_transform($data->{$this->base_alias}, 'path_case');
2599 if (!empty($this->options['transform_dash'])) {
2600 $value = strtr($value, ' ', '-');
2601 }
2602 return url("$url/$value");
2603 }
2604
2605 function case_transform($string, $option) {
2606 switch ($this->options[$option]) {
2607 default:
2608 return $string;
2609 case 'upper':
2610 return strtoupper($string);
2611 case 'lower':
2612 return strtolower($string);
2613 case 'upper':
2614 return strtoupper($string);
2615 case 'ucfirst':
2616 return ucfirst($string);
2617 case 'ucwords':
2618 return ucwords($string);
2619 }
2620 }
2621
2622 function title() {
2623 return check_plain($this->case_transform($this->argument, 'case'));
2624 }
2625}
2626
2627/**
013538bb
EM
2628 * @}
2629 */