Pull merge.
[yaffs-website] / web / core / modules / views / src / Plugin / views / display / DisplayPluginBase.php
1 <?php
2
3 namespace Drupal\views\Plugin\views\display;
4
5 use Drupal\Component\Plugin\DependentPluginInterface;
6 use Drupal\Component\Utility\Html;
7 use Drupal\Component\Render\FormattableMarkup;
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
22 /**
23  * Base class for views display plugins.
24  */
25 abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInterface, DependentPluginInterface {
26   use PluginDependencyTrait;
27
28   /**
29    * The top object of a view.
30    *
31    * @var \Drupal\views\ViewExecutable
32    */
33   public $view = NULL;
34
35   /**
36    * An array of instantiated handlers used in this display.
37    *
38    * @var \Drupal\views\Plugin\views\ViewsHandlerInterface[]
39    */
40   public $handlers = [];
41
42   /**
43    * An array of instantiated plugins used in this display.
44    *
45    * @var \Drupal\views\Plugin\views\ViewsPluginInterface[]
46    */
47   protected $plugins = [];
48
49   /**
50    * Stores all available display extenders.
51    *
52    * @var \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase[]
53    */
54   protected $extenders = [];
55
56   /**
57    * {@inheritdoc}
58    */
59   protected $usesOptions = TRUE;
60
61   /**
62    * Stores the rendered output of the display.
63    *
64    * @see View::render
65    * @var string
66    */
67   public $output = NULL;
68
69   /**
70    * Whether the display allows the use of AJAX or not.
71    *
72    * @var bool
73    */
74   protected $usesAJAX = TRUE;
75
76   /**
77    * Whether the display allows the use of a pager or not.
78    *
79    * @var bool
80    */
81   protected $usesPager = TRUE;
82
83   /**
84    * Whether the display allows the use of a 'more' link or not.
85    *
86    * @var bool
87    */
88   protected $usesMore = TRUE;
89
90   /**
91    * Whether the display allows attachments.
92    *
93    * @var bool
94    *   TRUE if the display can use attachments, or FALSE otherwise.
95    */
96   protected $usesAttachments = FALSE;
97
98   /**
99    * Whether the display allows area plugins.
100    *
101    * @var bool
102    */
103   protected $usesAreas = TRUE;
104
105   /**
106    * Static cache for unpackOptions, but not if we are in the UI.
107    *
108    * @var array
109    */
110   protected static $unpackOptions = [];
111
112   /**
113    * The display information coming directly from the view entity.
114    *
115    * @see \Drupal\views\Entity\View::getDisplay()
116    *
117    * @todo \Drupal\views\Entity\View::duplicateDisplayAsType directly access it.
118    *
119    * @var array
120    */
121   public $display;
122
123   /**
124    * Constructs a new DisplayPluginBase object.
125    *
126    * Because DisplayPluginBase::initDisplay() takes the display configuration by
127    * reference and handles it differently than usual plugin configuration, pass
128    * an empty array of configuration to the parent. This prevents our
129    * configuration from being duplicated.
130    *
131    * @todo Replace DisplayPluginBase::$display with
132    *   DisplayPluginBase::$configuration to standardize with other plugins.
133    *
134    * @param array $configuration
135    *   A configuration array containing information about the plugin instance.
136    * @param string $plugin_id
137    *   The plugin_id for the plugin instance.
138    * @param mixed $plugin_definition
139    *   The plugin implementation definition.
140    */
141   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
142     parent::__construct([], $plugin_id, $plugin_definition);
143   }
144
145   /**
146    * {@inheritdoc}
147    */
148   public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
149     $this->view = $view;
150
151     // Load extenders as soon as possible.
152     $display['display_options'] += ['display_extenders' => []];
153     $this->extenders = [];
154     if ($extenders = Views::getEnabledDisplayExtenders()) {
155       $manager = Views::pluginManager('display_extender');
156       $display_extender_options = $display['display_options']['display_extenders'];
157       foreach ($extenders as $extender) {
158         /** @var \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase $plugin */
159         if ($plugin = $manager->createInstance($extender)) {
160           $extender_options = isset($display_extender_options[$plugin->getPluginId()]) ? $display_extender_options[$plugin->getPluginId()] : [];
161           $plugin->init($this->view, $this, $extender_options);
162           $this->extenders[$extender] = $plugin;
163         }
164       }
165     }
166
167     $this->setOptionDefaults($this->options, $this->defineOptions());
168     $this->display = &$display;
169
170     // Track changes that the user should know about.
171     $changed = FALSE;
172
173     if (!isset($options) && isset($display['display_options'])) {
174       $options = $display['display_options'];
175     }
176
177     if ($this->isDefaultDisplay() && isset($options['defaults'])) {
178       unset($options['defaults']);
179     }
180
181     $skip_cache = \Drupal::config('views.settings')->get('skip_cache');
182
183     if (empty($view->editing) || !$skip_cache) {
184       $cid = 'views:unpack_options:' . hash('sha256', serialize([$this->options, $options])) . ':' . \Drupal::languageManager()->getCurrentLanguage()->getId();
185       if (empty(static::$unpackOptions[$cid])) {
186         $cache = \Drupal::cache('data')->get($cid);
187         if (!empty($cache->data)) {
188           $this->options = $cache->data;
189         }
190         else {
191           $this->unpackOptions($this->options, $options);
192           \Drupal::cache('data')->set($cid, $this->options, Cache::PERMANENT, $this->view->storage->getCacheTags());
193         }
194         static::$unpackOptions[$cid] = $this->options;
195       }
196       else {
197         $this->options = static::$unpackOptions[$cid];
198       }
199     }
200     else {
201       $this->unpackOptions($this->options, $options);
202     }
203
204     // Mark the view as changed so the user has a chance to save it.
205     if ($changed) {
206       $this->view->changed = TRUE;
207     }
208   }
209
210   /**
211    * {@inheritdoc}
212    */
213   public function destroy() {
214     parent::destroy();
215
216     foreach ($this->handlers as $type => $handlers) {
217       foreach ($handlers as $id => $handler) {
218         if (is_object($handler)) {
219           $this->handlers[$type][$id]->destroy();
220         }
221       }
222     }
223
224     if (isset($this->default_display)) {
225       unset($this->default_display);
226     }
227
228     foreach ($this->extenders as $extender) {
229       $extender->destroy();
230     }
231   }
232
233   /**
234    * {@inheritdoc}
235    */
236   public function isDefaultDisplay() {
237     return FALSE;
238   }
239
240   /**
241    * {@inheritdoc}
242    */
243   public function usesExposed() {
244     if (!isset($this->has_exposed)) {
245       foreach ($this->handlers as $type => $value) {
246         foreach ($this->view->$type as $handler) {
247           if ($handler->canExpose() && $handler->isExposed()) {
248             // One is all we need; if we find it, return TRUE.
249             $this->has_exposed = TRUE;
250             return TRUE;
251           }
252         }
253       }
254       $pager = $this->getPlugin('pager');
255       if (isset($pager) && $pager->usesExposed()) {
256         $this->has_exposed = TRUE;
257         return TRUE;
258       }
259       $this->has_exposed = FALSE;
260     }
261
262     return $this->has_exposed;
263   }
264
265   /**
266    * {@inheritdoc}
267    */
268   public function displaysExposed() {
269     return TRUE;
270   }
271
272   /**
273    * {@inheritdoc}
274    */
275   public function usesAJAX() {
276     return $this->usesAJAX;
277   }
278
279   /**
280    * {@inheritdoc}
281    */
282   public function ajaxEnabled() {
283     if ($this->usesAJAX()) {
284       return $this->getOption('use_ajax');
285     }
286     return FALSE;
287   }
288
289   /**
290    * {@inheritdoc}
291    */
292   public function isEnabled() {
293     return (bool) $this->getOption('enabled');
294   }
295
296   /**
297    * {@inheritdoc}
298    */
299   public function usesPager() {
300     return $this->usesPager;
301   }
302
303   /**
304    * {@inheritdoc}
305    */
306   public function isPagerEnabled() {
307     if ($this->usesPager()) {
308       $pager = $this->getPlugin('pager');
309       if ($pager) {
310         return $pager->usePager();
311       }
312     }
313     return FALSE;
314   }
315
316   /**
317    * {@inheritdoc}
318    */
319   public function usesMore() {
320     return $this->usesMore;
321   }
322
323   /**
324    * {@inheritdoc}
325    */
326   public function isMoreEnabled() {
327     if ($this->usesMore()) {
328       return $this->getOption('use_more');
329     }
330     return FALSE;
331   }
332
333   /**
334    * {@inheritdoc}
335    */
336   public function useGroupBy() {
337     return $this->getOption('group_by');
338   }
339
340   /**
341    * {@inheritdoc}
342    */
343   public function useMoreAlways() {
344     if ($this->usesMore()) {
345       return $this->getOption('use_more_always');
346     }
347     return FALSE;
348   }
349
350   /**
351    * {@inheritdoc}
352    */
353   public function useMoreText() {
354     if ($this->usesMore()) {
355       return $this->getOption('use_more_text');
356     }
357     return FALSE;
358   }
359
360   /**
361    * {@inheritdoc}
362    */
363   public function acceptAttachments() {
364     // To be able to accept attachments this display have to be able to use
365     // attachments but at the same time, you cannot attach a display to itself.
366     if (!$this->usesAttachments() || ($this->definition['id'] == $this->view->current_display)) {
367       return FALSE;
368     }
369
370     if (!empty($this->view->argument) && $this->getOption('hide_attachment_summary')) {
371       foreach ($this->view->argument as $argument) {
372         if ($argument->needsStylePlugin() && empty($argument->argument_validated)) {
373           return FALSE;
374         }
375       }
376     }
377
378     return TRUE;
379   }
380
381   /**
382    * {@inheritdoc}
383    */
384   public function usesAttachments() {
385     return $this->usesAttachments;
386   }
387
388   /**
389    * {@inheritdoc}
390    */
391   public function usesAreas() {
392     return $this->usesAreas;
393   }
394
395   /**
396    * {@inheritdoc}
397    */
398   public function attachTo(ViewExecutable $view, $display_id, array &$build) {}
399
400   /**
401    * {@inheritdoc}
402    */
403   public function defaultableSections($section = NULL) {
404     $sections = [
405       'access' => ['access'],
406       'cache' => ['cache'],
407       'title' => ['title'],
408       'css_class' => ['css_class'],
409       'use_ajax' => ['use_ajax'],
410       'hide_attachment_summary' => ['hide_attachment_summary'],
411       'show_admin_links' => ['show_admin_links'],
412       'group_by' => ['group_by'],
413       'query' => ['query'],
414       'use_more' => ['use_more', 'use_more_always', 'use_more_text'],
415       'use_more_always' => ['use_more', 'use_more_always', 'use_more_text'],
416       'use_more_text' => ['use_more', 'use_more_always', 'use_more_text'],
417       'link_display' => ['link_display', 'link_url'],
418
419       // Force these to cascade properly.
420       'style' => ['style', 'row'],
421       'row' => ['style', 'row'],
422
423       'pager' => ['pager'],
424
425       'exposed_form' => ['exposed_form'],
426
427       // These sections are special.
428       'header' => ['header'],
429       'footer' => ['footer'],
430       'empty' => ['empty'],
431       'relationships' => ['relationships'],
432       'fields' => ['fields'],
433       'sorts' => ['sorts'],
434       'arguments' => ['arguments'],
435       'filters' => ['filters', 'filter_groups'],
436       'filter_groups' => ['filters', 'filter_groups'],
437     ];
438
439     // If the display cannot use a pager, then we cannot default it.
440     if (!$this->usesPager()) {
441       unset($sections['pager']);
442       unset($sections['items_per_page']);
443     }
444
445     foreach ($this->extenders as $extender) {
446       $extender->defaultableSections($sections, $section);
447     }
448
449     if ($section) {
450       if (!empty($sections[$section])) {
451         return $sections[$section];
452       }
453     }
454     else {
455       return $sections;
456     }
457   }
458
459   protected function defineOptions() {
460     $options = [
461       'defaults' => [
462         'default' => [
463           'access' => TRUE,
464           'cache' => TRUE,
465           'query' => TRUE,
466           'title' => TRUE,
467           'css_class' => TRUE,
468
469           'display_description' => FALSE,
470           'use_ajax' => TRUE,
471           'hide_attachment_summary' => TRUE,
472           'show_admin_links' => TRUE,
473           'pager' => TRUE,
474           'use_more' => TRUE,
475           'use_more_always' => TRUE,
476           'use_more_text' => TRUE,
477           'exposed_form' => TRUE,
478
479           'link_display' => TRUE,
480           'link_url' => TRUE,
481           'group_by' => TRUE,
482
483           'style' => TRUE,
484           'row' => TRUE,
485
486           'header' => TRUE,
487           'footer' => TRUE,
488           'empty' => TRUE,
489
490           'relationships' => TRUE,
491           'fields' => TRUE,
492           'sorts' => TRUE,
493           'arguments' => TRUE,
494           'filters' => TRUE,
495           'filter_groups' => TRUE,
496         ],
497       ],
498
499       'title' => [
500         'default' => '',
501       ],
502       'enabled' => [
503         'default' => TRUE,
504       ],
505       'display_comment' => [
506         'default' => '',
507       ],
508       'css_class' => [
509         'default' => '',
510       ],
511       'display_description' => [
512         'default' => '',
513       ],
514       'use_ajax' => [
515         'default' => FALSE,
516       ],
517       'hide_attachment_summary' => [
518         'default' => FALSE,
519       ],
520       'show_admin_links' => [
521         'default' => TRUE,
522       ],
523       'use_more' => [
524         'default' => FALSE,
525       ],
526       'use_more_always' => [
527         'default' => TRUE,
528       ],
529       'use_more_text' => [
530         'default' => 'more',
531       ],
532       'link_display' => [
533         'default' => '',
534       ],
535       'link_url' => [
536         'default' => '',
537       ],
538       'group_by' => [
539         'default' => FALSE,
540       ],
541       'rendering_language' => [
542         'default' => '***LANGUAGE_entity_translation***',
543       ],
544
545       // These types are all plugins that can have individual settings
546       // and therefore need special handling.
547       'access' => [
548         'contains' => [
549           'type' => ['default' => 'none'],
550           'options' => ['default' => []],
551         ],
552         'merge_defaults' => [$this, 'mergePlugin'],
553       ],
554       'cache' => [
555         'contains' => [
556           'type' => ['default' => 'tag'],
557           'options' => ['default' => []],
558         ],
559         'merge_defaults' => [$this, 'mergePlugin'],
560       ],
561       'query' => [
562         'contains' => [
563           'type' => ['default' => 'views_query'],
564           'options' => ['default' => []],
565          ],
566         'merge_defaults' => [$this, 'mergePlugin'],
567       ],
568       'exposed_form' => [
569         'contains' => [
570           'type' => ['default' => 'basic'],
571           'options' => ['default' => []],
572          ],
573         'merge_defaults' => [$this, 'mergePlugin'],
574       ],
575       'pager' => [
576         'contains' => [
577           'type' => ['default' => 'mini'],
578           'options' => ['default' => []],
579          ],
580         'merge_defaults' => [$this, 'mergePlugin'],
581       ],
582       'style' => [
583         'contains' => [
584           'type' => ['default' => 'default'],
585           'options' => ['default' => []],
586         ],
587         'merge_defaults' => [$this, 'mergePlugin'],
588       ],
589       'row' => [
590         'contains' => [
591           'type' => ['default' => 'fields'],
592           'options' => ['default' => []],
593         ],
594         'merge_defaults' => [$this, 'mergePlugin'],
595       ],
596
597       'exposed_block' => [
598         'default' => FALSE,
599       ],
600
601       'header' => [
602         'default' => [],
603         'merge_defaults' => [$this, 'mergeHandler'],
604       ],
605       'footer' => [
606         'default' => [],
607         'merge_defaults' => [$this, 'mergeHandler'],
608       ],
609       'empty' => [
610         'default' => [],
611         'merge_defaults' => [$this, 'mergeHandler'],
612       ],
613
614       // We want these to export last.
615       // These are the 5 handler types.
616       'relationships' => [
617         'default' => [],
618         'merge_defaults' => [$this, 'mergeHandler'],
619       ],
620       'fields' => [
621         'default' => [],
622         'merge_defaults' => [$this, 'mergeHandler'],
623       ],
624       'sorts' => [
625         'default' => [],
626         'merge_defaults' => [$this, 'mergeHandler'],
627       ],
628       'arguments' => [
629         'default' => [],
630         'merge_defaults' => [$this, 'mergeHandler'],
631       ],
632       'filter_groups' => [
633         'contains' => [
634           'operator' => ['default' => 'AND'],
635           'groups' => ['default' => [1 => 'AND']],
636         ],
637       ],
638       'filters' => [
639         'default' => [],
640       ],
641     ];
642
643     if (!$this->usesPager()) {
644       $options['defaults']['default']['pager'] = FALSE;
645       $options['pager']['contains']['type']['default'] = 'some';
646     }
647
648     if ($this->isDefaultDisplay()) {
649       unset($options['defaults']);
650     }
651
652     $options['display_extenders'] = ['default' => []];
653     // First allow display extenders to provide new options.
654     foreach ($this->extenders as $extender_id => $extender) {
655       $options['display_extenders']['contains'][$extender_id]['contains'] = $extender->defineOptions();
656     }
657
658     // Then allow display extenders to alter existing default values.
659     foreach ($this->extenders as $extender) {
660       $extender->defineOptionsAlter($options);
661     }
662
663     return $options;
664   }
665
666   /**
667    * {@inheritdoc}
668    */
669   public function hasPath() {
670     return FALSE;
671   }
672
673   /**
674    * {@inheritdoc}
675    */
676   public function usesLinkDisplay() {
677     return !$this->hasPath();
678   }
679
680   /**
681    * {@inheritdoc}
682    */
683   public function usesExposedFormInBlock() {
684     return $this->hasPath();
685   }
686
687   /**
688    * {@inheritdoc}
689    */
690   public function getAttachedDisplays() {
691     $current_display_id = $this->display['id'];
692     $attached_displays = [];
693
694     // Go through all displays and search displays which link to this one.
695     foreach ($this->view->storage->get('display') as $display_id => $display) {
696       if (isset($display['display_options']['displays'])) {
697         $displays = $display['display_options']['displays'];
698         if (isset($displays[$current_display_id])) {
699           $attached_displays[] = $display_id;
700         }
701       }
702     }
703
704     return $attached_displays;
705   }
706
707   /**
708    * {@inheritdoc}
709    */
710   public function getLinkDisplay() {
711     $display_id = $this->getOption('link_display');
712     // If unknown, pick the first one.
713     if (empty($display_id) || !$this->view->displayHandlers->has($display_id)) {
714       foreach ($this->view->displayHandlers as $display_id => $display) {
715         if (!empty($display) && $display->hasPath()) {
716           return $display_id;
717         }
718       }
719     }
720     else {
721       return $display_id;
722     }
723     // Fall-through returns NULL.
724   }
725
726   /**
727    * {@inheritdoc}
728    */
729   public function getPath() {
730     if ($this->hasPath()) {
731       return $this->getOption('path');
732     }
733
734     $display_id = $this->getLinkDisplay();
735     if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) {
736       return $this->view->displayHandlers->get($display_id)->getPath();
737     }
738   }
739
740   /**
741    * {@inheritdoc}
742    */
743   public function getRoutedDisplay() {
744     // If this display has a route, return this display.
745     if ($this instanceof DisplayRouterInterface) {
746       return $this;
747     }
748
749     // If the display does not have a route (e.g. a block display), get the
750     // route for the linked display.
751     $display_id = $this->getLinkDisplay();
752     if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) {
753       return $this->view->displayHandlers->get($display_id)->getRoutedDisplay();
754     }
755
756     // No routed display exists, so return NULL
757     return NULL;
758   }
759
760   /**
761    * {@inheritdoc}
762    */
763   public function getUrl() {
764     return $this->view->getUrl(NULL, $this->display['id']);
765   }
766
767   /**
768    * {@inheritdoc}
769    */
770   public function isDefaulted($option) {
771     return !$this->isDefaultDisplay() && !empty($this->default_display) && !empty($this->options['defaults'][$option]);
772   }
773
774   /**
775    * {@inheritdoc}
776    */
777   public function getOption($option) {
778     if ($this->isDefaulted($option)) {
779       return $this->default_display->getOption($option);
780     }
781
782     if (isset($this->options[$option]) || array_key_exists($option, $this->options)) {
783       return $this->options[$option];
784     }
785   }
786
787   /**
788    * {@inheritdoc}
789    */
790   public function usesFields() {
791     return $this->getPlugin('style')->usesFields();
792   }
793
794   /**
795    * {@inheritdoc}
796    */
797   public function getPlugin($type) {
798     // Look up the plugin name to use for this instance.
799     $options = $this->getOption($type);
800
801     // Return now if no options have been loaded.
802     if (empty($options) || !isset($options['type'])) {
803       return;
804     }
805
806     // Query plugins allow specifying a specific query class per base table.
807     if ($type == 'query') {
808       $views_data = Views::viewsData()->get($this->view->storage->get('base_table'));
809       $name = isset($views_data['table']['base']['query_id']) ? $views_data['table']['base']['query_id'] : 'views_query';
810     }
811     else {
812       $name = $options['type'];
813     }
814
815     // Plugin instances are stored on the display for re-use.
816     if (!isset($this->plugins[$type][$name])) {
817       $plugin = Views::pluginManager($type)->createInstance($name);
818
819       // Initialize the plugin.
820       $plugin->init($this->view, $this, $options['options']);
821
822       $this->plugins[$type][$name] = $plugin;
823     }
824
825     return $this->plugins[$type][$name];
826   }
827
828   /**
829    * {@inheritdoc}
830    */
831   public function &getHandler($type, $id) {
832     if (!isset($this->handlers[$type])) {
833       $this->getHandlers($type);
834     }
835
836     if (isset($this->handlers[$type][$id])) {
837       return $this->handlers[$type][$id];
838     }
839
840     // So we can return a reference.
841     $null = NULL;
842     return $null;
843   }
844
845   /**
846    * {@inheritdoc}
847    */
848   public function &getHandlers($type) {
849     if (!isset($this->handlers[$type])) {
850       $this->handlers[$type] = [];
851       $types = ViewExecutable::getHandlerTypes();
852       $plural = $types[$type]['plural'];
853
854       // Cast to an array so that if the display does not have any handlers of
855       // this type there is no PHP error.
856       foreach ((array) $this->getOption($plural) as $id => $info) {
857         // If this is during form submission and there are temporary options
858         // which can only appear if the view is in the edit cache, use those
859         // options instead. This is used for AJAX multi-step stuff.
860         if ($this->view->getRequest()->request->get('form_id') && isset($this->view->temporary_options[$type][$id])) {
861           $info = $this->view->temporary_options[$type][$id];
862         }
863
864         if ($info['id'] != $id) {
865           $info['id'] = $id;
866         }
867
868         // If aggregation is on, the group type might override the actual
869         // handler that is in use. This piece of code checks that and,
870         // if necessary, sets the override handler.
871         $override = NULL;
872         if ($this->useGroupBy() && !empty($info['group_type'])) {
873           if (empty($this->view->query)) {
874             $this->view->initQuery();
875           }
876           $aggregate = $this->view->query->getAggregationInfo();
877           if (!empty($aggregate[$info['group_type']]['handler'][$type])) {
878             $override = $aggregate[$info['group_type']]['handler'][$type];
879           }
880         }
881
882         if (!empty($types[$type]['type'])) {
883           $handler_type = $types[$type]['type'];
884         }
885         else {
886           $handler_type = $type;
887         }
888
889         if ($handler = Views::handlerManager($handler_type)->getHandler($info, $override)) {
890           // Special override for area types so they know where they come from.
891           if ($handler instanceof AreaPluginBase) {
892             $handler->areaType = $type;
893           }
894
895           $handler->init($this->view, $this, $info);
896           $this->handlers[$type][$id] = &$handler;
897         }
898
899         // Prevent reference problems.
900         unset($handler);
901       }
902     }
903
904     return $this->handlers[$type];
905   }
906
907   /**
908    * Gets all the handlers used by the display.
909    *
910    * @param bool $only_overrides
911    *   Whether to include only overridden handlers.
912    *
913    * @return \Drupal\views\Plugin\views\ViewsHandlerInterface[]
914    */
915   protected function getAllHandlers($only_overrides = FALSE) {
916     $handler_types = Views::getHandlerTypes();
917     $handlers = [];
918     // Collect all dependencies of all handlers.
919     foreach ($handler_types as $handler_type => $handler_type_info) {
920       if ($only_overrides && $this->isDefaulted($handler_type_info['plural'])) {
921         continue;
922       }
923       $handlers = array_merge($handlers, array_values($this->getHandlers($handler_type)));
924     }
925     return $handlers;
926   }
927
928   /**
929    * Gets all the plugins used by the display.
930    *
931    * @param bool $only_overrides
932    *   Whether to include only overridden plugins.
933    *
934    * @return \Drupal\views\Plugin\views\ViewsPluginInterface[]
935    */
936   protected function getAllPlugins($only_overrides = FALSE) {
937     $plugins = [];
938     // Collect all dependencies of plugins.
939     foreach (Views::getPluginTypes('plugin') as $plugin_type) {
940       $plugin = $this->getPlugin($plugin_type);
941       if (!$plugin) {
942         continue;
943       }
944       if ($only_overrides && $this->isDefaulted($plugin_type)) {
945         continue;
946       }
947       $plugins[] = $plugin;
948     }
949     return $plugins;
950   }
951
952   /**
953    * {@inheritdoc}
954    */
955   public function calculateDependencies() {
956     $this->dependencies = parent::calculateDependencies();
957     // Collect all the dependencies of handlers and plugins. Only calculate
958     // their dependencies if they are configured by this display.
959     $plugins = array_merge($this->getAllHandlers(TRUE), $this->getAllPlugins(TRUE));
960     array_walk($plugins, [$this, 'calculatePluginDependencies']);
961
962     return $this->dependencies;
963   }
964
965   /**
966    * {@inheritdoc}
967    */
968   public function getFieldLabels($groupable_only = FALSE) {
969     $options = [];
970     foreach ($this->getHandlers('relationship') as $relationship => $handler) {
971       $relationships[$relationship] = $handler->adminLabel();
972     }
973
974     foreach ($this->getHandlers('field') as $id => $handler) {
975       if ($groupable_only && !$handler->useStringGroupBy()) {
976         // Continue to next handler if it's not groupable.
977         continue;
978       }
979       if ($label = $handler->label()) {
980         $options[$id] = $label;
981       }
982       else {
983         $options[$id] = $handler->adminLabel();
984       }
985       if (!empty($handler->options['relationship']) && !empty($relationships[$handler->options['relationship']])) {
986         $options[$id] = '(' . $relationships[$handler->options['relationship']] . ') ' . $options[$id];
987       }
988     }
989     return $options;
990   }
991
992   /**
993    * {@inheritdoc}
994    */
995   public function setOption($option, $value) {
996     if ($this->isDefaulted($option)) {
997       return $this->default_display->setOption($option, $value);
998     }
999
1000     // Set this in two places: On the handler where we'll notice it
1001     // but also on the display object so it gets saved. This should
1002     // only be a temporary fix.
1003     $this->display['display_options'][$option] = $value;
1004     return $this->options[$option] = $value;
1005   }
1006
1007   /**
1008    * {@inheritdoc}
1009    */
1010   public function overrideOption($option, $value) {
1011     $this->setOverride($option, FALSE);
1012     $this->setOption($option, $value);
1013   }
1014
1015   /**
1016    * {@inheritdoc}
1017    */
1018   public function optionLink($text, $section, $class = '', $title = '') {
1019     if (!trim($text)) {
1020       $text = $this->t('Broken field');
1021     }
1022
1023     if (!empty($class)) {
1024       $text = new FormattableMarkup('<span>@text</span>', ['@text' => $text]);
1025     }
1026
1027     if (empty($title)) {
1028       $title = $text;
1029     }
1030
1031     return \Drupal::l($text, new Url('views_ui.form_display', [
1032         'js' => 'nojs',
1033         'view' => $this->view->storage->id(),
1034         'display_id' => $this->display['id'],
1035         'type' => $section,
1036       ], [
1037         'attributes' => [
1038           'class' => ['views-ajax-link', $class],
1039           'title' => $title,
1040           'id' => Html::getUniqueId('views-' . $this->display['id'] . '-' . $section),
1041         ],
1042     ]));
1043   }
1044
1045   /**
1046    * {@inheritdoc}
1047    */
1048   public function getArgumentsTokens() {
1049     $tokens = [];
1050     if (!empty($this->view->build_info['substitutions'])) {
1051       $tokens = $this->view->build_info['substitutions'];
1052     }
1053
1054     return $tokens;
1055   }
1056
1057   /**
1058    * {@inheritdoc}
1059    */
1060   public function optionsSummary(&$categories, &$options) {
1061     $categories = [
1062       'title' => [
1063         'title' => $this->t('Title'),
1064         'column' => 'first',
1065       ],
1066       'format' => [
1067         'title' => $this->t('Format'),
1068         'column' => 'first',
1069       ],
1070       'filters' => [
1071         'title' => $this->t('Filters'),
1072         'column' => 'first',
1073       ],
1074       'fields' => [
1075         'title' => $this->t('Fields'),
1076         'column' => 'first',
1077       ],
1078       'pager' => [
1079         'title' => $this->t('Pager'),
1080         'column' => 'second',
1081       ],
1082       'language' => [
1083         'title' => $this->t('Language'),
1084         'column' => 'second',
1085       ],
1086       'exposed' => [
1087         'title' => $this->t('Exposed form'),
1088         'column' => 'third',
1089         'build' => [
1090           '#weight' => 1,
1091         ],
1092       ],
1093       'access' => [
1094         'title' => '',
1095         'column' => 'second',
1096         'build' => [
1097           '#weight' => -5,
1098         ],
1099       ],
1100       'other' => [
1101         'title' => $this->t('Other'),
1102         'column' => 'third',
1103         'build' => [
1104           '#weight' => 2,
1105         ],
1106       ],
1107     ];
1108
1109     if ($this->display['id'] != 'default') {
1110       $options['display_id'] = [
1111         'category' => 'other',
1112         'title' => $this->t('Machine Name'),
1113         'value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'],
1114         'desc' => $this->t('Change the machine name of this display.'),
1115       ];
1116     }
1117
1118     $display_comment = views_ui_truncate($this->getOption('display_comment'), 80);
1119     $options['display_comment'] = [
1120       'category' => 'other',
1121       'title' => $this->t('Administrative comment'),
1122       'value' => !empty($display_comment) ? $display_comment : $this->t('None'),
1123       'desc' => $this->t('Comment or document this display.'),
1124     ];
1125
1126     $title = strip_tags($this->getOption('title'));
1127     if (!$title) {
1128       $title = $this->t('None');
1129     }
1130
1131     $options['title'] = [
1132       'category' => 'title',
1133       'title' => $this->t('Title'),
1134       'value' => views_ui_truncate($title, 32),
1135       'desc' => $this->t('Change the title that this display will use.'),
1136     ];
1137
1138     $style_plugin_instance = $this->getPlugin('style');
1139     $style_summary = empty($style_plugin_instance->definition['title']) ? $this->t('Missing style plugin') : $style_plugin_instance->summaryTitle();
1140     $style_title = empty($style_plugin_instance->definition['title']) ? $this->t('Missing style plugin') : $style_plugin_instance->pluginTitle();
1141
1142     $options['style'] = [
1143       'category' => 'format',
1144       'title' => $this->t('Format'),
1145       'value' => $style_title,
1146       'setting' => $style_summary,
1147       'desc' => $this->t('Change the way content is formatted.'),
1148     ];
1149
1150     // This adds a 'Settings' link to the style_options setting if the style has
1151     // options.
1152     if ($style_plugin_instance->usesOptions()) {
1153       $options['style']['links']['style_options'] = $this->t('Change settings for this format');
1154     }
1155
1156     if ($style_plugin_instance->usesRowPlugin()) {
1157       $row_plugin_instance = $this->getPlugin('row');
1158       $row_summary = empty($row_plugin_instance->definition['title']) ? $this->t('Missing row plugin') : $row_plugin_instance->summaryTitle();
1159       $row_title = empty($row_plugin_instance->definition['title']) ? $this->t('Missing row plugin') : $row_plugin_instance->pluginTitle();
1160
1161       $options['row'] = [
1162         'category' => 'format',
1163         'title' => $this->t('Show'),
1164         'value' => $row_title,
1165         'setting' => $row_summary,
1166         'desc' => $this->t('Change the way each row in the view is styled.'),
1167       ];
1168       // This adds a 'Settings' link to the row_options setting if the row style
1169       // has options.
1170       if ($row_plugin_instance->usesOptions()) {
1171         $options['row']['links']['row_options'] = $this->t('Change settings for this style');
1172       }
1173     }
1174     if ($this->usesAJAX()) {
1175       $options['use_ajax'] = [
1176         'category' => 'other',
1177         'title' => $this->t('Use AJAX'),
1178         'value' => $this->getOption('use_ajax') ? $this->t('Yes') : $this->t('No'),
1179         'desc' => $this->t('Change whether or not this display will use AJAX.'),
1180       ];
1181     }
1182     if ($this->usesAttachments()) {
1183       $options['hide_attachment_summary'] = [
1184         'category' => 'other',
1185         'title' => $this->t('Hide attachments in summary'),
1186         'value' => $this->getOption('hide_attachment_summary') ? $this->t('Yes') : $this->t('No'),
1187         'desc' => $this->t('Change whether or not to display attachments when displaying a contextual filter summary.'),
1188       ];
1189     }
1190     if (!isset($this->definition['contextual links locations']) || !empty($this->definition['contextual links locations'])) {
1191       $options['show_admin_links'] = [
1192         'category' => 'other',
1193         'title' => $this->t('Contextual links'),
1194         'value' => $this->getOption('show_admin_links') ? $this->t('Shown') : $this->t('Hidden'),
1195         'desc' => $this->t('Change whether or not to display contextual links for this view.'),
1196       ];
1197     }
1198
1199     $pager_plugin = $this->getPlugin('pager');
1200     if (!$pager_plugin) {
1201       // Default to the no pager plugin.
1202       $pager_plugin = Views::pluginManager('pager')->createInstance('none');
1203     }
1204
1205     $pager_str = $pager_plugin->summaryTitle();
1206
1207     $options['pager'] = [
1208       'category' => 'pager',
1209       'title' => $this->t('Use pager'),
1210       'value' => $pager_plugin->pluginTitle(),
1211       'setting' => $pager_str,
1212       'desc' => $this->t("Change this display's pager setting."),
1213     ];
1214
1215     // If pagers aren't allowed, change the text of the item.
1216     if (!$this->usesPager()) {
1217       $options['pager']['title'] = $this->t('Items to display');
1218     }
1219
1220     if ($pager_plugin->usesOptions()) {
1221       $options['pager']['links']['pager_options'] = $this->t('Change settings for this pager type.');
1222     }
1223
1224     if ($this->usesMore()) {
1225       $options['use_more'] = [
1226         'category' => 'pager',
1227         'title' => $this->t('More link'),
1228         'value' => $this->getOption('use_more') ? $this->t('Yes') : $this->t('No'),
1229         'desc' => $this->t('Specify whether this display will provide a "more" link.'),
1230       ];
1231     }
1232
1233     $this->view->initQuery();
1234     if ($this->view->query->getAggregationInfo()) {
1235       $options['group_by'] = [
1236         'category' => 'other',
1237         'title' => $this->t('Use aggregation'),
1238         'value' => $this->getOption('group_by') ? $this->t('Yes') : $this->t('No'),
1239         'desc' => $this->t('Allow grouping and aggregation (calculation) of fields.'),
1240       ];
1241     }
1242
1243     $options['query'] = [
1244       'category' => 'other',
1245       'title' => $this->t('Query settings'),
1246       'value' => $this->t('Settings'),
1247       'desc' => $this->t('Allow to set some advanced settings for the query plugin'),
1248     ];
1249
1250     if (\Drupal::languageManager()->isMultilingual() && $this->isBaseTableTranslatable()) {
1251       $rendering_language_options = $this->buildRenderingLanguageOptions();
1252       $options['rendering_language'] = [
1253         'category' => 'language',
1254         'title' => $this->t('Rendering Language'),
1255         'value' => $rendering_language_options[$this->getOption('rendering_language')],
1256         'desc' => $this->t('All content that supports translations will be displayed in the selected language.'),
1257       ];
1258     }
1259
1260     $access_plugin = $this->getPlugin('access');
1261     if (!$access_plugin) {
1262       // Default to the no access control plugin.
1263       $access_plugin = Views::pluginManager('access')->createInstance('none');
1264     }
1265
1266     $access_str = $access_plugin->summaryTitle();
1267
1268     $options['access'] = [
1269       'category' => 'access',
1270       'title' => $this->t('Access'),
1271       'value' => $access_plugin->pluginTitle(),
1272       'setting' => $access_str,
1273       'desc' => $this->t('Specify access control type for this display.'),
1274     ];
1275
1276     if ($access_plugin->usesOptions()) {
1277       $options['access']['links']['access_options'] = $this->t('Change settings for this access type.');
1278     }
1279
1280     $cache_plugin = $this->getPlugin('cache');
1281     if (!$cache_plugin) {
1282       // Default to the no cache control plugin.
1283       $cache_plugin = Views::pluginManager('cache')->createInstance('none');
1284     }
1285
1286     $cache_str = $cache_plugin->summaryTitle();
1287
1288     $options['cache'] = [
1289       'category' => 'other',
1290       'title' => $this->t('Caching'),
1291       'value' => $cache_plugin->pluginTitle(),
1292       'setting' => $cache_str,
1293       'desc' => $this->t('Specify caching type for this display.'),
1294     ];
1295
1296     if ($cache_plugin->usesOptions()) {
1297       $options['cache']['links']['cache_options'] = $this->t('Change settings for this caching type.');
1298     }
1299
1300     if ($access_plugin->usesOptions()) {
1301       $options['access']['links']['access_options'] = $this->t('Change settings for this access type.');
1302     }
1303
1304     if ($this->usesLinkDisplay()) {
1305       $link_display_option = $this->getOption('link_display');
1306       $link_display = $this->t('None');
1307
1308       if ($link_display_option == 'custom_url') {
1309         $link_display = $this->t('Custom URL');
1310       }
1311       elseif (!empty($link_display_option)) {
1312         $display_id = $this->getLinkDisplay();
1313         $displays = $this->view->storage->get('display');
1314         if (!empty($displays[$display_id])) {
1315           $link_display = $displays[$display_id]['display_title'];
1316         }
1317       }
1318
1319       $options['link_display'] = [
1320         'category' => 'pager',
1321         'title' => $this->t('Link display'),
1322         'value' => $link_display,
1323         'desc' => $this->t('Specify which display or custom URL this display will link to.'),
1324       ];
1325     }
1326
1327     if ($this->usesExposedFormInBlock()) {
1328       $options['exposed_block'] = [
1329         'category' => 'exposed',
1330         'title' => $this->t('Exposed form in block'),
1331         'value' => $this->getOption('exposed_block') ? $this->t('Yes') : $this->t('No'),
1332         'desc' => $this->t('Allow the exposed form to appear in a block instead of the view.'),
1333       ];
1334     }
1335
1336     /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form_plugin */
1337     $exposed_form_plugin = $this->getPlugin('exposed_form');
1338     if (!$exposed_form_plugin) {
1339       // Default to the no cache control plugin.
1340       $exposed_form_plugin = Views::pluginManager('exposed_form')->createInstance('basic');
1341     }
1342
1343     $exposed_form_str = $exposed_form_plugin->summaryTitle();
1344
1345     $options['exposed_form'] = [
1346       'category' => 'exposed',
1347       'title' => $this->t('Exposed form style'),
1348       'value' => $exposed_form_plugin->pluginTitle(),
1349       'setting' => $exposed_form_str,
1350       'desc' => $this->t('Select the kind of exposed filter to use.'),
1351     ];
1352
1353     if ($exposed_form_plugin->usesOptions()) {
1354       $options['exposed_form']['links']['exposed_form_options'] = $this->t('Exposed form settings for this exposed form style.');
1355     }
1356
1357     $css_class = trim($this->getOption('css_class'));
1358     if (!$css_class) {
1359       $css_class = $this->t('None');
1360     }
1361
1362     $options['css_class'] = [
1363       'category' => 'other',
1364       'title' => $this->t('CSS class'),
1365       'value' => $css_class,
1366       'desc' => $this->t('Change the CSS class name(s) that will be added to this display.'),
1367     ];
1368
1369     foreach ($this->extenders as $extender) {
1370       $extender->optionsSummary($categories, $options);
1371     }
1372   }
1373
1374   /**
1375    * {@inheritdoc}
1376    */
1377   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
1378     parent::buildOptionsForm($form, $form_state);
1379     $section = $form_state->get('section');
1380     if ($this->defaultableSections($section)) {
1381       views_ui_standard_display_dropdown($form, $form_state, $section);
1382     }
1383     $form['#title'] = $this->display['display_title'] . ': ';
1384
1385     // Set the 'section' to highlight on the form.
1386     // If it's the item we're looking at is pulling from the default display,
1387     // reflect that. Don't use is_defaulted since we want it to show up even
1388     // on the default display.
1389     if (!empty($this->options['defaults'][$section])) {
1390       $form['#section'] = 'default-' . $section;
1391     }
1392     else {
1393       $form['#section'] = $this->display['id'] . '-' . $section;
1394     }
1395
1396     switch ($section) {
1397       case 'display_id':
1398         $form['#title'] .= $this->t('The machine name of this display');
1399         $form['display_id'] = [
1400           '#type' => 'textfield',
1401           '#title' => $this->t('Machine name of the display'),
1402           '#default_value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'],
1403           '#required' => TRUE,
1404           '#size' => 64,
1405         ];
1406         break;
1407       case 'display_title':
1408         $form['#title'] .= $this->t('The name and the description of this display');
1409         $form['display_title'] = [
1410           '#title' => $this->t('Administrative name'),
1411           '#type' => 'textfield',
1412           '#default_value' => $this->display['display_title'],
1413         ];
1414         $form['display_description'] = [
1415           '#title' => $this->t('Administrative description'),
1416           '#type' => 'textfield',
1417           '#default_value' => $this->getOption('display_description'),
1418         ];
1419         break;
1420       case 'display_comment':
1421         $form['#title'] .= $this->t('Administrative comment');
1422         $form['display_comment'] = [
1423           '#type' => 'textarea',
1424           '#title' => $this->t('Administrative comment'),
1425           '#description' => $this->t('This description will only be seen within the administrative interface and can be used to document this display.'),
1426           '#default_value' => $this->getOption('display_comment'),
1427         ];
1428         break;
1429       case 'title':
1430         $form['#title'] .= $this->t('The title of this view');
1431         $form['title'] = [
1432           '#title' => $this->t('Title'),
1433           '#type' => 'textfield',
1434           '#description' => $this->t('This title will be displayed with the view, wherever titles are normally displayed; i.e, as the page title, block title, etc.'),
1435           '#default_value' => $this->getOption('title'),
1436           '#maxlength' => 255,
1437         ];
1438         break;
1439       case 'css_class':
1440         $form['#title'] .= $this->t('CSS class');
1441         $form['css_class'] = [
1442           '#type' => 'textfield',
1443           '#title' => $this->t('CSS class name(s)'),
1444           '#description' => $this->t('Separate multiple classes by spaces.'),
1445           '#default_value' => $this->getOption('css_class'),
1446         ];
1447         break;
1448       case 'use_ajax':
1449         $form['#title'] .= $this->t('AJAX');
1450         $form['use_ajax'] = [
1451           '#description' => $this->t('Options such as paging, table sorting, and exposed filters will not initiate a page refresh.'),
1452           '#type' => 'checkbox',
1453           '#title' => $this->t('Use AJAX'),
1454           '#default_value' => $this->getOption('use_ajax') ? 1 : 0,
1455         ];
1456         break;
1457       case 'hide_attachment_summary':
1458         $form['#title'] .= $this->t('Hide attachments when displaying a contextual filter summary');
1459         $form['hide_attachment_summary'] = [
1460           '#type' => 'checkbox',
1461           '#title' => $this->t('Hide attachments in summary'),
1462           '#default_value' => $this->getOption('hide_attachment_summary') ? 1 : 0,
1463         ];
1464         break;
1465       case 'show_admin_links':
1466         $form['#title'] .= $this->t('Show contextual links on this view.');
1467         $form['show_admin_links'] = [
1468           '#type' => 'checkbox',
1469           '#title' => $this->t('Show contextual links'),
1470           '#default_value' => $this->getOption('show_admin_links'),
1471         ];
1472         break;
1473       case 'use_more':
1474         $form['#title'] .= $this->t('Add a more link to the bottom of the display.');
1475         $form['use_more'] = [
1476           '#type' => 'checkbox',
1477           '#title' => $this->t('Create more link'),
1478           '#description' => $this->t("This will add a more link to the bottom of this view, which will link to the page view. If you have more than one page view, the link will point to the display specified in 'Link display' section under pager. You can override the URL at the link display setting."),
1479           '#default_value' => $this->getOption('use_more'),
1480         ];
1481         $form['use_more_always'] = [
1482           '#type' => 'checkbox',
1483           '#title' => $this->t('Always display the more link'),
1484           '#description' => $this->t('Check this to display the more link even if there are no more items to display.'),
1485           '#default_value' => $this->getOption('use_more_always'),
1486           '#states' => [
1487             'visible' => [
1488               ':input[name="use_more"]' => ['checked' => TRUE],
1489             ],
1490           ],
1491         ];
1492         $form['use_more_text'] = [
1493           '#type' => 'textfield',
1494           '#title' => $this->t('More link text'),
1495           '#description' => $this->t('The text to display for the more link.'),
1496           '#default_value' => $this->getOption('use_more_text'),
1497           '#states' => [
1498             'visible' => [
1499               ':input[name="use_more"]' => ['checked' => TRUE],
1500             ],
1501           ],
1502         ];
1503         break;
1504       case 'group_by':
1505         $form['#title'] .= $this->t('Allow grouping and aggregation (calculation) of fields.');
1506         $form['group_by'] = [
1507           '#type' => 'checkbox',
1508           '#title' => $this->t('Aggregate'),
1509           '#description' => $this->t('If enabled, some fields may become unavailable. All fields that are selected for grouping will be collapsed to one record per distinct value. Other fields which are selected for aggregation will have the function run on them. For example, you can group nodes on title and count the number of nids in order to get a list of duplicate titles.'),
1510           '#default_value' => $this->getOption('group_by'),
1511         ];
1512         break;
1513       case 'access':
1514         $form['#title'] .= $this->t('Access restrictions');
1515         $form['access'] = [
1516           '#prefix' => '<div class="clearfix">',
1517           '#suffix' => '</div>',
1518           '#tree' => TRUE,
1519         ];
1520
1521         $access = $this->getOption('access');
1522         $form['access']['type'] = [
1523           '#title' => $this->t('Access'),
1524           '#title_display' => 'invisible',
1525           '#type' => 'radios',
1526           '#options' => Views::fetchPluginNames('access', $this->getType(), [$this->view->storage->get('base_table')]),
1527           '#default_value' => $access['type'],
1528         ];
1529
1530         $access_plugin = $this->getPlugin('access');
1531         if ($access_plugin->usesOptions()) {
1532           $form['markup'] = [
1533             '#prefix' => '<div class="js-form-item form-item description">',
1534             '#markup' => $this->t('You may also adjust the @settings for the currently selected access restriction.', ['@settings' => $this->optionLink($this->t('settings'), 'access_options')]),
1535             '#suffix' => '</div>',
1536           ];
1537         }
1538
1539         break;
1540       case 'access_options':
1541         $plugin = $this->getPlugin('access');
1542         $form['#title'] .= $this->t('Access options');
1543         if ($plugin) {
1544           $form['access_options'] = [
1545             '#tree' => TRUE,
1546           ];
1547           $plugin->buildOptionsForm($form['access_options'], $form_state);
1548         }
1549         break;
1550       case 'cache':
1551         $form['#title'] .= $this->t('Caching');
1552         $form['cache'] = [
1553           '#prefix' => '<div class="clearfix">',
1554           '#suffix' => '</div>',
1555           '#tree' => TRUE,
1556         ];
1557
1558         $cache = $this->getOption('cache');
1559         $form['cache']['type'] = [
1560           '#title' => $this->t('Caching'),
1561           '#title_display' => 'invisible',
1562           '#type' => 'radios',
1563           '#options' => Views::fetchPluginNames('cache', $this->getType(), [$this->view->storage->get('base_table')]),
1564           '#default_value' => $cache['type'],
1565         ];
1566
1567         $cache_plugin = $this->getPlugin('cache');
1568         if ($cache_plugin->usesOptions()) {
1569           $form['markup'] = [
1570             '#prefix' => '<div class="js-form-item form-item description">',
1571             '#suffix' => '</div>',
1572             '#markup' => $this->t('You may also adjust the @settings for the currently selected cache mechanism.', ['@settings' => $this->optionLink($this->t('settings'), 'cache_options')]),
1573           ];
1574         }
1575         break;
1576       case 'cache_options':
1577         $plugin = $this->getPlugin('cache');
1578         $form['#title'] .= $this->t('Caching options');
1579         if ($plugin) {
1580           $form['cache_options'] = [
1581             '#tree' => TRUE,
1582           ];
1583           $plugin->buildOptionsForm($form['cache_options'], $form_state);
1584         }
1585         break;
1586       case 'query':
1587         $query_options = $this->getOption('query');
1588         $plugin_name = $query_options['type'];
1589
1590         $form['#title'] .= $this->t('Query options');
1591         $this->view->initQuery();
1592         if ($this->view->query) {
1593           $form['query'] = [
1594             '#tree' => TRUE,
1595             'type' => [
1596               '#type' => 'value',
1597               '#value' => $plugin_name,
1598             ],
1599             'options' => [
1600               '#tree' => TRUE,
1601             ],
1602           ];
1603
1604           $this->view->query->buildOptionsForm($form['query']['options'], $form_state);
1605         }
1606         break;
1607       case 'rendering_language':
1608         $form['#title'] .= $this->t('Rendering language');
1609         if (\Drupal::languageManager()->isMultilingual() && $this->isBaseTableTranslatable()) {
1610           $options = $this->buildRenderingLanguageOptions();
1611           $form['rendering_language'] = [
1612             '#type' => 'select',
1613             '#options' => $options,
1614             '#title' => $this->t('Rendering language'),
1615             '#description' => $this->t('All content that supports translations will be displayed in the selected language.'),
1616             '#default_value' => $this->getOption('rendering_language'),
1617           ];
1618         }
1619         else {
1620           $form['rendering_language']['#markup'] = $this->t('The view is not based on a translatable entity type or the site is not multilingual.');
1621         }
1622         break;
1623       case 'style':
1624         $form['#title'] .= $this->t('How should this view be styled');
1625         $style_plugin = $this->getPlugin('style');
1626         $form['style'] = [
1627           '#prefix' => '<div class="clearfix">',
1628           '#suffix' => '</div>',
1629           '#tree' => TRUE,
1630         ];
1631         $form['style']['type'] = [
1632           '#title' => $this->t('Style'),
1633           '#title_display' => 'invisible',
1634           '#type' => 'radios',
1635           '#options' => Views::fetchPluginNames('style', $this->getType(), [$this->view->storage->get('base_table')]),
1636           '#default_value' => $style_plugin->definition['id'],
1637           '#description' => $this->t('If the style you choose has settings, be sure to click the settings button that will appear next to it in the View summary.'),
1638         ];
1639
1640         if ($style_plugin->usesOptions()) {
1641           $form['markup'] = [
1642             '#prefix' => '<div class="js-form-item form-item description">',
1643             '#suffix' => '</div>',
1644             '#markup' => $this->t('You may also adjust the @settings for the currently selected style.', ['@settings' => $this->optionLink($this->t('settings'), 'style_options')]),
1645           ];
1646         }
1647
1648         break;
1649       case 'style_options':
1650         $form['#title'] .= $this->t('Style options');
1651         $style = TRUE;
1652         $style_plugin = $this->getOption('style');
1653         $name = $style_plugin['type'];
1654
1655       case 'row_options':
1656         if (!isset($name)) {
1657           $row_plugin = $this->getOption('row');
1658           $name = $row_plugin['type'];
1659         }
1660         // If row, $style will be empty.
1661         if (empty($style)) {
1662           $form['#title'] .= $this->t('Row style options');
1663         }
1664         $plugin = $this->getPlugin(empty($style) ? 'row' : 'style', $name);
1665         if ($plugin) {
1666           $form[$section] = [
1667             '#tree' => TRUE,
1668           ];
1669           $plugin->buildOptionsForm($form[$section], $form_state);
1670         }
1671         break;
1672       case 'row':
1673         $form['#title'] .= $this->t('How should each row in this view be styled');
1674         $row_plugin_instance = $this->getPlugin('row');
1675         $form['row'] = [
1676           '#prefix' => '<div class="clearfix">',
1677           '#suffix' => '</div>',
1678           '#tree' => TRUE,
1679         ];
1680         $form['row']['type'] = [
1681           '#title' => $this->t('Row'),
1682           '#title_display' => 'invisible',
1683           '#type' => 'radios',
1684           '#options' => Views::fetchPluginNames('row', $this->getType(), [$this->view->storage->get('base_table')]),
1685           '#default_value' => $row_plugin_instance->definition['id'],
1686         ];
1687
1688         if ($row_plugin_instance->usesOptions()) {
1689           $form['markup'] = [
1690             '#prefix' => '<div class="js-form-item form-item description">',
1691             '#suffix' => '</div>',
1692             '#markup' => $this->t('You may also adjust the @settings for the currently selected row style.', ['@settings' => $this->optionLink($this->t('settings'), 'row_options')]),
1693           ];
1694         }
1695
1696         break;
1697       case 'link_display':
1698         $form['#title'] .= $this->t('Which display to use for path');
1699         $options = [FALSE => $this->t('None'), 'custom_url' => $this->t('Custom URL')];
1700
1701         foreach ($this->view->storage->get('display') as $display_id => $display) {
1702           if ($this->view->displayHandlers->get($display_id)->hasPath()) {
1703             $options[$display_id] = $display['display_title'];
1704           }
1705         }
1706
1707         $form['link_display'] = [
1708           '#type' => 'radios',
1709           '#options' => $options,
1710           '#description' => $this->t("Which display to use to get this display's path for things like summary links, rss feed links, more links, etc."),
1711           '#default_value' => $this->getOption('link_display'),
1712         ];
1713
1714         $options = [];
1715         $optgroup_arguments = (string) t('Arguments');
1716         foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
1717           $options[$optgroup_arguments]["{{ arguments.$arg }}"] = $this->t('@argument title', ['@argument' => $handler->adminLabel()]);
1718           $options[$optgroup_arguments]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', ['@argument' => $handler->adminLabel()]);
1719         }
1720
1721         // Default text.
1722         // We have some options, so make a list.
1723         $description = [];
1724         $description[] = [
1725           '#markup' => $this->t('A Drupal path or external URL the more link will point to. Note that this will override the link display setting above.'),
1726         ];
1727         if (!empty($options)) {
1728           $description[] = [
1729             '#prefix' => '<p>',
1730             '#markup' => $this->t('The following tokens are available for this link. You may use Twig syntax in this field.'),
1731             '#suffix' => '</p>',
1732           ];
1733           foreach (array_keys($options) as $type) {
1734             if (!empty($options[$type])) {
1735               $items = [];
1736               foreach ($options[$type] as $key => $value) {
1737                 $items[] = $key . ' == ' . $value;
1738               }
1739               $item_list = [
1740                 '#theme' => 'item_list',
1741                 '#items' => $items,
1742               ];
1743               $description[] = $item_list;
1744             }
1745           }
1746         }
1747
1748         $form['link_url'] = [
1749           '#type' => 'textfield',
1750           '#title' => $this->t('Custom URL'),
1751           '#default_value' => $this->getOption('link_url'),
1752           '#description' => $description,
1753           '#states' => [
1754             'visible' => [
1755               ':input[name="link_display"]' => ['value' => 'custom_url'],
1756             ],
1757           ],
1758         ];
1759         break;
1760       case 'exposed_block':
1761         $form['#title'] .= $this->t('Put the exposed form in a block');
1762         $form['description'] = [
1763           '#markup' => '<div class="js-form-item form-item description">' . $this->t('If set, any exposed widgets will not appear with this view. Instead, a block will be made available to the Drupal block administration system, and the exposed form will appear there. Note that this block must be enabled manually, Views will not enable it for you.') . '</div>',
1764         ];
1765         $form['exposed_block'] = [
1766           '#type' => 'radios',
1767           '#options' => [1 => $this->t('Yes'), 0 => $this->t('No')],
1768           '#default_value' => $this->getOption('exposed_block') ? 1 : 0,
1769         ];
1770         break;
1771       case 'exposed_form':
1772         $form['#title'] .= $this->t('Exposed Form');
1773         $form['exposed_form'] = [
1774           '#prefix' => '<div class="clearfix">',
1775           '#suffix' => '</div>',
1776           '#tree' => TRUE,
1777         ];
1778
1779         $exposed_form = $this->getOption('exposed_form');
1780         $form['exposed_form']['type'] = [
1781           '#title' => $this->t('Exposed form'),
1782           '#title_display' => 'invisible',
1783           '#type' => 'radios',
1784           '#options' => Views::fetchPluginNames('exposed_form', $this->getType(), [$this->view->storage->get('base_table')]),
1785           '#default_value' => $exposed_form['type'],
1786         ];
1787
1788         $exposed_form_plugin = $this->getPlugin('exposed_form');
1789         if ($exposed_form_plugin->usesOptions()) {
1790           $form['markup'] = [
1791             '#prefix' => '<div class="js-form-item form-item description">',
1792             '#suffix' => '</div>',
1793             '#markup' => $this->t('You may also adjust the @settings for the currently selected style.', ['@settings' => $this->optionLink($this->t('settings'), 'exposed_form_options')]),
1794           ];
1795         }
1796         break;
1797       case 'exposed_form_options':
1798         $plugin = $this->getPlugin('exposed_form');
1799         $form['#title'] .= $this->t('Exposed form options');
1800         if ($plugin) {
1801           $form['exposed_form_options'] = [
1802             '#tree' => TRUE,
1803           ];
1804           $plugin->buildOptionsForm($form['exposed_form_options'], $form_state);
1805         }
1806         break;
1807       case 'pager':
1808         $form['#title'] .= $this->t('Select pager');
1809         $form['pager'] = [
1810           '#prefix' => '<div class="clearfix">',
1811           '#suffix' => '</div>',
1812           '#tree' => TRUE,
1813         ];
1814
1815         $pager = $this->getOption('pager');
1816         $form['pager']['type'] = [
1817           '#title' => $this->t('Pager'),
1818           '#title_display' => 'invisible',
1819           '#type' => 'radios',
1820           '#options' => Views::fetchPluginNames('pager', !$this->usesPager() ? 'basic' : NULL, [$this->view->storage->get('base_table')]),
1821           '#default_value' => $pager['type'],
1822         ];
1823
1824         $pager_plugin = $this->getPlugin('pager');
1825         if ($pager_plugin->usesOptions()) {
1826           $form['markup'] = [
1827             '#prefix' => '<div class="js-form-item form-item description">',
1828             '#suffix' => '</div>',
1829             '#markup' => $this->t('You may also adjust the @settings for the currently selected pager.', ['@settings' => $this->optionLink($this->t('settings'), 'pager_options')]),
1830           ];
1831         }
1832
1833         break;
1834       case 'pager_options':
1835         $plugin = $this->getPlugin('pager');
1836         $form['#title'] .= $this->t('Pager options');
1837         if ($plugin) {
1838           $form['pager_options'] = [
1839             '#tree' => TRUE,
1840           ];
1841           $plugin->buildOptionsForm($form['pager_options'], $form_state);
1842         }
1843         break;
1844     }
1845
1846     foreach ($this->extenders as $extender) {
1847       $extender->buildOptionsForm($form, $form_state);
1848     }
1849   }
1850
1851   /**
1852    * {@inheritdoc}
1853    */
1854   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
1855     $section = $form_state->get('section');
1856     switch ($section) {
1857       case 'display_title':
1858         if ($form_state->isValueEmpty('display_title')) {
1859           $form_state->setError($form['display_title'], $this->t('Display title may not be empty.'));
1860         }
1861         break;
1862       case 'css_class':
1863         $css_class = $form_state->getValue('css_class');
1864         if (preg_match('/[^a-zA-Z0-9-_ ]/', $css_class)) {
1865           $form_state->setError($form['css_class'], $this->t('CSS classes must be alphanumeric or dashes only.'));
1866         }
1867         break;
1868       case 'display_id':
1869         if ($form_state->getValue('display_id')) {
1870           if (preg_match('/[^a-z0-9_]/', $form_state->getValue('display_id'))) {
1871             $form_state->setError($form['display_id'], $this->t('Display name must be letters, numbers, or underscores only.'));
1872           }
1873
1874           foreach ($this->view->displayHandlers as $id => $display) {
1875             if ($id != $this->view->current_display && ($form_state->getValue('display_id') == $id || (isset($display->new_id) && $form_state->getValue('display_id') == $display->new_id))) {
1876               $form_state->setError($form['display_id'], $this->t('Display id should be unique.'));
1877             }
1878           }
1879         }
1880         break;
1881       case 'query':
1882         if ($this->view->query) {
1883           $this->view->query->validateOptionsForm($form['query'], $form_state);
1884         }
1885         break;
1886     }
1887
1888     // Validate plugin options. Every section with "_options" in it, belongs to
1889     // a plugin type, like "style_options".
1890     if (strpos($section, '_options') !== FALSE) {
1891       $plugin_type = str_replace('_options', '', $section);
1892       // Load the plugin and let it handle the validation.
1893       if ($plugin = $this->getPlugin($plugin_type)) {
1894         $plugin->validateOptionsForm($form[$section], $form_state);
1895       }
1896     }
1897
1898     foreach ($this->extenders as $extender) {
1899       $extender->validateOptionsForm($form, $form_state);
1900     }
1901   }
1902
1903   /**
1904    * {@inheritdoc}
1905    */
1906   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
1907     // Not sure I like this being here, but it seems (?) like a logical place.
1908     $cache_plugin = $this->getPlugin('cache');
1909     if ($cache_plugin) {
1910       $cache_plugin->cacheFlush();
1911     }
1912
1913     $section = $form_state->get('section');
1914     switch ($section) {
1915       case 'display_id':
1916         if ($form_state->hasValue('display_id')) {
1917           $this->display['new_id'] = $form_state->getValue('display_id');
1918         }
1919         break;
1920       case 'display_title':
1921         $this->display['display_title'] = $form_state->getValue('display_title');
1922         $this->setOption('display_description', $form_state->getValue('display_description'));
1923         break;
1924       case 'query':
1925         $plugin = $this->getPlugin('query');
1926         if ($plugin) {
1927           $plugin->submitOptionsForm($form['query']['options'], $form_state);
1928           $this->setOption('query', $form_state->getValue($section));
1929         }
1930         break;
1931
1932       case 'link_display':
1933         $this->setOption('link_url', $form_state->getValue('link_url'));
1934       case 'title':
1935       case 'css_class':
1936       case 'display_comment':
1937       case 'distinct':
1938       case 'group_by':
1939         $this->setOption($section, $form_state->getValue($section));
1940         break;
1941       case 'rendering_language':
1942         $this->setOption('rendering_language', $form_state->getValue('rendering_language'));
1943         break;
1944       case 'use_ajax':
1945       case 'hide_attachment_summary':
1946       case 'show_admin_links':
1947       case 'exposed_block':
1948         $this->setOption($section, (bool) $form_state->getValue($section));
1949         break;
1950       case 'use_more':
1951         $this->setOption($section, intval($form_state->getValue($section)));
1952         $this->setOption('use_more_always', intval($form_state->getValue('use_more_always')));
1953         $this->setOption('use_more_text', $form_state->getValue('use_more_text'));
1954         break;
1955
1956       case 'access':
1957       case 'cache':
1958       case 'exposed_form':
1959       case 'pager':
1960       case 'row':
1961       case 'style':
1962         $plugin_type = $section;
1963         $plugin_options = $this->getOption($plugin_type);
1964         $type = $form_state->getValue([$plugin_type, 'type']);
1965         if ($plugin_options['type'] != $type) {
1966           /** @var \Drupal\views\Plugin\views\ViewsPluginInterface $plugin */
1967           $plugin = Views::pluginManager($plugin_type)->createInstance($type);
1968           if ($plugin) {
1969             $plugin->init($this->view, $this, $plugin_options['options']);
1970             $plugin_options = [
1971               'type' => $type,
1972               'options' => $plugin->options,
1973             ];
1974             $plugin->filterByDefinedOptions($plugin_options['options']);
1975             $this->setOption($plugin_type, $plugin_options);
1976             if ($plugin->usesOptions()) {
1977               $form_state->get('view')->addFormToStack('display', $this->display['id'], $plugin_type . '_options');
1978             }
1979           }
1980         }
1981         break;
1982
1983       case 'access_options':
1984       case 'cache_options':
1985       case 'exposed_form_options':
1986       case 'pager_options':
1987       case 'row_options':
1988       case 'style_options':
1989         // Submit plugin options. Every section with "_options" in it, belongs to
1990         // a plugin type, like "style_options".
1991         $plugin_type = str_replace('_options', '', $section);
1992         if ($plugin = $this->getPlugin($plugin_type)) {
1993           $plugin_options = $this->getOption($plugin_type);
1994           $plugin->submitOptionsForm($form[$plugin_type . '_options'], $form_state);
1995           $plugin_options['options'] = $form_state->getValue($section);
1996           $this->setOption($plugin_type, $plugin_options);
1997         }
1998         break;
1999     }
2000
2001     $extender_options = $this->getOption('display_extenders');
2002     foreach ($this->extenders as $extender) {
2003       $extender->submitOptionsForm($form, $form_state);
2004
2005       $plugin_id = $extender->getPluginId();
2006       $extender_options[$plugin_id] = $extender->options;
2007     }
2008     $this->setOption('display_extenders', $extender_options);
2009   }
2010
2011   /**
2012    * {@inheritdoc}
2013    */
2014   public function optionsOverride($form, FormStateInterface $form_state) {
2015     $this->setOverride($form_state->get('section'));
2016   }
2017
2018   /**
2019    * {@inheritdoc}
2020    */
2021   public function setOverride($section, $new_state = NULL) {
2022     $options = $this->defaultableSections($section);
2023     if (!$options) {
2024       return;
2025     }
2026
2027     if (!isset($new_state)) {
2028       $new_state = empty($this->options['defaults'][$section]);
2029     }
2030
2031     // For each option that is part of this group, fix our settings.
2032     foreach ($options as $option) {
2033       if ($new_state) {
2034         // Revert to defaults.
2035         unset($this->options[$option]);
2036         unset($this->display['display_options'][$option]);
2037       }
2038       else {
2039         // Copy existing values into our display.
2040         $this->options[$option] = $this->getOption($option);
2041         $this->display['display_options'][$option] = $this->options[$option];
2042       }
2043       $this->options['defaults'][$option] = $new_state;
2044       $this->display['display_options']['defaults'][$option] = $new_state;
2045     }
2046   }
2047
2048   /**
2049    * {@inheritdoc}
2050    */
2051   public function query() {
2052     foreach ($this->extenders as $extender) {
2053       $extender->query();
2054     }
2055   }
2056
2057   /**
2058    * {@inheritdoc}
2059    */
2060   public function renderFilters() {}
2061
2062   /**
2063    * {@inheritdoc}
2064    */
2065   public function renderPager() {
2066     return TRUE;
2067   }
2068
2069   /**
2070    * {@inheritdoc}
2071    */
2072   public function renderMoreLink() {
2073     if ($this->isMoreEnabled() && ($this->useMoreAlways() || (!empty($this->view->pager) && $this->view->pager->hasMoreRecords()))) {
2074       // If the user has supplied a custom "More" link path, replace any
2075       // argument tokens and use that for the URL.
2076       if ($this->getOption('link_display') == 'custom_url' && $override_path = $this->getOption('link_url')) {
2077         $tokens = $this->getArgumentsTokens();
2078         $path = $this->viewsTokenReplace($override_path, $tokens);
2079         // @todo Views should expect and store a leading /. See:
2080         //   https://www.drupal.org/node/2423913
2081         $url = Url::fromUserInput('/' . $path);
2082       }
2083       // Otherwise, use the URL for the display.
2084       else {
2085         $url = $this->view->getUrl(NULL, $this->display['id']);
2086       }
2087
2088       // If a URL is available (either from the display or a custom path),
2089       // render the "More" link.
2090       if ($url) {
2091         $url_options = [];
2092         if (!empty($this->view->exposed_raw_input)) {
2093           $url_options['query'] = $this->view->exposed_raw_input;
2094         }
2095         $url->setOptions($url_options);
2096
2097         return [
2098           '#type' => 'more_link',
2099           '#url' => $url,
2100           '#title' => $this->useMoreText(),
2101           '#view' => $this->view,
2102         ];
2103       }
2104     }
2105   }
2106
2107   /**
2108    * {@inheritdoc}
2109    */
2110   public function render() {
2111     $rows = (!empty($this->view->result) || $this->view->style_plugin->evenEmpty()) ? $this->view->style_plugin->render($this->view->result) : [];
2112
2113     $element = [
2114       '#theme' => $this->themeFunctions(),
2115       '#view' => $this->view,
2116       '#pre_render' => [[$this, 'elementPreRender']],
2117       '#rows' => $rows,
2118       // Assigned by reference so anything added in $element['#attached'] will
2119       // be available on the view.
2120       '#attached' => &$this->view->element['#attached'],
2121       '#cache' => &$this->view->element['#cache'],
2122     ];
2123
2124     $this->applyDisplayCacheabilityMetadata($this->view->element);
2125
2126     return $element;
2127   }
2128
2129   /**
2130    * Applies the cacheability of the current display to the given render array.
2131    *
2132    * @param array $element
2133    *   The render array with updated cacheability metadata.
2134    */
2135   protected function applyDisplayCacheabilityMetadata(array &$element) {
2136     /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
2137     $cache = $this->getPlugin('cache');
2138
2139     (new CacheableMetadata())
2140       ->setCacheTags(Cache::mergeTags($this->view->getCacheTags(), isset($this->display['cache_metadata']['tags']) ? $this->display['cache_metadata']['tags'] : []))
2141       ->setCacheContexts(isset($this->display['cache_metadata']['contexts']) ? $this->display['cache_metadata']['contexts'] : [])
2142       ->setCacheMaxAge(Cache::mergeMaxAges($cache->getCacheMaxAge(), isset($this->display['cache_metadata']['max-age']) ? $this->display['cache_metadata']['max-age'] : Cache::PERMANENT))
2143       ->merge(CacheableMetadata::createFromRenderArray($element))
2144       ->applyTo($element);
2145   }
2146
2147   /**
2148    * Applies the cacheability of the current display to the given render array.
2149    *
2150    * @param array $element
2151    *   The render array with updated cacheability metadata.
2152    *
2153    * @deprecated in Drupal 8.4.0, will be removed before Drupal 9.0. Use
2154    *   DisplayPluginBase::applyDisplayCacheabilityMetadata instead.
2155    *
2156    * @see \Drupal\views\Plugin\views\display\DisplayPluginBase::applyDisplayCacheabilityMetadata()
2157    */
2158   protected function applyDisplayCachablityMetadata(array &$element) {
2159     @trigger_error('The DisplayPluginBase::applyDisplayCachablityMetadata method is deprecated since version 8.4 and will be removed in 9.0. Use DisplayPluginBase::applyDisplayCacheabilityMetadata instead.', E_USER_DEPRECATED);
2160     $this->applyDisplayCacheabilityMetadata($element);
2161   }
2162
2163   /**
2164    * {@inheritdoc}
2165    */
2166   public function elementPreRender(array $element) {
2167     $view = $element['#view'];
2168     $empty = empty($view->result);
2169
2170     // Force a render array so CSS/JS can be attached.
2171     if (!is_array($element['#rows'])) {
2172       $element['#rows'] = ['#markup' => $element['#rows']];
2173     }
2174
2175     $element['#header'] = $view->display_handler->renderArea('header', $empty);
2176     $element['#footer'] = $view->display_handler->renderArea('footer', $empty);
2177     $element['#empty'] = $empty ? $view->display_handler->renderArea('empty', $empty) : [];
2178     $element['#exposed'] = !empty($view->exposed_widgets) ? $view->exposed_widgets : [];
2179     $element['#more'] = $view->display_handler->renderMoreLink();
2180     $element['#feed_icons'] = !empty($view->feedIcons) ? $view->feedIcons : [];
2181
2182     if ($view->display_handler->renderPager()) {
2183       $exposed_input = isset($view->exposed_raw_input) ? $view->exposed_raw_input : NULL;
2184       $element['#pager'] = $view->renderPager($exposed_input);
2185     }
2186
2187     if (!empty($view->attachment_before)) {
2188       $element['#attachment_before'] = $view->attachment_before;
2189     }
2190     if (!empty($view->attachment_after)) {
2191       $element['#attachment_after'] = $view->attachment_after;
2192     }
2193
2194     // If form fields were found in the view, reformat the view output as a form.
2195     if ($view->hasFormElements()) {
2196       // Only render row output if there are rows. Otherwise, render the empty
2197       // region.
2198       if (!empty($element['#rows'])) {
2199         $output = $element['#rows'];
2200       }
2201       else {
2202         $output = $element['#empty'];
2203       }
2204
2205       $form_object = ViewsForm::create(\Drupal::getContainer(), $view->storage->id(), $view->current_display, $view->args);
2206       $form = \Drupal::formBuilder()->getForm($form_object, $view, $output);
2207       // The form is requesting that all non-essential views elements be hidden,
2208       // usually because the rendered step is not a view result.
2209       if ($form['show_view_elements']['#value'] == FALSE) {
2210         $element['#header'] = [];
2211         $element['#exposed'] = [];
2212         $element['#pager'] = [];
2213         $element['#footer'] = [];
2214         $element['#more'] = [];
2215         $element['#feed_icons'] = [];
2216       }
2217
2218       $element['#rows'] = $form;
2219     }
2220
2221     return $element;
2222   }
2223
2224   /**
2225    * {@inheritdoc}
2226    */
2227   public function renderArea($area, $empty = FALSE) {
2228     $return = [];
2229     foreach ($this->getHandlers($area) as $key => $area_handler) {
2230       if ($area_render = $area_handler->render($empty)) {
2231         if (isset($area_handler->position)) {
2232           // Fix weight of area.
2233           $area_render['#weight'] = $area_handler->position;
2234         }
2235         $return[$key] = $area_render;
2236       }
2237     }
2238     return $return;
2239   }
2240
2241   /**
2242    * {@inheritdoc}
2243    */
2244   public function access(AccountInterface $account = NULL) {
2245     if (!isset($account)) {
2246       $account = \Drupal::currentUser();
2247     }
2248
2249     $plugin = $this->getPlugin('access');
2250     /** @var \Drupal\views\Plugin\views\access\AccessPluginBase $plugin */
2251     if ($plugin) {
2252       return $plugin->access($account);
2253     }
2254
2255     // Fallback to all access if no plugin.
2256     return TRUE;
2257   }
2258
2259   /**
2260    * {@inheritdoc}
2261    */
2262   public function preExecute() {
2263     $this->view->setAjaxEnabled($this->ajaxEnabled());
2264     if ($this->isMoreEnabled() && !$this->useMoreAlways()) {
2265       $this->view->get_total_rows = TRUE;
2266     }
2267     $this->view->initHandlers();
2268     if ($this->usesExposed()) {
2269       /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */
2270       $exposed_form = $this->getPlugin('exposed_form');
2271       $exposed_form->preExecute();
2272     }
2273
2274     foreach ($this->extenders as $extender) {
2275       $extender->preExecute();
2276     }
2277   }
2278
2279   /**
2280    * {@inheritdoc}
2281    */
2282   public function calculateCacheMetadata() {
2283     $cache_metadata = new CacheableMetadata();
2284
2285     // Iterate over ordinary views plugins.
2286     foreach (Views::getPluginTypes('plugin') as $plugin_type) {
2287       $plugin = $this->getPlugin($plugin_type);
2288       if ($plugin instanceof CacheableDependencyInterface) {
2289         $cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($plugin));
2290       }
2291     }
2292
2293     // Iterate over all handlers. Note that at least the argument handler will
2294     // need to ask all its subplugins.
2295     foreach (array_keys(Views::getHandlerTypes()) as $handler_type) {
2296       $handlers = $this->getHandlers($handler_type);
2297       foreach ($handlers as $handler) {
2298         if ($handler instanceof CacheableDependencyInterface) {
2299           $cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($handler));
2300         }
2301       }
2302     }
2303
2304     /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */
2305     if ($cache_plugin = $this->getPlugin('cache')) {
2306       $cache_plugin->alterCacheMetadata($cache_metadata);
2307     }
2308
2309     return $cache_metadata;
2310   }
2311
2312   /**
2313    * {@inheritdoc}
2314    */
2315   public function getCacheMetadata() {
2316     if (!isset($this->display['cache_metadata'])) {
2317       $cache_metadata = $this->calculateCacheMetadata();
2318       $this->display['cache_metadata']['max-age'] = $cache_metadata->getCacheMaxAge();
2319       $this->display['cache_metadata']['contexts'] = $cache_metadata->getCacheContexts();
2320       $this->display['cache_metadata']['tags'] = $cache_metadata->getCacheTags();
2321     }
2322     else {
2323       $cache_metadata = (new CacheableMetadata())
2324         ->setCacheMaxAge($this->display['cache_metadata']['max-age'])
2325         ->setCacheContexts($this->display['cache_metadata']['contexts'])
2326         ->setCacheTags($this->display['cache_metadata']['tags']);
2327     }
2328     return $cache_metadata;
2329   }
2330
2331   /**
2332    * {@inheritdoc}
2333    */
2334   public function execute() {}
2335
2336   /**
2337    * {@inheritdoc}
2338    */
2339   public function buildRenderable(array $args = [], $cache = TRUE) {
2340     $this->view->element += [
2341       '#type' => 'view',
2342       '#name' => $this->view->storage->id(),
2343       '#display_id' => $this->display['id'],
2344       '#arguments' => $args,
2345       '#embed' => FALSE,
2346       '#view' => $this->view,
2347       '#cache_properties' => ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'],
2348     ];
2349
2350     // When something passes $cache = FALSE, they're asking us not to create our
2351     // own render cache for it. However, we still need to include certain pieces
2352     // of cacheability metadata (e.g.: cache contexts), so they can bubble up.
2353     // Thus, we add the cacheability metadata first, then modify / remove the
2354     // cache keys depending on the $cache argument.
2355     $this->applyDisplayCacheabilityMetadata($this->view->element);
2356     if ($cache) {
2357       $this->view->element['#cache'] += ['keys' => []];
2358       // Places like \Drupal\views\ViewExecutable::setCurrentPage() set up an
2359       // additional cache context.
2360       $this->view->element['#cache']['keys'] = array_merge(['views', 'display', $this->view->element['#name'], $this->view->element['#display_id']], $this->view->element['#cache']['keys']);
2361     }
2362     else {
2363       // Remove the cache keys, to ensure render caching is not triggered. We
2364       // don't unset the other #cache values, to allow cacheability metadata to
2365       // still be bubbled.
2366       unset($this->view->element['#cache']['keys']);
2367     }
2368
2369     return $this->view->element;
2370   }
2371
2372   /**
2373    * {@inheritdoc}
2374    */
2375   public static function buildBasicRenderable($view_id, $display_id, array $args = []) {
2376     $build = [
2377       '#type' => 'view',
2378       '#name' => $view_id,
2379       '#display_id' => $display_id,
2380       '#arguments' => $args,
2381       '#embed' => FALSE,
2382       '#cache' => [
2383         'keys' => ['view', $view_id, 'display', $display_id],
2384       ],
2385     ];
2386
2387     if ($args) {
2388       $build['#cache']['keys'][] = 'args';
2389       $build['#cache']['keys'][] = implode(',', $args);
2390     }
2391
2392     $build['#cache_properties'] = ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'];
2393
2394     return $build;
2395
2396   }
2397
2398   /**
2399    * {@inheritdoc}
2400    */
2401   public function preview() {
2402     return $this->view->render();
2403   }
2404
2405   /**
2406    * {@inheritdoc}
2407    */
2408   public function getType() {
2409     return 'normal';
2410   }
2411
2412   /**
2413    * {@inheritdoc}
2414    */
2415   public function validate() {
2416     $errors = [];
2417     // Make sure displays that use fields HAVE fields.
2418     if ($this->usesFields()) {
2419       $fields = FALSE;
2420       foreach ($this->getHandlers('field') as $field) {
2421         if (empty($field->options['exclude'])) {
2422           $fields = TRUE;
2423         }
2424       }
2425
2426       if (!$fields) {
2427         $errors[] = $this->t('Display "@display" uses fields but there are none defined for it or all are excluded.', ['@display' => $this->display['display_title']]);
2428       }
2429     }
2430
2431     // Validate the more link.
2432     if ($this->isMoreEnabled() && $this->getOption('link_display') !== 'custom_url') {
2433       $routed_display = $this->getRoutedDisplay();
2434       if (!$routed_display || !$routed_display->isEnabled()) {
2435         $errors[] = $this->t('Display "@display" uses a "more" link but there are no displays it can link to. You need to specify a custom URL.', ['@display' => $this->display['display_title']]);
2436       }
2437     }
2438
2439     if ($this->hasPath() && !$this->getOption('path')) {
2440       $errors[] = $this->t('Display "@display" uses a path but the path is undefined.', ['@display' => $this->display['display_title']]);
2441     }
2442
2443     // Validate style plugin.
2444     $style = $this->getPlugin('style');
2445     if (empty($style)) {
2446       $errors[] = $this->t('Display "@display" has an invalid style plugin.', ['@display' => $this->display['display_title']]);
2447     }
2448     else {
2449       $result = $style->validate();
2450       if (!empty($result) && is_array($result)) {
2451         $errors = array_merge($errors, $result);
2452       }
2453     }
2454
2455     // Validate query plugin.
2456     $query = $this->getPlugin('query');
2457     $result = $query->validate();
2458     if (!empty($result) && is_array($result)) {
2459       $errors = array_merge($errors, $result);
2460     }
2461
2462     // Check for missing relationships.
2463     $relationships = array_keys($this->getHandlers('relationship'));
2464     foreach (ViewExecutable::getHandlerTypes() as $type => $handler_type_info) {
2465       foreach ($this->getHandlers($type) as $handler_id => $handler) {
2466         if (!empty($handler->options['relationship']) && $handler->options['relationship'] != 'none' && !in_array($handler->options['relationship'], $relationships)) {
2467           $errors[] = $this->t('The %handler_type %handler uses a relationship that has been removed.', ['%handler_type' => $handler_type_info['lstitle'], '%handler' => $handler->adminLabel()]);
2468         }
2469       }
2470     }
2471
2472     // Validate handlers.
2473     foreach (ViewExecutable::getHandlerTypes() as $type => $info) {
2474       foreach ($this->getHandlers($type) as $handler) {
2475         $result = $handler->validate();
2476         if (!empty($result) && is_array($result)) {
2477           $errors = array_merge($errors, $result);
2478         }
2479       }
2480     }
2481
2482     return $errors;
2483   }
2484
2485   /**
2486    * {@inheritdoc}
2487    */
2488   public function newDisplay() {
2489   }
2490
2491   /**
2492    * {@inheritdoc}
2493    */
2494   public function isIdentifierUnique($id, $identifier) {
2495     foreach (ViewExecutable::getHandlerTypes() as $type => $info) {
2496       foreach ($this->getHandlers($type) as $key => $handler) {
2497         if ($handler->canExpose() && $handler->isExposed()) {
2498           if ($handler->isAGroup()) {
2499             if ($id != $key && $identifier == $handler->options['group_info']['identifier']) {
2500               return FALSE;
2501             }
2502           }
2503           else {
2504             if ($id != $key && $identifier == $handler->options['expose']['identifier']) {
2505               return FALSE;
2506             }
2507           }
2508         }
2509       }
2510     }
2511     return TRUE;
2512   }
2513
2514   /**
2515    * {@inheritdoc}
2516    */
2517   public function outputIsEmpty() {
2518     if (!empty($this->view->result)) {
2519       return FALSE;
2520     }
2521
2522     // Check whether all of the area handlers are empty.
2523     foreach (['empty', 'footer', 'header'] as $type) {
2524       $handlers = $this->getHandlers($type);
2525       foreach ($handlers as $handler) {
2526         // If one is not empty, return FALSE now.
2527         if (!$handler->isEmpty()) {
2528           return FALSE;
2529         }
2530       }
2531     }
2532
2533     return TRUE;
2534   }
2535
2536   /**
2537    * {@inheritdoc}
2538    */
2539   public function getSpecialBlocks() {
2540     $blocks = [];
2541
2542     if ($this->usesExposedFormInBlock()) {
2543       $delta = '-exp-' . $this->view->storage->id() . '-' . $this->display['id'];
2544       $desc = $this->t('Exposed form: @view-@display_id', ['@view' => $this->view->storage->id(), '@display_id' => $this->display['id']]);
2545
2546       $blocks[$delta] = [
2547         'info' => $desc,
2548       ];
2549     }
2550
2551     return $blocks;
2552   }
2553
2554   /**
2555    * {@inheritdoc}
2556    */
2557   public function viewExposedFormBlocks() {
2558     // Avoid interfering with the admin forms.
2559     $route_name = \Drupal::routeMatch()->getRouteName();
2560     if (strpos($route_name, 'views_ui.') === 0) {
2561       return;
2562     }
2563     $this->view->initHandlers();
2564
2565     if ($this->usesExposed() && $this->getOption('exposed_block')) {
2566       /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */
2567       $exposed_form = $this->getPlugin('exposed_form');
2568       return $exposed_form->renderExposedForm(TRUE);
2569     }
2570   }
2571
2572   /**
2573    * {@inheritdoc}
2574    */
2575   public function getArgumentText() {
2576     return [
2577       'filter value not present' => $this->t('When the filter value is <em>NOT</em> available'),
2578       'filter value present' => $this->t('When the filter value <em>IS</em> available or a default is provided'),
2579       'description' => $this->t("This display does not have a source for contextual filters, so no contextual filter value will be available unless you select 'Provide default'."),
2580     ];
2581   }
2582
2583   /**
2584    * {@inheritdoc}
2585    */
2586   public function getPagerText() {
2587     return [
2588       'items per page title' => $this->t('Items to display'),
2589       'items per page description' => $this->t('Enter 0 for no limit.'),
2590     ];
2591   }
2592
2593   /**
2594    * {@inheritdoc}
2595    */
2596   public function mergeDefaults() {
2597     $defined_options = $this->defineOptions();
2598
2599     // Build a map of plural => singular for handler types.
2600     $type_map = [];
2601     foreach (ViewExecutable::getHandlerTypes() as $type => $info) {
2602       $type_map[$info['plural']] = $type;
2603     }
2604
2605     // Find all defined options, that have specified a merge_defaults callback.
2606     foreach ($defined_options as $type => $definition) {
2607       if (!isset($definition['merge_defaults']) || !is_callable($definition['merge_defaults'])) {
2608         continue;
2609       }
2610       // Switch the type to singular, if it's a plural handler.
2611       if (isset($type_map[$type])) {
2612         $type = $type_map[$type];
2613       }
2614
2615       call_user_func($definition['merge_defaults'], $type);
2616     }
2617   }
2618
2619   /**
2620    * {@inheritdoc}
2621    */
2622   public function remove() {
2623
2624   }
2625
2626   /**
2627    * Merges plugins default values.
2628    *
2629    * @param string $type
2630    *   The name of the plugin type option.
2631    */
2632   protected function mergePlugin($type) {
2633     if (($options = $this->getOption($type)) && isset($options['options'])) {
2634       $plugin = $this->getPlugin($type);
2635       $options['options'] = $options['options'] + $plugin->options;
2636       $this->setOption($type, $options);
2637     }
2638   }
2639
2640   /**
2641    * Merges handlers default values.
2642    *
2643    * @param string $type
2644    *   The name of the handler type option.
2645    */
2646   protected function mergeHandler($type) {
2647     $types = ViewExecutable::getHandlerTypes();
2648
2649     $options = $this->getOption($types[$type]['plural']);
2650     foreach ($this->getHandlers($type) as $id => $handler) {
2651       if (isset($options[$id])) {
2652         $options[$id] = $options[$id] + $handler->options;
2653       }
2654     }
2655
2656     $this->setOption($types[$type]['plural'], $options);
2657   }
2658
2659   /**
2660    * {@inheritdoc}
2661    */
2662   public function getExtenders() {
2663     return $this->extenders;
2664   }
2665
2666   /**
2667    * Returns the available rendering strategies for language-aware entities.
2668    *
2669    * @return array
2670    *   An array of available entity row renderers keyed by renderer identifiers.
2671    */
2672   protected function buildRenderingLanguageOptions() {
2673     // @todo Consider making these plugins. See
2674     //   https://www.drupal.org/node/2173811.
2675     // Pass the current rendering language (in this case a one element array) so
2676     // is not lost when there are language configuration changes.
2677     return $this->listLanguages(LanguageInterface::STATE_CONFIGURABLE | LanguageInterface::STATE_SITE_DEFAULT | PluginBase::INCLUDE_NEGOTIATED | PluginBase::INCLUDE_ENTITY, [$this->getOption('rendering_language')]);
2678   }
2679
2680   /**
2681    * Returns whether the base table is of a translatable entity type.
2682    *
2683    * @return bool
2684    *   TRUE if the base table is of a translatable entity type, FALSE otherwise.
2685    */
2686   protected function isBaseTableTranslatable() {
2687     if ($entity_type = $this->view->getBaseEntityType()) {
2688       return $entity_type->isTranslatable();
2689     }
2690     return FALSE;
2691   }
2692
2693 }
2694
2695 /**
2696  * @}
2697  */