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

/**
 * @file
5
 * Contains \Drupal\views\Plugin\views\display\DisplayPluginBase.
merlinofchaos's avatar
merlinofchaos committed
6 7
 */

8
namespace Drupal\views\Plugin\views\display;
9

10
use Drupal\Component\Plugin\DependentPluginInterface;
11
use Drupal\Component\Utility\Html;
12
use Drupal\Component\Utility\SafeMarkup;
13
use Drupal\Core\Cache\Cache;
14
use Drupal\Core\Cache\CacheableMetadata;
15
use Drupal\Core\Form\FormStateInterface;
16
use Drupal\Core\Language\LanguageInterface;
17
use Drupal\Core\Plugin\PluginDependencyTrait;
18
use Drupal\Core\Session\AccountInterface;
19
use Drupal\Core\Theme\Registry;
20
use Drupal\Core\Url;
21
use Drupal\views\Form\ViewsForm;
22
use Drupal\views\Plugin\CacheablePluginInterface;
23
use Drupal\views\Plugin\views\area\AreaPluginBase;
24
use Drupal\views\ViewExecutable;
25
use Drupal\views\Plugin\views\PluginBase;
26
use Drupal\views\Views;
27
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
28
use Symfony\Component\DependencyInjection\Exception\RuntimeException as DependencyInjectionRuntimeException;
29

merlinofchaos's avatar
merlinofchaos committed
30
/**
31
 * Base class for views display plugins.
merlinofchaos's avatar
merlinofchaos committed
32
 */
33 34
abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInterface, DependentPluginInterface {
  use PluginDependencyTrait;
35

merlinofchaos's avatar
merlinofchaos committed
36 37 38
  /**
   * The top object of a view.
   *
39
   * @var \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
40 41 42
   */
  var $view = NULL;

43 44 45 46 47 48
  /**
   * An array of instantiated handlers used in this display.
   *
   * @var \Drupal\views\Plugin\views\ViewsHandlerInterface[]
   */
   public $handlers = [];
merlinofchaos's avatar
merlinofchaos committed
49

50 51 52
  /**
   * An array of instantiated plugins used in this display.
   *
53
   * @var \Drupal\views\Plugin\views\ViewsPluginInterface[]
54 55 56
   */
  protected $plugins = array();

merlinofchaos's avatar
merlinofchaos committed
57 58
  /**
   * Stores all available display extenders.
59 60
   *
   * @var \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
61
   */
62
  protected $extenders = [];
merlinofchaos's avatar
merlinofchaos committed
63

64 65 66
  /**
   * Overrides Drupal\views\Plugin\Plugin::$usesOptions.
   */
67
  protected $usesOptions = TRUE;
68

69 70 71
  /**
   * Stores the rendered output of the display.
   *
72
   * @see View::render
73 74 75 76
   * @var string
   */
  public $output = NULL;

77 78 79 80 81 82 83
  /**
   * Whether the display allows the use of AJAX or not.
   *
   * @var bool
   */
  protected $usesAJAX = TRUE;

84 85 86 87 88 89 90 91 92 93 94 95 96 97
  /**
   * Whether the display allows the use of a pager or not.
   *
   * @var bool
   */
  protected $usesPager = TRUE;

  /**
   * Whether the display allows the use of a 'more' link or not.
   *
   * @var bool
   */
  protected $usesMore = TRUE;

98 99 100 101 102 103 104 105
  /**
   * Whether the display allows attachments.
   *
   * @var bool
   *   TRUE if the display can use attachments, or FALSE otherwise.
   */
  protected $usesAttachments = FALSE;

106 107 108 109 110 111 112
  /**
   * Whether the display allows area plugins.
   *
   * @var bool
   */
  protected $usesAreas = TRUE;

113 114 115 116 117 118 119
  /**
   * Static cache for unpackOptions, but not if we are in the UI.
   *
   * @var array
   */
  protected static $unpackOptions = array();

120 121 122 123 124 125 126 127 128 129 130
  /**
   * The display information coming directly from the view entity.
   *
   * @see \Drupal\views\Entity\View::getDisplay()
   *
   * @todo \Drupal\views\Entity\View::duplicateDisplayAsType directly access it.
   *
   * @var array
   */
  public $display;

131 132 133 134 135 136 137 138 139 140
  /**
   * Constructs a new DisplayPluginBase object.
   *
   * Because DisplayPluginBase::initDisplay() takes the display configuration by
   * reference and handles it differently than usual plugin configuration, pass
   * an empty array of configuration to the parent. This prevents our
   * configuration from being duplicated.
   *
   * @todo Replace DisplayPluginBase::$display with
   *   DisplayPluginBase::$configuration to standardize with other plugins.
141 142 143 144 145 146 147
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
148
   */
149
  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
150 151 152
    parent::__construct(array(), $plugin_id, $plugin_definition);
  }

