DisplayPluginBase.php 83.8 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\Unicode;
13
use Drupal\Component\Utility\SafeMarkup;
14
use Drupal\Core\Cache\Cache;
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' => 'none'),
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
981 982 983 984
      if ($label = $handler->label()) {
        $options[$id] = $label;
      }
      else {
985
        $options[$id] = $handler->adminLabel();
merlinofchaos's avatar
merlinofchaos committed
986 987 988 989 990 991 992 993 994
      }
      if (!empty($handler->options['relationship']) && !empty($relationships[$handler->options['relationship']])) {
        $options[$id] = '(' . $relationships[$handler->options['relationship']] . ') ' . $options[$id];
      }
    }
    return $options;
  }

  /**
995
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
996
   */
997 998 999
  public function setOption($option, $value) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->setOption($option, $value);
merlinofchaos's avatar
merlinofchaos committed
1000 1001 1002 1003 1004
    }

    // Set this in two places: On the handler where we'll notice it
    // but also on the display object so it gets saved. This should
    // only be a temporary fix.
1005
    $this->display['display_options'][$option] = $value;
merlinofchaos's avatar
merlinofchaos committed
1006 1007 1008 1009
    return $this->options[$option] = $value;
  }

  /**
1010
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
1011
   */
1012 1013 1014
  public function overrideOption($option, $value) {
    $this->setOverride($option, FALSE);
    $this->setOption($option, $value);
merlinofchaos's avatar
merlinofchaos committed
1015 1016 1017
  }

  /**
1018
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
1019
   */
1020
  public function optionLink($text, $section, $class = '', $title = '') {
1021 1022
    if (!trim($text)) {
      $text = $this->t('Broken field');
1023 1024
    }

1025
    if (!empty($class)) {
1026
      $text = SafeMarkup::format('<span>@text</span>', array('@text' => $text));
1027 1028
    }

merlinofchaos's avatar
merlinofchaos committed
1029 1030 1031 1032
    if (empty($title)) {
      $title = $text;
    }

1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044
    return \Drupal::l($text, new Url('views_ui.form_display', array(
        'js' => 'nojs',
        'view' => $this->view->storage->id(),
        'display_id' => $this->display['id'],
        'type' => $section
      ), array(
        'attributes' => array(
          'class' => array('views-ajax-link', $class),
          'title' => $title,
          'id' => Html::getUniqueId('views-' . $this->display['id'] . '-' . $section)
        )
    )));
merlinofchaos's avatar
merlinofchaos committed
1045 1046 1047
  }

  /**
1048
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
1049
   */
1050
  public function getArgumentsTokens() {
merlinofchaos's avatar
merlinofchaos committed
1051 1052 1053 1054 1055
    $tokens = array();
    if (!empty($this->view->build_info['substitutions'])) {
      $tokens = $this->view->build_info['substitutions'];
    }

1056 1057 1058 1059 1060 1061 1062 1063
    // Add tokens for every argument (contextual filter) and path arg.
    $handlers = count($this->view->display_handler->getHandlers('argument'));
    for ($count = 1; $count <= $handlers; $count++) {
      if (!isset($tokens["%$count"])) {
        $tokens["%$count"] = '';
      }
       // Use strip tags as there should never be HTML in the path.
       // However, we need to preserve special characters like " that
1064
       // were removed by SafeMarkup::checkPlain().
1065
      $tokens["!$count"] = isset($this->view->args[$count - 1]) ? strip_tags(Html::decodeEntities($this->view->args[$count - 1])) : '';
merlinofchaos's avatar
merlinofchaos committed
1066 1067 1068 1069 1070 1071
    }

    return $tokens;
  }

  /**
1072
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
1073
   */
