ViewExecutable.php 56.1 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;
11
use Symfony\Component\HttpFoundation\Response;
12
use Drupal\views\ViewStorageInterface;
13

merlinofchaos's avatar
merlinofchaos committed
14 15 16 17 18 19 20 21 22 23 24
/**
 * @defgroup views_objects Objects that represent a View or part of a view
 * @{
 * These objects are the core of Views do the bulk of the direction and
 * storing of data. All database activity is in these objects.
 */

/**
 * 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.
 */
25
class ViewExecutable {
26

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

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

  /**
   * Whether the view has been executed/query has been run.
   *
46 47
   * @todo Group with other static properties.
   *
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
   * @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
   */
71
  protected $ajaxEnabled = FALSE;
merlinofchaos's avatar
merlinofchaos committed
72

73 74 75 76 77 78 79
  /**
   * Where the results of a query will go.
   *
   * The array must use a numeric index starting at 0.
   *
   * @var array
   */
80
  public $result = array();
merlinofchaos's avatar
merlinofchaos committed
81 82 83

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

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
  /**
   * The current page. If the view uses pagination.
   *
   * @var int
   */
  public $current_page = NULL;

  /**
   * The number of items per page.
   *
   * @var int
   */
  public $items_per_page = NULL;

  /**
   * The pager offset.
   *
   * @var int
   */
  public $offset = NULL;

  /**
   * The total number of rows returned from the query.
   *
   * @var array
   */
  public $total_rows = NULL;

  /**
113
   * Attachments to place before the view.
114
   *
115
   * @var array()
116
   */
117
  public $attachment_before = array();
118 119

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

  // Exposed widget input

128 129 130 131 132 133
  /**
   * All the form data from $form_state['values'].
   *
   * @var array
   */
  public $exposed_data = array();
merlinofchaos's avatar
merlinofchaos committed
134

135 136 137 138 139 140
  /**
   * An array of input values from exposed forms.
   *
   * @var array
   */
  public $exposed_input = array();
merlinofchaos's avatar
merlinofchaos committed
141

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
  /**
   * Exposed widget input directly from the $form_state['values'].
   *
   * @var array
   */
  public $exposed_raw_input = array();

  /**
   * Used to store views that were previously running if we recurse.
   *
   * @var array
   */
  public $old_view = array();

  /**
   * To avoid recursion in views embedded into areas.
   *
   * @var array
   */
  public $parent_views = array();

  /**
   * Whether this view is an attachment to another view.
   *
   * @var bool
   */
  public $is_attachment = NULL;
merlinofchaos's avatar
merlinofchaos committed
169 170 171 172 173 174

  /**
   * Identifier of the current display.
   *
   * @var string
   */
175
  public $current_display;
merlinofchaos's avatar
merlinofchaos committed
176 177

  /**
178
   * Where the $query object will reside.
merlinofchaos's avatar
merlinofchaos committed
179
   *
180
   * @var Drupal\views\Plugin\query\QueryInterface
merlinofchaos's avatar
merlinofchaos committed
181
   */
182
  public $query = NULL;
merlinofchaos's avatar
merlinofchaos committed
183

184 185 186 187 188 189 190 191
  /**
   * The used pager plugin used by the current executed view.
   *
   * @var Drupal\views\Plugin\views\pager\PagerPluginBase
   */
  public $pager = NULL;

