DisplayPluginBase.php 94.6 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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
  /**
   * 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.
   */
  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
    parent::__construct(array(), $plugin_id, $plugin_definition);
  }

117
  public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
118
    $this->setOptionDefaults($this->options, $this->defineOptions());
119
    $this->view = $view;
merlinofchaos's avatar
merlinofchaos committed
120
121
122
123
124
    $this->display = &$display;

    // Load extenders as soon as possible.
    $this->extender = array();
    $extenders = views_get_enabled_display_extenders();
125
    if (!empty($extenders)) {
126
      $manager = Views::pluginManager('display_extender');
merlinofchaos's avatar
merlinofchaos committed
127
      foreach ($extenders as $extender) {
128
        $plugin = $manager->createInstance($extender);
merlinofchaos's avatar
merlinofchaos committed
129
130
131
132
133
134
135
136
137
138
139
        if ($plugin) {
          $plugin->init($this->view, $this);
          $this->extender[$extender] = $plugin;
        }
      }
    }

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

    // Make some modifications:
140
141
    if (!isset($options) && isset($display['display_options'])) {
      $options = $display['display_options'];
merlinofchaos's avatar
merlinofchaos committed
142
143
    }

144
    if ($this->isDefaultDisplay() && isset($options['defaults'])) {
merlinofchaos's avatar
merlinofchaos committed
145
146
147
      unset($options['defaults']);
    }

148
    // Cache for unpackOptions, but not if we are in the ui.
merlinofchaos's avatar
merlinofchaos committed
149
150
    static $unpack_options = array();
    if (empty($view->editing)) {
151
      $cid = 'unpackOptions:' . hash('sha256', serialize(array($this->options, $options)));
merlinofchaos's avatar
merlinofchaos committed
152
153
154
155
156
157
      if (empty($unpack_options[$cid])) {
        $cache = views_cache_get($cid, TRUE);
        if (!empty($cache->data)) {
          $this->options = $cache->data;
        }
        else {
158
          $this->unpackOptions($this->options, $options);
merlinofchaos's avatar
merlinofchaos committed
159
160
161
162
163
164
165
166
167
          views_cache_set($cid, $this->options, TRUE);
        }
        $unpack_options[$cid] = $this->options;
      }
      else {
        $this->options = $unpack_options[$cid];
      }
    }
    else {
168
      $this->unpackOptions($this->options, $options);
merlinofchaos's avatar
merlinofchaos committed
169
170
    }

171
    // Convert the field_language and field_language_add_to_query settings.
172
173
    $field_language = $this->getOption('field_language');
    $field_language_add_to_query = $this->getOption('field_language_add_to_query');
174
    if (isset($field_langcode)) {
175
176
      $this->setOption('field_langcode', $field_language);
      $this->setOption('field_langcode_add_to_query', $field_language_add_to_query);
177
178
179
      $changed = TRUE;
    }

merlinofchaos's avatar
merlinofchaos committed
180
181
182
183
184
185
    // Mark the view as changed so the user has a chance to save it.
    if ($changed) {
      $this->view->changed = TRUE;
    }
  }

