ViewExecutable.php 59.4 KB
Newer Older
merlinofchaos's avatar
merlinofchaos committed
1
2
3
4
<?php

/**
 * @file
5
 * Definition of Drupal\views\ViewExecutable.
merlinofchaos's avatar
merlinofchaos committed
6
7
 */

8
9
namespace Drupal\views;

10
use Drupal\Component\Utility\String;
11
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
12
use Drupal\Core\Form\FormState;
13
use Drupal\Core\Session\AccountInterface;
14
use Drupal\views\Plugin\views\display\DisplayRouterInterface;
15
use Drupal\views\Plugin\views\query\QueryPluginBase;
16
use Drupal\views\ViewEntityInterface;
17
use Drupal\Component\Utility\Tags;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\Response;
20

merlinofchaos's avatar
merlinofchaos committed
21
/**
22
23
 * Represents a view as a whole.
 *
merlinofchaos's avatar
merlinofchaos committed
24
25
26
 * An object to contain all of the data to generate a view, plus the member
 * functions to build the view query, execute the query and render the output.
 */
27
28
class ViewExecutable {
  use DependencySerializationTrait;
29

merlinofchaos's avatar
merlinofchaos committed
30
  /**
31
   * The config entity in which the view is stored.
merlinofchaos's avatar
merlinofchaos committed
32
   *
33
   * @var \Drupal\views\Entity\View
merlinofchaos's avatar
merlinofchaos committed
34
   */
35
  public $storage;
merlinofchaos's avatar
merlinofchaos committed
36

37
38
39
  /**
   * Whether or not the view has been built.
   *
40
41
   * @todo Group with other static properties.
   *
42
43
44
45
46
47
48
   * @var bool
   */
  public $built = FALSE;

  /**
   * Whether the view has been executed/query has been run.
   *
49
50
   * @todo Group with other static properties.
   *
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
   * @var bool
   */
  public $executed = FALSE;

  /**
   * Any arguments that have been passed into the view.
   *
   * @var array
   */
  public $args = array();

  /**
   * An array of build info.
   *
   * @var array
   */
  public $build_info = array();

  /**
   * Whether this view uses AJAX.
   *
   * @var bool
   */
74
  protected $ajaxEnabled = FALSE;
merlinofchaos's avatar
merlinofchaos committed
75

76
77
78
79
80
  /**
   * Where the results of a query will go.
   *
   * The array must use a numeric index starting at 0.
   *
81
   * @var \Drupal\views\ResultRow[]
82
   */
83
  public $result = array();
merlinofchaos's avatar
merlinofchaos committed
84
85
86

  // May be used to override the current pager info.

87
88
89
90
91
  /**
   * The current page. If the view uses pagination.
   *
   * @var int
   */
92
  protected $current_page = NULL;
93
94
95
96
97
98

  /**
   * The number of items per page.
   *
   * @var int
   */
99
  protected $items_per_page = NULL;
100
101
102
103
104
105

  /**
   * The pager offset.
   *
   * @var int
   */
106
  protected $offset = NULL;
107
108
109
110

  /**
   * The total number of rows returned from the query.
   *
111
   * @var int
112
113
114
115
   */
  public $total_rows = NULL;

  /**
116
   * Attachments to place before the view.
117
   *
118
   * @var array()
119
   */
120
  public $attachment_before = array();
121
122

  /**
123
   * Attachments to place after the view.
124
   *
125
   * @var array
126
   */
127
  public $attachment_after = array();
merlinofchaos's avatar
merlinofchaos committed
128

129
130
131
132
133
134
135
  /**
   * Feed icons attached to the view.
   *
   * @var array
   */
  public $feedIcons = array();

merlinofchaos's avatar
merlinofchaos committed
136
137
  // Exposed widget input

138
  /**
139
   * All the form data from $form_state->getValues().
140
141
142
143
   *
   * @var array
   */
  public $exposed_data = array();
merlinofchaos's avatar
merlinofchaos committed
144

145
146
147
148
149
  /**
   * An array of input values from exposed forms.
   *
   * @var array
   */
150
  protected $exposed_input = array();
merlinofchaos's avatar
merlinofchaos committed
151

152
  /**
153
   * Exposed widget input directly from the $form_state->getValues().
154
155
156
157
158
159
160
161
   *
   * @var array
   */
  public $exposed_raw_input = array();

  /**
   * Used to store views that were previously running if we recurse.
   *
162
   * @var \Drupal\views\ViewExecutable[]
163
164
165
166
167
168
   */
  public $old_view = array();

  /**
   * To avoid recursion in views embedded into areas.
   *
169
   * @var \Drupal\views\ViewExecutable[]
170
171
172
173
174
175
176
177
178
   */
  public $parent_views = array();

  /**
   * Whether this view is an attachment to another view.
   *
   * @var bool
   */
  public $is_attachment = NULL;
merlinofchaos's avatar
merlinofchaos committed
179
180
181
182
183
184

  /**
   * Identifier of the current display.
   *
   * @var string
   */
185
  public $current_display;
merlinofchaos's avatar
merlinofchaos committed
186
187