  /**
merlinofchaos's avatar
merlinofchaos committed
192 193
   * The current used display plugin.
   *
194
   * @var Drupal\views\Plugin\views\display\DisplayPluginBase
merlinofchaos's avatar
merlinofchaos committed
195
   */
196
  public $display_handler;
merlinofchaos's avatar
merlinofchaos committed
197

198 199 200 201 202 203 204 205 206 207
  /**
   * The list of used displays of the view.
   *
   * An array containing Drupal\views\Plugin\views\display\DisplayPluginBase
   * objects.
   *
   * @var array
   */
  public $displayHandlers;

merlinofchaos's avatar
merlinofchaos committed
208 209 210
  /**
   * The current used style plugin.
   *
211
   * @var \Drupal\views\Plugin\views\style\StylePluginBase
merlinofchaos's avatar
merlinofchaos committed
212
   */
213
  public $style_plugin;
merlinofchaos's avatar
merlinofchaos committed
214

215 216 217 218 219 220 221
  /**
   * 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
222 223 224 225 226
  /**
   * Stores the current active row while rendering.
   *
   * @var int
   */
227
  public $row_index;
merlinofchaos's avatar
merlinofchaos committed
228 229 230 231 232 233

   /**
   * Allow to override the url of the current view.
   *
   * @var string
   */
234
  public $override_url = NULL;
merlinofchaos's avatar
merlinofchaos committed
235 236 237 238 239 240

  /**
   * Allow to override the path used for generated urls.
   *
   * @var string
   */
241
  public $override_path = NULL;
merlinofchaos's avatar
merlinofchaos committed
242 243 244

  /**
   * Allow to override the used database which is used for this query.
245 246
   *
   * @var bool
merlinofchaos's avatar
merlinofchaos committed
247
   */
248
  public $base_database = NULL;
merlinofchaos's avatar
merlinofchaos committed
249

250
  // Handlers which are active on this view.
merlinofchaos's avatar
merlinofchaos committed
251 252 253

  /**
   * Stores the field handlers which are initialized on this view.
254 255 256 257 258
   *
   * An array containing Drupal\views\Plugin\views\field\FieldPluginBase
   * objects.
   *
   * @var array
merlinofchaos's avatar
merlinofchaos committed
259
   */
260
  public $field;
merlinofchaos's avatar
merlinofchaos committed
261 262 263

  /**
   * Stores the argument handlers which are initialized on this view.
264 265 266 267 268
   *
   * An array containing Drupal\views\Plugin\views\argument\ArgumentPluginBase
   * objects.
   *
   * @var array
merlinofchaos's avatar
merlinofchaos committed
269
   */
270
  public $argument;
merlinofchaos's avatar
merlinofchaos committed
271 272 273

  /**
   * Stores the sort handlers which are initialized on this view.
274 275 276 277
   *
   * An array containing Drupal\views\Plugin\views\sort\SortPluginBase objects.
   *
   * @var array
merlinofchaos's avatar
merlinofchaos committed
278
   */
279
  public $sort;
merlinofchaos's avatar
merlinofchaos committed
280 281 282

  /**
   * Stores the filter handlers which are initialized on this view.
283 284 285 286 287
   *
   * An array containing Drupal\views\Plugin\views\filter\FilterPluginBase
   * objects.
   *
   * @var array
merlinofchaos's avatar
merlinofchaos committed
288
   */
289
  public $filter;
merlinofchaos's avatar
merlinofchaos committed
290 291 292

  /**
   * Stores the relationship handlers which are initialized on this view.
293 294 295 296 297
   *
   * An array containing Drupal\views\Plugin\views\relationship\RelationshipPluginBase
   * objects.
   *
   * @var array
merlinofchaos's avatar
merlinofchaos committed
298
   */
299
  public $relationship;
merlinofchaos's avatar
merlinofchaos committed
300 301 302

  /**
   * Stores the area handlers for the header which are initialized on this view.
303 304 305 306
   *
   * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
   *
   * @var array
merlinofchaos's avatar
merlinofchaos committed
307
   */
308
  public $header;
merlinofchaos's avatar
merlinofchaos committed
309 310 311

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

