DisplayPluginBase.php 86.5 KB
Newer Older
merlinofchaos's avatar
merlinofchaos committed
1 2
<?php

3
namespace Drupal\views\Plugin\views\display;
4

5
use Drupal\Component\Plugin\DependentPluginInterface;
6
use Drupal\Component\Utility\Html;
7
use Drupal\Component\Utility\SafeMarkup;
8
use Drupal\Core\Cache\Cache;
9
use Drupal\Core\Cache\CacheableMetadata;
10
use Drupal\Core\Cache\CacheableDependencyInterface;
11
use Drupal\Core\Form\FormStateInterface;
12
use Drupal\Core\Language\LanguageInterface;
13
use Drupal\Core\Plugin\PluginDependencyTrait;
14
use Drupal\Core\Session\AccountInterface;
15
use Drupal\Core\Url;
16
use Drupal\views\Form\ViewsForm;
17
use Drupal\views\Plugin\views\area\AreaPluginBase;
18
use Drupal\views\ViewExecutable;
19
use Drupal\views\Plugin\views\PluginBase;
20
use Drupal\views\Views;
21

merlinofchaos's avatar
merlinofchaos committed
22
/**
23
 * Base class for views display plugins.
merlinofchaos's avatar
merlinofchaos committed
24
 */
25 26
abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInterface, DependentPluginInterface {
  use PluginDependencyTrait;
27

merlinofchaos's avatar
merlinofchaos committed
28 29 30
  /**
   * The top object of a view.
   *
31
   * @var \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
32 33 34
   */
  var $view = NULL;

35 36 37 38 39 40
  /**
   * An array of instantiated handlers used in this display.
   *
   * @var \Drupal\views\Plugin\views\ViewsHandlerInterface[]
   */
   public $handlers = [];
merlinofchaos's avatar
merlinofchaos committed
41

42 43 44
  /**
   * An array of instantiated plugins used in this display.
   *
45
   * @var \Drupal\views\Plugin\views\ViewsPluginInterface[]
46 47 48
   */
  protected $plugins = array();

merlinofchaos's avatar
merlinofchaos committed
49 50
  /**
   * Stores all available display extenders.
51 52
   *
   * @var \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase[]
merlinofchaos's avatar
merlinofchaos committed
53
   */
54
  protected $extenders = [];
merlinofchaos's avatar
merlinofchaos committed
55

56
  /**
57
   * {@inheritdoc}
58
   */
59
  protected $usesOptions = TRUE;
60

61 62 63
  /**
   * Stores the rendered output of the display.
   *
64
   * @see View::render
65 66 67 68
   * @var string
   */
  public $output = NULL;

69 70 71 72 73 74 75
  /**
   * Whether the display allows the use of AJAX or not.
   *
   * @var bool
   */
  protected $usesAJAX = TRUE;

76 77 78 79 80 81 82 83 84 85 86 87 88 89
  /**
   * 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;

90 91 92 93 94 95 96 97
  /**
   * Whether the display allows attachments.
   *
   * @var bool
   *   TRUE if the display can use attachments, or FALSE otherwise.
   */
  protected $usesAttachments = FALSE;

98 99 100 101 102 103 104
  /**
   * Whether the display allows area plugins.
   *
   * @var bool
   */
  protected $usesAreas = TRUE;

105 106 107 108 109 110 111
  /**
   * Static cache for unpackOptions, but not if we are in the UI.
   *
   * @var array
   */
  protected static $unpackOptions = array();

112 113 114 115 116 117 118 119 120 121 122
  /**
   * 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;

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

145 146 147
  /**
   * {@inheritdoc}
   */
148 149
  public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
    $this->view = $view;
merlinofchaos's avatar
merlinofchaos committed
150 151

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

167 168 169 170

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

merlinofchaos's avatar
merlinofchaos committed
171 172 173
    // Track changes that the user should know about.
    $changed = FALSE;

174 175
    if (!isset($options) && isset($display['display_options'])) {
      $options = $display['display_options'];
merlinofchaos's avatar
merlinofchaos committed
176 177
    }

178
    if ($this->isDefaultDisplay() && isset($options['defaults'])) {
merlinofchaos's avatar
merlinofchaos committed
179 180 181
      unset($options['defaults']);
    }

182 183 184
    $skip_cache = \Drupal::config('views.settings')->get('skip_cache');

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

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

211 212 213
  /**
   * {@inheritdoc}
   */
214
  public function destroy() {
merlinofchaos's avatar
merlinofchaos committed
215 216 217 218 219 220 221 222 223 224 225 226 227 228
    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);
    }