  /**
188
   * Where the $query object will reside.
merlinofchaos's avatar
merlinofchaos committed
189
   *
190
   * @var \Drupal\views\Plugin\views\query\QueryPluginBase
merlinofchaos's avatar
merlinofchaos committed
191
   */
192
  public $query = NULL;
merlinofchaos's avatar
merlinofchaos committed
193

194
195
196
  /**
   * The used pager plugin used by the current executed view.
   *
197
   * @var \Drupal\views\Plugin\views\pager\PagerPluginBase
198
199
200
201
   */
  public $pager = NULL;

  /**
merlinofchaos's avatar
merlinofchaos committed
202
203
   * The current used display plugin.
   *
204
   * @var \Drupal\views\Plugin\views\display\DisplayPluginBase
merlinofchaos's avatar
merlinofchaos committed
205
   */
206
  public $display_handler;
merlinofchaos's avatar
merlinofchaos committed
207

208
209
210
211
212
213
  /**
   * The list of used displays of the view.
   *
   * An array containing Drupal\views\Plugin\views\display\DisplayPluginBase
   * objects.
   *
214
   * @var \Drupal\views\DisplayPluginCollection
215
216
217
   */
  public $displayHandlers;

merlinofchaos's avatar
merlinofchaos committed
218
219
220
  /**
   * The current used style plugin.
   *
221
   * @var \Drupal\views\Plugin\views\style\StylePluginBase
merlinofchaos's avatar
merlinofchaos committed
222
   */
223
  public $style_plugin;
merlinofchaos's avatar
merlinofchaos committed
224

225
226
227
228
229
230
231
  /**
   * The current used row plugin, if the style plugin supports row plugins.
   *
   * @var \Drupal\views\Plugin\views\row\RowPluginBase
   */
  public $rowPlugin;

merlinofchaos's avatar
merlinofchaos committed
232
233
234
235
236
  /**
   * Stores the current active row while rendering.
   *
   * @var int
   */
237
  public $row_index;
merlinofchaos's avatar
merlinofchaos committed
238

239
  /**
merlinofchaos's avatar
merlinofchaos committed
240
241
242
243
   * Allow to override the url of the current view.
   *
   * @var string
   */
244
  public $override_url = NULL;
merlinofchaos's avatar
merlinofchaos committed
245
246
247
248
249
250

  /**
   * Allow to override the path used for generated urls.
   *
   * @var string
   */
251
  public $override_path = NULL;
merlinofchaos's avatar
merlinofchaos committed
252
253
254

  /**
   * Allow to override the used database which is used for this query.
255
256
   *
   * @var bool
merlinofchaos's avatar
merlinofchaos committed
257
   */
258
  public $base_database = NULL;
merlinofchaos's avatar
merlinofchaos committed
259

260
  // Handlers which are active on this view.
merlinofchaos's avatar
merlinofchaos committed
261
262
263

  /**
   * Stores the field handlers which are initialized on this view.
264
   *
265
   * @var \Drupal\views\Plugin\views\field\FieldPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
266
   */
267
  public $field;
merlinofchaos's avatar
merlinofchaos committed
268
269
270

  /**
   * Stores the argument handlers which are initialized on this view.
271
   *
272
   * @var \Drupal\views\Plugin\views\argument\ArgumentPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
273
   */
274
  public $argument;
merlinofchaos's avatar
merlinofchaos committed
275
276
277

  /**
   * Stores the sort handlers which are initialized on this view.
278
   *
279
   * @var \Drupal\views\Plugin\views\sort\SortPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
280
   */
281
  public $sort;
merlinofchaos's avatar
merlinofchaos committed
282
283
284

  /**
   * Stores the filter handlers which are initialized on this view.
285
   *
286
   * @var \Drupal\views\Plugin\views\filter\FilterPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
287
   */
288
  public $filter;
merlinofchaos's avatar
merlinofchaos committed
289
290
291

  /**
   * Stores the relationship handlers which are initialized on this view.
292
   *
293
   * @var \Drupal\views\Plugin\views\relationship\RelationshipPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
294
   */
295
  public $relationship;
merlinofchaos's avatar
merlinofchaos committed
296
297
298

  /**
   * Stores the area handlers for the header which are initialized on this view.
299
   *
300
   * @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
301
   */
302
  public $header;
merlinofchaos's avatar
merlinofchaos committed
303
304
305

  /**
   * Stores the area handlers for the footer which are initialized on this view.
306
   *
307
   * @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
308
   */
309
  public $footer;
merlinofchaos's avatar
merlinofchaos committed
310
311
312

  /**
   * Stores the area handlers for the empty text which are initialized on this view.
313
314
315
   *
   * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
   *
316
   * @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
317
   */
318
  public $empty;
merlinofchaos's avatar
merlinofchaos committed
319

320
321
322
  /**
   * Stores the current response object.
   *
323
   * @var \Symfony\Component\HttpFoundation\Response
324
325
326
   */
  protected $response = NULL;

327
328
329
330
331
332
333
  /**
   * Stores the current request object.
   *
   * @var \Symfony\Component\HttpFoundation\Request
   */
  protected $request;

334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
  /**
   * Does this view already have loaded it's handlers.
   *
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $inited;

  /**
   * The rendered output of the exposed form.
   *
   * @var string
   */
  public $exposed_widgets;

  /**
   * If this view has been previewed.
   *
   * @var bool
   */
  public $preview;