  /**
   * Stores the area handlers for the empty text which are initialized on this view.
321 322 323 324
   *
   * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
   *
   * @var array
merlinofchaos's avatar
merlinofchaos committed
325
   */
326
  public $empty;
merlinofchaos's avatar
merlinofchaos committed
327

328 329 330 331 332 333 334
  /**
   * Stores the current response object.
   *
   * @var Symfony\Component\HttpFoundation\Response
   */
  protected $response = NULL;

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 389
  /**
   * 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;

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

408 409 410 411 412 413 414
  /**
   * Should the admin links be shown on the rendered view.
   *
   * @var bool
   */
  protected $showAdminLinks;

415 416 417
  /**
   * Constructs a new ViewExecutable object.
   *
418
   * @param \Drupal\views\ViewStorageInterface $storage
419 420
   *   The view config entity the actual information is stored on.
   */
421
  public function __construct(ViewStorageInterface $storage) {
422 423
    // Reference the storage and the executable to each other.
    $this->storage = $storage;
424
    $this->storage->set('executable', $this);
425 426 427

    // Add the default css for a view.
    $this->element['#attached']['css'][] = drupal_get_path('module', 'views') . '/css/views.base.css';
428 429 430
  }

  /**
431
   * @todo.
432
   */
433 434
  public function save() {
    $this->storage->save();
merlinofchaos's avatar
merlinofchaos committed
435 436 437 438 439 440
  }

  /**
   * Set the arguments that come to this view. Usually from the URL
   * but possibly from elsewhere.
   */
441
  public function setArguments($args) {
merlinofchaos's avatar
merlinofchaos committed
442 443 444 445 446 447
    $this->args = $args;
  }

  /**
   * Change/Set the current page for the pager.
   */
448
  public function setCurrentPage($page) {
merlinofchaos's avatar
merlinofchaos committed
449
    $this->current_page = $page;
450 451

    // If the pager is already initialized, pass it through to the pager.
452 453
    if (!empty($this->pager)) {
      return $this->pager->set_current_page($page);
454
    }
merlinofchaos's avatar
merlinofchaos committed
455 456 457 458 459
  }

  /**
   * Get the current page from the pager.
   */
460
  public function getCurrentPage() {
461
    // If the pager is already initialized, pass it through to the pager.
462 463
    if (!empty($this->pager)) {
      return $this->pager->get_current_page();
merlinofchaos's avatar
merlinofchaos committed
464
    }
465 466 467 468

    if (isset($this->current_page)) {
      return $this->current_page;
    }
merlinofchaos's avatar
merlinofchaos committed
469 470 471 472 473
  }

  /**
   * Get the items per page from the pager.
   */
474
  public function getItemsPerPage() {
475
    // If the pager is already initialized, pass it through to the pager.
476 477
    if (!empty($this->pager)) {
      return $this->pager->get_items_per_page();
merlinofchaos's avatar
merlinofchaos committed
478
    }
479 480 481 482

    if (isset($this->items_per_page)) {
      return $this->items_per_page;
    }
merlinofchaos's avatar
merlinofchaos committed
483 484 485 486 487
  }

  /**
   * Set the items per page on the pager.
   */
488
  public function setItemsPerPage($items_per_page) {
merlinofchaos's avatar
merlinofchaos committed
489 490 491
    $this->items_per_page = $items_per_page;

    // If the pager is already initialized, pass it through to the pager.
492 493
    if (!empty($this->pager)) {
      $this->pager->set_items_per_page($items_per_page);
merlinofchaos's avatar
merlinofchaos committed
494 495 496 497 498 499
    }
  }

  /**
   * Get the pager offset from the pager.
   */
500
  public function getOffset() {
501
    // If the pager is already initialized, pass it through to the pager.
502 503
    if (!empty($this->pager)) {
      return $this->pager->get_offset();
merlinofchaos's avatar
merlinofchaos committed
504
    }
505 506 507 508

    if (isset($this->offset)) {
      return $this->offset;
    }
merlinofchaos's avatar
merlinofchaos committed
509 510 511 512 513
  }

  /**
   * Set the offset on the pager.
   */
514
  public function setOffset($offset) {
merlinofchaos's avatar
merlinofchaos committed
515 516 517
    $this->offset = $offset;

    // If the pager is already initialized, pass it through to the pager.
518 519
    if (!empty($this->pager)) {
      $this->pager->set_offset($offset);
merlinofchaos's avatar
merlinofchaos committed
520 521 522 523 524 525
    }
  }