153 154 155
  /**
   * {@inheritdoc}
   */
156 157
  public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
    $this->view = $view;
merlinofchaos's avatar
merlinofchaos committed
158 159

    // Load extenders as soon as possible.
160 161
    $display['display_options'] += ['display_extenders' => []];
    $this->extenders = array();
162
    if ($extenders = Views::getEnabledDisplayExtenders()) {
163
      $manager = Views::pluginManager('display_extender');
164
      $display_extender_options = $display['display_options']['display_extenders'];
merlinofchaos's avatar
merlinofchaos committed
165
      foreach ($extenders as $extender) {
166
        /** @var \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase $plugin */
167
        if ($plugin = $manager->createInstance($extender)) {
168 169 170
          $extender_options = isset($display_extender_options[$plugin->getPluginId()]) ? $display_extender_options[$plugin->getPluginId()] : [];
          $plugin->init($this->view, $this, $extender_options);
          $this->extenders[$extender] = $plugin;
merlinofchaos's avatar
merlinofchaos committed
171 172 173 174
        }
      }
    }

175 176 177 178

    $this->setOptionDefaults($this->options, $this->defineOptions());
    $this->display = &$display;

merlinofchaos's avatar
merlinofchaos committed
179 180 181
    // Track changes that the user should know about.
    $changed = FALSE;

182 183
    if (!isset($options) && isset($display['display_options'])) {
      $options = $display['display_options'];
merlinofchaos's avatar
merlinofchaos committed
184 185
    }

186
    if ($this->isDefaultDisplay() && isset($options['defaults'])) {
merlinofchaos's avatar
merlinofchaos committed
187 188 189
      unset($options['defaults']);
    }

190 191 192
    $skip_cache = \Drupal::config('views.settings')->get('skip_cache');

    if (empty($view->editing) || !$skip_cache) {
193
      $cid = 'views:unpack_options:' . hash('sha256', serialize(array($this->options, $options))) . ':' . \Drupal::languageManager()->getCurrentLanguage()->getId();
194
      if (empty(static::$unpackOptions[$cid])) {
195
        $cache = \Drupal::cache('data')->get($cid);
merlinofchaos's avatar
merlinofchaos committed
196 197 198 199
        if (!empty($cache->data)) {
          $this->options = $cache->data;
        }
        else {
200
          $this->unpackOptions($this->options, $options);
201
          \Drupal::cache('data')->set($cid, $this->options, Cache::PERMANENT, $this->view->storage->getCacheTags());
merlinofchaos's avatar
merlinofchaos committed
202
        }
203
        static::$unpackOptions[$cid] = $this->options;
merlinofchaos's avatar
merlinofchaos committed
204 205
      }
      else {
206
        $this->options = static::$unpackOptions[$cid];
merlinofchaos's avatar
merlinofchaos committed
207 208 209
      }
    }
    else {
210
      $this->unpackOptions($this->options, $options);
merlinofchaos's avatar
merlinofchaos committed
211 212 213 214 215 216 217 218
    }

    // Mark the view as changed so the user has a chance to save it.
    if ($changed) {
      $this->view->changed = TRUE;
    }
  }

219 220 221
  /**
   * {@inheritdoc}
   */
