DisplayPluginBase.php 93.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\Core\Language\Language;
11
use Drupal\views\Plugin\views\area\AreaPluginBase;
12
use Drupal\views\ViewExecutable;
13
use \Drupal\views\Plugin\views\PluginBase;
14
use Drupal\views\Views;
15

merlinofchaos's avatar
merlinofchaos committed
16 17 18 19 20 21 22
/**
 * @defgroup views_display_plugins Views display plugins
 * @{
 * Display plugins control how Views interact with the rest of Drupal.
 *
 * They can handle creating Views from a Drupal page hook; they can
 * handle creating Views from a Drupal block hook. They can also
23
 * handle creating Views from an external module source.
merlinofchaos's avatar
merlinofchaos committed
24 25 26 27 28 29
 */

/**
 * The default display plugin handler. Display plugins handle options and
 * basic mechanisms for different output methods.
 */
30
abstract class DisplayPluginBase extends PluginBase {
31

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

  var $handlers = array();

41 42 43 44 45 46 47
  /**
   * An array of instantiated plugins used in this display.
   *
   * @var array
   */
  protected $plugins = array();

merlinofchaos's avatar
merlinofchaos committed
48 49 50 51 52
  /**
   * Stores all available display extenders.
   */
  var $extender = array();

53 54 55
  /**
   * Overrides Drupal\views\Plugin\Plugin::$usesOptions.
   */
56
  protected $usesOptions = TRUE;
57

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

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

73 74 75 76 77 78 79 80 81 82 83 84 85 86
  /**
   * 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;

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

95 96 97 98 99 100 101
  /**
   * Whether the display allows area plugins.
   *
   * @var bool
   */
  protected $usesAreas = TRUE;

102
  public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
103
    $this->setOptionDefaults($this->options, $this->defineOptions());
104
    $this->view = $view;
merlinofchaos's avatar
merlinofchaos committed
105 106 107 108 109
    $this->display = &$display;

    // Load extenders as soon as possible.
    $this->extender = array();
    $extenders = views_get_enabled_display_extenders();
110
    if (!empty($extenders)) {
111
      $manager = Views::pluginManager('display_extender');
merlinofchaos's avatar
merlinofchaos committed
112
      foreach ($extenders as $extender) {
113
        $plugin = $manager->createInstance($extender);
merlinofchaos's avatar
merlinofchaos committed
114 115 116 117 118 119 120 121 122 123 124
        if ($plugin) {
          $plugin->init($this->view, $this);
          $this->extender[$extender] = $plugin;
        }
      }
    }

    // Track changes that the user should know about.
    $changed = FALSE;

    // Make some modifications:
125 126
    if (!isset($options) && isset($display['display_options'])) {
      $options = $display['display_options'];
merlinofchaos's avatar
merlinofchaos committed
127 128
    }

129
    if ($this->isDefaultDisplay() && isset($options['defaults'])) {
merlinofchaos's avatar
merlinofchaos committed
130 131 132
      unset($options['defaults']);
    }

133
    // Cache for unpackOptions, but not if we are in the ui.
merlinofchaos's avatar
merlinofchaos committed
134 135
    static $unpack_options = array();
    if (empty($view->editing)) {
136
      $cid = 'unpackOptions:' . hash('sha256', serialize(array($this->options, $options)));
merlinofchaos's avatar
merlinofchaos committed
137 138 139 140 141 142
      if (empty($unpack_options[$cid])) {
        $cache = views_cache_get($cid, TRUE);
        if (!empty($cache->data)) {
          $this->options = $cache->data;
        }
        else {
143
          $this->unpackOptions($this->options, $options);
merlinofchaos's avatar
merlinofchaos committed
144 145 146 147 148 149 150 151 152
          views_cache_set($cid, $this->options, TRUE);
        }
        $unpack_options[$cid] = $this->options;
      }
      else {
        $this->options = $unpack_options[$cid];
      }
    }
    else {
153
      $this->unpackOptions($this->options, $options);
merlinofchaos's avatar
merlinofchaos committed
154 155
    }

156
    // Convert the field_language and field_language_add_to_query settings.
157 158
    $field_language = $this->getOption('field_language');
    $field_language_add_to_query = $this->getOption('field_language_add_to_query');
159
    if (isset($field_langcode)) {
160 161
      $this->setOption('field_langcode', $field_language);
      $this->setOption('field_langcode_add_to_query', $field_language_add_to_query);
162 163 164
      $changed = TRUE;
    }

merlinofchaos's avatar
merlinofchaos committed
165 166 167 168 169 170
    // Mark the view as changed so the user has a chance to save it.
    if ($changed) {
      $this->view->changed = TRUE;
    }
  }