  /**
   * Force the query to calculate the total number of results.
   *
   * @todo Move to the query.
   *
   * @var bool
   */
  public $get_total_rows;

  /**
   * Indicates if the sorts have been built.
   *
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $build_sort;

  /**
   * Stores the many-to-one tables for performance.
   *
   * @var array
   */
  public $many_to_one_tables;

  /**
   * A unique identifier which allows to update multiple views output via js.
   *
   * @var string
   */
  public $dom_id;

389
390
391
  /**
   * A render array container to store render related information.
   *
392
393
394
   * For example you can alter the array and attach some asset library or JS
   * settings via the #attached key. This is the required way to add custom
   * CSS or JS.
395
396
397
398
399
   *
   * @var array
   *
   * @see drupal_process_attached
   */
400
401
402
403
404
405
  public $element = [
    '#attached' => [
      'library' => [],
      'drupalSettings' => [],
    ]
  ];
406

407
408
409
410
411
412
413
  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $user;

414
415
416
417
418
419
420
  /**
   * Should the admin links be shown on the rendered view.
   *
   * @var bool
   */
  protected $showAdminLinks;

421
422
423
424
425
426
427
  /**
   * The views data.
   *
   * @var \Drupal\views\ViewsData
   */
  protected $viewsData;

428
429
430
  /**
   * Constructs a new ViewExecutable object.
   *
431
   * @param \Drupal\views\ViewEntityInterface $storage
432
   *   The view config entity the actual information is stored on.
433
434
   * @param \Drupal\Core\Session\AccountInterface $user
   *   The current user.
435
436
   * @param \Drupal\views\ViewsData $views_data
   *   The views data.
437
   */
438
  public function __construct(ViewEntityInterface $storage, AccountInterface $user, ViewsData $views_data) {
439
440
    // Reference the storage and the executable to each other.
    $this->storage = $storage;
441
    $this->storage->set('executable', $this);
442
    $this->user = $user;
443
    $this->viewsData = $views_data;
444
445

    // Add the default css for a view.
446
    $this->element['#attached']['library'][] = 'views/views.module';
447
448
449
  }

  /**
450
   * @todo.
451
   */
452
453
  public function save() {
    $this->storage->save();
merlinofchaos's avatar
merlinofchaos committed
454
455
456
457
458
459
  }

  /**
   * Set the arguments that come to this view. Usually from the URL
   * but possibly from elsewhere.
   */
460
  public function setArguments($args) {
461
    $this->args = $args;
merlinofchaos's avatar
merlinofchaos committed
462
463
464
465
466
  }

  /**
   * Change/Set the current page for the pager.
   */
467
  public function setCurrentPage($page) {
merlinofchaos's avatar
merlinofchaos committed
468
    $this->current_page = $page;
469
470

    // If the pager is already initialized, pass it through to the pager.
471
    if (!empty($this->pager)) {
472
      return $this->pager->setCurrentPage($page);
473
    }
merlinofchaos's avatar
merlinofchaos committed
474
475
476
477
478
  }

  /**
   * Get the current page from the pager.
   */
479
  public function getCurrentPage() {
480
    // If the pager is already initialized, pass it through to the pager.
481
    if (!empty($this->pager)) {
482
      return $this->pager->getCurrentPage();
merlinofchaos's avatar
merlinofchaos committed
483
    }
484
485
486
487

    if (isset($this->current_page)) {
      return $this->current_page;
    }
merlinofchaos's avatar
merlinofchaos committed
488
489
490
491
492
  }

  /**
   * Get the items per page from the pager.
   */
493
  public function getItemsPerPage() {
494
    // If the pager is already initialized, pass it through to the pager.
495
    if (!empty($this->pager)) {
496
      return $this->pager->getItemsPerPage();
merlinofchaos's avatar
merlinofchaos committed
497
    }
498
499
500
501

    if (isset($this->items_per_page)) {
      return $this->items_per_page;
    }
merlinofchaos's avatar
merlinofchaos committed
502
503
504
505
506
  }

  /**
   * Set the items per page on the pager.
   */
507
  public function setItemsPerPage($items_per_page) {
merlinofchaos's avatar
merlinofchaos committed
508
509
510
    $this->items_per_page = $items_per_page;

    // If the pager is already initialized, pass it through to the pager.
511
    if (!empty($this->pager)) {
512
      $this->pager->setItemsPerPage($items_per_page);
merlinofchaos's avatar
merlinofchaos committed
513
514
515
516
517
518
    }
  }

  /**
   * Get the pager offset from the pager.
   */
519
  public function getOffset() {
520
    // If the pager is already initialized, pass it through to the pager.
521
    if (!empty($this->pager)) {
522
      return $this->pager->getOffset();
merlinofchaos's avatar
merlinofchaos committed
523
    }
524
525
526
527

    if (isset($this->offset)) {
      return $this->offset;
    }
merlinofchaos's avatar
merlinofchaos committed
528
529
530
531
532
  }

  /**
   * Set the offset on the pager.
   */
533
  public function setOffset($offset) {
merlinofchaos's avatar
merlinofchaos committed
534
535
536
    $this->offset = $offset;

    // If the pager is already initialized, pass it through to the pager.
537
    if (!empty($this->pager)) {
538
      $this->pager->setOffset($offset);
merlinofchaos's avatar
merlinofchaos committed
539
540
541
542
543
544
    }
  }

