Projet

Général

Profil

Paste
Télécharger (55,8 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / views / plugins / views_plugin_query_default.inc @ 5d12d676

1
<?php
2

    
3
/**
4
 * @file
5
 * Definition of views_plugin_query_default.
6
 */
7

    
8
/**
9
 * Object used to create a SELECT query.
10
 *
11
 * @ingroup views_query_plugins
12
 */
13
class views_plugin_query_default extends views_plugin_query {
14

    
15
  /**
16
   * A list of tables in the order they should be added, keyed by alias.
17
   */
18
  public $table_queue = array();
19

    
20
  /**
21
   * Holds an array of tables and counts added so that we can create aliases.
22
   */
23
  public $tables = array();
24

    
25
  /**
26
   * Holds an array of relationships, which are aliases of the primary
27
   * table that represent different ways to join the same table in.
28
   */
29
  public $relationships = array();
30

    
31
  /**
32
   * An array of sections of the WHERE query. Each section is in itself
33
   * an array of pieces and a flag as to whether or not it should be AND
34
   * or OR.
35
   */
36
  public $where = array();
37

    
38
  /**
39
   * An array of sections of the HAVING query. Each section is in itself
40
   * an array of pieces and a flag as to whether or not it should be AND
41
   * or OR.
42
   */
43
  public $having = array();
44

    
45
  /**
46
   * The default operator to use when connecting the WHERE groups. May be
47
   * AND or OR.
48
   */
49
  public $group_operator = 'AND';
50

    
51
  /**
52
   * A simple array of order by clauses.
53
   */
54
  public $orderby = array();
55

    
56
  /**
57
   * A simple array of group by clauses.
58
   */
59
  public $groupby = array();
60

    
61
  /**
62
   * An array of fields.
63
   */
64
  public $fields = array();
65

    
66
  /**
67
   * The table header to use for tablesort. This matters because tablesort
68
   * needs to modify the query and needs the header.
69
   */
70
  public $header = array();
71

    
72
  /**
73
   * A flag as to whether or not to make the primary field distinct.
74
   */
75
  public $distinct = FALSE;
76

    
77
  /**
78
   *
79
   */
80
  public $has_aggregate = FALSE;
81

    
82
  /**
83
   * Should this query be optimized for counts, for example no sorts.
84
   */
85
  public $get_count_optimized = NULL;
86

    
87
  /**
88
   * The current used pager plugin.
89
   *
90
   * @var views_plugin_pager
91
   */
92
  public $pager = NULL;
93

    
94
  /**
95
    * An array mapping table aliases and field names to field aliases.
96
    */
97
  public $field_aliases = array();
98

    
99
  /**
100
    * Query tags which will be passed over to the dbtng query object.
101
    */
102
  public $tags = array();
103

    
104
  /**
105
   * Is the view marked as not distinct.
106
   *
107
   * @var bool
108
   */
109
  public $no_distinct;
110

    
111
  /**
112
   * Defines the distinct type.
113
   * - FALSE if it's distinct by base field.
114
   * - TRUE if it just adds the sql distinct keyword.
115
   *
116
   * @var bool
117
   */
118
  public $pure_distinct = FALSE;
119

    
120
  /**
121
   * {@inheritdoc}
122
   */
123
  public function init($base_table = 'node', $base_field = 'nid', $options) {
124
    parent::init($base_table, $base_field, $options);
125
    $this->base_table = $base_table;
126
    // Predefine these above, for clarity.
127
    $this->base_field = $base_field;
128
    $this->relationships[$base_table] = array(
129
      'link' => NULL,
130
      'table' => $base_table,
131
      'alias' => $base_table,
132
      'base' => $base_table,
133
    );
134

    
135
    // Unit the table queue with our primary table.
136
    $this->table_queue[$base_table] = array(
137
      'alias' => $base_table,
138
      'table' => $base_table,
139
      'relationship' => $base_table,
140
      'join' => NULL,
141
    );
142

    
143
    // Init the tables with our primary table.
144
    $this->tables[$base_table][$base_table] = array(
145
      'count' => 1,
146
      'alias' => $base_table,
147
    );
148

    
149
    // We no longer want the base field to appear automatically.
150
    // if ($base_field) {
151
    //   $this->fields[$base_field] = array(
152
    //     'table' => $base_table,
153
    //     'field' => $base_field,
154
    //     'alias' => $base_field,
155
    //   );
156
    // }
157

    
158
    $this->count_field = array(
159
      'table' => $base_table,
160
      'field' => $base_field,
161
      'alias' => $base_field,
162
      'count' => TRUE,
163
    );
164
  }
165

    
166
  /**
167
   * Utility methods to set flags and data.
168
   */
169

    
170
  /**
171
   * Set the view to be distinct.
172
   *
173
   * There are either distinct per base field or distinct in the pure sql way,
174
   * based on $pure_distinct.
175
   *
176
   * @param bool $value
177
   *   Should the view by distincted.
178
   * @param bool $pure_distinct
179
   *   Should only the sql keyword be added.
180
   */
181
  public function set_distinct($value = TRUE, $pure_distinct = FALSE) {
182
    if (!(isset($this->no_distinct) && $value)) {
183
      $this->distinct = $value;
184
      $this->pure_distinct = $pure_distinct;
185
    }
186
  }
187

    
188
  /**
189
   * Set what field the query will count() on for paging.
190
   */
191
  public function set_count_field($table, $field, $alias = NULL) {
192
    if (empty($alias)) {
193
      $alias = $table . '_' . $field;
194
    }
195
    $this->count_field = array(
196
      'table' => $table,
197
      'field' => $field,
198
      'alias' => $alias,
199
      'count' => TRUE,
200
    );
201
  }
202

    
203
  /**
204
   * Set the table header.
205
   *
206
   * Used for click-sorting because it's needed info to modify the ORDER BY
207
   * clause.
208
   */
209
  public function set_header($header) {
210
    $this->header = $header;
211
  }
212

    
213
  /**
214
   * {@inheritdoc}
215
   */
216
  public function option_definition() {
217
    $options = parent::option_definition();
218
    $options['disable_sql_rewrite'] = array(
219
      'default' => FALSE,
220
      'translatable' => FALSE,
221
      'bool' => TRUE,
222
    );
223
    $options['distinct'] = array(
224
      'default' => FALSE,
225
      'bool' => TRUE,
226
    );
227
    $options['pure_distinct'] = array(
228
      'default' => FALSE,
229
      'bool' => TRUE,
230
    );
231
    $options['slave'] = array(
232
      'default' => FALSE,
233
      'bool' => TRUE,
234
    );
235
    $options['query_comment'] = array(
236
      'default' => '',
237
    );
238
    $options['query_tags'] = array(
239
      'default' => array(),
240
    );
241

    
242
    return $options;
243
  }
244

    
245
  /**
246
   * Add settings for the ui.
247
   */
248
  public function options_form(&$form, &$form_state) {
249
    parent::options_form($form, $form_state);
250

    
251
    $form['disable_sql_rewrite'] = array(
252
      '#title' => t('Disable SQL rewriting'),
253
      '#description' => t('Disabling SQL rewriting will disable node_access checks as well as other modules that implement hook_query_alter().'),
254
      '#type' => 'checkbox',
255
      '#default_value' => !empty($this->options['disable_sql_rewrite']),
256
      '#suffix' => '<div class="messages warning sql-rewrite-warning js-hide">'
257
        . t('WARNING: Disabling SQL rewriting means that node access security is disabled. This may allow users to see data they should not be able to see if your view is misconfigured. Please use this option only if you understand and accept this security risk.')
258
        . '</div>',
259
    );
260
    $form['distinct'] = array(
261
      '#type' => 'checkbox',
262
      '#title' => t('Distinct'),
263
      '#description' => t('This will make the view display only distinct items. If there are multiple identical items, each will be displayed only once. You can use this to try and remove duplicates from a view, though it does not always work. Note that this can slow queries down, so use it with caution.'),
264
      '#default_value' => !empty($this->options['distinct']),
265
    );
266
    $form['pure_distinct'] = array(
267
      '#type' => 'checkbox',
268
      '#title' => t('Pure Distinct'),
269
      '#description' => t('This will prevent views from adding the base column to the distinct field. If this is not selected and the base column is a primary key, then a non-pure distinct will not function properly because the primary key is always unique.'),
270
      '#default_value' => !empty($this->options['pure_distinct']),
271
      '#dependency' => array('edit-query-options-distinct' => '1'),
272
    );
273
    $form['slave'] = array(
274
      '#type' => 'checkbox',
275
      '#title' => t('Use Slave Server'),
276
      '#description' => t('This will make the query attempt to connect to a slave server if available.  If no slave server is defined or available, it will fall back to the default server.'),
277
      '#default_value' => !empty($this->options['slave']),
278
    );
279
    $form['query_comment'] = array(
280
      '#type' => 'textfield',
281
      '#title' => t('Query Comment'),
282
      '#description' => t('If set, this comment will be embedded in the query and passed to the SQL server. This can be helpful for logging or debugging.'),
283
      '#default_value' => $this->options['query_comment'],
284
    );
285
    $form['query_tags'] = array(
286
      '#type' => 'textfield',
287
      '#title' => t('Query Tags'),
288
      '#description' => t('If set, these tags will be appended to the query and can be used to identify the query in a module. This can be helpful for altering queries.'),
289
      '#default_value' => implode(', ', $this->options['query_tags']),
290
      '#element_validate' => array('views_element_validate_tags'),
291
    );
292
  }
293

    
294
  /**
295
   * Special submit handling.
296
   */
297
  public function options_submit(&$form, &$form_state) {
298
    $element = array('#parents' => array('query', 'options', 'query_tags'));
299
    $value = explode(',', drupal_array_get_nested_value($form_state['values'], $element['#parents']));
300
    $value = array_filter(array_map('trim', $value));
301
    form_set_value($element, $value, $form_state);
302
  }
303

    
304
  /**
305
   * Table/join adding.
306
   */
307

    
308
  /**
309
   * A relationship is an alternative endpoint to a series of table joins.
310
   *
311
   * Relationships must be aliases of the primary table and they must join
312
   * either to the primary table or to a pre-existing relationship.
313
   *
314
   * An example of a relationship would be a nodereference table. If you have a
315
   * nodereference named 'book_parent' which links to a parent node, you could
316
   * set up a relationship 'node_book_parent' to 'node'. Then, anything that
317
   * links to 'node' can link to 'node_book_parent' instead, thus allowing all
318
   * properties of both nodes to be available in the query.
319
   *
320
   * @param string $alias
321
   *   What this relationship will be called, and is also the alias for the
322
   *   table.
323
   * @param views_join $join
324
   *   A views_join object (or derived object) to join the alias in.
325
   * @param string $base
326
   *   The name of the 'base' table this relationship represents; this tells the
327
   *   join search which path to attempt to use when finding the path to this
328
   *   relationship.
329
   * @param string $link_point
330
   *   If this relationship links to something other than the primary table,
331
   *   specify that table here. For example, a 'track' node might have a
332
   *   relationship to an 'album' node, which might have a relationship to an
333
   *   'artist' node.
334
   */
335
  public function add_relationship($alias, $join, $base, $link_point = NULL) {
336
    if (empty($link_point)) {
337
      $link_point = $this->base_table;
338
    }
339
    elseif (!array_key_exists($link_point, $this->relationships)) {
340
      return FALSE;
341
    }
342

    
343
    // Make sure $alias isn't already used; if it, start adding stuff.
344
    $alias_base = $alias;
345
    $count = 1;
346
    while (!empty($this->relationships[$alias])) {
347
      $alias = $alias_base . '_' . $count++;
348
    }
349

    
350
    // Make sure this join is adjusted for our relationship.
351
    if ($link_point && isset($this->relationships[$link_point])) {
352
      $join = $this->adjust_join($join, $link_point);
353
    }
354

    
355
    // Add the table directly to the queue to avoid accidentally marking it.
356
    $this->table_queue[$alias] = array(
357
      'table' => $join->table,
358
      'num' => 1,
359
      'alias' => $alias,
360
      'join' => $join,
361
      'relationship' => $link_point,
362
    );
363

    
364
    $this->relationships[$alias] = array(
365
      'link' => $link_point,
366
      'table' => $join->table,
367
      'base' => $base,
368
    );
369

    
370
    $this->tables[$this->base_table][$alias] = array(
371
      'count' => 1,
372
      'alias' => $alias,
373
    );
374

    
375
    return $alias;
376
  }
377

    
378
  /**
379
   * Add a table to the query, ensuring the path exists.
380
   *
381
   * This function will test to ensure that the path back to the primary table
382
   * is valid and exists; if you do not wish for this testing to occur, use
383
   * $query->queue_table() instead.
384
   *
385
   * @param string $table
386
   *   The name of the table to add. It needs to exist in the global table
387
   *   array.
388
   * @param string $relationship
389
   *   An alias of a table; if this is set, the path back to this table will be
390
   *   tested prior to adding the table, making sure that all intermediary
391
   *   tables exist and are properly aliased. If set to NULL the path to the
392
   *   primary table will be ensured. If the path cannot be made, the table
393
   *   will NOT be added.
394
   * @param views_join $join
395
   *   In some join configurations this table may actually join back through a
396
   *   different method; this is most likely to be used when tracing a
397
   *   hierarchy path. (node->parent->parent2->parent3). This parameter will
398
   *   specify how this table joins if it is not the default.
399
   * @param string $alias
400
   *   A specific alias to use, rather than the default alias.
401
   *
402
   * @return string
403
   *   The alias of the table; this alias can be used to access information
404
   *   about the table and should always be used to refer to the table when
405
   *   adding parts to the query. Or FALSE if the table was not able to be
406
   *   added.
407
   */
408
  public function add_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
409
    if (!$this->ensure_path($table, $relationship, $join)) {
410
      return FALSE;
411
    }
412

    
413
    if ($join && $relationship) {
414
      $join = $this->adjust_join($join, $relationship);
415
    }
416

    
417
    return $this->queue_table($table, $relationship, $join, $alias);
418
  }
419

    
420
  /**
421
   * Add a table to the query without ensuring the path.
422
   *
423
   * This is a pretty internal function to Views and add_table() or
424
   * ensure_table() should be used instead of this one, unless you are
425
   * absolutely sure this is what you want.
426
   *
427
   * @param string $table
428
   *   The name of the table to add. It needs to exist in the global table
429
   *   array.
430
   * @param string $relationship
431
   *   The primary table alias this table is related to. If not set, the
432
   *   primary table will be used.
433
   * @param views_join $join
434
   *   In some join configurations this table may actually join back through a
435
   *   different method; this is most likely to be used when tracing a
436
   *   hierarchy path. (node->parent->parent2->parent3). This parameter will
437
   *   specify how this table joins if it is not the default.
438
   * @param string $alias
439
   *   A specific alias to use, rather than the default alias.
440
   *
441
   * @return string
442
   *   The alias of the table; this alias can be used to access information
443
   *   about the table and should always be used to refer to the table when
444
   *   adding parts to the query. Or FALSE if the table was not able to be
445
   *   added.
446
   */
447
  public function queue_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
448
    // If the alias is set, make sure it doesn't already exist.
449
    if (isset($this->table_queue[$alias])) {
450
      return $alias;
451
    }
452

    
453
    if (empty($relationship)) {
454
      $relationship = $this->base_table;
455
    }
456

    
457
    if (!array_key_exists($relationship, $this->relationships)) {
458
      return FALSE;
459
    }
460

    
461
    if (!$alias && $join && $relationship && !empty($join->adjusted) && $table != $join->table) {
462
      if ($relationship == $this->base_table) {
463
        $alias = $table;
464
      }
465
      else {
466
        $alias = $relationship . '_' . $table;
467
      }
468
    }
469

    
470
    // Check this again to make sure we don't blow up existing aliases for
471
    // already adjusted joins.
472
    if (isset($this->table_queue[$alias])) {
473
      return $alias;
474
    }
475

    
476
    $alias = $this->mark_table($table, $relationship, $alias);
477

    
478
    // If no alias is specified, give it the default.
479
    if (!isset($alias)) {
480
      $alias = $this->tables[$relationship][$table]['alias'] . $this->tables[$relationship][$table]['count'];
481
    }
482

    
483
    // If this is a relationship based table, add a marker with the
484
    // relationship as a primary table for the alias.
485
    if ($table != $alias) {
486
      $this->mark_table($alias, $this->base_table, $alias);
487
    }
488

    
489
    // If no join is specified, pull it from the table data.
490
    if (!isset($join)) {
491
      $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
492
      if (empty($join)) {
493
        return FALSE;
494
      }
495

    
496
      $join = $this->adjust_join($join, $relationship);
497
    }
498

    
499
    $this->table_queue[$alias] = array(
500
      'table' => $table,
501
      'num' => $this->tables[$relationship][$table]['count'],
502
      'alias' => $alias,
503
      'join' => $join,
504
      'relationship' => $relationship,
505
    );
506

    
507
    return $alias;
508
  }