229
    foreach ($this->extenders as $extender) {
merlinofchaos's avatar
merlinofchaos committed
230 231 232 233 234
      $extender->destroy();
    }
  }

  /**
235
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
236
   */
237
  public function isDefaultDisplay() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
238 239

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

    return $this->has_exposed;
  }

  /**
265
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
266
   */
267
  public function displaysExposed() {
merlinofchaos's avatar
merlinofchaos committed
268 269 270 271
    return TRUE;
  }

  /**
272
   * {@inheritdoc}
273
   */
274
  public function usesAJAX() {
275 276 277 278
    return $this->usesAJAX;
  }

  /**
279
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
280
   */
281
  public function ajaxEnabled() {
282
    if ($this->usesAJAX()) {
283
      return $this->getOption('use_ajax');
merlinofchaos's avatar
merlinofchaos committed
284 285 286 287
    }
    return FALSE;
  }

288
  /**
289
   * {@inheritdoc}
290 291 292 293 294
   */
  public function isEnabled() {
    return (bool) $this->getOption('enabled');
  }

merlinofchaos's avatar
merlinofchaos committed
295
  /**
296
   * {@inheritdoc}
297
   */
298
  public function usesPager() {
299 300 301 302
    return $this->usesPager;
  }

  /**
303
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
304
   */
305
  public function isPagerEnabled() {
306
    if ($this->usesPager()) {
307
      $pager = $this->getPlugin('pager');
308
      if ($pager) {
309
        return $pager->usePager();
310
      }
merlinofchaos's avatar
merlinofchaos committed
311
    }
312
    return FALSE;
merlinofchaos's avatar
merlinofchaos committed
313 314 315
  }

  /**
316
   * {@inheritdoc}
317
   */
318
  public function usesMore() {
319 320 321 322
    return $this->usesMore;
  }

  /**
323
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
324
   */
325
  public function isMoreEnabled() {
326
    if ($this->usesMore()) {
327
      return $this->getOption('use_more');
merlinofchaos's avatar
merlinofchaos committed
328 329 330 331 332
    }
    return FALSE;
  }

  /**
333
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
334
   */
335 336
  public function useGroupBy() {
    return $this->getOption('group_by');
merlinofchaos's avatar
merlinofchaos committed
337 338 339
  }

  /**
340
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
341
   */
342
  public function useMoreAlways() {
343
    if ($this->usesMore()) {
344
      return $this->getOption('use_more_always');
merlinofchaos's avatar
merlinofchaos committed
345 346 347 348 349
    }
    return FALSE;
  }

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

  /**
360
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
361
   */
362
  public function acceptAttachments() {
363 364 365
    // 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
366 367
      return FALSE;
    }
368

369
    if (!empty($this->view->argument) && $this->getOption('hide_attachment_summary')) {
370
      foreach ($this->view->argument as $argument) {
371
        if ($argument->needsStylePlugin() && empty($argument->argument_validated)) {
merlinofchaos's avatar
merlinofchaos committed
372 373 374 375
          return FALSE;
        }
      }
    }
376

merlinofchaos's avatar
merlinofchaos committed
377 378 379
    return TRUE;
  }

380
  /**
381
   * {@inheritdoc}
382
   */
383
  public function usesAttachments() {
384 385 386
    return $this->usesAttachments;
  }

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