171
  public function destroy() {
merlinofchaos's avatar
merlinofchaos committed
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
    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);
    }

    foreach ($this->extender as $extender) {
      $extender->destroy();
    }
  }

  /**
   * Determine if this display is the 'default' display which contains
   * fallback settings
   */
195
  public function isDefaultDisplay() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
196 197 198 199 200

  /**
   * Determine if this display uses exposed filters, so the view
   * will know whether or not to build them.
   */
201
  public function usesExposed() {
merlinofchaos's avatar
merlinofchaos committed
202 203 204
    if (!isset($this->has_exposed)) {
      foreach ($this->handlers as $type => $value) {
        foreach ($this->view->$type as $id => $handler) {
205
          if ($handler->canExpose() && $handler->isExposed()) {
merlinofchaos's avatar
merlinofchaos committed
206 207 208 209 210 211
            // one is all we need; if we find it, return true.
            $this->has_exposed = TRUE;
            return TRUE;
          }
        }
      }
212
      $pager = $this->getPlugin('pager');
merlinofchaos's avatar
merlinofchaos committed
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
      if (isset($pager) && $pager->uses_exposed()) {
        $this->has_exposed = TRUE;
        return TRUE;
      }
      $this->has_exposed = FALSE;
    }

    return $this->has_exposed;
  }

  /**
   * Determine if this display should display the exposed
   * filters widgets, so the view will know whether or not
   * to render them.
   *
   * Regardless of what this function
   * returns, exposed filters will not be used nor
230
   * displayed unless usesExposed() returns TRUE.
merlinofchaos's avatar
merlinofchaos committed
231
   */
232
  public function displaysExposed() {
merlinofchaos's avatar
merlinofchaos committed
233 234 235 236
    return TRUE;
  }

  /**
237 238 239 240
   * Whether the display allows the use of AJAX or not.
   *
   * @return bool
   */
241
  public function usesAJAX() {
242 243 244 245 246 247 248
    return $this->usesAJAX;
  }

  /**
   * Whether the display is actually using AJAX or not.
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
249
   */
250
  public function ajaxEnabled() {
251
    if ($this->usesAJAX()) {
252
      return $this->getOption('use_ajax');
merlinofchaos's avatar
merlinofchaos committed
253 254 255 256
    }
    return FALSE;
  }

257 258 259 260 261 262 263 264 265 266
  /**
   * Whether the display is enabled.
   *
   * @return bool
   *   Returns TRUE if the display is marked as enabled, else FALSE.
   */
  public function isEnabled() {
    return (bool) $this->getOption('enabled');
  }

merlinofchaos's avatar
merlinofchaos committed
267
  /**
268 269 270 271 272
   * Whether the display allows the use of a pager or not.
   *
   * @return bool
   */

273
  public function usesPager() {
274 275 276 277 278 279 280
    return $this->usesPager;
  }

  /**
   * Whether the display is using a pager or not.
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
281
   */
282
  public function isPagerEnabled() {
283
    if ($this->usesPager()) {
284
      $pager = $this->getPlugin('pager');
285
      if ($pager) {
286
        return $pager->usePager();
287
      }
merlinofchaos's avatar
merlinofchaos committed
288
    }
289
    return FALSE;
merlinofchaos's avatar
merlinofchaos committed
290 291 292
  }

  /**
293 294 295 296
   * Whether the display allows the use of a 'more' link or not.
   *
   * @return bool
   */
297
  public function usesMore() {
298 299 300 301 302 303 304
    return $this->usesMore;
  }

  /**
   * Whether the display is using the 'more' link or not.
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
305
   */
306
  public function isMoreEnabled() {
307
    if ($this->usesMore()) {
308
      return $this->getOption('use_more');
merlinofchaos's avatar
merlinofchaos committed
309 310 311 312 313 314 315
    }
    return FALSE;
  }

  /**
   * Does the display have groupby enabled?
   */
316 317
  public function useGroupBy() {
    return $this->getOption('group_by');
merlinofchaos's avatar
merlinofchaos committed
318 319 320 321 322
  }

  /**
   * Should the enabled display more link be shown when no more items?
   */
