Project

General

Profile

Paste
Download (54.7 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / views / includes / handlers.inc @ 6de61b86

1
<?php
2

    
3
/**
4
 * @file
5
 * Defines the various handler objects to help build and display views.
6
 */
7

    
8
/**
9
 * Instantiate and construct a new handler.
10
 */
11
function _views_create_handler($definition, $type = 'handler', $handler_type = NULL) {
12
  //  debug('Instantiating handler ' . $definition['handler']);
13
  if (empty($definition['handler'])) {
14
    vpr('_views_create_handler - type: @type - failed: handler has not been provided.',
15
      array('@type' => isset($handler_type) ? ($type . '(handler type: ' . $handler_type . ')') : $type)
16
    );
17
    return;
18
  }
19

    
20
  // class_exists will automatically load the code file.
21
  if (!empty($definition['override handler']) &&
22
      !class_exists($definition['override handler'])) {
23
    vpr(
24
      '_views_create_handler - loading override handler @type failed: class @override_handler could not be loaded. ' .
25
      'Verify the class file has been registered in the corresponding .info-file (files[]).',
26
      array(
27
        '@type' => isset($handler_type) ? ($type . '(handler type: ' . $handler_type . ')') : $type,
28
        '@override_handler' => $definition['override handler'],
29
      )
30
    );
31
    return;
32
  }
33

    
34
  if (!class_exists($definition['handler'])) {
35
    vpr(
36
      '_views_create_handler - loading handler @type failed: class @handler could not be loaded. ' .
37
      'Verify the class file has been registered in the corresponding .info-file (files[]).',
38
      array(
39
        '@type' => isset($handler_type) ? ($type . '(handler type: ' . $handler_type . ')') : $type,
40
        '@handler' => $definition['handler'],
41
      )
42
    );
43
    return;
44
  }
45

    
46
  if (!empty($definition['override handler'])) {
47
    $handler = new $definition['override handler'];
48
  }
49
  else {
50
    $handler = new $definition['handler'];
51
  }
52

    
53
  $handler->set_definition($definition);
54
  if ($type == 'handler') {
55
    $handler->is_handler = TRUE;
56
    $handler->handler_type = $handler_type;
57
  }
58
  else {
59
    $handler->is_plugin = TRUE;
60
    $handler->plugin_type = $type;
61
    $handler->plugin_name = $definition['name'];
62
  }
63

    
64
  // let the handler have something like a constructor.
65
  $handler->construct();
66

    
67
  return $handler;
68
}
69

    
70
/**
71
 * Prepare a handler's data by checking defaults and such.
72
 */
73
function _views_prepare_handler($definition, $data, $field, $type) {
74
  foreach (array('group', 'title', 'title short', 'help', 'real field') as $key) {
75
    if (!isset($definition[$key])) {
76
      // First check the field level.
77
      if (!empty($data[$field][$key])) {
78
        $definition[$key] = $data[$field][$key];
79
      }
80
      // Then if that doesn't work, check the table level.
81
      elseif (!empty($data['table'][$key])) {
82
        $definition[$key] = $data['table'][$key];
83
      }
84
    }
85
  }
86

    
87
  return _views_create_handler($definition, 'handler', $type);
88
}
89

    
90
/**
91
 * Fetch a handler to join one table to a primary table from the data cache.
92
 */
93
function views_get_table_join($table, $base_table) {
94
  $data = views_fetch_data($table);
95
  if (isset($data['table']['join'][$base_table])) {
96
    $h = $data['table']['join'][$base_table];
97
    if (!empty($h['handler']) && class_exists($h['handler'])) {
98
      $handler = new $h['handler'];
99
    }
100
    else {
101
      $handler = new views_join();
102
    }
103

    
104
    // Fill in some easy defaults.
105
    $handler->definition = $h;
106
    if (empty($handler->definition['table'])) {
107
      $handler->definition['table'] = $table;
108
    }
109
    // If this is empty, it's a direct link.
110
    if (empty($handler->definition['left_table'])) {
111
      $handler->definition['left_table'] = $base_table;
112
    }
113

    
114
    if (isset($h['arguments'])) {
115
      call_user_func_array(array(&$handler, 'construct'), $h['arguments']);
116
    }
117
    else {
118
      $handler->construct();
119
    }
120

    
121
    return $handler;
122
  }
123

    
124
  // DEBUG -- identify missing handlers.
125
  vpr("Missing join: @table @base_table", array('@table' => $table, '@base_table' => $base_table));
126
}
127

    
128
/**
129
 * Base handler, from which all the other handlers are derived.
130
 * It creates a common interface to create consistency amongst
131
 * handlers and data.
132
 *
133
 * This class would be abstract in PHP5, but PHP4 doesn't understand that.
134
 *
135
 * Definition terms:
136
 * - table: The actual table this uses; only specify if different from
137
 *          the table this is attached to.
138
 * - real field: The actual field this uses; only specify if different from
139
 *               the field this item is attached to.
140
 * - group: A text string representing the 'group' this item is attached to,
141
 *          for display in the UI. Examples: "Node", "Taxonomy", "Comment",
142
 *          "User", etc. This may be inherited from the parent definition or
143
 *          the 'table' definition.
144
 * - title: The title for this handler in the UI. This may be inherited from
145
 *          the parent definition or the 'table' definition.
146
 * - help: A more informative string to give to the user to explain what this
147
 *         field/handler is or does.
148
 * - access callback: If this field should have access control, this could
149
 *                    be a function to use. 'user_access' is a common
150
 *                    public function to use here. If not specified, no access
151
 *                    control is provided.
152
 * - access arguments: An array of arguments for the access callback.
153
 */
154
class views_handler extends views_object {
155

    
156
  /**
157
   * The top object of a view.
158
   *
159
   * @var view
160
   */
161
  public $view = NULL;
162

    
163
  /**
164
   * Where the $query object will reside:.
165
   *
166
   * @var views_plugin_query
167
   */
168
  public $query = NULL;
169

    
170
  /**
171
   * The type of the handler, for example filter/footer/field.
172
   */
173
  public $handler_type = NULL;
174

    
175
  /**
176
   * The alias of the table of this handler which is used in the query.
177
   */
178
  public $table_alias;
179

    
180
  /**
181
   * The actual field in the database table, maybe different
182
   * on other kind of query plugins/special handlers.
183
   */
184
  public $real_field;
185

    
186
  /**
187
   * The relationship used for this field.
188
   */
189
  public $relationship = NULL;
190

    
191
  /**
192
   * Init the handler with necessary data.
193
   *
194
   * @param view $view
195
   *   The $view object this handler is attached to.
196
   * @param array $options
197
   *   The item from the database; the actual contents of this will vary
198
   *   based upon the type of handler.
199
   */
200
  public function init(&$view, &$options) {
201
    $this->view = &$view;
202
    $display_id = $this->view->current_display;
203
    // Check to see if this handler type is defaulted. Note that
204
    // we have to do a lookup because the type is singular but the
205
    // option is stored as the plural.
206
    // If the 'moved to' keyword moved our handler, let's fix that now.
207
    if (isset($this->actual_table)) {
208
      $options['table'] = $this->actual_table;
209
    }
210

    
211
    if (isset($this->actual_field)) {
212
      $options['field'] = $this->actual_field;
213
    }
214

    
215
    $types = views_object_types();
216
    $plural = $this->handler_type;
217
    if (isset($types[$this->handler_type]['plural'])) {
218
      $plural = $types[$this->handler_type]['plural'];
219
    }
220
    if ($this->view->display_handler->is_defaulted($plural)) {
221
      $display_id = 'default';
222
    }
223

    
224
    $this->localization_keys = array(
225
      $display_id,
226
      $this->handler_type,
227
      $options['table'],
228
      $options['id'],
229
    );
230

    
231
    $this->unpack_options($this->options, $options);
232

    
233
    // This exist on most handlers, but not all. So they are still optional.
234
    if (isset($options['table'])) {
235
      $this->table = $options['table'];
236
    }
237

    
238
    if (isset($this->definition['real field'])) {
239
      $this->real_field = $this->definition['real field'];
240
    }
241

    
242
    if (isset($this->definition['field'])) {
243
      $this->real_field = $this->definition['field'];
244
    }
245

    
246
    if (isset($options['field'])) {
247
      $this->field = $options['field'];
248
      if (!isset($this->real_field)) {
249
        $this->real_field = $options['field'];
250
      }
251
    }
252

    
253
    $this->query = &$view->query;
254
  }
255

    
256
  /**
257
   * {@inheritdoc}
258
   */
259
  public function option_definition() {
260
    $options = parent::option_definition();
261

    
262
    $options['id'] = array('default' => '');
263
    $options['table'] = array('default' => '');
264
    $options['field'] = array('default' => '');
265
    $options['relationship'] = array('default' => 'none');
266
    $options['group_type'] = array('default' => 'group');
267
    $options['ui_name'] = array('default' => '');
268

    
269
    return $options;
270
  }
271

    
272
  /**
273
   * Return a string representing this handler's name in the UI.
274
   */
275
  public function ui_name($short = FALSE) {
276
    if (!empty($this->options['ui_name'])) {
277
      $title = check_plain($this->options['ui_name']);
278
      return $title;
279
    }
280
    $title = ($short && isset($this->definition['title short'])) ? $this->definition['title short'] : $this->definition['title'];
281
    return t('!group: !title', array('!group' => $this->definition['group'], '!title' => $title));
282
  }
283

    
284
  /**
285
   * Shortcut to get a handler's raw field value.
286
   *
287
   * This should be overridden for handlers with formulae or other
288
   * non-standard fields. Because this takes an argument, fields
289
   * overriding this can just call return parent::get_field($formula)
290
   */
291
  public function get_field($field = NULL) {
292
    if (!isset($field)) {
293
      if (!empty($this->formula)) {
294
        $field = $this->get_formula();
295
      }
296
      else {
297
        $field = $this->table_alias . '.' . $this->real_field;
298
      }
299
    }
300

    
301
    // If grouping, check to see if the aggregation method needs to modify the
302
    // field.
303
    if ($this->view->display_handler->use_group_by()) {
304
      $this->view->init_query();
305
      if ($this->query) {
306
        $info = $this->query->get_aggregation_info();
307
        if (!empty($info[$this->options['group_type']]['method']) && function_exists($info[$this->options['group_type']]['method'])) {
308
          return $info[$this->options['group_type']]['method']($this->options['group_type'], $field);
309
        }
310
      }
311
    }
312

    
313
    return $field;
314
  }
315

    
316
  /**
317
   * Sanitize the value for output.
318
   *
319
   * @param string $value
320
   *   The value being rendered.
321
   * @param string $type
322
   *   The type of sanitization needed. If not provided, check_plain() is used.
323
   *
324
   * @return string
325
   *   Returns the safe value.
326
   */
327
  public function sanitize_value($value, $type = NULL) {
328
    switch ($type) {
329
      case 'xss':
330
        $value = filter_xss($value);
331
        break;
332

    
333
      case 'xss_admin':
334
        $value = filter_xss_admin($value);
335
        break;
336

    
337
      case 'url':
338
        $value = check_url($value);
339
        break;
340

    
341
      default:
342
        $value = check_plain($value);
343
        break;
344
    }
345
    return $value;
346
  }
347

    
348
  /**
349
   * Transform a string by a certain method.
350
   *
351
   * @param string $string
352
   *    The input you want to transform.
353
   * @param array $option
354
   *    How do you want to transform it, possible values:
355
   *      - upper: Uppercase the string.
356
   *      - lower: lowercase the string.
357
   *      - ucfirst: Make the first char uppercase.
358
   *      - ucwords: Make each word in the string uppercase.
359
   *
360
   * @return string
361
   *    The transformed string.
362
   */
363
  public function case_transform($string, $option) {
364
    global $multibyte;
365

    
366
    switch ($option) {
367
      default:
368
        return $string;
369
      case 'upper':
370
        return drupal_strtoupper($string);
371

    
372
      case 'lower':
373
        return drupal_strtolower($string);
374

    
375
      case 'ucfirst':
376
        return drupal_strtoupper(drupal_substr($string, 0, 1)) . drupal_substr($string, 1);
377

    
378
      case 'ucwords':
379
        if ($multibyte == UNICODE_MULTIBYTE) {
380
          return mb_convert_case($string, MB_CASE_TITLE);
381
        }
382
        else {
383
          return ucwords($string);
384
        }
385
    }
386
  }
387

    
388
  /**
389
   * Validate the options form.
390
   */
391
  public function options_validate(&$form, &$form_state) {
392
  }
393

    
394
  /**
395
   * Build the options form.
396
   */
397
  public function options_form(&$form, &$form_state) {
398
    // Some form elements belong in a fieldset for presentation, but can't
399
    // be moved into one because of the form_state['values'] hierarchy. Those
400
    // elements can add a #fieldset => 'fieldset_name' property, and they'll
401
    // be moved to their fieldset during pre_render.
402
    $form['#pre_render'][] = 'views_ui_pre_render_add_fieldset_markup';
403

    
404
    $form['ui_name'] = array(
405
      '#type' => 'textfield',
406
      '#title' => t('Administrative title'),
407
      '#description' => t('This title will be displayed on the views edit page instead of the default one. This might be useful if you have the same item twice.'),
408
      '#default_value' => $this->options['ui_name'],
409
      '#fieldset' => 'more',
410
    );
411

    
412
    // This form is long and messy enough that the "Administrative title" option
413
    // belongs in a "more options" fieldset at the bottom of the form.
414
    $form['more'] = array(
415
      '#type' => 'fieldset',
416
      '#title' => t('More'),
417
      '#collapsible' => TRUE,
418
      '#collapsed' => TRUE,
419
      '#weight' => 150,
420
    );
421
    // Allow to alter the default values brought into the form.
422
    drupal_alter('views_handler_options', $this->options, $view);
423
  }
424

    
425
  /**
426
   * Perform any necessary changes to the form values prior to storage.
427
   * There is no need for this function to actually store the data.
428
   */
429
  public function options_submit(&$form, &$form_state) {
430
  }
431

    
432
  /**
433
   * Provides the handler some groupby.
434
   */
435
  public function use_group_by() {
436
    return TRUE;
437
  }
438

    
439
  /**
440
   * Provide a form for aggregation settings.
441
   */
442
  public function groupby_form(&$form, &$form_state) {
443
    $view = &$form_state['view'];
444
    $display_id = $form_state['display_id'];
445
    $types = views_object_types();
446
    $type = $form_state['type'];
447
    $id = $form_state['id'];
448

    
449
    $form['#title'] = check_plain($view->display[$display_id]->display_title) . ': ';
450
    $form['#title'] .= t('Configure aggregation settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $this->ui_name()));
451

    
452
    $form['#section'] = $display_id . '-' . $type . '-' . $id;
453

    
454
    $view->init_query();
455
    $info = $view->query->get_aggregation_info();
456
    foreach ($info as $id => $aggregate) {
457
      $group_types[$id] = $aggregate['title'];
458
    }
459

    
460
    $form['group_type'] = array(
461
      '#type' => 'select',
462
      '#title' => t('Aggregation type'),
463
      '#default_value' => $this->options['group_type'],
464
      '#description' => t('Select the aggregation function to use on this field.'),
465
      '#options' => $group_types,
466
    );
467
  }
468

    
469
  /**
470
   * Perform any necessary changes to the form values prior to storage.
471
   * There is no need for this function to actually store the data.
472
   */
473
  public function groupby_form_submit(&$form, &$form_state) {
474
    $item =& $form_state['handler']->options;
475

    
476
    $item['group_type'] = $form_state['values']['options']['group_type'];
477
  }
478

    
479
  /**
480
   * If a handler has 'extra options' it will get a little settings widget and
481
   * another form called extra_options.
482
   */
483
  public function has_extra_options() {
484
    return FALSE;
485
  }
486

    
487
  /**
488
   * Provide defaults for the handler.
489
   */
490
  public function extra_options(&$option) {
491
  }
492

    
493
  /**
494
   * Provide a form for setting options.
495
   */
496
  public function extra_options_form(&$form, &$form_state) {
497
  }
498

    
499
  /**
500
   * Validate the options form.
501
   */
502
  public function extra_options_validate($form, &$form_state) {
503
  }
504

    
505
  /**
506
   * Perform any necessary changes to the form values prior to storage.
507
   * There is no need for this function to actually store the data.
508
   */
509
  public function extra_options_submit($form, &$form_state) {
510
  }
511

    
512
  /**
513
   * Determine if a handler can be exposed.
514
   */
515
  public function can_expose() {
516
    return FALSE;
517
  }
518

    
519
  /**
520
   * Set new exposed option defaults when exposed setting is flipped
521
   * on.
522
   */
523
  public function expose_options() {
524
  }
525

    
526
  /**
527
   * Get information about the exposed form for the form renderer.
528
   */
529
  public function exposed_info() {
530
  }
531

    
532
  /**
533
   * Render our chunk of the exposed handler form when selecting.
534
   */
535
  public function exposed_form(&$form, &$form_state) {
536
  }
537

    
538
  /**
539
   * Validate the exposed handler form.
540
   */
541
  public function exposed_validate(&$form, &$form_state) {
542
  }
543

    
544
  /**
545
   * Submit the exposed handler form.
546
   */
547
  public function exposed_submit(&$form, &$form_state) {
548
  }
549

    
550
  /**
551
   * Form for exposed handler options.
552
   */
553
  public function expose_form(&$form, &$form_state) {
554
  }
555

    
556
  /**
557
   * Validate the options form.
558
   */
559
  public function expose_validate($form, &$form_state) {
560
  }
561

    
562
  /**
563
   * Perform any necessary changes to the form exposes prior to storage.
564
   * There is no need for this function to actually store the data.
565
   */
566
  public function expose_submit($form, &$form_state) {
567
  }
568

    
569
  /**
570
   * Shortcut to display the expose/hide button.
571
   */
572
  public function show_expose_button(&$form, &$form_state) {
573
  }
574

    
575
  /**
576
   * Shortcut to display the exposed options form.
577
   */
578
  public function show_expose_form(&$form, &$form_state) {
579
    if (empty($this->options['exposed'])) {
580
      return;
581
    }
582

    
583
    $this->expose_form($form, $form_state);
584

    
585
    // When we click the expose button, we add new gadgets to the form but they
586
    // have no data in $_POST so their defaults get wiped out. This prevents
587
    // these defaults from getting wiped out. This setting will only be TRUE
588
    // during a 2nd pass rerender.
589
    if (!empty($form_state['force_expose_options'])) {
590
      foreach (element_children($form['expose']) as $id) {
591
        if (isset($form['expose'][$id]['#default_value']) && !isset($form['expose'][$id]['#value'])) {
592
          $form['expose'][$id]['#value'] = $form['expose'][$id]['#default_value'];
593
        }
594
      }
595
    }
596
  }
597

    
598
  /**
599
   * Check whether current user has access to this handler.
600
   *
601
   * @return bool
602
   */
603
  public function access() {
604
    if (isset($this->definition['access callback']) && function_exists($this->definition['access callback'])) {
605
      if (isset($this->definition['access arguments']) && is_array($this->definition['access arguments'])) {
606
        return call_user_func_array($this->definition['access callback'], $this->definition['access arguments']);
607
      }
608
      return $this->definition['access callback']();
609
    }
610

    
611
    return TRUE;
612
  }
613

    
614
  /**
615
   * Run before the view is built.
616
   *
617
   * This gives all the handlers some time to set up before any handler has
618
   * been fully run.
619
   */
620
  public function pre_query() {
621
  }
622

    
623
  /**
624
   * Run after the view is executed, before the result is cached.
625
   *
626
   * This gives all the handlers some time to modify values. This is primarily
627
   * used so that handlers that pull up secondary data can put it in the
628
   * $values so that the raw data can be utilized externally.
629
   */
630
  public function post_execute(&$values) {
631
  }
632

    
633
  /**
634
   * Provides a unique placeholders for handlers.
635
   */
636
  public function placeholder() {
637
    return $this->query->placeholder($this->options['table'] . '_' . $this->options['field']);
638
  }
639

    
640
  /**
641
   * Called just prior to query(), this lets a handler set up any relationship
642
   * it needs.
643
   */
644
  public function set_relationship() {
645
    // Ensure this gets set to something.
646
    $this->relationship = NULL;
647

    
648
    // Don't process non-existant relationships.
649
    if (empty($this->options['relationship']) || $this->options['relationship'] == 'none') {
650
      return;
651
    }
652

    
653
    $relationship = $this->options['relationship'];
654

    
655
    // Ignore missing/broken relationships.
656
    if (empty($this->view->relationship[$relationship])) {
657
      return;
658
    }
659

    
660
    // Check to see if the relationship has already processed. If not, then we
661
    // cannot process it.
662
    if (empty($this->view->relationship[$relationship]->alias)) {
663
      return;
664
    }
665

    
666
    // Finally!
667
    $this->relationship = $this->view->relationship[$relationship]->alias;
668
  }
669

    
670
  /**
671
   * Ensure the main table for this handler is in the query. This is used
672
   * a lot.
673
   */
674
  public function ensure_my_table() {
675
    if (!isset($this->table_alias)) {
676
      if (!method_exists($this->query, 'ensure_table')) {
677
        vpr(t('Ensure my table called but query has no ensure_table method.'));
678
        return;
679
      }
680
      $this->table_alias = $this->query->ensure_table($this->table, $this->relationship);
681
    }
682
    return $this->table_alias;
683
  }
684

    
685
  /**
686
   * Provide text for the administrative summary.
687
   */
688
  public function admin_summary() {
689
  }
690

    
691
  /**
692
   * Determine if the argument needs a style plugin.
693
   *
694
   * @return bool
695
   */
696
  public function needs_style_plugin() {
697
    return FALSE;
698
  }
699

    
700
  /**
701
   * Determine if this item is 'exposed', meaning it provides form elements
702
   * to let users modify the view.
703
   *
704
   * @return bool
705
   */
706
  public function is_exposed() {
707
    return !empty($this->options['exposed']);
708
  }
709

    
710
  /**
711
   * Returns TRUE if the exposed filter works like a grouped filter.
712
   */
713
  public function is_a_group() {
714
    return FALSE;
715
  }
716

    
717
  /**
718
   * Define if the exposed input has to be submitted multiple times.
719
   * This is TRUE when exposed filters grouped are using checkboxes as
720
   * widgets.
721
   */
722
  public function multiple_exposed_input() {
723
    return FALSE;
724
  }
725

    
726
  /**
727
   * Take input from exposed handlers and assign to this handler, if necessary.
728
   */
729
  public function accept_exposed_input($input) {
730
    return TRUE;
731
  }
732

    
733
  /**
734
   * If set to remember exposed input in the session, store it there.
735
   */
736
  public function store_exposed_input($input, $status) {
737
    return TRUE;
738
  }
739

    
740
  /**
741
   * Get the join object that should be used for this handler.
742
   *
743
   * This method isn't used a great deal, but it's very handy for easily
744
   * getting the join if it is necessary to make some changes to it, such
745
   * as adding an 'extra'.
746
   */
747
  public function get_join() {
748
    // get the join from this table that links back to the base table.
749
    // Determine the primary table to seek.
750
    if (empty($this->query->relationships[$this->relationship])) {
751
      $base_table = $this->query->base_table;
752
    }
753
    else {
754
      $base_table = $this->query->relationships[$this->relationship]['base'];
755
    }
756

    
757
    $join = views_get_table_join($this->table, $base_table);
758
    if ($join) {
759
      return clone $join;
760
    }
761
  }
762

    
763
  /**
764
   * Validates the handler against the complete View.
765
   *
766
   * This is called when the complete View is being validated. For validating
767
   * the handler options form use options_validate().
768
   *
769
   * @see views_handler::options_validate()
770
   *
771
   * @return array
772
   *   Empty array if the handler is valid; an array of error strings if it is
773
   *   not.
774
   */
775
  public function validate() {
776
    return array();
777
  }
778

    
779
  /**
780
   * Determine if the handler is considered 'broken'.
781
   *
782
   * Generally only returns TRUE if the handler can't be found.
783
   *
784
   * @return bool
785
   *   The handler could not be loaded.
786
   */
787
  public function broken() {
788
  }
789

    
790
}
791

    
792
/**
793
 * This many to one helper object is used on both arguments and filters.
794
 *
795
 * @todo This requires extensive documentation on how this class is to
796
 * be used. For now, look at the arguments and filters that use it. Lots
797
 * of stuff is just pass-through but there are definitely some interesting
798
 * areas where they interact.
799
 *
800
 * Any handler that uses this can have the following possibly additional
801
 * definition terms:
802
 * - numeric: If true, treat this field as numeric, using %d instead of %s in
803
 *            queries.
804
 */
805
class views_many_to_one_helper {
806

    
807
  /**
808
   * Contains possible existing placeholders used by the query.
809
   *
810
   * @var array
811
   */
812
  public $placeholders = array();
813

    
814
  /**
815
   * {@inheritdoc}
816
   */
817
  public function __construct(&$handler) {
818
    $this->handler = &$handler;
819
  }
820

    
821
  /**
822
   * {@inheritdoc}
823
   */
824
  static function option_definition(&$options) {
825
    $options['reduce_duplicates'] = array('default' => FALSE, 'bool' => TRUE);
826
  }
827

    
828
  /**
829
   * {@inheritdoc}
830
   */
831
  public function options_form(&$form, &$form_state) {
832
    $form['reduce_duplicates'] = array(
833
      '#type' => 'checkbox',
834
      '#title' => t('Reduce duplicates'),
835
      '#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.'),
836
      '#default_value' => !empty($this->handler->options['reduce_duplicates']),
837
      '#weight' => 4,
838
    );
839
  }
840

    
841
  /**
842
   * Provide an option to use a formula.
843
   *
844
   * If it wants us to do this, it must set $helper->formula = TRUE and
845
   * implement handler->get_formula();.
846
   */
847
  public function get_field() {
848
    if (!empty($this->formula)) {
849
      return $this->handler->get_formula();
850
    }
851
    else {
852
      return $this->handler->table_alias . '.' . $this->handler->real_field;
853
    }
854
  }
855

    
856
  /**
857
   * Add a table to the query.
858
   *
859
   * This is an advanced concept; not only does it add a new instance of the
860
   * table, but it follows the relationship path all the way down to the
861
   * relationship link point and adds *that* as a new relationship and then adds
862
   * the table to the relationship, if necessary.
863
   */
864
  public function add_table($join = NULL, $alias = NULL) {
865
    // This is used for lookups in the many_to_one table.
866
    $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
867

    
868
    if (empty($join)) {
869
      $join = $this->get_join();
870
    }
871

    
872
    // See if there's a chain between us and the base relationship. If so, we
873
    // need to create a new relationship to use.
874
    $relationship = $this->handler->relationship;
875

    
876
    // Determine the primary table to seek.
877
    if (empty($this->handler->query->relationships[$relationship])) {
878
      $base_table = $this->handler->query->base_table;
879
    }
880
    else {
881
      $base_table = $this->handler->query->relationships[$relationship]['base'];
882
    }
883

    
884
    // Cycle through the joins. This isn't as error-safe as the normal
885
    // ensure_path logic. Perhaps it should be.
886
    $r_join = clone $join;
887
    while (!empty($r_join) && $r_join->left_table != $base_table) {
888
      $r_join = views_get_table_join($r_join->left_table, $base_table);
889
    }
890
    // If we found that there are tables in between, add the relationship.
891
    if ($r_join->table != $join->table) {
892
      $relationship = $this->handler->query->add_relationship($this->handler->table . '_' . $r_join->table, $r_join, $r_join->table, $this->handler->relationship);
893
    }
894

    
895
    // And now add our table, using the new relationship if one was used.
896
    $alias = $this->handler->query->add_table($this->handler->table, $relationship, $join, $alias);
897

    
898
    // Store what values are used by this table chain so that other chains can
899
    // automatically discard those values.
900
    if (empty($this->handler->view->many_to_one_tables[$field])) {
901
      $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
902
    }
903
    else {
904
      $this->handler->view->many_to_one_tables[$field] = array_merge($this->handler->view->many_to_one_tables[$field], $this->handler->value);
905
    }
906

    
907
    return $alias;
908
  }
909

    
910
  /**
911
   * {@inheritdoc}
912
   */
913
  public function get_join() {
914
    return $this->handler->get_join();
915
  }
916

    
917
  /**
918
   * Provide the proper join for summary queries.
919
   *
920
   * This is important in part because it will cooperate with other arguments if
921
   * possible.
922
   */
923
  public function summary_join() {
924
    $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
925
    $join = $this->get_join();
926

    
927
    // Shortcuts.
928
    $options = $this->handler->options;
929
    $view = &$this->handler->view;
930
    $query = &$this->handler->query;
931

    
932
    if (!empty($options['require_value'])) {
933
      $join->type = 'INNER';
934
    }
935

    
936
    if (empty($options['add_table']) || empty($view->many_to_one_tables[$field])) {
937
      return $query->ensure_table($this->handler->table, $this->handler->relationship, $join);
938
    }
939
    else {
940
      if (!empty($view->many_to_one_tables[$field])) {
941
        foreach ($view->many_to_one_tables[$field] as $value) {
942
          $join->extra = array(
943
            array(
944
              'field' => $this->handler->real_field,
945
              'operator' => '!=',
946
              'value' => $value,
947
              'numeric' => !empty($this->definition['numeric']),
948
            ),
949
          );
950
        }
951
      }
952
      return $this->add_table($join);
953
    }
954
  }
955

    
956
  /**
957
   * Override ensure_my_table so we can control how this joins in.
958
   *
959
   * The operator actually has influence over joining.
960
   */
961
  public function ensure_my_table() {
962
    if (!isset($this->handler->table_alias)) {
963
      // Case 1: Operator is an 'or' and we're not reducing duplicates.
964
      // We hence get the absolute simplest.
965
      $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
966
      if ($this->handler->operator == 'or' && empty($this->handler->options['reduce_duplicates'])) {
967
        if (empty($this->handler->options['add_table']) && empty($this->handler->view->many_to_one_tables[$field])) {
968
          // Query optimization, INNER joins are slightly faster, so use them
969
          // when we know we can.
970
          $join = $this->get_join();
971
          if (isset($join)) {
972
            $join->type = 'INNER';
973
          }
974
          $this->handler->table_alias = $this->handler->query->ensure_table($this->handler->table, $this->handler->relationship, $join);
975
          $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
976
        }
977
        else {
978
          $join = $this->get_join();
979
          $join->type = 'LEFT';
980
          if (!empty($this->handler->view->many_to_one_tables[$field])) {
981
            foreach ($this->handler->view->many_to_one_tables[$field] as $value) {
982
              $join->extra = array(
983
                array(
984
                  'field' => $this->handler->real_field,
985
                  'operator' => '!=',
986
                  'value' => $value,
987
                  'numeric' => !empty($this->handler->definition['numeric']),
988
                ),
989
              );
990
            }
991
          }
992

    
993
          $this->handler->table_alias = $this->add_table($join);
994
        }
995

    
996
        return $this->handler->table_alias;
997
      }
998

    
999
      // Case 2: it's anything but an 'or'.
1000
      // We do one join per selected value.
1001

    
1002
      // Clone the join for each table:
1003
      $this->handler->table_aliases = array();
1004
      foreach ($this->handler->value as $value) {
1005
        $join = $this->get_join();
1006
        if ($this->handler->operator == 'and') {
1007
          $join->type = 'INNER';
1008
        }
1009
        if (empty($join->extra)) {
1010
          $join->extra = array();
1011
        }
1012
        $join->extra[] = array(
1013
          'field' => $this->handler->real_field,
1014
          'value' => $value,
1015
          'numeric' => !empty($this->handler->definition['numeric']),
1016
        );
1017
        // The table alias needs to be unique to this value across the
1018
        // multiple times the filter or argument is called by the view.
1019
        if (!isset($this->handler->view->many_to_one_aliases[$field][$value])) {
1020
          if (!isset($this->handler->view->many_to_one_count[$this->handler->table])) {
1021
            $this->handler->view->many_to_one_count[$this->handler->table] = 0;
1022
          }
1023
          $this->handler->view->many_to_one_aliases[$field][$value] = $this->handler->table . '_value_' . ($this->handler->view->many_to_one_count[$this->handler->table]++);
1024
          $alias = $this->handler->table_aliases[$value] = $this->add_table($join,
1025
            $this->handler->view->many_to_one_aliases[$field][$value]);
1026
          // and set table_alias to the first of these.
1027
          if (empty($this->handler->table_alias)) {
1028
            $this->handler->table_alias = $alias;
1029
          }
1030
        }
1031
      }
1032
    }
1033
    return $this->handler->table_alias;
1034
  }
1035

    
1036
  /**
1037
   * Provides a unique placeholders for handlers.
1038
   */
1039
  public function placeholder() {
1040
    return $this->handler->query->placeholder($this->handler->options['table'] . '_' . $this->handler->options['field']);
1041
  }
1042

    
1043
  /**
1044
   *
1045
   */
1046
  public function add_filter() {
1047
    if (empty($this->handler->value)) {
1048
      return;
1049
    }
1050
    $this->handler->ensure_my_table();
1051

    
1052
    // Shorten some variables.
1053
    $field = $this->get_field();
1054
    $options = $this->handler->options;
1055
    $operator = $this->handler->operator;
1056
    $formula = !empty($this->formula);
1057
    $value = $this->handler->value;
1058
    if (empty($options['group'])) {
1059
      $options['group'] = 0;
1060
    }
1061

    
1062
    // Determine whether a single expression is enough(FALSE) or the conditions
1063
    // should be added via an db_or()/db_and() (TRUE).
1064
    $add_condition = TRUE;
1065
    if ($operator == 'or' && empty($options['reduce_duplicates'])) {
1066
      if (count($value) > 1) {
1067
        $operator = 'IN';
1068
      }
1069
      else {
1070
        $value = is_array($value) ? array_pop($value) : $value;
1071
        $operator = '=';
1072
      }
1073
      $add_condition = FALSE;
1074
    }
1075

    
1076
    if (!$add_condition) {
1077
      if ($formula) {
1078
        $placeholder = $this->placeholder();
1079
        if ($operator == 'IN') {
1080
          $operator = "$operator IN($placeholder)";
1081
        }
1082
        else {
1083
          $operator = "$operator $placeholder";
1084
        }
1085
        $placeholders = array(
1086
          $placeholder => $value,
1087
        ) + $this->placeholders;
1088
        $this->handler->query->add_where_expression($options['group'], "$field $operator", $placeholders);
1089
      }
1090
      else {
1091
        $this->handler->query->add_where($options['group'], $field, $value, $operator);
1092
      }
1093
    }
1094

    
1095
    if ($add_condition) {
1096
      $field = $this->handler->real_field;
1097
      $clause = $operator == 'or' ? db_or() : db_and();
1098
      foreach ($this->handler->table_aliases as $value => $alias) {
1099
        if ($operator == 'not') {
1100
          $value = NULL;
1101
        }
1102
        $clause->condition("$alias.$field", $value);
1103
      }
1104

    
1105
      // Implode on either AND or OR.
1106
      $this->handler->query->add_where($options['group'], $clause);
1107
    }
1108
  }
1109

    
1110
}
1111

    
1112
/**
1113
 * Break x,y,z and x+y+z into an array. Works for strings.
1114
 *
1115
 * @param string $str
1116
 *   The string to parse.
1117
 * @param object $object
1118
 *   The object to use as a base. If not specified one will be created.
1119
 *
1120
 * @return object
1121
 *   An object containing
1122
 *   - operator: Either 'and' or 'or'
1123
 *   - value: An array of numeric values.
1124
 */
1125
function views_break_phrase_string($str, &$handler = NULL) {
1126
  if (!$handler) {
1127
    $handler = new stdClass();
1128
  }
1129

    
1130
  // Set up defaults.
1131
  if (!isset($handler->value)) {
1132
    $handler->value = array();
1133
  }
1134

    
1135
  if (!isset($handler->operator)) {
1136
    $handler->operator = 'or';
1137
  }
1138

    
1139
  if ($str == '') {
1140
    return $handler;
1141
  }
1142

    
1143
  // Determine if the string has 'or' operators (plus signs) or 'and' operators
1144
  // (commas) and split the string accordingly. If we have an 'and' operator,
1145
  // spaces are treated as part of the word being split, but otherwise they are
1146
  // treated the same as a plus sign.
1147
  $or_wildcard = '[^\s+,]';
1148
  $and_wildcard = '[^+,]';
1149
  if (preg_match("/^({$or_wildcard}+[+ ])+{$or_wildcard}+$/", $str)) {
1150
    $handler->operator = 'or';
1151
    $handler->value = preg_split('/[+ ]/', $str);
1152
  }
1153
  elseif (preg_match("/^({$and_wildcard}+,)*{$and_wildcard}+$/", $str)) {
1154
    $handler->operator = 'and';
1155
    $handler->value = explode(',', $str);
1156
  }
1157

    
1158
  // Keep an 'error' value if invalid strings were given.
1159
  if (!empty($str) && (empty($handler->value) || !is_array($handler->value))) {
1160
    $handler->value = array(-1);
1161
    return $handler;
1162
  }
1163

    
1164
  // Doubly ensure that all values are strings only.
1165
  foreach ($handler->value as $id => $value) {
1166
    $handler->value[$id] = (string) $value;
1167
  }
1168

    
1169
  return $handler;
1170
}
1171

    
1172
/**
1173
 * Break x,y,z and x+y+z into an array. Numeric only.
1174
 *
1175
 * @param string $str
1176
 *   The string to parse.
1177
 * @param object $handler
1178
 *   The handler object to use as a base. If not specified one will be created.
1179
 *
1180
 * @return $handler
1181
 *   The new handler object.
1182
 */
1183
function views_break_phrase($str, &$handler = NULL) {
1184
  if (!$handler) {
1185
    $handler = new stdClass();
1186
  }
1187

    
1188
  // Set up defaults.
1189
  if (!isset($handler->value)) {
1190
    $handler->value = array();
1191
  }
1192

    
1193
  if (!isset($handler->operator)) {
1194
    $handler->operator = 'or';
1195
  }
1196

    
1197
  if (empty($str)) {
1198
    return $handler;
1199
  }
1200

    
1201
  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
1202
    // The '+' character in a query string may be parsed as ' '.
1203
    $handler->operator = 'or';
1204
    $handler->value = preg_split('/[+ ]/', $str);
1205
  }
1206
  elseif (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
1207
    $handler->operator = 'and';
1208
    $handler->value = explode(',', $str);
1209
  }
1210

    
1211
  // Keep an 'error' value if invalid strings were given.
1212
  if (!empty($str) && (empty($handler->value) || !is_array($handler->value))) {
1213
    $handler->value = array(-1);
1214
    return $handler;
1215
  }
1216

    
1217
  // Doubly ensure that all values are numeric only.
1218
  foreach ($handler->value as $id => $value) {
1219
    $handler->value[$id] = intval($value);
1220
  }
1221

    
1222
  return $handler;
1223
}
1224

    
1225
// --------------------------------------------------------------------------
1226
// Date helper functions.
1227
/**
1228
 * Figure out what timezone we're in; needed for some date manipulations.
1229
 */