509

    
510
  /**
511
   *
512
   */
513
  public function mark_table($table, $relationship, $alias) {
514
    // Mark that this table has been added.
515
    if (empty($this->tables[$relationship][$table])) {
516
      if (!isset($alias)) {
517
        $alias = '';
518
        if ($relationship != $this->base_table) {
519
          // Double underscore will help prevent accidental name space
520
          // collisions.
521
          $alias = $relationship . '__';
522
        }
523
        $alias .= $table;
524
      }
525
      $this->tables[$relationship][$table] = array(
526
        'count' => 1,
527
        'alias' => $alias,
528
      );
529
    }
530
    else {
531
      $this->tables[$relationship][$table]['count']++;
532
    }
533

    
534
    return $alias;
535
  }
536

    
537
  /**
538
   * Ensure a table exists in the queue; if it already exists it won't do
539
   * anything, but if it doesn't it will add the table queue. It will ensure a
540
   * path leads back to the relationship table.
541
   *
542
   * @param string $table
543
   *   The unaliased name of the table to ensure.
544
   * @param string $relationship
545
   *   The relationship to ensure the table links to. Each relationship will
546
   *   get a unique instance of the table being added. If not specified, will
547
   *   be the primary table.
548
   * @param views_join $join
549
   *   A views_join object (or derived object) to join the alias in.
550
   *
551
   * @return string
552
   *   The alias used to refer to this specific table, or NULL if the table
553
   *   cannot be ensured.
554
   */