  /**
   * Determine if the pager actually uses a pager.
   */
545
  public function usePager() {
546
    if (!empty($this->pager)) {
547
      return $this->pager->usePager();
merlinofchaos's avatar
merlinofchaos committed
548
549
550
551
    }
  }

  /**
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
   * Sets whether or not AJAX should be used.
   *
   * If AJAX is used, paging, tablesorting and exposed filters will be fetched
   * via an AJAX call rather than a page refresh.
   *
   * @param bool $use_ajax
   *   TRUE if AJAX should be used, FALSE otherwise.
   */
  public function setAjaxEnabled($ajax_enabled) {
    $this->ajaxEnabled = (bool) $ajax_enabled;
  }

  /**
   * Whether or not AJAX should be used.
   *
   * @see \Drupal\views\ViewExecutable::setAjaxEnabled().
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
570
   */
571
572
  public function ajaxEnabled() {
    return $this->ajaxEnabled;
merlinofchaos's avatar
merlinofchaos committed
573
574
575
576
  }

  /**
   * Set the exposed filters input to an array. If unset they will be taken
577
   * from \Drupal::request()->query when the time comes.
merlinofchaos's avatar
merlinofchaos committed
578
   */
579
  public function setExposedInput($filters) {
merlinofchaos's avatar
merlinofchaos committed
580
581
582
583
584
585
    $this->exposed_input = $filters;
  }

  /**
   * Figure out what the exposed input for this view is.
   */
586
  public function getExposedInput() {
587
588
    // Fill our input either from \Drupal::request()->query or from something
    // previously set on the view.
merlinofchaos's avatar
merlinofchaos committed
589
    if (empty($this->exposed_input)) {
590
591
592
      // Ensure that we can call the method at any point in time.
      $this->initDisplay();

593
      $this->exposed_input = \Drupal::request()->query->all();
merlinofchaos's avatar
merlinofchaos committed
594
595
596
597
598
599
600
601
602
603
604
605
606
      // unset items that are definitely not our input:
      foreach (array('page', 'q') as $key) {
        if (isset($this->exposed_input[$key])) {
          unset($this->exposed_input[$key]);
        }
      }

      // If we have no input at all, check for remembered input via session.

      // If filters are not overridden, store the 'remember' settings on the
      // default display. If they are, store them on this display. This way,
      // multiple displays in the same view can share the same filters and
      // remember settings.
607
      $display_id = ($this->display_handler->isDefaulted('filters')) ? 'default' : $this->current_display;
merlinofchaos's avatar
merlinofchaos committed
608

609
610
      if (empty($this->exposed_input) && !empty($_SESSION['views'][$this->storage->id()][$display_id])) {
        $this->exposed_input = $_SESSION['views'][$this->storage->id()][$display_id];
merlinofchaos's avatar
merlinofchaos committed
611
612
613
614
615
616
617
618
619
      }
    }

    return $this->exposed_input;
  }

  /**
   * Set the display for this view and initialize the display handler.
   */
620
  public function initDisplay() {
merlinofchaos's avatar
merlinofchaos committed
621
622
623
624
    if (isset($this->current_display)) {
      return TRUE;
    }

625
    // Initialize the display cache array.
626
    $this->displayHandlers = new DisplayPluginCollection($this, Views::pluginManager('display'));
merlinofchaos's avatar
merlinofchaos committed
627
628

    $this->current_display = 'default';
629
    $this->display_handler = $this->displayHandlers->get('default');
merlinofchaos's avatar
merlinofchaos committed
630
631
632
633
634
635
636

    return TRUE;
  }