222
  public function destroy() {
merlinofchaos's avatar
merlinofchaos committed
223 224 225 226 227 228 229 230 231 232 233 234 235 236
    parent::destroy();

    foreach ($this->handlers as $type => $handlers) {
      foreach ($handlers as $id => $handler) {
        if (is_object($handler)) {
          $this->handlers[$type][$id]->destroy();
        }
      }
    }

    if (isset($this->default_display)) {
      unset($this->default_display);
    }

237
    foreach ($this->extenders as $extender) {
merlinofchaos's avatar
merlinofchaos committed
238 239 240 241 242
      $extender->destroy();
    }
  }

  /**
243
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
244
   */
245
  public function isDefaultDisplay() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
246 247

  /**
248
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
249
   */
250
  public function usesExposed() {
merlinofchaos's avatar
merlinofchaos committed
251 252
    if (!isset($this->has_exposed)) {
      foreach ($this->handlers as $type => $value) {
253
        foreach ($this->view->$type as $handler) {
254
          if ($handler->canExpose() && $handler->isExposed()) {
255
            // One is all we need; if we find it, return TRUE.
merlinofchaos's avatar
merlinofchaos committed
256 257 258 259 260
            $this->has_exposed = TRUE;
            return TRUE;
          }
        }
      }
261
      $pager = $this->getPlugin('pager');
262
      if (isset($pager) && $pager->usesExposed()) {
merlinofchaos's avatar
merlinofchaos committed
263 264 265 266 267 268 269 270 271 272
        $this->has_exposed = TRUE;
        return TRUE;
      }
      $this->has_exposed = FALSE;
    }

    return $this->has_exposed;
  }

  /**
273
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
274
   */
275
  public function displaysExposed() {
merlinofchaos's avatar
merlinofchaos committed
276 277 278 279
    return TRUE;
  }

  /**
280
   * {@inheritdoc}
281
   */
282
  public function usesAJAX() {
283 284 285 286
    return $this->usesAJAX;
  }

  /**
287
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
288
   */
289
  public function ajaxEnabled() {
290
    if ($this->usesAJAX()) {
291
      return $this->getOption('use_ajax');
merlinofchaos's avatar
merlinofchaos committed
292 293 294 295
    }
    return FALSE;
  }

296
  /**
297
   * {@inheritdoc}
298 299 300 301 302
   */
  public function isEnabled() {
    return (bool) $this->getOption('enabled');
  }

merlinofchaos's avatar
merlinofchaos committed
303
  /**
304
   * {@inheritdoc}
305
   */
306
  public function usesPager() {
307 308 309 310
    return $this->usesPager;
  }

  /**
311
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
312
   */
313
  public function isPagerEnabled() {
314
    if ($this->usesPager()) {
315
      $pager = $this->getPlugin('pager');
316
      if ($pager) {
317
        return $pager->usePager();
318
      }
merlinofchaos's avatar
merlinofchaos committed
319
    }
320
    return FALSE;
merlinofchaos's avatar
merlinofchaos committed
321 322 323
  }

  /**
324
   * {@inheritdoc}
325
   */
326
  public function usesMore() {
327 328 329 330
    return $this->usesMore;
  }

  /**
331
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
332
   */
333
  public function isMoreEnabled() {
334
    if ($this->usesMore()) {
335
      return $this->getOption('use_more');
merlinofchaos's avatar
merlinofchaos committed
336 337 338 339 340
    }
    return FALSE;
  }

  /**
341
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
342
   */
343 344
  public function useGroupBy() {
    return $this->getOption('group_by');
merlinofchaos's avatar
merlinofchaos committed
345 346 347
  }

  /**
348
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
349
   */
350
  public function useMoreAlways() {
351
    if ($this->usesMore()) {
352
      return $this->getOption('use_more_always');
merlinofchaos's avatar
merlinofchaos committed
353 354 355 356 357
    }
    return FALSE;
  }

  /**
358
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
359
   */
360
  public function useMoreText() {
361
    if ($this->usesMore()) {
362
      return $this->getOption('use_more_text');
merlinofchaos's avatar
merlinofchaos committed
363 364 365 366 367
    }
    return FALSE;
  }

  /**
368
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
369
   */