186
  public function destroy() {
merlinofchaos's avatar
merlinofchaos committed
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
    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
   */
210
  public function isDefaultDisplay() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
211
212
213
214
215

  /**
   * Determine if this display uses exposed filters, so the view
   * will know whether or not to build them.
   */
216
  public function usesExposed() {
merlinofchaos's avatar
merlinofchaos committed
217
218
219
    if (!isset($this->has_exposed)) {
      foreach ($this->handlers as $type => $value) {
        foreach ($this->view->$type as $id => $handler) {
220
          if ($handler->canExpose() && $handler->isExposed()) {
merlinofchaos's avatar
merlinofchaos committed
221
222
223
224
225
226
            // one is all we need; if we find it, return true.
            $this->has_exposed = TRUE;
            return TRUE;
          }
        }
      }
227
      $pager = $this->getPlugin('pager');
228
      if (isset($pager) && $pager->usesExposed()) {
merlinofchaos's avatar
merlinofchaos committed
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
        $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
245
   * displayed unless usesExposed() returns TRUE.
merlinofchaos's avatar
merlinofchaos committed
246
   */
247
  public function displaysExposed() {
merlinofchaos's avatar
merlinofchaos committed
248
249
250
251
    return TRUE;
  }

  /**
252
253
254
255
   * Whether the display allows the use of AJAX or not.
   *
   * @return bool
   */
256
  public function usesAJAX() {
257
258
259
260
261
262
263
    return $this->usesAJAX;
  }

  /**
   * Whether the display is actually using AJAX or not.
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
264
   */
265
  public function ajaxEnabled() {
266
    if ($this->usesAJAX()) {
267
      return $this->getOption('use_ajax');
merlinofchaos's avatar
merlinofchaos committed
268
269
270
271
    }
    return FALSE;
  }

272
273
274
275
276
277
278
279
280
281
  /**
   * 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
282
  /**
283
284
285
286
287
   * Whether the display allows the use of a pager or not.
   *
   * @return bool
   */

288
  public function usesPager() {
289
290
291
292
293
294
295
    return $this->usesPager;
  }

  /**
   * Whether the display is using a pager or not.
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
296
   */
297
  public function isPagerEnabled() {
298
    if ($this->usesPager()) {
299
      $pager = $this->getPlugin('pager');
300
      if ($pager) {
301
        return $pager->usePager();
302
      }
merlinofchaos's avatar
merlinofchaos committed
303
    }
304
    return FALSE;
merlinofchaos's avatar
merlinofchaos committed
305
306
307
  }

  /**
308
309
310
311
   * Whether the display allows the use of a 'more' link or not.
   *
   * @return bool
   */
312
  public function usesMore() {
313
314
315
316
317
318
319
    return $this->usesMore;
  }

  /**
   * Whether the display is using the 'more' link or not.
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
320
   */
321
  public function isMoreEnabled() {
322
    if ($this->usesMore()) {
323
      return $this->getOption('use_more');
merlinofchaos's avatar
merlinofchaos committed
324
325
326
327
328
329
330
    }
    return FALSE;
  }

  /**
   * Does the display have groupby enabled?
   */
331
332
  public function useGroupBy() {
    return $this->getOption('group_by');
merlinofchaos's avatar
merlinofchaos committed
333
334
335
336
337
  }

  /**
   * Should the enabled display more link be shown when no more items?
   */
338
  public function useMoreAlways() {
339
    if ($this->usesMore()) {
340
      return $this->getOption('use_more_always');
merlinofchaos's avatar
merlinofchaos committed
341
342
343
344
345
346
347
    }
    return FALSE;
  }

  /**
   * Does the display have custom link text?
   */
348
  public function useMoreText() {
349
    if ($this->usesMore()) {
350
      return $this->getOption('use_more_text');
merlinofchaos's avatar
merlinofchaos committed
351
352
353
354
355
    }
    return FALSE;
  }

  /**
356
357
358
   * Determines whether this display can use attachments.
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
359
   */
360
  public function acceptAttachments() {
361
362
363
    // 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
364
365
      return FALSE;
    }
366

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

merlinofchaos's avatar
merlinofchaos committed
375
376
377
    return TRUE;
  }

378
379
380
381
382
  /**
   * Returns whether the display can use attachments.
   *
   * @return bool
   */
383
  public function usesAttachments() {
384
385
386
    return $this->usesAttachments;
  }

387
388
389
390
391
392
393
394
395
396
  /**
   * 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
397
398
399
  /**
   * Allow displays to attach to other views.
   */
400
  public function attachTo(ViewExecutable $view, $display_id) { }
merlinofchaos's avatar
merlinofchaos committed
401
402
403
404
405

  /**
   * Static member function to list which sections are defaultable
   * and what items each section contains.
   */
406
  public function defaultableSections($section = NULL) {
merlinofchaos's avatar
merlinofchaos committed
407
    $sections = array(
408
409
      'access' => array('access'),
      'cache' => array('cache'),
merlinofchaos's avatar
merlinofchaos committed
410
411
412
413
      'title' => array('title'),
      'css_class' => array('css_class'),
      'use_ajax' => array('use_ajax'),
      'hide_attachment_summary' => array('hide_attachment_summary'),
414
      'show_admin_links' => array('show_admin_links'),
merlinofchaos's avatar
merlinofchaos committed
415
416
417
      'group_by' => array('group_by'),
      'query' => array('query'),
      'use_more' => array('use_more', 'use_more_always', 'use_more_text'),
418
419
      '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
420
421
422
      'link_display' => array('link_display', 'link_url'),

      // Force these to cascade properly.
423
424
      'style' => array('style', 'row'),
      'row' => array('style', 'row'),
merlinofchaos's avatar
merlinofchaos committed
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444

      '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.
445
    if (!$this->usesPager()) {
merlinofchaos's avatar
merlinofchaos committed
446
447
448
449
450
      unset($sections['pager']);
      unset($sections['items_per_page']);
    }

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

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

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

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

488
489
          'style' => TRUE,
          'row' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
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
524
525
526
527
528
529
530
531

          '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,
      ),
532
533
      'show_admin_links' => array(
        'default' => TRUE,
534
535
        'bool' => TRUE,
      ),
merlinofchaos's avatar
merlinofchaos committed
536
537
538
539
540
541
      'use_more' => array(
        'default' => FALSE,
        'bool' => TRUE,
      ),
      'use_more_always' => array(
        'default' => FALSE,
542
        'bool' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
      ),
      'use_more_text' => array(
        'default' => 'more',
        'translatable' => TRUE,
      ),
      'link_display' => array(
        'default' => '',
      ),
      'link_url' => array(
        'default' => '',
      ),
      'group_by' => array(
        'default' => FALSE,
        'bool' => TRUE,
      ),
558
      'field_langcode' => array(
merlinofchaos's avatar
merlinofchaos committed
559
560
        'default' => '***CURRENT_LANGUAGE***',
      ),
561
562
563
      'field_langcode_add_to_query' => array(
        'default' => TRUE,
        'bool' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
564
565
566
567
568
569
      ),

      // These types are all plugins that can have individual settings
      // and therefore need special handling.
      'access' => array(
        'contains' => array(
570
          'type' => array('default' => 'none'),
571
572
          'options' => array('default' => array()),
        ),
573
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
574
575
576
      ),
      'cache' => array(
        'contains' => array(
577
          'type' => array('default' => 'none'),
578
579
          'options' => array('default' => array()),
        ),
580
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
581
582
583
      ),
      'query' => array(
        'contains' => array(
584
585
          'type' => array('default' => 'views_query'),
          'options' => array('default' => array()),
merlinofchaos's avatar
merlinofchaos committed
586
         ),
587
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
588
589
590
      ),
      'exposed_form' => array(
        'contains' => array(
591
592
          'type' => array('default' => 'basic'),
          'options' => array('default' => array()),
merlinofchaos's avatar
merlinofchaos committed
593
         ),
594
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
595
596
597
      ),
      'pager' => array(
        'contains' => array(
598
          'type' => array('default' => 'mini'),
599
          'options' => array('default' => array()),
merlinofchaos's avatar
merlinofchaos committed
600
         ),
601
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
602
      ),
603
604
605
606
607
      'style' => array(
        'contains' => array(
          'type' => array('default' => 'default'),
          'options' => array('default' => array()),
        ),
608
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
609
      ),
610
611
612
613
614
      'row' => array(
        'contains' => array(
          'type' => array('default' => 'fields'),
          'options' => array('default' => array()),
        ),
615
        'merge_defaults' => array($this, 'mergePlugin'),
merlinofchaos's avatar
merlinofchaos committed
616
617
618
619
620
621
622
623
      ),

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

      'header' => array(
        'default' => array(),
624
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
625
626
627
      ),
      'footer' => array(
        'default' => array(),
628
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
629
630
631
      ),
      'empty' => array(
        'default' => array(),
632
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
633
634
635
636
637
638
      ),

      // We want these to export last.
      // These are the 5 handler types.
      'relationships' => array(
        'default' => array(),
639
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
640
641
642
      ),
      'fields' => array(
        'default' => array(),
643
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
644
645
646
      ),
      'sorts' => array(
        'default' => array(),
647
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
648
649
650
      ),
      'arguments' => array(
        'default' => array(),
651
        'merge_defaults' => array($this, 'mergeHandler'),
merlinofchaos's avatar
merlinofchaos committed
652
653
654
655
656
657
658
659
660
661
662
663
      ),
      'filter_groups' => array(
        'contains' => array(
          'operator' => array('default' => 'AND'),
          'groups' => array('default' => array(1 => 'AND')),
        ),
      ),
      'filters' => array(
        'default' => array(),
      ),
    );

664
    if (!$this->usesPager()) {
merlinofchaos's avatar
merlinofchaos committed
665
666
667
668
669
670
671
      $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';
    }

672
    if ($this->isDefaultDisplay()) {
merlinofchaos's avatar
merlinofchaos committed
673
674
675
676
      unset($options['defaults']);
    }

    foreach ($this->extender as $extender) {
677
      $extender->defineOptionsAlter($options);
merlinofchaos's avatar
merlinofchaos committed
678
679
680
681
682
683
684
685
686
687
688
689
690
691
    }

    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.
   */
692
  public function hasPath() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
693
694
695
696
697
698
699
700

  /**
   * 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.
   */
701
  public function usesLinkDisplay() { return !$this->hasPath(); }
merlinofchaos's avatar
merlinofchaos committed
702
703
704
705
706
707
708
709
710

  /**
   * 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.
   */
711
  public function usesExposedFormInBlock() { return $this->hasPath(); }
merlinofchaos's avatar
merlinofchaos committed
712

713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
  /**
   * 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
736
737
738
739
  /**
   * Check to see which display to use when creating links within
   * a view using this display.
   */
740
741
  public function getLinkDisplay() {
    $display_id = $this->getOption('link_display');
merlinofchaos's avatar
merlinofchaos committed
742
    // If unknown, pick the first one.
743
    if (empty($display_id) || !$this->view->displayHandlers->has($display_id)) {
744
745
      foreach ($this->view->displayHandlers as $display_id => $display) {
        if (!empty($display) && $display->hasPath()) {
merlinofchaos's avatar
merlinofchaos committed
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
          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.
   */
762
763
764
  public function getPath() {
    if ($this->hasPath()) {
      return $this->getOption('path');
merlinofchaos's avatar
merlinofchaos committed
765
766
    }

767
    $display_id = $this->getLinkDisplay();
768
769
    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
770
771
772
    }
  }

773
  public function getUrl() {
774
    return $this->view->getUrl();
merlinofchaos's avatar
merlinofchaos committed
775
776
777
778
779
780
781
  }

  /**
   * Check to see if the display needs a breadcrumb
   *
   * By default, displays do not need breadcrumbs
   */
782
  public function usesBreadcrumb() { return FALSE; }
merlinofchaos's avatar
merlinofchaos committed
783
784
785
786
787
788
789
790

  /**
   * Determine if a given option is set to use the default display or the
   * current display
   *
   * @return
   *   TRUE for the default display
   */
791
792
  public function isDefaulted($option) {
    return !$this->isDefaultDisplay() && !empty($this->default_display) && !empty($this->options['defaults'][$option]);
merlinofchaos's avatar
merlinofchaos committed
793
794
795
796
797
798
  }

  /**
   * Intelligently get an option either from this display or from the
   * default display, if directed to do so.
   */
799
800
801
  public function getOption($option) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->getOption($option);
merlinofchaos's avatar
merlinofchaos committed
802
803
804
805
806
807
808
809
810
    }

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

  /**
   * Determine if the display's style uses fields.
811
812
   *
   * @return bool
merlinofchaos's avatar
merlinofchaos committed
813
   */
814
  public function usesFields() {
815
    return $this->getPlugin('style')->usesFields();
merlinofchaos's avatar
merlinofchaos committed
816
817
818
819
820
821
822
823
  }

  /**
   * Get the instance of a plugin, for example style or row.
   *
   * @param string $type
   *   The type of the plugin.
   *
824
   * @return \Drupal\views\Plugin\views\PluginBase
825
826
   */
  public function getPlugin($type) {
827
    // Look up the plugin name to use for this instance.
828
    $options = $this->getOption($type);
829
830
831
832
833

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

835
836
    // Query plugins allow specifying a specific query class per base table.
    if ($type == 'query') {
837
      $views_data = Views::viewsData()->get($this->view->storage->get('base_table'));
838
      $name = isset($views_data['table']['base']['query_id']) ? $views_data['table']['base']['query_id'] : 'views_query';
839
    }
840
841
842
    else {
      $name = $options['type'];
    }
843

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

848
849
      // Initialize the plugin.
      $plugin->init($this->view, $this, $options['options']);
850

851
      $this->plugins[$type][$name] = $plugin;
merlinofchaos's avatar
merlinofchaos committed
852
853
    }

854
    return $this->plugins[$type][$name];
merlinofchaos's avatar
merlinofchaos committed
855
856
857
858
859
  }

  /**
   * Get the handler object for a single handler.
   */
860
  public function &getHandler($type, $id) {
merlinofchaos's avatar
merlinofchaos committed
861
    if (!isset($this->handlers[$type])) {
862
      $this->getHandlers($type);
merlinofchaos's avatar
merlinofchaos committed
863
864
865
866
867
868
869
870
871
872
873
874
875
876
    }

    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.
   */
877
  public function getHandlers($type) {
merlinofchaos's avatar
merlinofchaos committed
878
879
    if (!isset($this->handlers[$type])) {
      $this->handlers[$type] = array();
880
      $types = ViewExecutable::viewsHandlerTypes();
merlinofchaos's avatar
merlinofchaos committed
881
882
      $plural = $types[$type]['plural'];

883
      foreach ($this->getOption($plural) as $id => $info) {
merlinofchaos's avatar
merlinofchaos committed
884
885
886
        // 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.
887
        if (\Drupal::request()->request->get('form_id') && isset($this->view->temporary_options[$type][$id])) {
merlinofchaos's avatar
merlinofchaos committed
888
889
890
891
892
893
894
895
896
897
898
          $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;
899
        if ($this->useGroupBy() && !empty($info['group_type'])) {
merlinofchaos's avatar
merlinofchaos committed
900
          if (empty($this->view->query)) {
901
            $this->view->initQuery();
merlinofchaos's avatar
merlinofchaos committed
902
          }
903
          $aggregate = $this->view->query->getAggregationInfo();
merlinofchaos's avatar
merlinofchaos committed
904
905
906
907
908
909
910
911
912
913
914
915
          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;
        }

916
        if ($handler = Views::handlerManager($handler_type)->getHandler($info, $override)) {
merlinofchaos's avatar
merlinofchaos committed
917
          // Special override for area types so they know where they come from.
918
919
          if ($handler instanceof AreaPluginBase) {
            $handler->areaType = $type;
merlinofchaos's avatar
merlinofchaos committed
920
921
          }

922
          $handler->init($this->view, $this, $info);
merlinofchaos's avatar
merlinofchaos committed
923
924
925
926
927
928
929
930
931
932
933
934
          $this->handlers[$type][$id] = &$handler;
        }

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

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

  /**
935
   * Retrieves a list of fields for the current display.
936
   *
937
938
939
940
   * 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
941
   *   that support the useStringGroupBy method, defaults to FALSE.
942
943
944
   *
   * @return array
   *   An array of applicable field options, keyed by ID.
merlinofchaos's avatar
merlinofchaos committed
945
   */
946
  public function getFieldLabels($groupable_only = FALSE) {
merlinofchaos's avatar
merlinofchaos committed
947
    $options = array();
948
    foreach ($this->getHandlers('relationship') as $relationship => $handler) {
949
      $relationships[$relationship] = $handler->adminLabel();
merlinofchaos's avatar
merlinofchaos committed
950
951
    }

952
    foreach ($this->getHandlers('field') as $id => $handler) {
953
      if ($groupable_only && !$handler->useStringGroupBy()) {
954
955
956
        // Continue to next handler if it's not groupable.
        continue;
      }
merlinofchaos's avatar
merlinofchaos committed
957
958
959
960
      if ($label = $handler->label()) {
        $options[$id] = $label;
      }
      else {
961
        $options[$id] = $handler->adminLabel();
merlinofchaos's avatar
merlinofchaos committed
962
963
964
965
966
967
968
969
970
971
972
973
      }
      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.
   */
974
975
976
  public function setOption($option, $value) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->setOption($option, $value);
merlinofchaos's avatar
merlinofchaos committed
977
978
979
980
981
    }

    // 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.
982
    $this->display['display_options'][$option] = $value;
merlinofchaos's avatar
merlinofchaos committed
983
984
985
986
987
988
    return $this->options[$option] = $value;
  }

  /**
   * Set an option and force it to be an override.
   */
989
990
991
  public function overrideOption($option, $value) {
    $this->setOverride($option, FALSE);
    $this->setOption($option, $value);
merlinofchaos's avatar
merlinofchaos committed
992
993
994
995
996
997
  }

  /**
   * Because forms may be split up into sections, this provides
   * an easy URL to exactly the right section. Don't override this.
   */
998
  public function optionLink($text, $section, $class = '', $title = '') {
merlinofchaos's avatar
merlinofchaos committed
999
1000
    if (!empty($class)) {
      $text = '<span>' . $text . '</span>';
For faster browsing, not all history is shown. View entire blame