555
  public function ensure_table($table, $relationship = NULL, $join = NULL) {
556
    // Ensure a relationship.
557
    if (empty($relationship)) {
558
      $relationship = $this->base_table;
559
    }
560

    
561
    // If the relationship is the primary table, this actually be a relationship
562
    // link back from an alias. We store all aliases along with the primary
563
    // table to detect this state, because eventually it'll hit a table we
564
    // already have and that's when we want to stop.
565
    if ($relationship == $this->base_table && !empty($this->tables[$relationship][$table])) {
566
      return $this->tables[$relationship][$table]['alias'];
567
    }
568

    
569
    if (!array_key_exists($relationship, $this->relationships)) {
570
      return FALSE;
571
    }
572

    
573
    if ($table == $this->relationships[$relationship]['base']) {
574
      return $relationship;
575
    }
576

    
577
    // If we do not have join info, fetch it.
578
    if (!isset($join)) {
579
      $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
580
    }
581

    
582
    // If it can't be fetched, this won't work.
583
    if (empty($join)) {
584
      return;
585
    }
586

    
587
    // Adjust this join for the relationship, which will ensure that the 'base'
588
    // table it links to is correct. Tables adjoined to a relationship
589
    // join to a link point, not the base table.
590
    $join = $this->adjust_join($join, $relationship);
591

    
592
    if ($this->ensure_path($table, $relationship, $join)) {
593
      // Attempt to eliminate redundant joins.  If this table's relationship
594
      // and join exactly matches an existing table's relationship and join, we
595
      // do not have to join to it again; just return the existing table's
596
      // alias.
597
      // @see http://groups.drupal.org/node/11288
598
      //
599
      // This can be done safely here but not lower down in queue_table(),
600
      // because queue_table() is also used by add_table() which requires the
601
      // ability to intentionally add the same table with the same join
602
      // multiple times.  For example, a view that filters on 3 taxonomy terms
603
      // using AND needs to join taxonomy_term_data 3 times with the same join.
604
      // scan through the table queue to see if a matching join and
605
      // relationship exists.  If so, use it instead of this join.
606
      // @todo Scanning through $this->table_queue results in an O(N^2)
607
      // algorithm, and this code runs every time the view is instantiated
608
      // (Views 2 does not currently cache queries). There are a couple
609
      // possible "improvements" but we should do some performance testing
610
      // before picking one.
611
      foreach ($this->table_queue as $queued_table) {
612
        // In PHP 4 and 5, the == operation returns TRUE for two objects if
613
        // they are instances of the same class and have the same attributes
614
        // and values.
615
        if ($queued_table['relationship'] == $relationship && $queued_table['join'] == $join) {
616
          return $queued_table['alias'];
617
        }
618
      }
619

    
620
      return $this->queue_table($table, $relationship, $join);
621
    }
622
  }
623

    
624
  /**
625
   * Make sure that the specified table can be properly linked to the primary
626
   * table in the JOINs. This function uses recursion. If the tables needed
627
   * to complete the path back to the primary table are not in the query they
628
   * will be added, but additional copies will NOT be added if the table is
629
   * already there.
630
   */
631
  public function ensure_path($table, $relationship = NULL, $join = NULL, $traced = array(), $add = array()) {
632
    if (!isset($relationship)) {
633
      $relationship = $this->base_table;
634
    }
635

    
636
    if (!array_key_exists($relationship, $this->relationships)) {
637
      return FALSE;
638
    }
639

    
640
    // If we do not have join info, fetch it.
641
    if (!isset($join)) {
642
      $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
643
    }
644

    
645
    // If it can't be fetched, this won't work.
646
    if (empty($join)) {
647
      return FALSE;
648
    }
649

    
650
    // Does a table along this path exist?
651
    if (isset($this->tables[$relationship][$table]) ||
652
      ($join && $join->left_table == $relationship) ||
653
      ($join && $join->left_table == $this->relationships[$relationship]['table'])) {
654

    
655
      // Make sure that we're linking to the correct table for our relationship.
656
      foreach (array_reverse($add) as $table => $path_join) {
657
        $this->queue_table($table, $relationship, $this->adjust_join($path_join, $relationship));
658
      }
659
      return TRUE;
660
    }
661

    
662
    // Have we been this way?
663
    if (isset($traced[$join->left_table])) {
664
      // We looped. Broked.
665
      return FALSE;
666
    }
667

    
668
    // Do we have to add this table?
669
    $left_join = $this->get_join_data($join->left_table, $this->relationships[$relationship]['base']);
670
    if (!isset($this->tables[$relationship][$join->left_table])) {
671
      $add[$join->left_table] = $left_join;
672
    }
673

    
674
    // Keep looking.
675
    $traced[$join->left_table] = TRUE;
676
    return $this->ensure_path($join->left_table, $relationship, $left_join, $traced, $add);
677
  }