1074
  public function optionsSummary(&$categories, &$options) {
merlinofchaos's avatar
merlinofchaos committed
1075 1076
    $categories = array(
      'title' => array(
1077
        'title' => $this->t('Title'),
merlinofchaos's avatar
merlinofchaos committed
1078 1079 1080
        'column' => 'first',
      ),
      'format' => array(
1081
        'title' => $this->t('Format'),
merlinofchaos's avatar
merlinofchaos committed
1082 1083 1084
        'column' => 'first',
      ),
      'filters' => array(
1085
        'title' => $this->t('Filters'),
merlinofchaos's avatar
merlinofchaos committed
1086 1087 1088
        'column' => 'first',
      ),
      'fields' => array(
1089
        'title' => $this->t('Fields'),
merlinofchaos's avatar
merlinofchaos committed
1090 1091 1092
        'column' => 'first',
      ),
      'pager' => array(
1093
        'title' => $this->t('Pager'),
merlinofchaos's avatar
merlinofchaos committed
1094 1095
        'column' => 'second',
      ),
1096 1097 1098 1099
      'language' => array(
        'title' => $this->t('Language'),
        'column' => 'second',
      ),
merlinofchaos's avatar
merlinofchaos committed
1100
      'exposed' => array(
1101
        'title' => $this->t('Exposed form'),
merlinofchaos's avatar
merlinofchaos committed
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
        'column' => 'third',
        'build' => array(
          '#weight' => 1,
        ),
      ),
      'access' => array(
        'title' => '',
        'column' => 'second',
        'build' => array(
          '#weight' => -5,
        ),
      ),
      'other' => array(
1115
        'title' => $this->t('Other'),
merlinofchaos's avatar
merlinofchaos committed
1116 1117 1118 1119 1120 1121 1122
        'column' => 'third',
        'build' => array(
          '#weight' => 2,
        ),
      ),
    );

1123
    if ($this->display['id'] != 'default') {
merlinofchaos's avatar
merlinofchaos committed
1124 1125
      $options['display_id'] = array(
        'category' => 'other',
1126
        'title' => $this->t('Machine Name'),
1127
        'value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'],
1128
        'desc' => $this->t('Change the machine name of this display.'),
merlinofchaos's avatar
merlinofchaos committed
1129 1130 1131
      );
    }

1132
    $display_comment = Unicode::substr($this->getOption('display_comment'), 0, 10);
merlinofchaos's avatar
merlinofchaos committed
1133 1134
    $options['display_comment'] = array(
      'category' => 'other',
1135 1136 1137
      'title' => $this->t('Administrative comment'),
      'value' => !empty($display_comment) ? $display_comment : $this->t('None'),
      'desc' => $this->t('Comment or document this display.'),
merlinofchaos's avatar
merlinofchaos committed
1138 1139
    );

1140
    $title = strip_tags($this->getOption('title'));
merlinofchaos's avatar
merlinofchaos committed
1141
    if (!$title) {
1142
      $title = $this->t('None');
merlinofchaos's avatar
merlinofchaos committed
1143 1144 1145 1146
    }

    $options['title'] = array(
      'category' => 'title',
1147
      'title' => $this->t('Title'),
1148
      'value' => views_ui_truncate($title, 32),
1149
      'desc' => $this->t('Change the title that this display will use.'),
merlinofchaos's avatar
merlinofchaos committed
1150 1151
    );

1152
    $style_plugin_instance = $this->getPlugin('style');
1153 1154
    $style_summary = empty($style_plugin_instance->definition['title']) ? $this->t('Missing style plugin') : $style_plugin_instance->summaryTitle();
    $style_title = empty($style_plugin_instance->definition['title']) ? $this->t('Missing style plugin') : $style_plugin_instance->pluginTitle();
merlinofchaos's avatar
merlinofchaos committed
1155

1156
    $options['style'] = array(
merlinofchaos's avatar
merlinofchaos committed
1157
      'category' => 'format',
1158
      'title' => $this->t('Format'),
merlinofchaos's avatar
merlinofchaos committed
1159 1160
      'value' => $style_title,
      'setting' => $style_summary,
1161
      'desc' => $this->t('Change the way content is formatted.'),
merlinofchaos's avatar
merlinofchaos committed
1162 1163
    );

1164 1165
    // This adds a 'Settings' link to the style_options setting if the style has
    // options.
1166
    if ($style_plugin_instance->usesOptions()) {
1167
      $options['style']['links']['style_options'] = $this->t('Change settings for this format');
merlinofchaos's avatar
merlinofchaos committed
1168 1169
    }

1170
    if ($style_plugin_instance->usesRowPlugin()) {
1171
      $row_plugin_instance = $this->getPlugin('row');