1230
function views_get_timezone() {
1231
  global $user;
1232
  if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
1233
    $timezone = $user->timezone;
1234
  }
1235
  else {
1236
    $timezone = variable_get('date_default_timezone', 0);
1237
  }
1238

    
1239
  // Set up the database timezone.
1240
  $db_type = Database::getConnection()->databaseType();
1241
  if (in_array($db_type, array('mysql', 'pgsql'))) {
1242
    $offset = '+00:00';
1243
    static $already_set = FALSE;
1244
    if (!$already_set) {
1245
      if ($db_type == 'pgsql') {
1246
        db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
1247
      }
1248
      elseif ($db_type == 'mysql') {
1249
        db_query("SET @@session.time_zone = '$offset'");
1250
      }
1251

    
1252
      $already_set = TRUE;
1253
    }
1254
  }
1255

    
1256
  return $timezone;
1257
}
1258

    
1259
/**
1260
 * Helper function to create cross-database SQL dates.
1261
 *
1262
 * @param string $field
1263
 *   The real table and field name, like 'tablename.fieldname'.
1264
 * @param string $field_type
1265
 *  The type of date field, 'int' or 'datetime'.
1266
 * @param string $set_offset
1267
 *   The name of a field that holds the timezone offset or a fixed timezone
1268
 *   offset value. If not provided, the normal Drupal timezone handling
1269
 *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1270
 *
1271
 * @return string
1272
 *   An appropriate SQL string for the db type and field type.
1273
 */