678

    
679
  /**
680
   * Fix a join to adhere to the proper relationship.
681
   *
682
   * The left table can vary based upon what relationship items are joined in
683
   * on.
684
   */
685
  public function adjust_join($join, $relationship) {
686
    if (!empty($join->adjusted)) {
687
      return $join;
688
    }
689

    
690
    if (empty($relationship) || empty($this->relationships[$relationship])) {
691
      return $join;
692
    }
693

    
694
    // Adjusts the left table for our relationship.
695
    if ($relationship != $this->base_table) {
696
      // If we're linking to the primary table, the relationship to use will
697
      // be the prior relationship. Unless it's a direct link. Safety! Don't
698
      // modify an original here.
699
      $join = clone $join;
700

    
701
      // Do we need to try to ensure a path?
702
      if ($join->left_table != $this->relationships[$relationship]['table'] &&
703
          $join->left_table != $this->relationships[$relationship]['base'] &&
704
          !isset($this->tables[$relationship][$join->left_table]['alias'])) {
705
        $this->ensure_table($join->left_table, $relationship);
706
      }
707

    
708
      // First, if this is our link point/anchor table, just use the
709
      // relationship.
710
      if ($join->left_table == $this->relationships[$relationship]['table']) {
711
        $join->left_table = $relationship;
712
      }
713
      // then, try the base alias.
714
      elseif (isset($this->tables[$relationship][$join->left_table]['alias'])) {
715
        $join->left_table = $this->tables[$relationship][$join->left_table]['alias'];
716
      }
717
      // But if we're already looking at an alias, use that instead.
718
      elseif (isset($this->table_queue[$relationship]['alias'])) {
719
        $join->left_table = $this->table_queue[$relationship]['alias'];
720
      }
721
    }
722

    
723
    $join->adjusted = TRUE;
724
    return $join;
725
  }
726

    
727
  /**
728
   * Retrieve join data from the larger join data cache.
729
   *
730
   * @param string $table
731
   *   The table to get the join information for.
732
   * @param string $base_table
733
   *   The path we're following to get this join.
734
   *
735
   * @return views_join
736
   *   A views_join object or child object, if one exists.
737
   */
738
  public function get_join_data($table, $base_table) {
739
    // Check to see if we're linking to a known alias. If so, get the real
740
    // table's data instead.
741
    if (!empty($this->table_queue[$table])) {
742
      $table = $this->table_queue[$table]['table'];
743
    }
744
    return views_get_table_join($table, $base_table);
745
  }
746

    
747
  /**
748
   * Get the information associated with a table.
749
   *
750
   * If you need the alias of a table with a particular relationship, use
751
   * ensure_table().
752
   */
753
  public function get_table_info($table) {
754
    if (!empty($this->table_queue[$table])) {
755
      return $this->table_queue[$table];
756
    }
757

    
758
    // In rare cases we might *only* have aliased versions of the table.
759
    if (!empty($this->tables[$this->base_table][$table])) {
760
      $alias = $this->tables[$this->base_table][$table]['alias'];
761
      if (!empty($this->table_queue[$alias])) {
762
        return $this->table_queue[$alias];
763
      }
764
    }
765
  }
766

    
767
  /**
768
   * Add a field to the query table, possibly with an alias. This will
769
   * automatically call ensure_table to make sure the required table exists,
770
   * *unless* $table is unset.
771
   *
772
   * @param string $table
773
   *   The table this field is attached to. If NULL, it is assumed this will be
774
   *   a formula; otherwise, ensure_table is used to make sure the table exists.
775
   * @param string $field
776
   *   The name of the field to add. This may be a real field or a formula.
777
   * @param string $alias
778
   *   The alias to create. If not specified, the alias will be $table_$field
779
   *   unless $table is NULL. When adding formulae, it is recommended that an
780
   *   alias be used.
781
   * @param array $params
782
   *   An array of parameters additional to the field that will control items
783
   *   such as aggregation functions and DISTINCT.
784
   *
785
   * @return string
786
   *   The name that this field can be referred to as, usually the alias.
787
   */
788
  public function add_field($table, $field, $alias = '', $params = array()) {
789
    // We check for this specifically because it gets a special alias.
790
    if ($table == $this->base_table && $field == $this->base_field && empty($alias)) {
791
      $alias = $this->base_field;
792
    }
793

    
794
    if ($table && empty($this->table_queue[$table])) {
795
      $this->ensure_table($table);
796
    }
797

    
798
    if (!$alias && $table) {
799
      $alias = $table . '_' . $field;
800
    }
801

    
802
    // Make sure an alias is assigned.
803
    $alias = $alias ? $alias : $field;
804

    
805
    // PostgreSQL truncates aliases to 63 characters.
806
    // @see http://drupal.org/node/571548
807
    // We limit the length of the original alias up to 60 characters
808
    // to get a unique alias later if its have duplicates.
809
    $alias = strtolower(substr($alias, 0, 60));
810

    
811
    // Create a field info array.
812
    $field_info = array(
813
      'field' => $field,
814
      'table' => $table,
815
      'alias' => $alias,
816
    ) + $params;
817

    
818
    // Test to see if the field is actually the same or not. Due to differing
819
    // parameters changing the aggregation function, we need to do some
820
    // automatic alias collision detection.
821
    $base = $alias;
822
    $counter = 0;
823
    while (!empty($this->fields[$alias]) && $this->fields[$alias] != $field_info) {
824
      $field_info['alias'] = $alias = $base . '_' . ++$counter;
825
    }
826

    
827
    if (empty($this->fields[$alias])) {
828
      $this->fields[$alias] = $field_info;
829
    }
830

    
831
    // Keep track of all aliases used.
832
    $this->field_aliases[$table][$field] = $alias;
833

    
834
    return $alias;
835
  }
836

    
837
  /**
838
   * Remove all fields that may've been added.
839
   *
840
   * Primarily used for summary mode where we're changing the query because we
841
   * didn't get data we needed.
842
   */
843
  public function clear_fields() {
844
    $this->fields = array();
845
  }
846

    
847
  /**
848
   * Add a simple WHERE clause to the query.
849
   *
850
   * The caller is responsible for ensuring that all fields are fully qualified
851
   * (TABLE.FIELD) and that the table already exists in the query.
852
   *
853
   * @param string $group
854
   *   The WHERE group to add these to; groups are used to create AND/OR
855
   *   sections. Groups cannot be nested. Use 0 as the default group. If the
856
   *   group does not yet exist it will be created as an AND group.
857
   * @param string $field
858
   *   The name of the field to check.
859
   * @param string $value
860
   *   The value to test the field against. In most cases, this is a scalar. For
861
   *   more complex options, it is an array. The meaning of each element in the
862
   *   array is dependent on the $operator.
863
   * @param string $operator
864
   *   The comparison operator, such as =, <, or >=. It also accepts more
865
   *   complex options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is
866
   *   an array = otherwise. If $field is a string you have to use 'formula'
867
   *   here.
868
   *
869
   * The $field, $value and $operator arguments can also be passed in with a
870
   * single DatabaseCondition object, like this:
871
   *
872
   * @code
873
   *   $this->query->add_where(
874
   *     $this->options['group'],
875
   *     db_or()
876
   *       ->condition($field, $value, 'NOT IN')
877
   *       ->condition($field, $value, 'IS NULL')
878
   *   );
879
   * @endcode
880
   *
881
   * @see QueryConditionInterface::condition()
882
   * @see DatabaseCondition
883
   */
