DisplayPluginBase.php 85.7 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\Cache\CacheableDependencyInterface;
16
use Drupal\Core\Form\FormStateInterface;
17
use Drupal\Core\Language\LanguageInterface;
18
use Drupal\Core\Plugin\PluginDependencyTrait;
19
use Drupal\Core\Session\AccountInterface;
20
use Drupal\Core\Url;
21
use Drupal\views\Form\ViewsForm;
22
use Drupal\views\Plugin\views\area\AreaPluginBase;
23
use Drupal\views\ViewExecutable;
24
use Drupal\views\Plugin\views\PluginBase;
25
use Drupal\views\Views;
26

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

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

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

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

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

61
  /**
62
   * {@inheritdoc}
63
   */
64
  protected $usesOptions = TRUE;
65

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

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

81 82 83 84 85 86 87 88 89 90 91 92 93 94
  /**
   * 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;

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

103 104 105 106 107 108 109
  /**
   * Whether the display allows area plugins.
   *
   * @var bool
   */
  protected $usesAreas = TRUE;

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

117 118 119 120 121 122 123 124 125 126 127
  /**
   * 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;

128 129 130 131 132 133 134 135 136 137
  /**
   * 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.
138 139 140 141 142 143 144
   *
   * @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.
145
   */
146
  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
147 148 149
    parent::__construct(array(), $plugin_id, $plugin_definition);
  }

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

    // Load extenders as soon as possible.
157 158
    $display['display_options'] += ['display_extenders' => []];
    $this->extenders = array();
159
    if ($extenders = Views::getEnabledDisplayExtenders()) {
160
      $manager = Views::pluginManager('display_extender');
161
      $display_extender_options = $display['display_options']['display_extenders'];
merlinofchaos's avatar
merlinofchaos committed
162
      foreach ($extenders as $extender) {
163
        /** @var \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase $plugin */
164
        if ($plugin = $manager->createInstance($extender)) {
165 166 167
          $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
168 169 170 171
        }
      }
    }

172 173 174 175

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

merlinofchaos's avatar
merlinofchaos committed
176 177 178
    // Track changes that the user should know about.
    $changed = FALSE;

179 180
    if (!isset($options) && isset($display['display_options'])) {
      $options = $display['display_options'];
merlinofchaos's avatar
merlinofchaos committed
181 182
    }

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

187 188 189
    $skip_cache = \Drupal::config('views.settings')->get('skip_cache');

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

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

216 217 218
  /**
   * {@inheritdoc}
   */
219
  public function destroy() {
merlinofchaos's avatar
merlinofchaos committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233
    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);
    }

234
    foreach ($this->extenders as $extender) {
merlinofchaos's avatar
merlinofchaos committed
235 236 237 238 239
      $extender->destroy();
    }
  }

  /**
240
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
241
   */
242
  public function isDefaultDisplay() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
243 244

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

    return $this->has_exposed;
  }

  /**
270
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
271
   */
272
  public function displaysExposed() {
merlinofchaos's avatar
merlinofchaos committed
273 274 275 276
    return TRUE;
  }

  /**
277
   * {@inheritdoc}
278
   */
279
  public function usesAJAX() {
280 281 282 283
    return $this->usesAJAX;
  }

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

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

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

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

  /**
321
   * {@inheritdoc}
322
   */
323
  public function usesMore() {
324 325 326 327
    return $this->usesMore;
  }

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

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

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

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

  /**
365
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
366
   */
367
  public function acceptAttachments() {
368 369 370
    // 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
371 372
      return FALSE;
    }
373

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

merlinofchaos's avatar
merlinofchaos committed
382 383 384
    return TRUE;
  }

385
  /**
386
   * {@inheritdoc}
387
   */
388
  public function usesAttachments() {
389 390 391
    return $this->usesAttachments;
  }

392
  /**
393
   * {@inheritdoc}
394 395 396 397 398
   */
  public function usesAreas() {
    return $this->usesAreas;
  }

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

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

      // Force these to cascade properly.
424 425
      'style' => array('style', 'row'),
      'row' => array('style', 'row'),
merlinofchaos's avatar
merlinofchaos committed
426

427
      'pager' => array('pager'),
merlinofchaos's avatar
merlinofchaos committed
428

429
      'exposed_form' => array('exposed_form'),
merlinofchaos's avatar
merlinofchaos committed
430

431
      // These sections are special.
merlinofchaos's avatar
merlinofchaos committed
432 433 434 435 436 437 438 439 440 441 442 443
      '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.