1274
function views_date_sql_field($field, $field_type = 'int', $set_offset = NULL) {
1275
  $db_type = Database::getConnection()->databaseType();
1276
  $offset = $set_offset !== NULL ? $set_offset : views_get_timezone();
1277
  if (isset($offset) && !is_numeric($offset)) {
1278
    $dtz = new DateTimeZone($offset);
1279
    $dt = new DateTime("now", $dtz);
1280
    $offset_seconds = $dtz->getOffset($dt);
1281
  }
1282

    
1283
  switch ($db_type) {
1284
    case 'mysql':
1285
      switch ($field_type) {
1286
        case 'int':
1287
          $field = "DATE_ADD('19700101', INTERVAL $field SECOND)";
1288
          break;
1289

    
1290
        case 'datetime':
1291
          break;
1292
      }
1293
      if (!empty($offset)) {
1294
        $field = "($field + INTERVAL $offset_seconds SECOND)";
1295
      }
1296
      return $field;
1297

    
1298
    case 'pgsql':
1299
      switch ($field_type) {
1300
        case 'int':
1301
          $field = "TO_TIMESTAMP($field)";
1302
          break;
1303

    
1304
        case 'datetime':
1305
          break;
1306
      }
1307
      if (!empty($offset)) {
1308
        $field = "($field + INTERVAL '$offset_seconds SECONDS')";
1309
      }
1310
      return $field;
1311

    
1312
    case 'sqlite':
1313
      if (!empty($offset)) {
1314
        $field = "($field + '$offset_seconds')";
1315
      }
1316
      return $field;
1317
  }
1318
}
1319

    
1320
/**
1321
 * Helper function to create cross-database SQL date formatting.
1322
 *
1323
 * @param string $format
1324
 *   A format string for the result, like 'Y-m-d H:i:s'.
1325
 * @param string $field
1326
 *   The real table and field name, like 'tablename.fieldname'.
1327
 * @param string $field_type
1328
 *   The type of date field, 'int' or 'datetime'.
1329
 * @param string $set_offset
1330
 *   The name of a field that holds the timezone offset or a fixed timezone
1331
 *   offset value. If not provided, the normal Drupal timezone handling will be
1332
 *   used, i.e. $set_offset = 0 will make no timezone adjustment.
1333
 *
1334
 * @return string
1335
 *   An appropriate SQL string for the db type and field type.
1336
 */