  /**
   * Get the first display that is accessible to the user.
   *
637
   * @param array|string $displays
merlinofchaos's avatar
merlinofchaos committed
638
   *   Either a single display id or an array of display ids.
639
640
641
   *
   * @return string
   *   The first accessible display id, at least default.
merlinofchaos's avatar
merlinofchaos committed
642
   */
643
  public function chooseDisplay($displays) {
merlinofchaos's avatar
merlinofchaos committed
644
645
646
647
    if (!is_array($displays)) {
      return $displays;
    }

648
    $this->initDisplay();
merlinofchaos's avatar
merlinofchaos committed
649
650

    foreach ($displays as $display_id) {
651
      if ($this->displayHandlers->get($display_id)->access($this->user)) {
merlinofchaos's avatar
merlinofchaos committed
652
653
654
655
656
657
658
        return $display_id;
      }
    }

    return 'default';
  }

659
660
661
662
663
664
665
666
667
668
669
670
671
  /**
   * Gets the current display plugin.
   *
   * @return \Drupal\views\Plugin\views\display\DisplayPluginBase
   */
  public function getDisplay() {
    if (!isset($this->display_handler)) {
      $this->initDisplay();
    }

    return $this->display_handler;
  }

merlinofchaos's avatar
merlinofchaos committed
672
  /**
673
   * Sets the current display.
merlinofchaos's avatar
merlinofchaos committed
674
   *
675
676
677
678
679
   * @param string $display_id
   *   The ID of the display to mark as current.
   *
   * @return bool
   *   TRUE if the display was correctly set, FALSE otherwise.
merlinofchaos's avatar
merlinofchaos committed
680
   */
681
  public function setDisplay($display_id = NULL) {
682
683
684
685
    // If we have not already initialized the display, do so.
    if (!isset($this->current_display)) {
      // This will set the default display and instantiate the default display
      // plugin.
686
      $this->initDisplay();
687
    }
merlinofchaos's avatar
merlinofchaos committed
688

689
690
691
692
    // If no display ID is passed, we either have initialized the default or
    // already have a display set.
    if (!isset($display_id)) {
      return TRUE;
merlinofchaos's avatar
merlinofchaos committed
693
694
    }

695
    $display_id = $this->chooseDisplay($display_id);
merlinofchaos's avatar
merlinofchaos committed
696
697

    // Ensure the requested display exists.
698
    if (!$this->displayHandlers->has($display_id)) {
699
700
      debug(format_string('setDisplay() called with invalid display ID "@display".', array('@display' => $display_id)));
      return FALSE;
merlinofchaos's avatar
merlinofchaos committed
701
702
    }

703
704
705
706
707
708
709
710
711
712
713
714
    // Reset if the display has changed. It could be called multiple times for
    // the same display, especially in the UI.
    if ($this->current_display != $display_id) {
      // Set the current display.
      $this->current_display = $display_id;

      // Reset the style and row plugins.
      $this->style_plugin = NULL;
      $this->plugin_name = NULL;
      $this->rowPlugin = NULL;
    }

715
716
717
718
719
    if ($display = $this->displayHandlers->get($display_id)) {
      // Set a shortcut.
      $this->display_handler = $display;
      return TRUE;
    }
merlinofchaos's avatar
merlinofchaos committed
720

721
    return FALSE;
merlinofchaos's avatar
merlinofchaos committed
722
723
  }

724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
  /**
   * Creates a new display and a display handler instance for it.
   *
   * @param string $plugin_id
   *   (optional) The plugin type from the Views plugin annotation. Defaults to
   *   'page'.
   * @param string $title
   *   (optional) The title of the display. Defaults to NULL.
   * @param string $id
   *   (optional) The ID to use, e.g., 'default', 'page_1', 'block_2'. Defaults
   *   to NULL.
   *
   * @return \Drupal\views\Plugin\views\display\DisplayPluginBase
   *   A new display plugin instance if executable is set, the new display ID
   *   otherwise.
   */
  public function newDisplay($plugin_id = 'page', $title = NULL, $id = NULL) {
    $this->initDisplay();

    $id = $this->storage->addDisplay($plugin_id, $title, $id);
744
    $this->displayHandlers->addInstanceId($id);
745

746
747
748
    $display = $this->displayHandlers->get($id);
    $display->newDisplay();
    return $display;
749
750
  }

751
752
753
754
755
756
757
758
759
760
761
762
763
  /**
   * Gets the current style plugin.
   *
   * @return \Drupal\views\Plugin\views\style\StylePluginBase
   */
  public function getStyle() {
    if (!isset($this->style_plugin)) {
      $this->initStyle();
    }

    return $this->style_plugin;
  }

merlinofchaos's avatar
merlinofchaos committed
764
765
766
767
768
769
  /**
   * Find and initialize the style plugin.
   *
   * Note that arguments may have changed which style plugin we use, so
   * check the view object first, then ask the display handler.
   */
770
  public function initStyle() {
merlinofchaos's avatar
merlinofchaos committed
771
    if (isset($this->style_plugin)) {
772
      return TRUE;
merlinofchaos's avatar
merlinofchaos committed
773
774
    }

775
    $this->style_plugin = $this->display_handler->getPlugin('style');
merlinofchaos's avatar
merlinofchaos committed
776
777
778
779
780
781
782
783
784
785
786

    if (empty($this->style_plugin)) {
      return FALSE;
    }

    return TRUE;
  }