  /**
   * Determine if the pager actually uses a pager.
   */
526
  public function usePager() {
527 528
    if (!empty($this->pager)) {
      return $this->pager->use_pager();
merlinofchaos's avatar
merlinofchaos committed
529 530 531 532
    }
  }

  /**
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
   * 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
551
   */
552 553
  public function ajaxEnabled() {
    return $this->ajaxEnabled;
merlinofchaos's avatar
merlinofchaos committed
554 555 556 557 558 559
  }

  /**
   * Set the exposed filters input to an array. If unset they will be taken
   * from $_GET when the time comes.
   */
560
  public function setExposedInput($filters) {
merlinofchaos's avatar
merlinofchaos committed
561 562 563 564 565 566
    $this->exposed_input = $filters;
  }

  /**
   * Figure out what the exposed input for this view is.
   */
567
  public function getExposedInput() {
merlinofchaos's avatar
merlinofchaos committed
568 569 570
    // Fill our input either from $_GET or from something previously set on the
    // view.
    if (empty($this->exposed_input)) {
571
      $this->exposed_input = drupal_container()->get('request')->query->all();
merlinofchaos's avatar
merlinofchaos committed
572 573 574 575 576 577 578 579 580 581 582 583 584
      // 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.
585
      $display_id = ($this->display_handler->isDefaulted('filters')) ? 'default' : $this->current_display;
merlinofchaos's avatar
merlinofchaos committed
586

587 588
      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
589 590 591 592 593 594 595 596 597
      }
    }

    return $this->exposed_input;
  }

  /**
   * Set the display for this view and initialize the display handler.
   */
598
  public function initDisplay() {
merlinofchaos's avatar
merlinofchaos committed
599 600 601 602
    if (isset($this->current_display)) {
      return TRUE;
    }

603
    // Initialize the display cache array.
604
    $this->displayHandlers = new DisplayBag($this, Views::pluginManager('display'));
merlinofchaos's avatar
merlinofchaos committed
605 606

    $this->current_display = 'default';
607
    $this->display_handler = $this->displayHandlers->get('default');
merlinofchaos's avatar
merlinofchaos committed
608 609 610 611 612 613 614

    return TRUE;
  }

  /**
   * Get the first display that is accessible to the user.
   *
615
   * @param array|string $displays
merlinofchaos's avatar
merlinofchaos committed
616
   *   Either a single display id or an array of display ids.
617 618 619
   *
   * @return string
   *   The first accessible display id, at least default.
merlinofchaos's avatar
merlinofchaos committed
620
   */
621
  public function chooseDisplay($displays) {
merlinofchaos's avatar
merlinofchaos committed
622 623 624 625
    if (!is_array($displays)) {
      return $displays;
    }

626
    $this->initDisplay();
merlinofchaos's avatar
merlinofchaos committed
627 628

    foreach ($displays as $display_id) {
629
      if ($this->displayHandlers->get($display_id)->access()) {
merlinofchaos's avatar
merlinofchaos committed
630 631 632 633 634 635 636 637
        return $display_id;
      }
    }

    return 'default';
  }

  /**
638
   * Sets the current display.
merlinofchaos's avatar
merlinofchaos committed
639
   *
640 641 642 643 644
   * @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
645
   */
646
  public function setDisplay($display_id = NULL) {
647 648 649 650
    // 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.
651
      $this->initDisplay();
652
    }
merlinofchaos's avatar
merlinofchaos committed
653

654 655 656 657
    // 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
658 659
    }

660
    $display_id = $this->chooseDisplay($display_id);
merlinofchaos's avatar
merlinofchaos committed
661 662

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

668 669 670 671 672 673 674 675 676 677 678 679
    // 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;
    }

680
    // Set a shortcut.