1337
function views_date_sql_format($format, $field, $field_type = 'int', $set_offset = NULL) {
1338
  $db_type = Database::getConnection()->databaseType();
1339
  $field = views_date_sql_field($field, $field_type, $set_offset);
1340
  switch ($db_type) {
1341
    case 'mysql':
1342
      $replace = array(
1343
        'Y' => '%Y',
1344
        'y' => '%y',
1345
        'M' => '%b',
1346
        'm' => '%m',
1347
        'n' => '%c',
1348
        'F' => '%M',
1349
        'D' => '%a',
1350
        'd' => '%d',
1351
        'l' => '%W',
1352
        'j' => '%e',
1353
        'W' => '%v',
1354
        'H' => '%H',
1355
        'h' => '%h',
1356
        'i' => '%i',
1357
        's' => '%s',
1358
        'A' => '%p',
1359
      );
1360
      $format = strtr($format, $replace);
1361
      return "DATE_FORMAT($field, '$format')";
1362

    
1363
    case 'pgsql':
1364
      $replace = array(
1365
        'Y' => 'YYYY',
1366
        'y' => 'YY',
1367
        'M' => 'Mon',
1368
        'm' => 'MM',
1369
        // No format for Numeric representation of a month, without leading
1370
        // zeros.
1371
        'n' => 'MM',
1372
        'F' => 'Month',
1373
        'D' => 'Dy',
1374
        'd' => 'DD',
1375
        'l' => 'Day',
1376
        // No format for Day of the month without leading zeros.
1377
        'j' => 'DD',
1378
        'W' => 'WW',
1379
        'H' => 'HH24',
1380
        'h' => 'HH12',
1381
        'i' => 'MI',
1382
        's' => 'SS',
1383
        'A' => 'AM',
1384
      );
1385
      $format = strtr($format, $replace);
1386
      return "TO_CHAR($field, '$format')";
1387

    
1388
    case 'sqlite':
1389
      $replace = array(
1390
        // 4 digit year number.
1391
        'Y' => '%Y',
1392
        // No format for 2 digit year number.
1393
        'y' => '%Y',
1394
        // No format for 3 letter month name.
1395
        'M' => '%m',
1396
        // Month number with leading zeros.
1397
        'm' => '%m',
1398
        // No format for month number without leading zeros.
1399
        'n' => '%m',
1400
        // No format for full month name.
1401
        'F' => '%m',
1402
        // No format for 3 letter day name.
1403
        'D' => '%d',
1404
        // Day of month number with leading zeros.
1405
        'd' => '%d',
1406
        // No format for full day name.
1407
        'l' => '%d',
1408
        // No format for day of month number without leading zeros.
1409
        'j' => '%d',
1410
        // ISO week number.
1411
        'W' => '%W',
1412
        // 24 hour hour with leading zeros.
1413
        'H' => '%H',
1414
        // No format for 12 hour hour with leading zeros.
1415
        'h' => '%H',
1416
        // Minutes with leading zeros.
1417
        'i' => '%M',
1418
        // Seconds with leading zeros.
1419
        's' => '%S',
1420
        // No format for AM/PM.
1421
        'A' => '',
1422
      );
1423
      $format = strtr($format, $replace);
1424
      return "strftime('$format', $field, 'unixepoch')";
1425
  }
1426
}
1427

    
1428
/**
1429
 * Helper function to create cross-database SQL date extraction.
1430
 *
1431
 * @param string $extract_type
1432
 *   The type of value to extract from the date, like 'MONTH'.
1433
 * @param string $field
1434
 *   The real table and field name, like 'tablename.fieldname'.
1435
 * @param string $field_type
1436
 *   The type of date field, 'int' or 'datetime'.
1437
 * @param string $set_offset
1438
 *   The name of a field that holds the timezone offset or a fixed timezone
1439
 *   offset value. If not provided, the normal Drupal timezone handling
1440
 *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1441
 *
1442
 * @return string
1443
 *   An appropriate SQL string for the db type and field type.
1444
 */