884
  public function add_where($group, $field, $value = NULL, $operator = NULL) {
885
    // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all the
886
    // default group.
887
    if (empty($group)) {
888
      $group = 0;
889
    }
890

    
891
    // Check for a group.
892
    if (!isset($this->where[$group])) {
893
      $this->set_where_group('AND', $group);
894
    }
895

    
896
    $this->where[$group]['conditions'][] = array(
897
      'field' => $field,
898
      'value' => $value,
899
      'operator' => $operator,
900
    );
901
  }
902

    
903
  /**
904
   * Add a complex WHERE clause to the query.
905
   *
906
   * The caller is responsible for ensuring that all fields are fully qualified
907
   * (TABLE.FIELD) and that the table already exists in the query. Internally
908
   * the dbtng method "where" is used.
909
   *
910
   * @param string $group
911
   *   The WHERE group to add these to; groups are used to create AND/OR
912
   *   sections. Groups cannot be nested. Use 0 as the default group. If the
913
   *   group does not yet exist it will be created as an AND group.
914
   * @param string $snippet
915
   *   The snippet to check. This can be either a column or a complex expression
916
   *   like "UPPER(table.field) = 'value'".
917
   * @param array $args
918
   *   An associative array of arguments.
919
   *
920
   * @see QueryConditionInterface::where()
921
   */
922
  public function add_where_expression($group, $snippet, $args = array()) {
923
    // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
924
    // the default group.
925
    if (empty($group)) {
926
      $group = 0;
927
    }
928

    
929
    // Check for a group.
930
    if (!isset($this->where[$group])) {
931
      $this->set_where_group('AND', $group);
932
    }
933

    
934
    $this->where[$group]['conditions'][] = array(
935
      'field' => $snippet,
936
      'value' => $args,
937
      'operator' => 'formula',
938
    );
939
  }
940

    
941
  /**
942
   * Add a simple HAVING clause to the query.
943
   *
944
   * The caller is responsible for ensuring that all fields are fully qualified
945
   * (TABLE.FIELD) and that the table and an appropriate GROUP BY already exist
946
   * in the query. Internally the dbtng method "havingCondition" is used.
947
   *
948
   * @param string $group
949
   *   The HAVING group to add these to; groups are used to create AND/OR
950
   *   sections. Groups cannot be nested. Use 0 as the default group. If the
951
   *   group does not yet exist it will be created as an AND group.
952
   * @param string $field
953
   *   The name of the field to check.
954
   * @param string $value
955
   *   The value to test the field against. In most cases, this is a scalar. For
956
   *   more complex options, it is an array. The meaning of each element in the
957
   *   array is dependent on the $operator.
958
   * @param string $operator
959
   *   The comparison operator, such as =, <, or >=. It also accepts more
960
   *   complex options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is
961
   *   an array = otherwise.  If $field is a string you have to use 'formula'
962
   *   here.
963
   *
964
   * @see SelectQueryInterface::havingCondition()
965
   */
966
  public function add_having($group, $field, $value = NULL, $operator = NULL) {
967
    // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all the
968
    // default group.
969
    if (empty($group)) {
970
      $group = 0;
971
    }
972

    
973
    // Check for a group.
974
    if (!isset($this->having[$group])) {
975
      $this->set_where_group('AND', $group, 'having');
976
    }
977

    
978
    // Add the clause and the args.
979
    $this->having[$group]['conditions'][] = array(
980
      'field' => $field,
981
      'value' => $value,
982
      'operator' => $operator,
983
    );
984
  }
985

    
986
  /**
987
   * Add a complex HAVING clause to the query.
988
   *
989
   * The caller is responsible for ensuring that all fields are fully qualified
990
   * (TABLE.FIELD) and that the table and an appropriate GROUP BY already exist
991
   * in the query. Internally the dbtng method "having" is used.
992
   *
993
   * @param string $group
994
   *   The HAVING group to add these to; groups are used to create AND/OR
995
   *   sections. Groups cannot be nested. Use 0 as the default group. If the
996
   *   group does not yet exist it will be created as an AND group.
997
   * @param string $snippet
998
   *   The snippet to check. This can be either a column or a complex
999
   *   expression like "COUNT(table.field) > 3"
1000
   * @param array $args
1001
   *   An associative array of arguments.
1002
   *
1003
   * @see QueryConditionInterface::having()
1004
   */
1005
  public function add_having_expression($group, $snippet, $args = array()) {
1006
    // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
1007
    // the default group.
1008
    if (empty($group)) {
1009
      $group = 0;
1010
    }
1011

    
1012
    // Check for a group.
1013
    if (!isset($this->having[$group])) {
1014
      $this->set_where_group('AND', $group, 'having');
1015
    }
1016

    
1017
    // Add the clause and the args.
1018
    $this->having[$group]['conditions'][] = array(
1019
      'field' => $snippet,
1020
      'value' => $args,
1021
      'operator' => 'formula',
1022
    );
1023
  }
1024

    
1025
  /**
1026
   * Add an ORDER BY clause to the query.
1027
   *
1028
   * @param string $table
1029
   *   The table this field is part of. If a formula, enter NULL. If you want to
1030
   *   orderby random use "rand" as table and nothing else.
1031
   * @param string $field
1032
   *   The field or formula to sort on. If already a field, enter NULL and put
1033
   *   in the alias.
1034
   * @param string $order
1035
   *   Either ASC or DESC.
1036
   * @param string $alias
1037
   *   The alias to add the field as. In SQL, all fields in the order by must
1038
   *   also be in the SELECT portion. If an $alias isn't specified one will be
1039
   *   generated for from the $field; however, if the $field is a formula, this
1040
   *   alias will likely fail.
1041
   * @param string $params
1042
   *   Any params that should be passed through to the add_field.
1043
   */
1044
  public function add_orderby($table, $field = NULL, $order = 'ASC', $alias = '', $params = array()) {
1045
    // Only ensure the table if it's not the special random key.
1046
    // @todo Maybe it would make sense to just add a add_orderby_rand or
1047
    // something similar.
1048
    if ($table && $table != 'rand') {
1049
      $this->ensure_table($table);
1050
    }
1051

    
1052
    // Only fill out this aliasing if there is a table; otherwise we assume it
1053
    // is a formula.
1054
    if (!$alias && $table) {
1055
      $as = $table . '_' . $field;
1056
    }
1057
    else {
1058
      $as = $alias;
1059
    }
1060

    
1061
    if ($field) {
1062
      $as = $this->add_field($table, $field, $as, $params);
1063
    }
1064

    
1065
    $this->orderby[] = array(
1066
      'field' => $as,
1067
      'direction' => strtoupper($order),
1068
    );
1069
  }
1070

    
1071
  /**
1072
   * Add a simple GROUP BY clause to the query.
1073
   *
1074
   * The caller is responsible for ensuring that the fields are fully qualified
1075
   * and the table is properly added.
1076
   */
1077
  public function add_groupby($clause) {
1078
    // Only add it if it's not already in there.
1079
    if (!in_array($clause, $this->groupby)) {
1080
      $this->groupby[] = $clause;
1081
    }
1082
  }
1083

    
1084
  /**
1085
   * Returns the alias for the given field added to $table.
1086
   *
1087
   * @see views_plugin_query_default::add_field()
1088
   */
1089
  public function get_field_alias($table_alias, $field) {
1090
    return isset($this->field_aliases[$table_alias][$field]) ? $this->field_aliases[$table_alias][$field] : FALSE;
1091
  }
1092

    
1093
  /**
1094
   * Adds a query tag to the sql object.
1095
   *
1096
   * @see SelectQuery::addTag()
1097
   */