  /**
   * Acquire and attach all of the handlers.
   */
787
  public function initHandlers() {
788
    $this->initDisplay();
merlinofchaos's avatar
merlinofchaos committed
789
    if (empty($this->inited)) {
790
      foreach ($this::getHandlerTypes() as $key => $info) {
791
        $this->_initHandler($key, $info);
merlinofchaos's avatar
merlinofchaos committed
792
793
794
795
796
      }
      $this->inited = TRUE;
    }
  }

797
798
799
800
801
802
803
804
805
806
807
808
809
  /**
   * Get the current pager plugin.
   *
   * @return \Drupal\views\Plugin\views\pager\PagerPluginBase
   */
  public function getPager() {
    if (!isset($this->pager)) {
      $this->initPager();
    }

    return $this->pager;
  }

merlinofchaos's avatar
merlinofchaos committed
810
811
812
813
814
815
  /**
   * Initialize the pager
   *
   * Like style initialization, pager initialization is held until late
   * to allow for overrides.
   */
816
  public function initPager() {
817
    if (!isset($this->pager)) {
818
      $this->pager = $this->display_handler->getPlugin('pager');
merlinofchaos's avatar
merlinofchaos committed
819

820
      if ($this->pager->usePager()) {
821
        $this->pager->setCurrentPage($this->current_page);
merlinofchaos's avatar
merlinofchaos committed
822
823
824
825
826
      }

      // These overrides may have been set earlier via $view->set_*
      // functions.
      if (isset($this->items_per_page)) {
827
        $this->pager->setItemsPerPage($this->items_per_page);
merlinofchaos's avatar
merlinofchaos committed
828
829
830
      }

      if (isset($this->offset)) {
831
        $this->pager->setOffset($this->offset);
merlinofchaos's avatar
merlinofchaos committed
832
833
834
835
      }
    }
  }

836
837
838
  /**
   * Render the pager, if necessary.
   */
839
  public function renderPager($exposed_input) {
840
    if (!empty($this->pager) && $this->pager->usePager()) {
841
842
843
844
845
846
      return $this->pager->render($exposed_input);
    }

    return '';
  }

merlinofchaos's avatar
merlinofchaos committed
847
848
849
850
  /**
   * Create a list of base tables eligible for this view. Used primarily
   * for the UI. Display must be already initialized.
   */
851
  public function getBaseTables() {
merlinofchaos's avatar
merlinofchaos committed
852
    $base_tables = array(
853
      $this->storage->get('base_table') => TRUE,
merlinofchaos's avatar
merlinofchaos committed
854
855
856
      '#global' => TRUE,
    );

857
    foreach ($this->display_handler->getHandlers('relationship') as $handler) {
merlinofchaos's avatar
merlinofchaos committed
858
859
860
861
862
863
      $base_tables[$handler->definition['base']] = TRUE;
    }
    return $base_tables;
  }

  /**
864
   * Run the preQuery() on all active handlers.
merlinofchaos's avatar
merlinofchaos committed
865
   */
866
  protected function _preQuery() {
867
    foreach ($this::getHandlerTypes() as $key => $info) {
merlinofchaos's avatar
merlinofchaos committed
868
869
870
871
      $handlers = &$this->$key;
      $position = 0;
      foreach ($handlers as $id => $handler) {
        $handlers[$id]->position = $position;
872
        $handlers[$id]->preQuery();
merlinofchaos's avatar
merlinofchaos committed
873
874
875
876
877
878
        $position++;
      }
    }
  }

  /**
879
   * Run the postExecute() on all active handlers.
merlinofchaos's avatar
merlinofchaos committed
880
   */
881
  protected function _postExecute() {
882
    foreach ($this::getHandlerTypes() as $key => $info) {
merlinofchaos's avatar
merlinofchaos committed
883
884
      $handlers = &$this->$key;
      foreach ($handlers as $id => $handler) {
885
        $handlers[$id]->postExecute($this->result);
merlinofchaos's avatar
merlinofchaos committed
886
887
888
889
890
891
892
893
894
895
      }
    }
  }

  /**
   * Attach all of the handlers for each type.
   *
   * @param $key
   *   One of 'argument', 'field', 'sort', 'filter', 'relationship'
   * @param $info
896
   *   The $info from getHandlerTypes for this object.
merlinofchaos's avatar
merlinofchaos committed
897
   */
898
  protected function _initHandler($key, $info) {
merlinofchaos's avatar
merlinofchaos committed
899
    // Load the requested items from the display onto the object.
900
    $this->$key = &$this->display_handler->getHandlers($key);
merlinofchaos's avatar
merlinofchaos committed
901
902
903
904
905
906

    // This reference deals with difficult PHP indirection.
    $handlers = &$this->$key;

    // Run through and test for accessibility.
    foreach ($handlers as $id => $handler) {
907
      if (!$handler->access($this->user)) {
merlinofchaos's avatar
merlinofchaos committed
908
909
910
911
912
913
914
915
        unset($handlers[$id]);
      }
    }
  }