1445
function views_date_sql_extract($extract_type, $field, $field_type = 'int', $set_offset = NULL) {
1446
  $db_type = Database::getConnection()->databaseType();
1447
  $field = views_date_sql_field($field, $field_type, $set_offset);
1448

    
1449
  // Note there is no space after FROM to avoid db_rewrite problems
1450
  // @see http://drupal.org/node/79904.
1451
  switch ($extract_type) {
1452
    case('DATE'):
1453
      return $field;
1454

    
1455
    case('YEAR'):
1456
      return "EXTRACT(YEAR FROM($field))";
1457

    
1458
    case('MONTH'):
1459
      return "EXTRACT(MONTH FROM($field))";
1460

    
1461
    case('DAY'):
1462
      return "EXTRACT(DAY FROM($field))";
1463

    
1464
    case('HOUR'):
1465
      return "EXTRACT(HOUR FROM($field))";
1466

    
1467
    case('MINUTE'):
1468
      return "EXTRACT(MINUTE FROM($field))";
1469

    
1470
    case('SECOND'):
1471
      return "EXTRACT(SECOND FROM($field))";
1472

    
1473
    case('WEEK'):
1474
      // ISO week number for date.
1475
      switch ($db_type) {
1476
        case('mysql'):
1477
          // WEEK using arg 3 in mysql should return the same value as postgres
1478
          // EXTRACT.
1479
          return "WEEK($field, 3)";
1480

    
1481
        case('pgsql'):
1482
          return "EXTRACT(WEEK FROM($field))";
1483
      }
1484
    case('DOW'):
1485
      switch ($db_type) {
1486
        // MySQL returns 1 for Sunday through 7 for Saturday.
1487
        case('mysql'):
1488
          return "INTEGER(DAYOFWEEK($field) - 1)";
1489

    
1490
        // PHP date functions and postgres use 0 for Sunday and 6 for Saturday.
1491
        case('pgsql'):
1492
          return "EXTRACT(DOW FROM($field))";
1493
      }
1494
    case('DOY'):
1495
      switch ($db_type) {
1496
        case('mysql'):
1497
          return "DAYOFYEAR($field)";
1498

    
1499
        case('pgsql'):
1500
          return "EXTRACT(DOY FROM($field))";
1501
      }
1502
  }
1503
}
1504

    
1505
/**
1506
 * @}
1507
 */