323
  public function useMoreAlways() {
324
    if ($this->usesMore()) {
325
      return $this->getOption('use_more_always');
merlinofchaos's avatar
merlinofchaos committed
326 327 328 329 330 331 332
    }
    return FALSE;
  }

  /**
   * Does the display have custom link text?
   */
333
  public function useMoreText() {
334
    if ($this->usesMore()) {
335
      return $this->getOption('use_more_text');
merlinofchaos's avatar
merlinofchaos committed
336 337 338 339 340
    }
    return FALSE;
  }

  /**
341 342 343
   * Determines whether this display can use attachments.
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
344
   */
345
  public function acceptAttachments() {
346 347 348
    // 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
349 350
      return FALSE;
    }
351

352
    if (!empty($this->view->argument) && $this->getOption('hide_attachment_summary')) {
merlinofchaos's avatar
merlinofchaos committed
353
      foreach ($this->view->argument as $argument_id => $argument) {
354
        if ($argument->needsStylePlugin() && empty($argument->argument_validated)) {
merlinofchaos's avatar
merlinofchaos committed
355 356 357 358
          return FALSE;
        }
      }
    }
359

merlinofchaos's avatar
merlinofchaos committed
360 361 362
    return TRUE;
  }

363 364 365 366 367
  /**
   * Returns whether the display can use attachments.
   *
   * @return bool
   */
368
  public function usesAttachments() {
369 370 371
    return $this->usesAttachments;
  }

372 373 374 375 376 377 378 379 380 381
  /**
   * Returns whether the display can use areas.
   *
   * @return bool
   *   TRUE if the display can use areas, or FALSE otherwise.
   */
  public function usesAreas() {
    return $this->usesAreas;
  }

merlinofchaos's avatar
merlinofchaos committed
382 383 384
  /**
   * Allow displays to attach to other views.
   */
385
  public function attachTo(ViewExecutable $view, $display_id) { }
merlinofchaos's avatar
merlinofchaos committed
386 387 388 389 390

  /**
   * Static member function to list which sections are defaultable
   * and what items each section contains.
   */