1098
  public function add_tag($tag) {
1099
    $this->tags[] = $tag;
1100
  }
1101

    
1102
  /**
1103
   * Generates a unique placeholder used in the db query.
1104
   */
1105
  public function placeholder($base = 'views') {
1106
    static $placeholders = array();
1107
    if (!isset($placeholders[$base])) {
1108
      $placeholders[$base] = 0;
1109
      return ':' . $base;
1110
    }
1111
    else {
1112
      return ':' . $base . ++$placeholders[$base];
1113
    }
1114
  }
1115

    
1116
  /**
1117
   * Construct the "WHERE" or "HAVING" part of the query.
1118
   *
1119
   * As views has to wrap the conditions from arguments with AND, a special
1120
   * group is wrapped around all conditions. This special group has the ID 0.
1121
   * There is other code in filters which makes sure that the group IDs are
1122
   * higher than zero.
1123
   *
1124
   * @param string $where
1125
   *   Either 'where' or 'having'.
1126
   */
1127
  public function build_condition($where = 'where') {
1128
    $has_condition = FALSE;
1129
    $has_arguments = FALSE;
1130
    $has_filter = FALSE;
1131

    
1132
    $main_group = db_and();
1133
    $filter_group = $this->group_operator == 'OR' ? db_or() : db_and();
1134

    
1135
    foreach ($this->$where as $group => $info) {
1136

    
1137
      if (!empty($info['conditions'])) {
1138
        $sub_group = $info['type'] == 'OR' ? db_or() : db_and();
1139
        foreach ($info['conditions'] as $key => $clause) {
1140
          // DBTNG doesn't support to add the same subquery twice to the main
1141
          // query and the count query, so clone the subquery to have two
1142
          // instances of the same object.
1143
          // @see http://drupal.org/node/1112854
1144
          if (is_object($clause['value']) && $clause['value'] instanceof SelectQuery) {
1145
            $clause['value'] = clone $clause['value'];
1146
          }
1147
          if ($clause['operator'] == 'formula') {
1148
            $has_condition = TRUE;
1149
            $sub_group->where($clause['field'], $clause['value']);
1150
          }
1151
          else {
1152
            $has_condition = TRUE;
1153
            $sub_group->condition($clause['field'], $clause['value'], $clause['operator']);
1154
          }
1155
        }
1156

    
1157
        // Add the item to the filter group.
1158
        if ($group != 0) {
1159
          $has_filter = TRUE;
1160
          $filter_group->condition($sub_group);
1161
        }
1162
        else {
1163
          $has_arguments = TRUE;
1164
          $main_group->condition($sub_group);
1165
        }
1166
      }
1167
    }
1168

    
1169
    if ($has_filter) {
1170
      $main_group->condition($filter_group);
1171
    }
1172

    
1173
    if (!$has_arguments && $has_condition) {
1174
      return $filter_group;
1175
    }
1176
    if ($has_arguments && $has_condition) {
1177
      return $main_group;
1178
    }
1179
  }
1180

    
1181
  /**
1182
   * Build fields array.
1183
   */
1184
  public function compile_fields($fields_array, $query) {
1185
    $non_aggregates = array();
1186
    foreach ($fields_array as $field) {
1187
      $string = '';
1188
      if (!empty($field['table'])) {
1189
        $string .= $field['table'] . '.';
1190
      }
1191
      $string .= $field['field'];
1192
      $fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
1193

    
1194
      if (!empty($field['distinct'])) {
1195
        throw new Exception("Column-level distinct is not supported anymore.");
1196
      }
1197

    
1198
      if (!empty($field['count'])) {
1199
        // Retained for compatibility.
1200
        $field['function'] = 'count';
1201
        // It seems there's no way to abstract the table+column reference
1202
        // without adding a field, aliasing, and then using the alias.
1203
      }
1204

    
1205
      if (!empty($field['function'])) {
1206
        $info = $this->get_aggregation_info();
1207
        if (!empty($info[$field['function']]['method']) && function_exists($info[$field['function']]['method'])) {
1208
          $string = $info[$field['function']]['method']($field['function'], $string);
1209
          $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : array();
1210
          $query->addExpression($string, $fieldname, $placeholders);
1211
        }
1212

    
1213
        $this->has_aggregate = TRUE;
1214
      }
1215
      // This is a formula, using no tables.
1216
      elseif (empty($field['table'])) {
1217
        if (!in_array($fieldname, $non_aggregates)) {
1218
          $non_aggregates[] = $fieldname;
1219
        }
1220
        $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : array();
1221
        $query->addExpression($string, $fieldname, $placeholders);
1222
      }
1223

    
1224
      elseif ($this->distinct && !in_array($fieldname, $this->groupby)) {
1225
        // d7cx: This code was there, apparently needed for PostgreSQL
1226
        // $string = db_driver() == 'pgsql' ? "FIRST($string)" : $string;
1227
        if (!in_array($string, $non_aggregates)) {
1228
          $non_aggregates[] = $string;
1229
        }
1230
        $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
1231
      }
1232
      elseif (empty($field['aggregate'])) {
1233
        if (!in_array($string, $non_aggregates)) {
1234
          $non_aggregates[] = $string;
1235
        }
1236
        $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
1237
      }
1238

    
1239
      // @todo Remove this old code.
1240
      if (!empty($field['distinct']) && empty($field['function'])) {
1241
        $distinct[] = $string;
1242
      }
1243
      else {
1244
        $fields[] = $string;
1245
      }
1246

    
1247
      if ($this->get_count_optimized) {
1248
        // We only want the first field in this case.
1249
        break;
1250
      }
1251
    }
1252
    return array(
1253
      $non_aggregates,
1254
    );
1255
  }
1256

    
1257
  /**
1258
   * Generate a query and a countquery from all of the information supplied
1259
   * to the object.
1260
   *
1261
   * @param bool $get_count
1262
   *   Provide a countquery if this is true, otherwise provide a normal query.
1263
   *
1264
   * @return SelectQuery
1265
   *   A SelectQuery object.
1266
   */