1508

    
1509
/**
1510
 * @defgroup views_join_handlers Views join handlers
1511
 * @{
1512
 * Handlers to tell Views how to join tables together.
1513
 *
1514
 * Here is how you do complex joins:
1515
 *
1516
 * @code
1517
 * class views_join_complex extends views_join {
1518
 *   // PHP 4 doesn't call constructors of the base class automatically from a
1519
 *   // constructor of a derived class. It is your responsibility to propagate
1520
 *   // the call to constructors upstream where appropriate.
1521
 *   public function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1522
 *     parent::construct($table, $left_table, $left_field, $field, $extra, $type);
1523
 *   }
1524
 *
1525
 *   public function build_join($select_query, $table, $view_query) {
1526
 *     $this->extra = 'foo.bar = baz.boing';
1527
 *     parent::build_join($select_query, $table, $view_query);
1528
 *   }
1529
 * }
1530
 * @endcode
1531
 */
1532

    
1533
/**
1534
 * A function class to represent a join and create the SQL necessary
1535
 * to implement the join.
1536
 *
1537
 * This is the Delegation pattern. If we had PHP5 exclusively, we would
1538
 * declare this an interface.
1539
 *
1540
 * Extensions of this class can be used to create more interesting joins.
1541
 *
1542
 * 'join' definition:
1543
 *   - table: table to join (right table)
1544
 *   - field: field to join on (right field)
1545
 *   - left_table: The table we join to
1546
 *   - left_field: The field we join to
1547
 *   - type: either LEFT (default) or INNER
1548
 *   - extra: An array of extra conditions on the join. Each condition is
1549
 *     either a string that's directly added, or an array of items:
1550
 *   - - table: If not set, current table; if NULL, no table. If you specify a
1551
 *       table in cached definition, Views will try to load from an existing
1552
 *       alias. If you use realtime joins, it works better.
1553
 *   - - field: Field or formula
1554
 *       in formulas we can reference the right table by using %alias
1555
 *
1556
 * @see SelectQueryInterface::addJoin()
1557
 *   - operator: Defaults to =.
1558
 *   - value: Must be set. If an array, operator will be defaulted to IN.
1559
 *   - numeric: If true, the value will not be surrounded in quotes.
1560
 *   - extra type: How all the extras will be combined. Either AND or OR.
1561
 *     Defaults to AND.
1562
 */