391
  public function defaultableSections($section = NULL) {
merlinofchaos's avatar
merlinofchaos committed
392
    $sections = array(
393 394
      'access' => array('access'),
      'cache' => array('cache'),
merlinofchaos's avatar
merlinofchaos committed
395 396 397 398
      'title' => array('title'),
      'css_class' => array('css_class'),
      'use_ajax' => array('use_ajax'),
      'hide_attachment_summary' => array('hide_attachment_summary'),
399
      'show_admin_links' => array('show_admin_links'),
merlinofchaos's avatar
merlinofchaos committed
400 401 402
      'group_by' => array('group_by'),
      'query' => array('query'),
      'use_more' => array('use_more', 'use_more_always', 'use_more_text'),
403 404
      '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
405 406 407
      'link_display' => array('link_display', 'link_url'),

      // Force these to cascade properly.
408 409
      'style' => array('style', 'row'),
      'row' => array('style', 'row'),
merlinofchaos's avatar
merlinofchaos committed
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429

      'pager' => array('pager', 'pager_options'),
      'pager_options' => array('pager', 'pager_options'),

      'exposed_form' => array('exposed_form', 'exposed_form_options'),
      'exposed_form_options' => array('exposed_form', 'exposed_form_options'),

      // These guys are special
      '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.
430
    if (!$this->usesPager()) {
merlinofchaos's avatar
merlinofchaos committed
431 432 433 434 435
      unset($sections['pager']);
      unset($sections['items_per_page']);
    }

    foreach ($this->extender as $extender) {
436
      $extender->defaultableSections($sections, $section);
merlinofchaos's avatar
merlinofchaos committed
437 438 439 440 441 442 443 444 445 446 447 448
    }

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

449
  protected function defineOptions() {
merlinofchaos's avatar
merlinofchaos committed
450 451 452 453 454 455 456 457 458 459 460 461
    $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,
462
          'show_admin_links' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
463 464 465 466 467 468 469 470 471 472
          'pager' => TRUE,
          'use_more' => TRUE,
          'use_more_always' => TRUE,
          'use_more_text' => TRUE,
          'exposed_form' => TRUE,

          'link_display' => TRUE,
          'link_url' => '',
          'group_by' => TRUE,

473 474
          'style' => TRUE,
          'row' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
475 476 477 478 479 480 481 482 483 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

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

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

      'title' => array(
        'default' => '',
        'translatable' => TRUE,
      ),
      'enabled' => array(
        'default' => TRUE,
        'translatable' => FALSE,
        'bool' => TRUE,
      ),
      'display_comment' => array(
        'default' => '',
      ),
      'css_class' => array(
        'default' => '',
        'translatable' => FALSE,
      ),
      'display_description' => array(
        'default' => '',
        'translatable' => TRUE,
      ),
      'use_ajax' => array(
        'default' => FALSE,
        'bool' => TRUE,
      ),
      'hide_attachment_summary' => array(
        'default' => FALSE,
        'bool' => TRUE,
      ),
517 518
      'show_admin_links' => array(
        'default' => TRUE,
519 520
        'bool' => TRUE,
      ),
merlinofchaos's avatar
merlinofchaos committed
521 522 523 524 525 526
      'use_more' => array(
        'default' => FALSE,
        'bool' => TRUE,
      ),
      'use_more_always' => array(
        'default' => FALSE,
527
        'bool' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
      ),
      'use_more_text' => array(
        'default' => 'more',
        'translatable' => TRUE,
      ),
      'link_display' => array(
        'default' => '',
      ),
      'link_url' => array(
        'default' => '',
      ),
      'group_by' => array(
        'default' => FALSE,
        'bool' => TRUE,
      ),
543
      'field_langcode' => array(
merlinofchaos's avatar
merlinofchaos committed
544 545
        'default' => '***CURRENT_LANGUAGE***',
      ),
546 547 548
      'field_langcode_add_to_query' => array(
        'default' => TRUE,
        'bool' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
549 550 551 552 553 554
      ),

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

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

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

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

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

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

    foreach ($this->extender as $extender) {
662
      $extender->defineOptionsAlter($options);
merlinofchaos's avatar
merlinofchaos committed
663 664 665 666 667 668 669 670 671 672 673 674 675 676
    }

    return $options;
  }

  /**
   * Check to see if the display has a 'path' field.
   *
   * This is a pure function and not just a setting on the definition
   * because some displays (such as a panel pane) may have a path based
   * upon configuration.
   *
   * By default, displays do not have a path.
   */
677
  public function hasPath() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
678 679 680 681 682 683 684 685

  /**
   * Check to see if the display has some need to link to another display.
   *
   * For the most part, displays without a path will use a link display. However,
   * sometimes displays that have a path might also need to link to another display.
   * This is true for feeds.
   */
686
  public function usesLinkDisplay() { return !$this->hasPath(); }
merlinofchaos's avatar
merlinofchaos committed
687 688 689 690 691 692 693 694 695

  /**
   * Check to see if the display can put the exposed formin a block.
   *
   * By default, displays that do not have a path cannot disconnect
   * the exposed form and put it in a block, because the form has no
   * place to go and Views really wants the forms to go to a specific
   * page.
   */
696
  public function usesExposedFormInBlock() { return $this->hasPath(); }
merlinofchaos's avatar
merlinofchaos committed
697

698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720
  /**
   * Find out all displays which are attached to this display.
   *
   * The method is just using the pure storage object to avoid loading of the
   * sub displays which would kill lazy loading.
   */
  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
721 722 723 724
  /**
   * Check to see which display to use when creating links within
   * a view using this display.
   */
725 726
  public function getLinkDisplay() {
    $display_id = $this->getOption('link_display');
merlinofchaos's avatar
merlinofchaos committed
727
    // If unknown, pick the first one.
728
    if (empty($display_id) || !$this->view->displayHandlers->has($display_id)) {
729 730
      foreach ($this->view->displayHandlers as $display_id => $display) {
        if (!empty($display) && $display->hasPath()) {
merlinofchaos's avatar
merlinofchaos committed
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
          return $display_id;
        }
      }
    }
    else {
      return $display_id;
    }
    // fall-through returns NULL
  }

  /**
   * Return the base path to use for this display.
   *
   * This can be overridden for displays that do strange things
   * with the path.
   */
747 748 749
  public function getPath() {
    if ($this->hasPath()) {
      return $this->getOption('path');
merlinofchaos's avatar
merlinofchaos committed
750 751
    }

752
    $display_id = $this->getLinkDisplay();
753 754
    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
755 756 757
    }
  }

758
  public function getUrl() {
759
    return $this->view->getUrl();
merlinofchaos's avatar
merlinofchaos committed
760 761 762 763 764 765 766
  }

  /**
   * Check to see if the display needs a breadcrumb
   *
   * By default, displays do not need breadcrumbs
   */
767
  public function usesBreadcrumb() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
768 769 770 771 772 773 774 775

  /**
   * Determine if a given option is set to use the default display or the
   * current display
   *
   * @return
   *   TRUE for the default display
   */
776 777
  public function isDefaulted($option) {
    return !$this->isDefaultDisplay() && !empty($this->default_display) && !empty($this->options['defaults'][$option]);
merlinofchaos's avatar
merlinofchaos committed
778 779 780 781 782 783
  }

  /**
   * Intelligently get an option either from this display or from the
   * default display, if directed to do so.
   */
784 785 786
  public function getOption($option) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->getOption($option);
merlinofchaos's avatar
merlinofchaos committed
787 788 789 790 791 792 793 794 795
    }

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

  /**
   * Determine if the display's style uses fields.
796 797
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
798
   */