1267
  public function query($get_count = FALSE) {
1268
    // Check query distinct value.
1269
    if (empty($this->no_distinct) && $this->distinct && !empty($this->fields)) {
1270
      if ($this->pure_distinct === FALSE) {
1271
        $base_field_alias = $this->add_field($this->base_table, $this->base_field);
1272
        $this->add_groupby($base_field_alias);
1273
      }
1274
      $distinct = TRUE;
1275
    }
1276

    
1277
    // An optimized count query includes just the base field instead of all the
1278
    // fields. Determine of this query qualifies by checking for a groupby or
1279
    // distinct.
1280
    $fields_array = $this->fields;
1281
    if ($get_count && !$this->groupby) {
1282
      foreach ($fields_array as $field) {
1283
        if (!empty($field['distinct']) || !empty($field['function'])) {
1284
          $this->get_count_optimized = FALSE;
1285
          break;
1286
        }
1287
      }
1288
    }
1289
    else {
1290
      $this->get_count_optimized = FALSE;
1291
    }
1292
    if (!isset($this->get_count_optimized)) {
1293
      $this->get_count_optimized = TRUE;
1294
    }
1295

    
1296
    $options = array();
1297
    $target = 'default';
1298
    $key = 'default';
1299
    // Detect an external database and set the.
1300
    if (isset($this->view->base_database)) {
1301
      $key = $this->view->base_database;
1302
    }
1303

    
1304
    // Set the slave target if the slave option is set.
1305
    if (!empty($this->options['slave'])) {
1306
      $target = 'slave';
1307
    }
1308

    
1309
    // Go ahead and build the query. db_select doesn't support to specify the
1310
    // key, so use getConnection directly.
1311
    $query = Database::getConnection($target, $key)
1312
      ->select($this->base_table, $this->base_table, $options)
1313
      ->addTag('views')
1314
      ->addTag('views_' . $this->view->name);
1315

    
1316
    // Add the tags added to the view itself.
1317
    foreach ($this->tags as $tag) {
1318
      $query->addTag($tag);
1319
    }
1320

    
1321
    if (!empty($distinct)) {
1322
      $query->distinct();
1323
    }
1324

    
1325
    $joins = $where = $having = $orderby = $groupby = '';
1326
    $fields = $distinct = array();
1327

    
1328
    // Add all the tables to the query via joins. We assume all LEFT joins.
1329
    foreach ($this->table_queue as $table) {
1330
      if (is_object($table['join'])) {
1331
        $table['join']->build_join($query, $table, $this);
1332
      }
1333
    }
1334

    
1335
    $this->has_aggregate = FALSE;
1336
    $non_aggregates = array();
1337

    
1338
    list($non_aggregates) = $this->compile_fields($fields_array, $query);
1339

    
1340
    if (count($this->having)) {
1341
      $this->has_aggregate = TRUE;
1342
    }
1343
    elseif (!$this->has_aggregate) {
1344
      // Allow 'GROUP BY' even no aggregation function has been set.
1345
      $this->has_aggregate = $this->view->display_handler->get_option('group_by');
1346
    }
1347
    if ($this->has_aggregate && (!empty($this->groupby) || !empty($non_aggregates))) {
1348
      $groupby = array_unique(array_merge($this->groupby, $non_aggregates));
1349
      foreach ($groupby as $field) {
1350
        $query->groupBy($field);
1351
      }
1352
      if (!empty($this->having) && $condition = $this->build_condition('having')) {
1353
        $query->havingCondition($condition);
1354
      }
1355
    }
1356

    
1357
    if (!$this->get_count_optimized) {
1358
      // We only add the orderby if we're not counting.
1359
      if ($this->orderby) {
1360
        foreach ($this->orderby as $order) {
1361
          if ($order['field'] == 'rand_') {
1362
            $query->orderRandom();
1363
          }
1364
          else {
1365
            $query->orderBy($order['field'], $order['direction']);
1366
          }
1367
        }
1368
      }
1369
    }
1370

    
1371
    if (!empty($this->where) && $condition = $this->build_condition('where')) {
1372
      $query->condition($condition);
1373
    }
1374

    
1375
    // Add a query comment.
1376
    if (!empty($this->options['query_comment'])) {
1377
      $query->comment($this->options['query_comment']);
1378
    }
1379

    
1380
    // Add the query tags.
1381
    if (!empty($this->options['query_tags'])) {
1382
      foreach ($this->options['query_tags'] as $tag) {
1383
        $query->addTag($tag);
1384
      }
1385
    }
1386

    
1387
    // Add all query substitutions as metadata.
1388
    $query->addMetaData('views_substitutions', module_invoke_all('views_query_substitutions', $this->view));
1389

    
1390
    if (!$get_count) {
1391
      if (!empty($this->limit) || !empty($this->offset)) {
1392
        // We can't have an offset without a limit, so provide a very large
1393
        // limit instead.
1394
        $limit  = intval(!empty($this->limit) ? $this->limit : 999999);
1395
        $offset = intval(!empty($this->offset) ? $this->offset : 0);
1396
        $query->range($offset, $limit);
1397
      }
1398
    }
1399

    
1400
    return $query;
1401
  }
1402

    
1403
  /**
1404
   * Get the arguments attached to the WHERE and HAVING clauses of this query.
1405
   */
1406
  public function get_where_args() {
1407
    $args = array();
1408
    foreach ($this->where as $group => $where) {
1409
      $args = array_merge($args, $where['args']);
1410
    }
1411
    foreach ($this->having as $group => $having) {
1412
      $args = array_merge($args, $having['args']);
1413
    }
1414
    return $args;
1415
  }
1416

    
1417
  /**
1418
   * Let modules modify the query just prior to finalizing it.
1419
   */
1420
  public function alter(&$view) {
1421
    foreach (module_implements('views_query_alter') as $module) {
1422
      $function = $module . '_views_query_alter';
1423
      $function($view, $this);
1424
    }
1425
  }
1426

    
1427
  /**
1428
   * Builds the necessary info to execute the query.
1429
   */
1430
  public function build(&$view) {
1431
    // Make the query distinct if the option was set.
1432
    if (!empty($this->options['distinct'])) {
1433
      $this->set_distinct(TRUE, !empty($this->options['pure_distinct']));
1434
    }
1435

    
1436
    // Store the view in the object to be able to use it later.
1437
    $this->view = $view;
1438

    
1439
    $view->init_pager();
1440

    
1441
    // Let the pager modify the query to add limits.
1442
    $this->pager->query();
1443

    
1444
    $view->build_info['query'] = $this->query();
1445
    $view->build_info['count_query'] = $this->query(TRUE);
1446
  }
1447

    
1448
  /**
1449
   * Executes the query and fills the associated view object with according
1450
   * values.
1451
   *
1452
   * Values to set: $view->result, $view->total_rows, $view->execute_time,
1453
   * $view->current_page.
1454
   */
1455
  public function execute(&$view) {
1456
    // Whether this query will run against an external database.
1457
    $external = FALSE;
1458
    $query = $view->build_info['query'];
1459
    $count_query = $view->build_info['count_query'];
1460

    
1461
    $query->addMetaData('view', $view);
1462
    $count_query->addMetaData('view', $view);
1463

    
1464
    if (empty($this->options['disable_sql_rewrite'])) {
1465
      $base_table_data = views_fetch_data($this->base_table);
1466
      if (isset($base_table_data['table']['base']['access query tag'])) {
1467
        $access_tag = $base_table_data['table']['base']['access query tag'];
1468
        $query->addTag($access_tag);
1469
        $count_query->addTag($access_tag);
1470
      }
1471
    }
1472

    
1473
    $items = array();
1474
    if ($query) {
1475
      $additional_arguments = module_invoke_all('views_query_substitutions', $view);
1476

    
1477
      // Count queries must be run through the preExecute() method.
1478
      // If not, then hook_query_node_access_alter() may munge the count by
1479
      // adding a distinct against an empty query string
1480
      // (e.g. COUNT DISTINCT(1) ...) and no pager will return.
1481
      // @see pager.inc > PagerDefault::execute()
1482
      // @see http://api.drupal.org/api/drupal/includes--pager.inc/function/PagerDefault::execute/7
1483
      // @see http://drupal.org/node/1046170.
1484
      $count_query->preExecute();
1485

    
1486
      // Build the count query.
1487
      $count_query = $count_query->countQuery();
1488

    
1489
      // Add additional arguments as a fake condition.
1490
      // XXX: this doesn't work... because PDO mandates that all bound arguments
1491
      // are used on the query. TODO: Find a better way to do this.
1492
      if (!empty($additional_arguments)) {
1493
        // $query->where('1 = 1', $additional_arguments);
1494
        // $count_query->where('1 = 1', $additional_arguments);
1495
      }
1496

    
1497
      $start = microtime(TRUE);
1498

    
1499
      try {
1500
        if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
1501
          $this->pager->execute_count_query($count_query);
1502
        }
1503

    
1504
        $this->pager->pre_execute($query);
1505

    
1506
        $result = $query->execute();
1507

    
1508
        $view->result = array();
1509
        foreach ($result as $item) {
1510
          $view->result[] = $item;
1511
        }
1512

    
1513
        $this->pager->post_execute($view->result);
1514

    
1515
        if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
1516
          $view->total_rows = $this->pager->get_total_items();
1517
        }
1518
      }