merlinofchaos's avatar
merlinofchaos committed
394
  /**
395
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
396
   */
397
  public function attachTo(ViewExecutable $view, $display_id, array &$build) { }
merlinofchaos's avatar
merlinofchaos committed
398 399

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

      // Force these to cascade properly.
419 420
      'style' => array('style', 'row'),
      'row' => array('style', 'row'),
merlinofchaos's avatar
merlinofchaos committed
421

422
      'pager' => array('pager'),
merlinofchaos's avatar
merlinofchaos committed
423

424
      'exposed_form' => array('exposed_form'),
merlinofchaos's avatar
merlinofchaos committed
425

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

444
    foreach ($this->extenders as $extender) {
445
      $extender->defaultableSections($sections, $section);
merlinofchaos's avatar
merlinofchaos committed
446 447 448 449 450 451 452 453 454 455 456 457
    }

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

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

          'link_display' => TRUE,
479
          'link_url' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
480 481
          'group_by' => TRUE,

482 483
          'style' => TRUE,
          'row' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
484 485 486 487 488 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

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

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

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

      'header' => array(
        'default' => array(),
602
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
603 604 605
      ),
      'footer' => array(
        'default' => array(),
606
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
607 608 609
      ),
      'empty' => array(
        'default' => array(),
610
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
611 612 613 614 615 616
      ),

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

642
    if (!$this->usesPager()) {
merlinofchaos's avatar
merlinofchaos committed
643 644 645 646
      $options['defaults']['default']['pager'] = FALSE;
      $options['pager']['contains']['type']['default'] = 'some';
    }

647
    if ($this->isDefaultDisplay()) {
merlinofchaos's avatar
merlinofchaos committed
648 649 650
      unset($options['defaults']);
    }

651 652 653 654 655 656 657 658
    $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) {
659
      $extender->defineOptionsAlter($options);
merlinofchaos's avatar
merlinofchaos committed
660 661 662 663 664 665
    }

    return $options;
  }

  /**
666
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
667
   */
668
  public function hasPath() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
669 670

  /**
671
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
672
   */
673
  public function usesLinkDisplay() { return !$this->hasPath(); }
merlinofchaos's avatar
merlinofchaos committed
674 675

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

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

  /**
720
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
721
   */
722 723 724
  public function getPath() {
    if ($this->hasPath()) {
      return $this->getOption('path');
merlinofchaos's avatar
merlinofchaos committed
725 726
    }

727
    $display_id = $this->getLinkDisplay();
728 729
    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
730 731 732
    }
  }

733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
  /**
   * {@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;
  }

753 754 755
  /**
   * {@inheritdoc}
   */
756
  public function getUrl() {
757
    return $this->view->getUrl(NULL, $this->display['id']);
merlinofchaos's avatar
merlinofchaos committed
758 759 760
  }

  /**
761
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
762
   */
763 764
  public function isDefaulted($option) {
    return !$this->isDefaultDisplay() && !empty($this->default_display) && !empty($this->options['defaults'][$option]);
merlinofchaos's avatar
merlinofchaos committed
765 766 767
  }

  /**
768
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
769
   */
770 771 772
  public function getOption($option) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->getOption($option);
merlinofchaos's avatar
merlinofchaos committed
773 774 775 776 777 778 779 780
    }

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

  /**
781
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
782
   */
783
  public function usesFields() {
784
    return $this->getPlugin('style')->usesFields();
merlinofchaos's avatar
merlinofchaos committed
785 786 787
  }

  /**
788
   * {@inheritdoc}
789 790
   */
  public function getPlugin($type) {
791
    // Look up the plugin name to use for this instance.
792
    $options = $this->getOption($type);
793 794 795 796 797

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

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

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

812 813
      // Initialize the plugin.
      $plugin->init($this->view, $this, $options['options']);
814

815
      $this->plugins[$type][$name] = $plugin;
merlinofchaos's avatar
merlinofchaos committed
816 817
    }

818
    return $this->plugins[$type][$name];