370
  public function acceptAttachments() {
371 372 373
    // To be able to accept attachments this display have to be able to use
    // attachments but at the same time, you cannot attach a display to itself.
    if (!$this->usesAttachments() || ($this->definition['id'] == $this->view->current_display)) {
merlinofchaos's avatar
merlinofchaos committed
374 375
      return FALSE;
    }
376

377
    if (!empty($this->view->argument) && $this->getOption('hide_attachment_summary')) {
378
      foreach ($this->view->argument as $argument) {
379
        if ($argument->needsStylePlugin() && empty($argument->argument_validated)) {
merlinofchaos's avatar
merlinofchaos committed
380 381 382 383
          return FALSE;
        }
      }
    }
384

merlinofchaos's avatar
merlinofchaos committed
385 386 387
    return TRUE;
  }

388
  /**
389
   * {@inheritdoc}
390
   */
391
  public function usesAttachments() {
392 393 394
    return $this->usesAttachments;
  }

395
  /**
396
   * {@inheritdoc}
397 398 399 400 401
   */
  public function usesAreas() {
    return $this->usesAreas;
  }

merlinofchaos's avatar
merlinofchaos committed
402
  /**
403
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
404
   */
405
  public function attachTo(ViewExecutable $view, $display_id, array &$build) { }
merlinofchaos's avatar
merlinofchaos committed
406 407

  /**
408
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
409
   */
410
  public function defaultableSections($section = NULL) {
merlinofchaos's avatar
merlinofchaos committed
411
    $sections = array(
412 413
      'access' => array('access'),
      'cache' => array('cache'),
merlinofchaos's avatar
merlinofchaos committed
414 415 416 417
      'title' => array('title'),
      'css_class' => array('css_class'),
      'use_ajax' => array('use_ajax'),
      'hide_attachment_summary' => array('hide_attachment_summary'),
418
      'show_admin_links' => array('show_admin_links'),
merlinofchaos's avatar
merlinofchaos committed
419 420 421
      'group_by' => array('group_by'),
      'query' => array('query'),
      'use_more' => array('use_more', 'use_more_always', 'use_more_text'),
422 423
      'use_more_always' => array('use_more', 'use_more_always', 'use_more_text'),
      'use_more_text' => array('use_more', 'use_more_always', 'use_more_text'),
merlinofchaos's avatar
merlinofchaos committed
424 425 426
      'link_display' => array('link_display', 'link_url'),

      // Force these to cascade properly.
427 428
      'style' => array('style', 'row'),
      'row' => array('style', 'row'),
merlinofchaos's avatar
merlinofchaos committed
429

430
      'pager' => array('pager'),
merlinofchaos's avatar
merlinofchaos committed
431

432
      'exposed_form' => array('exposed_form'),
merlinofchaos's avatar
merlinofchaos committed
433

434
      // These sections are special.
merlinofchaos's avatar
merlinofchaos committed
435 436 437 438 439 440 441 442 443 444 445 446
      'header' => array('header'),
      'footer' => array('footer'),
      'empty' => array('empty'),
      'relationships' => array('relationships'),
      'fields' => array('fields'),
      'sorts' => array('sorts'),
      'arguments' => array('arguments'),
      'filters' => array('filters', 'filter_groups'),
      'filter_groups' => array('filters', 'filter_groups'),
    );

    // If the display cannot use a pager, then we cannot default it.
447
    if (!$this->usesPager()) {
merlinofchaos's avatar
merlinofchaos committed
448 449 450 451
      unset($sections['pager']);
      unset($sections['items_per_page']);
    }

452
    foreach ($this->extenders as $extender) {
453
      $extender->defaultableSections($sections, $section);
merlinofchaos's avatar
merlinofchaos committed
454 455 456 457 458 459 460 461 462 463 464 465
    }

    if ($section) {
      if (!empty($sections[$section])) {
        return $sections[$section];
      }
    }
    else {
      return $sections;
    }
  }