1519
      catch (Exception $e) {
1520
        $view->result = array();
1521
        if (!empty($view->live_preview)) {
1522
          drupal_set_message($e->getMessage(), 'error');
1523
        }
1524
        else {
1525
          vpr('Exception in @human_name[@view_name]: @message', array('@human_name' => $view->human_name, '@view_name' => $view->name, '@message' => $e->getMessage()));
1526
        }
1527
      }
1528

    
1529
    }
1530
    else {
1531
      $start = microtime(TRUE);
1532
    }
1533
    $view->execute_time = microtime(TRUE) - $start;
1534
  }
1535

    
1536
  /**
1537
   *
1538
   */
1539
  public function add_signature(&$view) {
1540
    $view->query->add_field(NULL, "'" . $view->name . ':' . $view->current_display . "'", 'view_name');
1541
  }
1542

    
1543
  /**
1544
   *
1545
   */
1546
  public function get_aggregation_info() {
1547
    // @todo Need a way to get database specific and customized aggregation
1548
    // functions into here.
1549
    return array(
1550
      'group' => array(
1551
        'title' => t('Group results together'),
1552
        'is aggregate' => FALSE,
1553
      ),
1554
      'count' => array(
1555
        'title' => t('Count'),
1556
        'method' => 'views_query_default_aggregation_method_simple',
1557
        'handler' => array(
1558
          'argument' => 'views_handler_argument_group_by_numeric',
1559
          'field' => 'views_handler_field_numeric',
1560
          'filter' => 'views_handler_filter_group_by_numeric',
1561
          'sort' => 'views_handler_sort_group_by_numeric',
1562
        ),
1563
      ),
1564
      'count_distinct' => array(
1565
        'title' => t('Count DISTINCT'),
1566
        'method' => 'views_query_default_aggregation_method_distinct',
1567
        'handler' => array(
1568
          'argument' => 'views_handler_argument_group_by_numeric',
1569
          'field' => 'views_handler_field_numeric',
1570
          'filter' => 'views_handler_filter_group_by_numeric',
1571
          'sort' => 'views_handler_sort_group_by_numeric',
1572
        ),
1573
      ),
1574
      'sum' => array(
1575
        'title' => t('Sum'),
1576
        'method' => 'views_query_default_aggregation_method_simple',
1577
        'handler' => array(
1578
          'argument' => 'views_handler_argument_group_by_numeric',
1579
          'filter' => 'views_handler_filter_group_by_numeric',
1580
          'sort' => 'views_handler_sort_group_by_numeric',
1581
        ),
1582
      ),
1583
      'avg' => array(
1584
        'title' => t('Average'),
1585
        'method' => 'views_query_default_aggregation_method_simple',
1586
        'handler' => array(
1587
          'argument' => 'views_handler_argument_group_by_numeric',
1588
          'filter' => 'views_handler_filter_group_by_numeric',
1589
          'sort' => 'views_handler_sort_group_by_numeric',
1590
        ),
1591
      ),
1592
      'min' => array(
1593
        'title' => t('Minimum'),
1594
        'method' => 'views_query_default_aggregation_method_simple',
1595
        'handler' => array(
1596
          'argument' => 'views_handler_argument_group_by_numeric',
1597
          'filter' => 'views_handler_filter_group_by_numeric',
1598
          'sort' => 'views_handler_sort_group_by_numeric',
1599
        ),
1600
      ),
1601
      'max' => array(
1602
        'title' => t('Maximum'),
1603
        'method' => 'views_query_default_aggregation_method_simple',
1604
        'handler' => array(
1605
          'argument' => 'views_handler_argument_group_by_numeric',
1606
          'filter' => 'views_handler_filter_group_by_numeric',
1607
          'sort' => 'views_handler_sort_group_by_numeric',
1608
        ),
1609
      ),
1610
      'stddev_pop' => array(
1611
        'title' => t('Standard deviation'),
1612
        'method' => 'views_query_default_aggregation_method_simple',
1613
        'handler' => array(
1614
          'argument' => 'views_handler_argument_group_by_numeric',
1615
          'filter' => 'views_handler_filter_group_by_numeric',
1616
          'sort' => 'views_handler_sort_group_by_numeric',
1617
        ),
1618
      ),
1619
    ) + views_fetch_plugin_data('query_aggregate');
1620
  }
1621

    
1622
  /**
1623
   * Returns the according entity objects for the given query results.
1624
   */
1625
  public function get_result_entities($results, $relationship = NULL) {
1626
    $base_table = $this->base_table;
1627
    $base_table_alias = $base_table;
1628

    
1629
    if (!empty($relationship)) {
1630
      foreach ($this->view->relationship as $current) {
1631
        if ($current->alias == $relationship) {
1632
          $base_table = $current->definition['base'];
1633
          $base_table_alias = $relationship;
1634
          break;
1635
        }
1636
      }
1637
    }
1638
    $table_data = views_fetch_data($base_table);
1639

    
1640
    // Bail out if the table has not specified the according entity-type.
1641
    if (!isset($table_data['table']['entity type'])) {
1642
      return FALSE;
1643
    }
1644
    $entity_type = $table_data['table']['entity type'];
1645
    $info = entity_get_info($entity_type);
1646
    $is_revision = !empty($table_data['table']['revision']);
1647
    $id_alias = $this->get_field_alias($base_table_alias, $info['entity keys'][$is_revision ? 'revision' : 'id']);
1648

    
1649
    // Assemble the ids of the entities to load.
1650
    $ids = array();
1651
    foreach ($results as $key => $result) {
1652
      if (isset($result->$id_alias)) {
1653
        $ids[$key] = $result->$id_alias;
1654
      }
1655
    }
1656

    
1657
    if (!$is_revision) {
1658
      $entities = entity_load($entity_type, $ids);
1659

    
1660
      // Re-key the array by row-index.
1661
      $result = array();
1662
      foreach ($ids as $key => $id) {
1663
        $result[$key] = isset($entities[$id]) ? $entities[$id] : FALSE;
1664
      }
1665
    }
1666
    else {
1667
      // There's no way in core to load revisions in bulk.
1668
      $result = array();
1669
      foreach ($ids as $key => $id) {
1670
        // Nodes can be dealt with in core.
1671
        if ($entity_type == 'node') {
1672
          $result[$key] = node_load(NULL, $id);
1673
        }
1674
        // Otherwise see if entity is enabled.
1675
        elseif (module_exists('entity')) {
1676
          $result[$key] = entity_revision_load($entity_type, $id);
1677
        }
1678
        else {
1679
          // Otherwise this isn't supported.
1680
          watchdog('views', 'Attempt to load a revision on an unsupported entity type @entity_type.', array('@entity_type' => $entity_type), WATCHDOG_WARNING);
1681
        }
1682
      }
1683
    }
1684

    
1685
    return array($entity_type, $result);
1686
  }
1687

    
1688
}
1689

    
1690
/**
1691
 *
1692
 */
1693
function views_query_default_aggregation_method_simple($group_type, $field) {
1694
  return strtoupper($group_type) . '(' . $field . ')';
1695
}
1696

    
1697
/**
1698
 *
1699
 */
1700
function views_query_default_aggregation_method_distinct($group_type, $field) {
1701
  $group_type = str_replace('_distinct', '', $group_type);
1702
  return strtoupper($group_type) . '(DISTINCT ' . $field . ')';
1703
}
1704

    
1705
/**
1706
 * Validation callback for query tags.
1707
 */
1708
function views_element_validate_tags($element, &$form_state) {
1709
  $values = array_map('trim', explode(',', $element['#value']));
1710
  foreach ($values as $value) {
1711
    if (preg_match("/[^a-z_]/", $value)) {
1712
      form_error($element, t('The query tags may only contain lower-case alphabetical characters and underscores.'));
1713
      return;
1714
    }
1715
  }
1716
}