merlinofchaos's avatar
merlinofchaos committed
819 820 821
  }

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

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

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

  /**
839
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
840
   */
841
  public function &getHandlers($type) {
merlinofchaos's avatar
merlinofchaos committed
842 843
    if (!isset($this->handlers[$type])) {
      $this->handlers[$type] = array();
844
      $types = ViewExecutable::getHandlerTypes();
merlinofchaos's avatar
merlinofchaos committed
845 846
      $plural = $types[$type]['plural'];

847 848 849
      // 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
850 851 852
        // 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.
853 854
        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
855 856 857 858 859 860 861 862 863 864
        }

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

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

888
          $handler->init($this->view, $this, $info);
merlinofchaos's avatar
merlinofchaos committed
889 890 891 892 893 894 895 896 897 898 899
          $this->handlers[$type][$id] = &$handler;
        }

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

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

900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
  /**
   * 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() {
949
    $this->dependencies = parent::calculateDependencies();
950 951 952 953 954 955 956 957 958
    // 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
959
  /**
960
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
961
   */
962
  public function getFieldLabels($groupable_only = FALSE) {
merlinofchaos's avatar
merlinofchaos committed
963
    $options = array();
964
    foreach ($this->getHandlers('relationship') as $relationship => $handler) {
965
      $relationships[$relationship] = $handler->adminLabel();
merlinofchaos's avatar
merlinofchaos committed
966 967
    }

968
    foreach ($this->getHandlers('field') as $id => $handler) {
969
      if ($groupable_only && !$handler->useStringGroupBy()) {
970 971 972
        // Continue to next handler if it's not groupable.
        continue;
      }
merlinofchaos's avatar
merlinofchaos committed
973 974 975 976
      if ($label = $handler->label()) {
        $options[$id] = $label;
      }
      else {
977
        $options[$id] = $handler->adminLabel();
merlinofchaos's avatar
merlinofchaos committed
978 979 980 981 982 983 984 985 986
      }
      if (!empty($handler->options['relationship']) && !empty($relationships[$handler->options['relationship']])) {
        $options[$id] = '(' . $relationships[$handler->options['relationship']] . ') ' . $options[$id];
      }
    }
    return $options;
  }

  /**
987
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
988
   */
989 990 991
  public function setOption($option, $value) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->setOption($option, $value);
merlinofchaos's avatar
merlinofchaos committed
992 993 994 995 996
    }

    // 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.
997
    $this->display['display_options'][$option] = $value;
merlinofchaos's avatar
merlinofchaos committed
998 999 1000 1001
    return $this->options[$option] = $value;
  }

  /**
1002
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
1003
   */
1004 1005 1006
  public function overrideOption($option, $value) {
    $this->setOverride($option, FALSE);
    $this->setOption($option, $value);
merlinofchaos's avatar
merlinofchaos committed
1007 1008 1009
  }

  /**
1010
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
1011
   */
1012
  public function optionLink($text, $section, $class = '', $title = '') {
1013 1014
    if (!trim($text)) {
      $text = $this->t('Broken field');
1015 1016
    }

1017
    if (!empty($class)) {
1018
      $text = SafeMarkup::format('<span>@text</span>', array('@text' => $text));
1019 1020
    }

merlinofchaos's avatar
merlinofchaos committed
1021 1022 1023 1024
    if (empty($title)) {
      $title = $text;
    }

1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036
    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
1037 1038 1039
  }

  /**
1040
   * {@inheritdoc}
merlinofchaos's avatar
merlinofchaos committed
1041
   */
1042
  public function getArgumentsTokens() {
merlinofchaos's avatar
merlinofchaos committed
1043 1044 1045 1046 1047
    $tokens = array();
    if (!empty($this->view->build_info['substitutions'])) {
      $tokens = $this->view->build_info['substitutions'];
    }

1048 1049 1050 1051 1052 1053 1054 1055
    // 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
1056
       // were encoded by \Drupal\Component\Utility\Html::escape().
alexpott's avatar