  /**
   * Build all the arguments.
   */
916
  protected function _buildArguments() {
merlinofchaos's avatar
merlinofchaos committed
917
918
919
920
921
922
923
924
925
926
927
    // Initially, we want to build sorts and fields. This can change, though,
    // if we get a summary view.
    if (empty($this->argument)) {
      return TRUE;
    }

    // build arguments.
    $position = -1;
    $substitutions = array();
    $status = TRUE;

928
929
930
    // Get the title.
    $title = $this->display_handler->getOption('title');

merlinofchaos's avatar
merlinofchaos committed
931
932
933
    // Iterate through each argument and process.
    foreach ($this->argument as $id => $arg) {
      $position++;
934
      $argument = $this->argument[$id];
merlinofchaos's avatar
merlinofchaos committed
935
936
937
938
939

      if ($argument->broken()) {
        continue;
      }

940
      $argument->setRelationship();
merlinofchaos's avatar
merlinofchaos committed
941
942
943
944

      $arg = isset($this->args[$position]) ? $this->args[$position] : NULL;
      $argument->position = $position;

945
      if (isset($arg) || $argument->hasDefaultArgument()) {
merlinofchaos's avatar
merlinofchaos committed
946
        if (!isset($arg)) {
947
          $arg = $argument->getDefaultArgument();
merlinofchaos's avatar
merlinofchaos committed
948
949
950
951
952
953
954
955
956
          // make sure default args get put back.
          if (isset($arg)) {
            $this->args[$position] = $arg;
          }
          // remember that this argument was computed, not passed on the URL.
          $argument->is_default = TRUE;
        }

        // Set the argument, which will also validate that the argument can be set.
957
        if (!$argument->setArgument($arg)) {
958
          $status = $argument->validateFail($arg);
merlinofchaos's avatar
merlinofchaos committed
959
960
961
          break;
        }

962
        if ($argument->isException()) {
963
          $arg_title = $argument->exceptionTitle();
merlinofchaos's avatar
merlinofchaos committed
964
965
        }
        else {
966
          $arg_title = $argument->getTitle();
967
          $argument->query($this->display_handler->useGroupBy());
merlinofchaos's avatar
merlinofchaos committed
968
969
970
971
        }

        // Add this argument's substitution
        $substitutions['%' . ($position + 1)] = $arg_title;
972
        $substitutions['!' . ($position + 1)] = strip_tags(String::decodeEntities($arg));
merlinofchaos's avatar
merlinofchaos committed
973
974
975
976
977
978
979
980

        // Test to see if we should use this argument's title
        if (!empty($argument->options['title_enable']) && !empty($argument->options['title'])) {
          $title = $argument->options['title'];
        }
      }
      else {
        // determine default condition and handle.
981
        $status = $argument->defaultAction();
merlinofchaos's avatar
merlinofchaos committed
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
        break;
      }

      // Be safe with references and loops:
      unset($argument);
    }

    // set the title in the build info.
    if (!empty($title)) {
      $this->build_info['title'] = $title;
    }

    // Store the arguments for later use.
    $this->build_info['substitutions'] = $substitutions;

    return $status;
  }

1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
  /**
   * Gets the current query plugin.
   *
   * @return \Drupal\views\Plugin\views\query\QueryPluginBase
   */
  public function getQuery() {
    if (!isset($this->query)) {
      $this->initQuery();
    }

    return $this->query;
  }

merlinofchaos's avatar
merlinofchaos committed
1013
1014
1015
  /**
   * Do some common building initialization.
   */
1016
  public function initQuery() {
merlinofchaos's avatar
merlinofchaos committed
1017
1018
1019
1020
1021
1022
1023
1024
1025
    if (!empty($this->query)) {
      $class = get_class($this->query);
      if ($class && $class != 'stdClass') {
        // return if query is already initialized.
        return TRUE;
      }
    }

    // Create and initialize the query object.
1026
    $views_data = Views::viewsData()->get($this->storage->get('base_table'));
1027
    $this->storage->set('base_field', !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : '');
merlinofchaos's avatar
merlinofchaos committed
1028
1029
1030
1031
    if (!empty($views_data['table']['base']['database'])) {
      $this->base_database = $views_data['table']['base']['database'];
    }

1032
    $this->query = $this->display_handler->getPlugin('query');
merlinofchaos's avatar
merlinofchaos committed
1033
1034
1035
1036
1037
1038
    return TRUE;
  }