444
    if (!$this->usesPager()) {
merlinofchaos's avatar
merlinofchaos committed
445 446 447 448
      unset($sections['pager']);
      unset($sections['items_per_page']);
    }

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

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

463
  protected function defineOptions() {
merlinofchaos's avatar
merlinofchaos committed
464 465 466 467 468 469 470 471 472 473 474 475
    $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,
476
          'show_admin_links' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
477 478 479 480 481 482 483
          'pager' => TRUE,
          'use_more' => TRUE,
          'use_more_always' => TRUE,
          'use_more_text' => TRUE,
          'exposed_form' => TRUE,

          'link_display' => TRUE,
484
          'link_url' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
485 486
          'group_by' => TRUE,

487 488
          'style' => TRUE,
          'row' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
489 490 491 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

          '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,
      ),
524 525
      'show_admin_links' => array(
        'default' => TRUE,
526
      ),
merlinofchaos's avatar
merlinofchaos committed
527 528 529 530
      'use_more' => array(
        'default' => FALSE,
      ),
      'use_more_always' => array(
531
        'default' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
532 533 534 535 536 537 538 539 540 541 542 543 544
      ),
      'use_more_text' => array(
        'default' => 'more',
      ),
      'link_display' => array(
        'default' => '',
      ),
      'link_url' => array(
        'default' => '',
      ),
      'group_by' => array(
        'default' => FALSE,
      ),
545
      'rendering_language' => array(
546
        'default' => '***LANGUAGE_entity_translation***',
547
      ),
merlinofchaos's avatar
merlinofchaos committed
548 549 550 551 552

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

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

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

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

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

652
    if ($this->isDefaultDisplay()) {
merlinofchaos's avatar
merlinofchaos committed
653 654 655
      unset($options['defaults']);
    }

656 657 658 659 660 661 662 663
    $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) {
664
      $extender->defineOptionsAlter($options);
merlinofchaos's avatar
merlinofchaos committed
665 666 667 668 669 670
    }

    return $options;
  }

  /**
671
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
672
   */
673
  public function hasPath() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
674 675

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

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

685
  /**
686
   * {@inheritdoc}
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
   */
  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
705
  /**
706
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
707
   */
708 709
  public function getLinkDisplay() {
    $display_id = $this->getOption('link_display');
merlinofchaos's avatar
merlinofchaos committed
710
    // If unknown, pick the first one.
711
    if (empty($display_id) || !$this->view->displayHandlers->has($display_id)) {
712 713
      foreach ($this->view->displayHandlers as $display_id => $display) {
        if (!empty($display) && $display->hasPath()) {
merlinofchaos's avatar
merlinofchaos committed
714 715 716 717 718 719 720
          return $display_id;
        }
      }
    }
    else {
      return $display_id;
    }
721
    // Fall-through returns NULL.
merlinofchaos's avatar
merlinofchaos committed
722 723 724
  }

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

732
    $display_id = $this->getLinkDisplay();
733 734
    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
735 736 737
    }
  }

738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
  /**
   * {@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;
  }

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

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

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

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

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

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

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

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

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

817 818
      // Initialize the plugin.
      $plugin->init($this->view, $this, $options['options']);
819

820
      $this->plugins[$type][$name] = $plugin;
merlinofchaos's avatar
merlinofchaos committed
821 822
    }

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

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

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

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

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

852 853 854
      // 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
855 856 857
        // 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.
858 859
        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
860 861 862 863 864 865 866 867 868 869
        }

        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;
870
        if ($this->useGroupBy() && !empty($info['group_type'])) {
merlinofchaos's avatar
merlinofchaos committed
871
          if (empty($this->view->query)) {
872
            $this->view->initQuery();
merlinofchaos's avatar
merlinofchaos committed
873
          }
874
          $aggregate = $this->view->query->getAggregationInfo();
merlinofchaos's avatar
merlinofchaos committed
875 876 877 878 879 880 881 882 883 884 885 886
          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;
        }

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

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

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

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

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 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
  /**
   * 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
964
  /**
965
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
966
   */
967
  public function getFieldLabels($groupable_only = FALSE) {
merlinofchaos's avatar
merlinofchaos committed
968
    $options = array();
969
    foreach ($this->getHandlers('relationship') as $relationship => $handler) {
970
      $relationships[$relationship] = $handler->adminLabel();
merlinofchaos's avatar
merlinofchaos committed
971 972
    }

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