466
  protected function defineOptions() {
merlinofchaos's avatar
merlinofchaos committed
467 468 469 470 471 472 473 474 475 476 477 478
    $options = array(
      'defaults' => array(
        'default' => array(
          'access' => TRUE,
          'cache' => TRUE,
          'query' => TRUE,
          'title' => TRUE,
          'css_class' => TRUE,

          'display_description' => FALSE,
          'use_ajax' => TRUE,
          'hide_attachment_summary' => TRUE,
479
          'show_admin_links' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
480 481 482 483 484 485 486
          'pager' => TRUE,
          'use_more' => TRUE,
          'use_more_always' => TRUE,
          'use_more_text' => TRUE,
          'exposed_form' => TRUE,

          'link_display' => TRUE,
487
          'link_url' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
488 489
          'group_by' => TRUE,

490 491
          'style' => TRUE,
          'row' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526

          'header' => TRUE,
          'footer' => TRUE,
          'empty' => TRUE,

          'relationships' => TRUE,
          'fields' => TRUE,
          'sorts' => TRUE,
          'arguments' => TRUE,
          'filters' => TRUE,
          'filter_groups' => TRUE,
        ),
      ),

      'title' => array(
        'default' => '',
      ),
      'enabled' => array(
        'default' => TRUE,
      ),
      'display_comment' => array(
        'default' => '',
      ),
      'css_class' => array(
        'default' => '',
      ),
      'display_description' => array(
        'default' => '',
      ),
      'use_ajax' => array(
        'default' => FALSE,
      ),
      'hide_attachment_summary' => array(
        'default' => FALSE,
      ),
527 528
      'show_admin_links' => array(
        'default' => TRUE,
529
      ),
merlinofchaos's avatar
merlinofchaos committed
530 531 532 533
      'use_more' => array(
        'default' => FALSE,
      ),
      'use_more_always' => array(
534
        'default' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
535 536 537 538 539 540 541 542 543 544 545 546 547
      ),
      'use_more_text' => array(
        'default' => 'more',
      ),
      'link_display' => array(
        'default' => '',
      ),
      'link_url' => array(
        'default' => '',
      ),
      'group_by' => array(
        'default' => FALSE,
      ),
548
      'rendering_language' => array(
549
        'default' => '***LANGUAGE_entity_translation***',
550
      ),
merlinofchaos's avatar
merlinofchaos committed
551 552 553 554 555

      // These types are all plugins that can have individual settings
      // and therefore need special handling.
      'access' => array(
        'contains' => array(
556
          'type' => array('default' => 'none'),
557 558
          'options' => array('default' => array()),
        ),
559
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
560 561 562
      ),
      'cache' => array(
        'contains' => array(
563
          'type' => array('default' => 'tag'),
564 565
          'options' => array('default' => array()),
        ),
566
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
567 568 569
      ),
      'query' => array(
        'contains' => array(
570 571
          'type' => array('default' => 'views_query'),
          'options' => array('default' => array()),
merlinofchaos's avatar
merlinofchaos committed
572
         ),
573
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
574 575 576
      ),
      'exposed_form' => array(
        'contains' => array(
577 578
          'type' => array('default' => 'basic'),
          'options' => array('default' => array()),
merlinofchaos's avatar
merlinofchaos committed
579
         ),
580
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
581 582 583
      ),
      'pager' => array(
        'contains' => array(
584
          'type' => array('default' => 'mini'),
585
          'options' => array('default' => array()),
merlinofchaos's avatar
merlinofchaos committed
586
         ),
587
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
588
      ),
589 590 591 592 593
      'style' => array(
        'contains' => array(
          'type' => array('default' => 'default'),
          'options' => array('default' => array()),
        ),
594
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
595
      ),
596 597 598 599 600
      'row' => array(
        'contains' => array(
          'type' => array('default' => 'fields'),
          'options' => array('default' => array()),
        ),
601
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
602 603 604 605 606 607 608 609
      ),

      'exposed_block' => array(
        'default' => FALSE,
      ),

      'header' => array(
        'default' => array(),
610
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
611 612 613
      ),
      'footer' => array(
        'default' => array(),
614
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
615 616 617
      ),
      'empty' => array(
        'default' => array(),
618
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
619 620 621 622 623 624
      ),

      // We want these to export last.
      // These are the 5 handler types.
      'relationships' => array(
        'default' => array(),
625
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
626 627 628
      ),
      'fields' => array(
        'default' => array(),
629
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
630 631 632
      ),
      'sorts' => array(
        'default' => array(),
633
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
634 635 636
      ),
      'arguments' => array(
        'default' => array(),
637
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
638 639 640 641 642 643 644 645 646 647 648 649
      ),
      'filter_groups' => array(
        'contains' => array(
          'operator' => array('default' => 'AND'),
          'groups' => array('default' => array(1 => 'AND')),
        ),
      ),
      'filters' => array(
        'default' => array(),
      ),
    );

650
    if (!$this->usesPager()) {
merlinofchaos's avatar
merlinofchaos committed
651 652 653 654
      $options['defaults']['default']['pager'] = FALSE;
      $options['pager']['contains']['type']['default'] = 'some';
    }

655
    if ($this->isDefaultDisplay()) {
merlinofchaos's avatar
merlinofchaos committed
656 657 658
      unset($options['defaults']);
    }

659 660 661 662 663 664 665 666
    $options['display_extenders'] = ['default' => []];
    // First allow display extenders to provide new options.
    foreach ($this->extenders as $extender_id => $extender) {
      $options['display_extenders']['contains'][$extender_id]['contains'] = $extender->defineOptions();
    }

    // Then allow display extenders to alter existing default values.
    foreach ($this->extenders as $extender) {
667
      $extender->defineOptionsAlter($options);
merlinofchaos's avatar
merlinofchaos committed
668 669 670 671 672 673
    }

    return $options;
  }

  /**
674
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
675
   */