1563
class views_join {
1564
  public $table = NULL;
1565
  public $left_table = NULL;
1566
  public $left_field = NULL;
1567
  public $field = NULL;
1568
  public $extra = NULL;
1569
  public $type = NULL;
1570
  public $definition = array();
1571

    
1572
  /**
1573
   * Construct the views_join object.
1574
   */
1575
  public function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1576
    $this->extra_type = 'AND';
1577
    if (!empty($table)) {
1578
      $this->table = $table;
1579
      $this->left_table = $left_table;
1580
      $this->left_field = $left_field;
1581
      $this->field = $field;
1582
      $this->extra = $extra;
1583
      $this->type = strtoupper($type);
1584
    }
1585
    elseif (!empty($this->definition)) {
1586
      // If no arguments, construct from definition. These four must exist or
1587
      // it will throw notices.
1588
      $this->table = $this->definition['table'];
1589
      $this->left_table = $this->definition['left_table'];
1590
      $this->left_field = $this->definition['left_field'];
1591
      $this->field = $this->definition['field'];
1592
      if (!empty($this->definition['extra'])) {
1593
        $this->extra = $this->definition['extra'];
1594
      }
1595
      if (!empty($this->definition['extra type'])) {
1596
        $this->extra_type = strtoupper($this->definition['extra type']);
1597
      }
1598

    
1599
      $this->type = !empty($this->definition['type']) ? strtoupper($this->definition['type']) : 'LEFT';
1600
    }
1601
  }