799
  public function usesFields() {
800
    return $this->getPlugin('style')->usesFields();
merlinofchaos's avatar
merlinofchaos committed
801 802 803 804 805 806 807 808
  }

  /**
   * Get the instance of a plugin, for example style or row.
   *
   * @param string $type
   *   The type of the plugin.
   *
809
   * @return \Drupal\views\Plugin\views\PluginBase
810 811
   */
  public function getPlugin($type) {
812
    // Look up the plugin name to use for this instance.
813 814
    $options = $this->getOption($type);
    $name = $options['type'];
merlinofchaos's avatar
merlinofchaos committed
815

816 817
    // Query plugins allow specifying a specific query class per base table.
    if ($type == 'query') {
818
      $views_data = Views::viewsData()->get($this->view->storage->get('base_table'));
819
      $name = isset($views_data['table']['base']['query_id']) ? $views_data['table']['base']['query_id'] : 'views_query';
820
    }
821

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

826 827
      // Initialize the plugin.
      $plugin->init($this->view, $this, $options['options']);
828

829
      $this->plugins[$type][$name] = $plugin;
merlinofchaos's avatar
merlinofchaos committed
830 831
    }

832
    return $this->plugins[$type][$name];
merlinofchaos's avatar
merlinofchaos committed
833 834 835 836 837
  }

  /**
   * Get the handler object for a single handler.
   */