676
  public function hasPath() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
677 678

  /**
679
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
680
   */
681
  public function usesLinkDisplay() { return !$this->hasPath(); }
merlinofchaos's avatar
merlinofchaos committed
682 683

  /**
684
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
685
   */
686
  public function usesExposedFormInBlock() { return $this->hasPath(); }
merlinofchaos's avatar
merlinofchaos committed
687

688
  /**
689
   * {@inheritdoc}
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
   */
  public function getAttachedDisplays() {
    $current_display_id = $this->display['id'];
    $attached_displays = array();

    // Go through all displays and search displays which link to this one.
    foreach ($this->view->storage->get('display') as $display_id => $display) {
      if (isset($display['display_options']['displays'])) {
        $displays = $display['display_options']['displays'];
        if (isset($displays[$current_display_id])) {
          $attached_displays[] = $display_id;
        }
      }
    }

    return $attached_displays;
  }

merlinofchaos's avatar
merlinofchaos committed
708
  /**
709
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
710
   */
711 712
  public function getLinkDisplay() {
    $display_id = $this->getOption('link_display');
merlinofchaos's avatar
merlinofchaos committed
713
    // If unknown, pick the first one.
714
    if (empty($display_id) || !$this->view->displayHandlers->has($display_id)) {
715 716
      foreach ($this->view->displayHandlers as $display_id => $display) {
        if (!empty($display) && $display->hasPath()) {
merlinofchaos's avatar
merlinofchaos committed
717 718 719 720 721 722 723
          return $display_id;
        }
      }
    }
    else {
      return $display_id;
    }
724
    // Fall-through returns NULL.
merlinofchaos's avatar
merlinofchaos committed
725 726 727
  }

  /**
728
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
729
   */
730 731 732
  public function getPath() {
    if ($this->hasPath()) {
      return $this->getOption('path');
merlinofchaos's avatar
merlinofchaos committed
733 734
    }

735
    $display_id = $this->getLinkDisplay();
736 737
    if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) {
      return $this->view->displayHandlers->get($display_id)->getPath();
merlinofchaos's avatar
merlinofchaos committed
738 739 740
    }
  }

741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
  /**
   * {@inheritdoc}
   */
  public function getRoutedDisplay() {
    // If this display has a route, return this display.
    if ($this instanceof DisplayRouterInterface) {
      return $this;
    }

    // If the display does not have a route (e.g. a block display), get the
    // route for the linked display.
    $display_id = $this->getLinkDisplay();
    if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) {
      return $this->view->displayHandlers->get($display_id)->getRoutedDisplay();
    }

    // No routed display exists, so return NULL
    return NULL;
  }