681
    $this->display_handler = $this->displayHandlers->get($display_id);
merlinofchaos's avatar
merlinofchaos committed
682 683 684 685 686 687 688 689 690 691

    return TRUE;
  }

  /**
   * 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.
   */
692
  public function initStyle() {
merlinofchaos's avatar
merlinofchaos committed
693
    if (isset($this->style_plugin)) {
694
      return TRUE;
merlinofchaos's avatar
merlinofchaos committed
695 696
    }

697
    $this->style_plugin = $this->display_handler->getPlugin('style');
merlinofchaos's avatar
merlinofchaos committed
698 699 700 701 702 703 704 705 706 707 708

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

    return TRUE;
  }

  /**
   * Acquire and attach all of the handlers.
   */
709
  public function initHandlers() {
710
    $this->initDisplay();
merlinofchaos's avatar
merlinofchaos committed
711
    if (empty($this->inited)) {
712
      foreach ($this::viewsHandlerTypes() as $key => $info) {
713
        $this->_initHandler($key, $info);
merlinofchaos's avatar
merlinofchaos committed
714 715 716 717 718 719 720 721 722 723 724
      }
      $this->inited = TRUE;
    }
  }

  /**
   * Initialize the pager
   *
   * Like style initialization, pager initialization is held until late
   * to allow for overrides.
   */
725
  public function initPager() {
726
    if (!isset($this->pager)) {
727
      $this->pager = $this->display_handler->getPlugin('pager');
merlinofchaos's avatar
merlinofchaos committed
728

729 730
      if ($this->pager->use_pager()) {
        $this->pager->set_current_page($this->current_page);
merlinofchaos's avatar
merlinofchaos committed
731 732 733 734 735
      }

      // These overrides may have been set earlier via $view->set_*
      // functions.
      if (isset($this->items_per_page)) {
736
        $this->pager->set_items_per_page($this->items_per_page);
merlinofchaos's avatar
merlinofchaos committed
737 738 739
      }

      if (isset($this->offset)) {
740
        $this->pager->set_offset($this->offset);
merlinofchaos's avatar
merlinofchaos committed
741 742 743 744
      }
    }
  }

745 746 747
  /**
   * Render the pager, if necessary.
   */
748
  public function renderPager($exposed_input) {
749 750 751 752 753 754 755
    if (!empty($this->pager) && $this->pager->use_pager()) {
      return $this->pager->render($exposed_input);
    }

    return '';
  }

merlinofchaos's avatar
merlinofchaos committed
756 757 758 759
  /**
   * Create a list of base tables eligible for this view. Used primarily
   * for the UI. Display must be already initialized.
   */
760
  public function getBaseTables() {
merlinofchaos's avatar
merlinofchaos committed
761
    $base_tables = array(
762
      $this->storage->get('base_table') => TRUE,
merlinofchaos's avatar
merlinofchaos committed
763 764 765
      '#global' => TRUE,
    );

766
    foreach ($this->display_handler->getHandlers('relationship') as $handler) {
merlinofchaos's avatar
merlinofchaos committed
767 768 769 770 771 772
      $base_tables[$handler->definition['base']] = TRUE;
    }
    return $base_tables;
  }

  /**
773
   * Run the preQuery() on all active handlers.
merlinofchaos's avatar
merlinofchaos committed
774
   */
775
  protected function _preQuery() {
776
    foreach ($this::viewsHandlerTypes() as $key => $info) {
merlinofchaos's avatar
merlinofchaos committed
777 778 779 780
      $handlers = &$this->$key;
      $position = 0;
      foreach ($handlers as $id => $handler) {
        $handlers[$id]->position = $position;
781
        $handlers[$id]->preQuery();
merlinofchaos's avatar
merlinofchaos committed
782 783 784 785 786 787
        $position++;
      }
    }
  }

  /**
788
   * Run the postExecute() on all active handlers.
merlinofchaos's avatar
merlinofchaos committed
789
   */