1602

    
1603
  /**
1604
   * Build the SQL for the join this object represents.
1605
   *
1606
   * When possible, try to use table alias instead of table names.
1607
   *
1608
   * @param SelectQueryInterface $select_query
1609
   *   An Implements SelectQueryInterface.
1610
   * @param string $table
1611
   *   The base table to join.
1612
   * @param views_plugin_query $view_query
1613
   *   The source query, Implements views_plugin_query.
1614
   */
1615
  public function build_join($select_query, $table, $view_query) {
1616
    if (empty($this->definition['table formula'])) {
1617
      $right_table = $this->table;
1618
    }
1619
    else {
1620
      $right_table = $this->definition['table formula'];
1621
    }
1622

    
1623
    if ($this->left_table) {
1624
      $left = $view_query->get_table_info($this->left_table);
1625
      $left_field = "$left[alias].$this->left_field";
1626
    }
1627
    else {
1628
      // This can be used if left_field is a formula or something. It should be
1629
      // used only *very* rarely.
1630
      $left_field = $this->left_field;
1631
    }
1632

    
1633
    $condition = "$left_field = $table[alias].$this->field";
1634
    $arguments = array();
1635

    
1636
    // Tack on the extra.
1637
    if (isset($this->extra)) {
1638
      // If extra has been provided as string instead of an array, convert it
1639
      // to an array.
1640
      if (!is_array($this->extra)) {
1641
        $this->extra = array($this->extra);
1642
      }
1643

    
1644
      $extras = array();
1645
      foreach ($this->extra as $info) {
1646
        if (is_array($info)) {
1647
          $extra = '';
1648
          // Figure out the table name. Remember, only use aliases provided if
1649
          // at all possible.
1650
          $join_table = '';
1651
          if (!array_key_exists('table', $info)) {
1652
            $join_table = $table['alias'] . '.';
1653
          }
1654
          elseif (isset($info['table'])) {
1655
            // If we're aware of a table alias for this table, use the table
1656
            // alias instead of the table name.
1657
            if (isset($left) && $left['table'] == $info['table']) {
1658
              $join_table = $left['alias'] . '.';
1659
            }
1660
            else {
1661
              $join_table = $info['table'] . '.';
1662
            }
1663
          }
1664

    
1665
          // Convert a single-valued array of values to the single-value case,
1666
          // and transform from IN() notation to = notation.
1667
          if (is_array($info['value']) && count($info['value']) == 1) {
1668
            if (empty($info['operator'])) {
1669
              $operator = '=';
1670
            }
1671
            else {
1672
              $operator = $info['operator'] == 'NOT IN' ? '!=' : '=';
1673
            }
1674
            $info['value'] = array_shift($info['value']);
1675
          }
1676

    
1677
          if (is_array($info['value'])) {
1678
            // With an array of values, we need multiple placeholders and the
1679
            // 'IN' operator is implicit.
1680
            foreach ($info['value'] as $value) {
1681
              $placeholder_i = $view_query->placeholder('views_join_condition_');
1682
              $arguments[$placeholder_i] = $value;
1683
            }
1684

    
1685
            $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
1686
            $placeholder = '( ' . implode(', ', array_keys($arguments)) . ' )';
1687
          }
1688
          else {
1689
            // With a single value, the '=' operator is implicit.
1690
            $operator = !empty($info['operator']) ? $info['operator'] : '=';
1691
            $placeholder = $view_query->placeholder('views_join_condition_');
1692
            $arguments[$placeholder] = $info['value'];
1693
          }
1694
          $extras[] = "$join_table$info[field] $operator $placeholder";
1695
        }
1696
        elseif (is_string($info)) {
1697
          $extras[] = $info;
1698
        }
1699
      }
1700

    
1701
      if ($extras) {
1702
        if (count($extras) == 1) {
1703
          $condition .= ' AND (' . array_shift($extras) . ')';
1704
        }
1705
        else {
1706
          $condition .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
1707
        }
1708
      }
1709
    }
1710

    
1711
    $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
1712
  }
1713
}
1714

    
1715
/**
1716
 * Join handler for relationships that join with a subquery as the left field.
1717
 *
1718
 * For example:
1719
 *  LEFT JOIN node node_term_data ON ([YOUR SUBQUERY HERE]) = node_term_data.nid
1720
 *
1721
 * 'join' definition:
1722
 *   Same as views_join class above, except:
1723
 *   - left_query: The subquery to use in the left side of the join clause.
1724
 */
1725
class views_join_subquery extends views_join {
1726

    
1727
  /**
1728
   * {@inheritdoc}
1729
   */
1730
  public function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
1731
    parent::construct($table, $left_table, $left_field, $field, $extra, $type);
1732
    $this->left_query = $this->definition['left_query'];
1733
  }
1734

    
1735
  /**
1736
   * Build the SQL for the join this object represents.
1737
   *
1738
   * @param object $select_query
1739
   *   An Implements SelectQueryInterface.
1740
   * @param string $table
1741
   *   The base table to join.
1742
   * @param array $view_query
1743
   *   The source query, Implements views_plugin_query.
1744
   */
1745
  public function build_join($select_query, $table, $view_query) {
1746
    if (empty($this->definition['table formula'])) {
1747
      $right_table = "{" . $this->table . "}";
1748
    }
1749
    else {
1750
      $right_table = $this->definition['table formula'];
1751
    }
1752

    
1753
    // Add our join condition, using a subquery on the left instead of a field.
1754
    $condition = "($this->left_query) = $table[alias].$this->field";
1755
    $arguments = array();
1756

    
1757
    // Tack on the extra.
1758
    // This is just copied verbatim from the parent class, which itself has a
1759
    // bug.
1760
    // @see http://drupal.org/node/1118100
1761
    if (isset($this->extra)) {
1762
      // If extra has been provided as string instead of an array, convert it
1763
      // to an array.
1764
      if (!is_array($this->extra)) {
1765
        $this->extra = array($this->extra);
1766
      }
1767

    
1768
      $extras = array();
1769
      foreach ($this->extra as $info) {
1770
        if (is_array($info)) {
1771
          $extra = '';
1772
          // Figure out the table name. Remember, only use aliases provided if
1773
          // at all possible.
1774
          $join_table = '';
1775
          if (!array_key_exists('table', $info)) {
1776
            $join_table = $table['alias'] . '.';
1777
          }
1778
          elseif (isset($info['table'])) {
1779
            $join_table = $info['table'] . '.';
1780
          }
1781

    
1782
          $placeholder = ':views_join_condition_' . $select_query->nextPlaceholder();
1783

    
1784
          if (is_array($info['value'])) {
1785
            $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
1786
            // Transform from IN() notation to = notation if just one value.
1787
            if (count($info['value']) == 1) {
1788
              $info['value'] = array_shift($info['value']);
1789
              $operator = $operator == 'NOT IN' ? '!=' : '=';
1790
            }
1791
          }
1792
          else {
1793
            $operator = !empty($info['operator']) ? $info['operator'] : '=';
1794
          }
1795

    
1796
          $extras[] = "$join_table$info[field] $operator $placeholder";
1797
          $arguments[$placeholder] = $info['value'];
1798
        }
1799
        elseif (is_string($info)) {
1800
          $extras[] = $info;
1801
        }
1802
      }
1803

    
1804
      if ($extras) {
1805
        if (count($extras) == 1) {
1806
          $condition .= ' AND (' . array_shift($extras) . ')';
1807
        }
1808
        else {
1809
          $condition .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
1810
        }
1811
      }
1812
    }
1813

    
1814
    $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
1815
  }
1816
}
1817

    
1818
/**
1819
 * @}
1820
 */