761 762 763
  /**
   * {@inheritdoc}
   */
764
  public function getUrl() {
765
    return $this->view->getUrl(NULL, $this->display['id']);
merlinofchaos's avatar
merlinofchaos committed
766 767 768
  }

  /**
769
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
770
   */
771 772
  public function isDefaulted($option) {
    return !$this->isDefaultDisplay() && !empty($this->default_display) && !empty($this->options['defaults'][$option]);
merlinofchaos's avatar
merlinofchaos committed
773 774 775
  }

  /**
776
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
777
   */
778 779 780
  public function getOption($option) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->getOption($option);
merlinofchaos's avatar
merlinofchaos committed
781 782 783 784 785 786 787 788
    }

    if (array_key_exists($option, $this->options)) {
      return $this->options[$option];
    }
  }

  /**
789
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
790
   */
791
  public function usesFields() {
792
    return $this->getPlugin('style')->usesFields();
merlinofchaos's avatar
merlinofchaos committed
793 794 795
  }

  /**
796
   * {@inheritdoc}
797 798
   */
  public function getPlugin($type) {
799
    // Look up the plugin name to use for this instance.
800
    $options = $this->getOption($type);
801 802 803 804 805

    // Return now if no options have been loaded.
    if (empty($options) || !isset($options['type'])) {
      return;
    }
merlinofchaos's avatar
merlinofchaos committed
806

807 808
    // Query plugins allow specifying a specific query class per base table.
    if ($type == 'query') {
809
      $views_data = Views::viewsData()->get($this->view->storage->get('base_table'));
810
      $name = isset($views_data['table']['base']['query_id']) ? $views_data['table']['base']['query_id'] : 'views_query';
811
    }
812 813 814
    else {
      $name = $options['type'];
    }
815

816 817
    // Plugin instances are stored on the display for re-use.
    if (!isset($this->plugins[$type][$name])) {
818
      $plugin = Views::pluginManager($type)->createInstance($name);
merlinofchaos's avatar
merlinofchaos committed
819

820 821
      // Initialize the plugin.
      $plugin->init($this->view, $this, $options['options']);
822

823
      $this->plugins[$type][$name] = $plugin;
merlinofchaos's avatar
merlinofchaos committed
824 825
    }

826
    return $this->plugins[$type][$name];
merlinofchaos's avatar
merlinofchaos committed
827 828 829
  }

  /**
830
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
831
   */
832
  public function &getHandler($type, $id) {
merlinofchaos's avatar
merlinofchaos committed
833
    if (!isset($this->handlers[$type])) {
834
      $this->getHandlers($type);
merlinofchaos's avatar
merlinofchaos committed
835 836 837 838 839 840 841 842 843 844 845 846
    }

    if (isset($this->handlers[$type][$id])) {
      return $this->handlers[$type][$id];
    }

    // So we can return a reference.
    $null = NULL;
    return $null;
  }

  /**
847
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
848
   */