  /**
   * Build the query for the view.
   */
1039
  public function build($display_id = NULL) {
merlinofchaos's avatar
merlinofchaos committed
1040
1041
1042
1043
1044
    if (!empty($this->built)) {
      return;
    }

    if (empty($this->current_display) || $display_id) {
1045
      if (!$this->setDisplay($display_id)) {
merlinofchaos's avatar
merlinofchaos committed
1046
1047
1048
1049
1050
        return FALSE;
      }
    }

    // Let modules modify the view just prior to building it.
1051
1052
    $module_handler = \Drupal::moduleHandler();
    $module_handler->invokeAll('views_pre_build', array($this));
merlinofchaos's avatar
merlinofchaos committed
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064

    // Attempt to load from cache.
    // @todo Load a build_info from cache.

    $start = microtime(TRUE);
    // If that fails, let's build!
    $this->build_info = array(
      'query' => '',
      'count_query' => '',
      'query_args' => array(),
    );

1065
    $this->initQuery();
merlinofchaos's avatar
merlinofchaos committed
1066
1067
1068
1069
1070
1071
1072

    // Call a module hook and see if it wants to present us with a
    // pre-built query or instruct us not to build the query for
    // some reason.
    // @todo: Implement this. Use the same mechanism Panels uses.

    // Run through our handlers and ensure they have necessary information.
1073
    $this->initHandlers();
merlinofchaos's avatar
merlinofchaos committed
1074
1075

    // Let the handlers interact with each other if they really want.
1076
    $this->_preQuery();
merlinofchaos's avatar
merlinofchaos committed
1077

1078
1079
    if ($this->display_handler->usesExposed()) {
      $exposed_form = $this->display_handler->getPlugin('exposed_form');
1080
      $this->exposed_widgets = $exposed_form->renderExposedForm();
1081
      if (FormState::hasAnyErrors() || !empty($this->build_info['abort'])) {
merlinofchaos's avatar
merlinofchaos committed
1082
        $this->built = TRUE;
1083
        // Don't execute the query, $form_state, but rendering will still be executed to display the empty text.
merlinofchaos's avatar
merlinofchaos committed
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
        $this->executed = TRUE;
        return empty($this->build_info['fail']);
      }
    }

    // Build all the relationships first thing.
    $this->_build('relationship');

    // Set the filtering groups.
    if (!empty($this->filter)) {
1094
      $filter_groups = $this->display_handler->getOption('filter_groups');
merlinofchaos's avatar
merlinofchaos committed
1095
      if ($filter_groups) {
1096
        $this->query->setGroupOperator($filter_groups['operator']);
1097
        foreach ($filter_groups['groups'] as $id => $operator) {
1098
          $this->query->setWhereGroup($operator, $id);
merlinofchaos's avatar
merlinofchaos committed
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
        }
      }
    }

    // Build all the filters.
    $this->_build('filter');

    $this->build_sort = TRUE;

    // Arguments can, in fact, cause this whole thing to abort.
1109
    if (!$this->_buildArguments()) {
merlinofchaos's avatar
merlinofchaos committed
1110
      $this->build_time = microtime(TRUE) - $start;
1111
      $this->attachDisplays();
merlinofchaos's avatar
merlinofchaos committed
1112
1113
1114
1115
1116
1117
      return $this->built;
    }

    // Initialize the style; arguments may have changed which style we use,
    // so waiting as long as possible is important. But we need to know
    // about the style when we go to build fields.
1118
    if (!$this->initStyle()) {
merlinofchaos's avatar
merlinofchaos committed
1119
1120
1121
1122
      $this->build_info['fail'] = TRUE;
      return FALSE;
    }

1123
    if ($this->style_plugin->usesFields()) {
merlinofchaos's avatar
merlinofchaos committed
1124
1125
1126
1127
1128
1129
      $this->_build('field');
    }

    // Build our sort criteria if we were instructed to do so.
    if (!empty($this->build_sort)) {
      // Allow the style handler to deal with sorting.
1130
      if ($this->style_plugin->buildSort()) {
merlinofchaos's avatar
merlinofchaos committed
1131
1132
1133
        $this->_build('sort');
      }
      // allow the plugin to build second sorts as well.
1134
      $this->style_plugin->buildSortPost();
merlinofchaos's avatar
merlinofchaos committed
1135
1136
1137
1138
1139
1140
1141
1142
    }

    // Allow area handlers to affect the query.
    $this->_build('header');
    $this->_build('footer');
    $this->_build('empty');

    // Allow display handler to affect the query:
1143
    $this->display_handler->query($this->display_handler->useGroupBy());
merlinofchaos's avatar
merlinofchaos committed
1144
1145

    // Allow style handler to affect the query:
1146
    $this->style_plugin->query($this->display_handler->useGroupBy());
merlinofchaos's avatar
merlinofchaos committed
1147
1148
1149
1150
1151
1152

    // Allow exposed form to affect the query:
    if (isset($exposed_form)) {
      $exposed_form->query();
    }

1153
    if (\Drupal::config('views.settings')->get('sql_signature')) {
1154
      $this->query->addSignature($this);
merlinofchaos's avatar
merlinofchaos committed
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
    }

    // Let modules modify the query just prior to finalizing it.
    $this->query->alter($this);

    // Only build the query if we weren't interrupted.
    if (empty($this->built)) {
      // Build the necessary info to execute the query.
      $this->query->build($this);
    }

    $this->built = TRUE;
    $this->build_time = microtime(TRUE) - $start;

    // Attach displays
1170
    $this->attachDisplays();
merlinofchaos's avatar
merlinofchaos committed
1171
1172

    // Let modules modify the view just after building it.
1173
    $module_handler->invokeAll('views_post_build', array($this));
merlinofchaos's avatar
merlinofchaos committed
1174
1175
1176
1177
1178
1179

    return TRUE;
  }

  /**
   * Internal method to build an individual set of handlers.
1180
   *
1181
1182
   * @todo Some filter needs this function, even it is internal.
   *
1183
1184
1185
   * @param string $key
   *    The type of handlers (filter etc.) which should be iterated over to
   *    build the relationship and query information.
merlinofchaos's avatar
merlinofchaos committed
1186
   */
1187
  public function _build($key) {
merlinofchaos's avatar
merlinofchaos committed
1188
1189
    $handlers = &$this->$key;
    foreach ($handlers as $id => $data) {
1190

merlinofchaos's avatar
merlinofchaos committed
1191
      if (!empty($handlers[$id]) && is_object($handlers[$id])) {
1192
        $multiple_exposed_input = array(0 => NULL);
1193
        if ($handlers[$id]->multipleExposedInput()) {
1194
          $multiple_exposed_input = $handlers[$id]->groupMultipleExposedInput($this->exposed_data);
1195
1196
1197
1198
        }
        foreach ($multiple_exposed_input as $group_id) {
          // Give this handler access to the exposed filter input.
          if (!empty($this->exposed_data)) {
1199
            if ($handlers[$id]->isAGroup()) {
1200
              $converted = $handlers[$id]->convertExposedInput($this->exposed_data, $group_id);
1201
              $handlers[$id]->storeGroupInput($this->exposed_data, $converted);
1202
1203
1204
1205
              if (!$converted) {
                continue;
              }
            }
1206
1207
            $rc = $handlers[$id]->acceptExposedInput($this->exposed_data);
            $handlers[$id]->storeExposedInput($this->exposed_data, $rc);
1208
1209
1210
            if (!$rc) {
              continue;
            }
merlinofchaos's avatar
merlinofchaos committed