838
  public function &getHandler($type, $id) {
merlinofchaos's avatar
merlinofchaos committed
839
    if (!isset($this->handlers[$type])) {
840
      $this->getHandlers($type);
merlinofchaos's avatar
merlinofchaos committed
841 842 843 844 845 846 847 848 849 850 851 852 853 854
    }

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

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

  /**
   * Get a full array of handlers for $type. This caches them.
   */
855
  public function getHandlers($type) {
merlinofchaos's avatar
merlinofchaos committed
856 857
    if (!isset($this->handlers[$type])) {
      $this->handlers[$type] = array();
858
      $types = ViewExecutable::viewsHandlerTypes();
merlinofchaos's avatar
merlinofchaos committed
859 860
      $plural = $types[$type]['plural'];

861
      foreach ($this->getOption($plural) as $id => $info) {
merlinofchaos's avatar
merlinofchaos committed
862 863 864 865 866 867 868 869 870 871 872 873 874 875 876
        // 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.
        if (isset($_POST['form_id']) && isset($this->view->temporary_options[$type][$id])) {
          $info = $this->view->temporary_options[$type][$id];
        }

        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;
877
        if ($this->useGroupBy() && !empty($info['group_type'])) {
merlinofchaos's avatar
merlinofchaos committed
878
          if (empty($this->view->query)) {
879
            $this->view->initQuery();
merlinofchaos's avatar
merlinofchaos committed
880 881 882 883 884 885 886 887 888 889 890 891 892 893
          }
          $aggregate = $this->view->query->get_aggregation_info();
          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;
        }

894
        if ($handler = views_get_handler($info, $handler_type, $override)) {
merlinofchaos's avatar
merlinofchaos committed
895
          // Special override for area types so they know where they come from.
896 897
          if ($handler instanceof AreaPluginBase) {
            $handler->areaType = $type;
merlinofchaos's avatar
merlinofchaos committed
898 899
          }

900
          $handler->init($this->view, $this, $info);
merlinofchaos's avatar
merlinofchaos committed
901 902 903 904 905 906 907 908 909 910 911 912
          $this->handlers[$type][$id] = &$handler;
        }

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

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

  /**
913
   * Retrieves a list of fields for the current display.
914
   *
915 916 917 918
   * This also takes into account any associated relationships, if they exist.
   *
   * @param bool $groupable_only
   *   (optional) TRUE to only return an array of field labels from handlers
919
   *   that support the useStringGroupBy method, defaults to FALSE.
920 921 922
   *
   * @return array
   *   An array of applicable field options, keyed by ID.
merlinofchaos's avatar
merlinofchaos committed
923
   */
924
  public function getFieldLabels($groupable_only = FALSE) {
merlinofchaos's avatar
merlinofchaos committed
925
    $options = array();
926
    foreach ($this->getHandlers('relationship') as $relationship => $handler) {
927
      $relationships[$relationship] = $handler->adminLabel();
merlinofchaos's avatar
merlinofchaos committed
928 929
    }

930
    foreach ($this->getHandlers('field') as $id => $handler) {
931
      if ($groupable_only && !$handler->useStringGroupBy()) {
932 933 934
        // Continue to next handler if it's not groupable.
        continue;
      }
merlinofchaos's avatar
merlinofchaos committed
935 936 937 938
      if ($label = $handler->label()) {
        $options[$id] = $label;
      }
      else {
939
        $options[$id] = $handler->adminLabel();
merlinofchaos's avatar
merlinofchaos committed
940 941 942 943 944 945 946 947 948 949 950 951
      }
      if (!empty($handler->options['relationship']) && !empty($relationships[$handler->options['relationship']])) {
        $options[$id] = '(' . $relationships[$handler->options['relationship']] . ') ' . $options[$id];
      }
    }
    return $options;
  }

  /**
   * Intelligently set an option either from this display or from the
   * default display, if directed to do so.
   */
952 953 954
  public function setOption($option, $value) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->setOption($option, $value);
merlinofchaos's avatar
merlinofchaos committed
955 956 957 958 959
    }

    // 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.
960
    $this->display['display_options'][$option] = $value;
merlinofchaos's avatar
merlinofchaos committed
961 962 963 964 965 966
    return $this->options[$option] = $value;
  }

  /**
   * Set an option and force it to be an override.
   */
967 968 969
  public function overrideOption($option, $value) {
    $this->setOverride($option, FALSE);
    $this->setOption($option, $value);
merlinofchaos's avatar
merlinofchaos committed
970 971 972 973 974 975
  }

  /**
   * Because forms may be split up into sections, this provides
   * an easy URL to exactly the right section. Don't override this.
   */
976
  public function optionLink($text, $section, $class = '', $title = '') {
merlinofchaos's avatar
merlinofchaos committed
977 978 979 980 981 982 983 984 985 986 987 988
    if (!empty($class)) {
      $text = '<span>' . $text . '</span>';
    }

    if (!trim($text)) {
      $text = t('Broken field');
    }

    if (empty($title)) {
      $title = $text;
    }

989
    return l($text, 'admin/structure/views/nojs/display/' . $this->view->storage->id() . '/' . $this->display['id'] . '/' . $section, array('attributes' => array('class' => 'views-ajax-link ' . $class, 'title' => $title, 'id' => drupal_html_id('views-' . $this->display['id'] . '-' . $section)), 'html' => TRUE));
merlinofchaos's avatar
merlinofchaos committed
990 991 992 993 994
  }

  /**
   * Returns to tokens for arguments.
   *
995
   * This function is similar to views_handler_field::getRenderTokens()
merlinofchaos's avatar
merlinofchaos committed
996 997
   * but without fields tokens.
   */
998
  public function getArgumentsTokens() {
merlinofchaos's avatar
merlinofchaos committed
999 1000 1001 1002 1003
    $tokens = array();
    if (!empty($this->view->build_info['substitutions'])) {
      $tokens = $this->view->build_info['substitutions'];
    }
    $count = 0;
1004
    foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
merlinofchaos's avatar
merlinofchaos committed
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
      $token = '%' . ++$count;
      if (!isset($tokens[$token])) {
        $tokens[$token] = '';
      }

      // Use strip tags as there should never be HTML in the path.
      // However, we need to preserve special characters like " that
      // were removed by check_plain().
      $tokens['!' . $count] = isset($this->view->args[$count - 1]) ? strip_tags(decode_entities($this->view->args[$count - 1])) : '';
    }

    return $tokens;
  }

  /**
   * Provide the default summary for options in the views UI.
   *
   * This output is returned as an array.
   */
1024
  public function optionsSummary(&$categories, &$options) {
merlinofchaos's avatar
merlinofchaos committed
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
    $categories = array(
      'title' => array(
        'title' => t('Title'),
        'column' => 'first',
      ),
      'format' => array(
        'title' => t('Format'),
        'column' => 'first',
      ),
      'filters' => array(
        'title' => t('Filters'),
        'column' => 'first',
      ),
      'fields' => array(
        'title' => t('Fields'),
        'column' => 'first',
      ),
      'pager' => array(
        'title' => t('Pager'),
        'column' => 'second',
      ),
      'exposed' => array(
        'title' => t('Exposed form'),
        'column' => 'third',
        'build' => array(
          '#weight' => 1,
        ),
      ),
      'access' => array(
        'title' => '',
        'column' => 'second',
        'build' => array(
          '#weight' => -5,
        ),
      ),
      'other' => array(
        'title' => t('Other'),
        'column' => 'third',
        'build' => array(
          '#weight' => 2,
        ),
      ),
    );

1069
    if ($this->display['id'] != 'default') {
merlinofchaos's avatar
merlinofchaos committed
1070 1071 1072
      $options['display_id'] = array(
        'category' => 'other',
        'title' => t('Machine Name'),
1073
        'value' => !empty($this->display['new_id']) ? check_plain($this->display['new_id']) : check_plain($this->display['id']),
merlinofchaos's avatar
merlinofchaos committed
1074 1075 1076 1077
        'desc' => t('Change the machine name of this display.'),
      );
    }

1078
    $display_comment = check_plain(drupal_substr($this->getOption('display_comment'), 0, 10));
merlinofchaos's avatar
merlinofchaos committed
1079 1080
    $options['display_comment'] = array(
      'category' => 'other',
1081 1082
      'title' => t('Administrative comment'),
      'value' => !empty($display_comment) ? $display_comment : t('None'),
merlinofchaos's avatar
merlinofchaos committed
1083 1084 1085
      'desc' => t('Comment or document this display.'),
    );

1086
    $title = strip_tags($this->getOption('title'));
merlinofchaos's avatar
merlinofchaos committed
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
    if (!$title) {
      $title = t('None');
    }

    $options['title'] = array(
      'category' => 'title',
      'title' => t('Title'),
      'value' => $title,
      'desc' => t('Change the title that this display will use.'),
    );

1098
    $style_plugin_instance = $this->getPlugin('style');
1099 1100
    $style_summary = empty($style_plugin_instance->definition['title']) ? t('Missing style plugin') : $style_plugin_instance->summaryTitle();
    $style_title = empty($style_plugin_instance->definition['title']) ? t('Missing style plugin') : $style_plugin_instance->pluginTitle();
merlinofchaos's avatar
merlinofchaos committed
1101 1102 1103

    $style = '';

1104
    $options['style'] = array(
merlinofchaos's avatar
merlinofchaos committed
1105 1106 1107 1108 1109 1110 1111 1112
      'category' => 'format',
      'title' => t('Format'),
      'value' => $style_title,
      'setting' => $style_summary,
      'desc' => t('Change the way content is formatted.'),
    );

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

1117
    if ($style_plugin_instance->usesRowPlugin()) {
1118 1119 1120
      $row_plugin_instance = $this->getPlugin('row');
      $row_summary = empty($row_plugin_instance->definition['title']) ? t('Missing style plugin') : $row_plugin_instance->summaryTitle();
      $row_title = empty($row_plugin_instance->definition['title']) ? t('Missing style plugin') : $row_plugin_instance->pluginTitle();
merlinofchaos's avatar
merlinofchaos committed
1121

1122
      $options['row'] = array(
merlinofchaos's avatar
merlinofchaos committed
1123 1124 1125 1126 1127 1128 1129
        'category' => 'format',
        'title' => t('Show'),
        'value' => $row_title,
        'setting' => $row_summary,
        'desc' => t('Change the way each row in the view is styled.'),
      );
      // This adds a 'Settings' link to the row_options setting if the row style has options.
1130
      if ($row_plugin_instance->usesOptions()) {
1131
        $options['row']['links']['row_options'] = t('Change settings for this style');
merlinofchaos's avatar
merlinofchaos committed
1132 1133
      }
    }
1134
    if ($this->usesAJAX()) {
merlinofchaos's avatar
merlinofchaos committed
1135 1136 1137
      $options['use_ajax'] = array(
        'category' => 'other',
        'title' => t('Use AJAX'),
1138
        'value' => $this->getOption('use_ajax') ? t('Yes') : t('No'),
merlinofchaos's avatar
merlinofchaos committed
1139 1140 1141
        'desc' => t('Change whether or not this display will use AJAX.'),
      );
    }
1142
    if ($this->usesAttachments()) {
merlinofchaos's avatar
merlinofchaos committed
1143 1144 1145
      $options['hide_attachment_summary'] = array(
        'category' => 'other',
        'title' => t('Hide attachments in summary'),
1146
        'value' => $this->getOption('hide_attachment_summary') ? t('Yes') : t('No'),
merlinofchaos's avatar
merlinofchaos committed
1147 1148 1149
        'desc' => t('Change whether or not to display attachments when displaying a contextual filter summary.'),
      );
    }
1150
    if (!isset($this->definition['contextual links locations']) || !empty($this->definition['contextual links locations'])) {
1151
      $options['show_admin_links'] = array(
1152
        'category' => 'other',
1153 1154
        'title' => t('Contextual links'),
        'value' => $this->getOption('show_admin_links') ? t('Shown') : t('Hidden'),
1155 1156 1157
        'desc' => t('Change whether or not to display contextual links for this view.'),
      );
    }
merlinofchaos's avatar
merlinofchaos committed
1158

1159
    $pager_plugin = $this->getPlugin('pager');
merlinofchaos's avatar
merlinofchaos committed
1160 1161
    if (!$pager_plugin) {
      // default to the no pager plugin.
1162
      $pager_plugin = Views::pluginManager('pager')->createInstance('none');
merlinofchaos's avatar
merlinofchaos committed
1163 1164
    }

1165
    $pager_str = $pager_plugin->summaryTitle();
merlinofchaos's avatar
merlinofchaos committed
1166 1167 1168 1169

    $options['pager'] = array(
      'category' => 'pager',
      'title' => t('Use pager'),
1170
      'value' => $pager_plugin->pluginTitle(),
merlinofchaos's avatar
merlinofchaos committed
1171 1172 1173 1174 1175
      'setting' => $pager_str,
      'desc' => t("Change this display's pager setting."),
    );

    // If pagers aren't allowed, change the text of the item:
1176
    if (!$this->usesPager()) {
merlinofchaos's avatar
merlinofchaos committed
1177 1178 1179
      $options['pager']['title'] = t('Items to display');
    }

1180
    if ($pager_plugin->usesOptions()) {
merlinofchaos's avatar
merlinofchaos committed
1181 1182 1183
      $options['pager']['links']['pager_options'] = t('Change settings for this pager type.');
    }

1184
    if ($this->usesMore()) {
merlinofchaos's avatar
merlinofchaos committed
1185 1186 1187
      $options['use_more'] = array(
        'category' => 'pager',
        'title' => t('More link'),
1188
        'value' => $this->getOption('use_more') ? t('Yes') : t('No'),
merlinofchaos's avatar
merlinofchaos committed
1189 1190 1191 1192
        'desc' => t('Specify whether this display will provide a "more" link.'),
      );
    }

1193
    $this->view->initQuery();
merlinofchaos's avatar
merlinofchaos committed
1194 1195 1196 1197
    if ($this->view->query->get_aggregation_info()) {
      $options['group_by'] = array(
        'category' => 'other',
        'title' => t('Use aggregation'),
1198
        'value' => $this->getOption('group_by') ? t('Yes') : t('No'),
merlinofchaos's avatar
merlinofchaos committed
1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212
        'desc' => t('Allow grouping and aggregation (calculation) of fields.'),
      );
    }

    $options['query'] = array(
      'category' => 'other',
      'title' => t('Query settings'),
      'value' => t('Settings'),
      'desc' => t('Allow to set some advanced settings for the query plugin'),
    );

    $languages = array(
        '***CURRENT_LANGUAGE***' => t("Current user's language"),
        '***DEFAULT_LANGUAGE***' => t("Default site language"),
1213
        Language::LANGCODE_NOT_SPECIFIED => t('Language neutral'),
merlinofchaos's avatar
merlinofchaos committed
1214
    );
1215
    if (\Drupal::moduleHandler()->moduleExists(