849
  public function &getHandlers($type) {
merlinofchaos's avatar
merlinofchaos committed
850 851
    if (!isset($this->handlers[$type])) {
      $this->handlers[$type] = array();
852
      $types = ViewExecutable::getHandlerTypes();
merlinofchaos's avatar
merlinofchaos committed
853 854
      $plural = $types[$type]['plural'];

855 856 857
      // Cast to an array so that if the display does not have any handlers of
      // this type there is no PHP error.
      foreach ((array) $this->getOption($plural) as $id => $info) {
merlinofchaos's avatar
merlinofchaos committed
858 859 860
        // If this is during form submission and there are temporary options
        // which can only appear if the view is in the edit cache, use those
        // options instead. This is used for AJAX multi-step stuff.
861 862
        if ($this->view->getRequest()->request->get('form_id') && isset($this->view->temporary_options[$type][$id])) {
          $info = $this->view->temporary_options[$type][$id];
merlinofchaos's avatar
merlinofchaos committed
863 864 865 866 867 868 869 870 871 872
        }

        if ($info['id'] != $id) {
          $info['id'] = $id;
        }

        // If aggregation is on, the group type might override the actual
        // handler that is in use. This piece of code checks that and,
        // if necessary, sets the override handler.
        $override = NULL;
873
        if ($this->useGroupBy() && !empty($info['group_type'])) {
merlinofchaos's avatar
merlinofchaos committed
874
          if (empty($this->view->query)) {
875
            $this->view->initQuery();
merlinofchaos's avatar
merlinofchaos committed
876
          }
877
          $aggregate = $this->view->query->getAggregationInfo();
merlinofchaos's avatar
merlinofchaos committed
878 879 880 881 882 883 884 885 886 887 888 889
          if (!empty($aggregate[$info['group_type']]['handler'][$type])) {
            $override = $aggregate[$info['group_type']]['handler'][$type];
          }
        }

        if (!empty($types[$type]['type'])) {
          $handler_type = $types[$type]['type'];
        }
        else {
          $handler_type = $type;
        }

890
        if ($handler = Views::handlerManager($handler_type)->getHandler($info, $override)) {
merlinofchaos's avatar
merlinofchaos committed
891
          // Special override for area types so they know where they come from.
892 893
          if ($handler instanceof AreaPluginBase) {
            $handler->areaType = $type;
merlinofchaos's avatar
merlinofchaos committed
894 895
          }

896
          $handler->init($this->view, $this, $info);
merlinofchaos's avatar
merlinofchaos committed
897 898 899 900 901 902 903 904 905 906 907
          $this->handlers[$type][$id] = &$handler;
        }

        // Prevent reference problems.
        unset($handler);
      }
    }

    return $this->handlers[$type];
  }

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 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966
  /**
   * Gets all the handlers used by the display.
   *
   * @param bool $only_overrides
   *   Whether to include only overridden handlers.
   *
   * @return \Drupal\views\Plugin\views\ViewsHandlerInterface[]
   */
  protected function getAllHandlers($only_overrides = FALSE) {
    $handler_types = Views::getHandlerTypes();
    $handlers = [];
    // Collect all dependencies of all handlers.
    foreach ($handler_types as $handler_type => $handler_type_info) {
      if ($only_overrides && $this->isDefaulted($handler_type_info['plural'])) {
        continue;
      }
      $handlers = array_merge($handlers, array_values($this->getHandlers($handler_type)));
    }
    return $handlers;
  }

  /**
   * Gets all the plugins used by the display.
   *
   * @param bool $only_overrides
   *   Whether to include only overridden plugins.
   *
   * @return \Drupal\views\Plugin\views\ViewsPluginInterface[]
   */
  protected function getAllPlugins($only_overrides = FALSE) {
    $plugins = [];
    // Collect all dependencies of plugins.
    foreach (Views::getPluginTypes('plugin') as $plugin_type) {
      $plugin = $this->getPlugin($plugin_type);
      if (!$plugin) {
        continue;
      }
      if ($only_overrides && $this->isDefaulted($plugin_type)) {
        continue;
      }
      $plugins[] = $plugin;
    }
    return $plugins;
  }

  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    $this->addDependencies(parent::calculateDependencies());
    // Collect all the dependencies of handlers and plugins. Only calculate
    // their dependencies if they are configured by this display.
    $plugins = array_merge($this->getAllHandlers(TRUE), $this->getAllPlugins(TRUE));
    array_walk($plugins, array($this, 'calculatePluginDependencies'));

    return $this->dependencies;
  }


merlinofchaos's avatar
merlinofchaos committed
967
  /**
968
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
969
   */
970
  public function getFieldLabels($groupable_only = FALSE) {
merlinofchaos's avatar
merlinofchaos committed
971
    $options = array();
972
    foreach ($this->getHandlers('relationship') as $relationship => $handler) {
973
      $relationships[$relationship] = $handler->adminLabel();
merlinofchaos's avatar
merlinofchaos committed
974 975
    }

976
    foreach ($this->getHandlers('field') as $id => $handler) {
977
      if ($groupable_only && !$handler->useStringGroupBy()) {
978 979 980
        // Continue to next handler if it's not groupable.
        continue;
      }
merlinofchaos's avatar
merlinofchaos committed