790
  protected function _postExecute() {
791
    foreach ($this::viewsHandlerTypes() as $key => $info) {
merlinofchaos's avatar
merlinofchaos committed
792 793
      $handlers = &$this->$key;
      foreach ($handlers as $id => $handler) {
794
        $handlers[$id]->postExecute($this->result);
merlinofchaos's avatar
merlinofchaos committed
795 796 797 798 799 800 801 802 803 804
      }
    }
  }

  /**
   * Attach all of the handlers for each type.
   *
   * @param $key
   *   One of 'argument', 'field', 'sort', 'filter', 'relationship'
   * @param $info
805
   *   The $info from viewsHandlerTypes for this object.
merlinofchaos's avatar
merlinofchaos committed
806
   */
807
  protected function _initHandler($key, $info) {
merlinofchaos's avatar
merlinofchaos committed
808
    // Load the requested items from the display onto the object.
809
    $this->$key = $this->display_handler->getHandlers($key);
merlinofchaos's avatar
merlinofchaos committed
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824

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

    // Run through and test for accessibility.
    foreach ($handlers as $id => $handler) {
      if (!$handler->access()) {
        unset($handlers[$id]);
      }
    }
  }

  /**
   * Build all the arguments.
   */
825
  protected function _buildArguments() {
merlinofchaos's avatar
merlinofchaos committed
826 827 828 829 830 831 832 833 834 835
    // 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;

    // Create a title for use in the breadcrumb trail.
836
    $title = $this->display_handler->getOption('title');
merlinofchaos's avatar
merlinofchaos committed
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852

    $this->build_info['breadcrumb'] = array();
    $breadcrumb_args = array();
    $substitutions = array();

    $status = TRUE;

    // Iterate through each argument and process.
    foreach ($this->argument as $id => $arg) {
      $position++;
      $argument = &$this->argument[$id];

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

853
      $argument->setRelationship();
merlinofchaos's avatar
merlinofchaos committed
854 855 856 857

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

858
      if (isset($arg) || $argument->hasDefaultArgument()) {
merlinofchaos's avatar
merlinofchaos committed
859 860 861 862 863 864 865 866 867 868 869 870
        if (!isset($arg)) {
          $arg = $argument->get_default_argument();
          // 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.
        if (!$argument->set_argument($arg)) {
871
          $status = $argument->validateFail($arg);
merlinofchaos's avatar
merlinofchaos committed
872 873 874 875 876 877 878
          break;
        }

        if ($argument->is_exception()) {
          $arg_title = $argument->exception_title();
        }
        else {
879
          $arg_title = $argument->getTitle();
880
          $argument->query($this->display_handler->useGroupBy());
merlinofchaos's avatar
merlinofchaos committed
881 882 883 884 885 886 887 888
        }

        // Add this argument's substitution
        $substitutions['%' . ($position + 1)] = $arg_title;
        $substitutions['!' . ($position + 1)] = strip_tags(decode_entities($arg));

        // Since we're really generating the breadcrumb for the item above us,
        // check the default action of this argument.
889
        if ($this->display_handler->usesBreadcrumb() && $argument->uses_breadcrumb()) {
890
          $path = $this->getUrl($breadcrumb_args);
merlinofchaos's avatar
merlinofchaos committed
891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
          if (strpos($path, '%') === FALSE) {
            if (!empty($argument->options['breadcrumb_enable']) && !empty($argument->options['breadcrumb'])) {
              $breadcrumb = $argument->options['breadcrumb'];
            }
            else {
              $breadcrumb = $title;
            }
            $this->build_info['breadcrumb'][$path] = str_replace(array_keys($substitutions), $substitutions, $breadcrumb);
          }
        }

        // Allow the argument to muck with this breadcrumb.
        $argument->set_breadcrumb($this->build_info['breadcrumb']);

        // 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'];
        }

        $breadcrumb_args[] = $arg;
      }
      else {
        // determine default condition and handle.
        $status = $argument->default_action();
        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;
  }

  /**
   * Do some common building initialization.
   */
936
  public function initQuery() {
merlinofchaos's avatar
merlinofchaos committed
937 938 939 940 941 942 943 944 945
    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.
946
    $views_data = Views::viewsData()->get($this->storage->get('base_table'));
947
    $this->storage->set('base_field', !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : '');
merlinofchaos's avatar
merlinofchaos committed
948 949 950 951
    if (!empty($views_data['table']['base']['database'])) {
      $this->base_database = $views_data['table']['base']['database'];
    }

952
    $this->query = $this->display_handler->getPlugin('query');
merlinofchaos's avatar
merlinofchaos committed
953 954 955 956 957 958
    return TRUE;
  }

  /**
   * Build the query for the view.
   */
959
  public function build($display_id = NULL) {
merlinofchaos's avatar
merlinofchaos committed
960 961 962 963 964
    if (!empty($this->built)) {
      return;
    }

    if (empty($this->current_display) || $display_id) {
965
      if (!$this->setDisplay($display_id)) {
merlinofchaos's avatar
merlinofchaos committed
966 967 968 969 970
        return FALSE;
      }
    }

    // Let modules modify the view just prior to building it.
971 972
    $module_handler = \Drupal::moduleHandler();
    $module_handler->invokeAll('views_pre_build', array($this));
merlinofchaos's avatar
merlinofchaos committed
973 974 975 976 977 978 979 980 981 982 983 984

    // 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(),
    );

985
    $this->initQuery();
merlinofchaos's avatar
merlinofchaos committed
986 987 988 989 990 991 992

    // 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.
993
    $this->initHandlers();
merlinofchaos's avatar
merlinofchaos committed
994 995

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

998 999
    if ($this->display_handler->usesExposed()) {
      $exposed_form = $this->display_handler->getPlugin('exposed_form');
merlinofchaos's avatar
merlinofchaos committed
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
      $this->exposed_widgets = $exposed_form->render_exposed_form();
      if (form_set_error() || !empty($this->build_info['abort'])) {
        $this->built = TRUE;
        // Don't execute the query, but rendering will still be executed to display the empty text.
        $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)) {
1014
      $filter_groups = $this->display_handler->getOption('filter_groups');
merlinofchaos's avatar
merlinofchaos committed
1015 1016
      if ($filter_groups) {
        $this->query->set_group_operator($filter_groups['operator']);
1017
        foreach ($filter_groups['groups'] as $id => $operator) {
merlinofchaos's avatar
merlinofchaos committed
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
          $this->query->set_where_group($operator, $id);
        }
      }
    }

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

    $this->build_sort = TRUE;

    // Arguments can, in fact, cause this whole thing to abort.
1029
    if (!$this->_buildArguments()) {
merlinofchaos's avatar
merlinofchaos committed
1030
      $this->build_time = microtime(TRUE) - $start;
1031
      $this->attachDisplays();
merlinofchaos's avatar
merlinofchaos committed
1032 1033 1034 1035 1036 1037
      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.
1038
    if (!$this->initStyle()) {
merlinofchaos's avatar
merlinofchaos committed
1039 1040 1041 1042
      $this->build_info['fail'] = TRUE;
      return FALSE;
    }

1043
    if ($this->style_plugin->usesFields()) {
merlinofchaos's avatar
merlinofchaos committed
1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
      $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.
      if ($this->style_plugin->build_sort()) {
        $this->_build('sort');
      }
      // allow the plugin to build second sorts as well.
      $this->style_plugin->build_sort_post();
    }

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

    // Allow display handler to affect the query:
1063
    $this->display_handler->query($this->display_handler->useGroupBy());
merlinofchaos's avatar
merlinofchaos committed
1064 1065

    // Allow style handler to affect the query:
1066
    $this->style_plugin->query($this->display_handler->useGroupBy());
merlinofchaos's avatar
merlinofchaos committed
1067 1068 1069 1070 1071 1072

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

1073
    if (config('views.settings')->get('sql_signature')) {
merlinofchaos's avatar
merlinofchaos committed
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
      $this->query->add_signature($this);
    }

    // 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
1090
    $this->attachDisplays();
merlinofchaos's avatar
merlinofchaos committed
1091 1092

    // Let modules modify the view just after building it.
1093
    $module_handler->invokeAll('views_post_build', array($this));
merlinofchaos's avatar
merlinofchaos committed
1094 1095 1096 1097 1098 1099

    return TRUE;
  }

  /**
   * Internal method to build an individual set of handlers.
1100
   *
1101 1102
   * @todo Some filter needs this function, even it is internal.
   *
1103 1104 1105
   * @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
1106
   */
1107
  public function _build($key) {
merlinofchaos's avatar
merlinofchaos committed
1108 1109
    $handlers = &$this->$key;
    foreach ($handlers as $id => $data) {
1110

merlinofchaos's avatar
merlinofchaos committed
1111
      if (!empty($handlers[$id]) && is_object($handlers[$id])) {
1112
        $multiple_exposed_input = array(0 => NULL);
1113
        if ($handlers[$id]->multipleExposedInput()) {
1114 1115 1116 1117 1118 1119
          $multiple_exposed_input = $handlers[$id]->group_multiple_exposed_input($this->exposed_data);
        }
        foreach ($multiple_exposed_input as $group_id) {
          // Give this handler access to the exposed filter input.
          if (!empty($this->exposed_data)) {
            $converted = FALSE;
1120
            if ($handlers[$id]->isAGroup()) {
1121 1122 1123 1124 1125 1126
              $converted = $handlers[$id]->convert_exposed_input($this->exposed_data, $group_id);
              $handlers[$id]->store_group_input($this->exposed_data, $converted);
              if (!$converted) {
                continue;
              }
            }
1127 1128
            $rc = $handlers[$id]->acceptExposedInput($this->exposed_data);
            $handlers[$id]->storeExposedInput($this->exposed_data, $rc);
1129 1130 1131
            if (!$rc) {
              continue;
            }
merlinofchaos's avatar
merlinofchaos committed
1132
          }
1133
          $handlers[$id]->setRelationship();
1134
          $handlers[$id]->query($this->display_handler->useGroupBy());
merlinofchaos's avatar
merlinofchaos committed
1135 1136 1137 1138 1139 1140 1141
        }
      }
    }
  }

  /**
   * Execute the view's query.
1142 1143 1144 1145 1146 1147 1148
   *
   * @param string $display_id
   *   The machine name of the display, which should be executed.
   *
   * @return bool
   *   Return whether the executing was successful, for example an argument
   *   could stop the process.
merlinofchaos's avatar
merlinofchaos committed
1149
   */
1150
  public function execute($display_id = NULL) {
merlinofchaos's avatar
merlinofchaos committed
1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161
    if (empty($this->built)) {
      if (!$this->build($display_id)) {
        return FALSE;
      }
    }

    if (!empty($this->executed)) {
      return TRUE;
    }

    // Don't allow to use deactivated displays, but display them on the live preview.
1162
    if (!$this->display_handler->isEnabled() && empty($this->live_preview)) {
merlinofchaos's avatar
merlinofchaos committed
1163 1164 1165 1166 1167
      $this->build_info['fail'] = TRUE;
      return FALSE;
    }

    // Let modules modify the view just prior to executing it.
1168 1169
    $module_handler = \Drupal::moduleHandler();
    $module_handler->invokeAll('views_pre_execute', array($this));
merlinofchaos's avatar
merlinofchaos committed
1170 1171 1172

    // Check for already-cached results.
    if (!empty($this->live_preview)) {
1173
      $cache = $this->display_handler->getPlugin('cache', 'none');
merlinofchaos's avatar
merlinofchaos committed
1174 1175
    }
    else {
1176
      $cache = $this->display_handler->getPlugin('cache');