Updated to Drupal 8.5. Core Media not yet in use.
[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\Utility\SafeMarkup;
8 use Drupal\Core\Cache\Cache;
9 use Drupal\Core\Cache\CacheableMetadata;
10 use Drupal\Core\Cache\CacheableDependencyInterface;
11 use Drupal\Core\Form\FormStateInterface;
12 use Drupal\Core\Language\LanguageInterface;
13 use Drupal\Core\Plugin\PluginDependencyTrait;
14 use Drupal\Core\Session\AccountInterface;
15 use Drupal\Core\Url;
16 use Drupal\views\Form\ViewsForm;
17 use Drupal\views\Plugin\views\area\AreaPluginBase;
18 use Drupal\views\ViewExecutable;
19 use Drupal\views\Plugin\views\PluginBase;
20 use Drupal\views\Views;
21
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   /**
967    * {@inheritdoc}
968    */
969   public function getFieldLabels($groupable_only = FALSE) {
970     $options = [];
971     foreach ($this->getHandlers('relationship') as $relationship => $handler) {
972       $relationships[$relationship] = $handler->adminLabel();
973     }
974
975     foreach ($this->getHandlers('field') as $id => $handler) {
976       if ($groupable_only && !$handler->useStringGroupBy()) {
977         // Continue to next handler if it's not groupable.
978         continue;
979       }
980       if ($label = $handler->label()) {
981         $options[$id] = $label;
982       }
983       else {
984         $options[$id] = $handler->adminLabel();
985       }
986       if (!empty($handler->options['relationship']) && !empty($relationships[$handler->options['relationship']])) {
987         $options[$id] = '(' . $relationships[$handler->options['relationship']] . ') ' . $options[$id];
988       }
989     }
990     return $options;
991   }
992
993   /**
994    * {@inheritdoc}
995    */
996   public function setOption($option, $value) {
997     if ($this->isDefaulted($option)) {
998       return $this->default_display->setOption($option, $value);
999     }
1000
1001     // Set this in two places: On the handler where we'll notice it
1002     // but also on the display object so it gets saved. This should
1003     // only be a temporary fix.
1004     $this->display['display_options'][$option] = $value;
1005     return $this->options[$option] = $value;
1006   }
1007
1008   /**
1009    * {@inheritdoc}
1010    */
1011   public function overrideOption($option, $value) {
1012     $this->setOverride($option, FALSE);
1013     $this->setOption($option, $value);
1014   }
1015
1016   /**
1017    * {@inheritdoc}
1018    */
1019   public function optionLink($text, $section, $class = '', $title = '') {
1020     if (!trim($text)) {
1021       $text = $this->t('Broken field');
1022     }
1023
1024     if (!empty($class)) {
1025       $text = SafeMarkup::format('<span>@text</span>', ['@text' => $text]);
1026     }
1027
1028     if (empty($title)) {
1029       $title = $text;
1030     }
1031
1032     return \Drupal::l($text, new Url('views_ui.form_display', [
1033         'js' => 'nojs',
1034         'view' => $this->view->storage->id(),
1035         'display_id' => $this->display['id'],
1036         'type' => $section
1037       ], [
1038         'attributes' => [
1039           'class' => ['views-ajax-link', $class],
1040           'title' => $title,
1041           'id' => Html::getUniqueId('views-' . $this->display['id'] . '-' . $section)
1042         ]
1043     ]));
1044   }
1045
1046   /**
1047    * {@inheritdoc}
1048    */
1049   public function getArgumentsTokens() {
1050     $tokens = [];
1051     if (!empty($this->view->build_info['substitutions'])) {
1052       $tokens = $this->view->build_info['substitutions'];
1053     }
1054
1055     return $tokens;
1056   }
1057
1058   /**
1059    * {@inheritdoc}
1060    */
1061   public function optionsSummary(&$categories, &$options) {
1062     $categories = [
1063       'title' => [
1064         'title' => $this->t('Title'),
1065         'column' => 'first',
1066       ],
1067       'format' => [
1068         'title' => $this->t('Format'),
1069         'column' => 'first',
1070       ],
1071       'filters' => [
1072         'title' => $this->t('Filters'),
1073         'column' => 'first',
1074       ],
1075       'fields' => [
1076         'title' => $this->t('Fields'),
1077         'column' => 'first',
1078       ],
1079       'pager' => [
1080         'title' => $this->t('Pager'),
1081         'column' => 'second',
1082       ],
1083       'language' => [
1084         'title' => $this->t('Language'),
1085         'column' => 'second',
1086       ],
1087       'exposed' => [
1088         'title' => $this->t('Exposed form'),
1089         'column' => 'third',
1090         'build' => [
1091           '#weight' => 1,
1092         ],
1093       ],
1094       'access' => [
1095         'title' => '',
1096         'column' => 'second',
1097         'build' => [
1098           '#weight' => -5,
1099         ],
1100       ],
1101       'other' => [
1102         'title' => $this->t('Other'),
1103         'column' => 'third',
1104         'build' => [
1105           '#weight' => 2,
1106         ],
1107       ],
1108     ];
1109
1110     if ($this->display['id'] != 'default') {
1111       $options['display_id'] = [
1112         'category' => 'other',
1113         'title' => $this->t('Machine Name'),
1114         'value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'],
1115         'desc' => $this->t('Change the machine name of this display.'),
1116       ];
1117     }
1118
1119     $display_comment = views_ui_truncate($this->getOption('display_comment'), 80);
1120     $options['display_comment'] = [
1121       'category' => 'other',
1122       'title' => $this->t('Administrative comment'),
1123       'value' => !empty($display_comment) ? $display_comment : $this->t('None'),
1124       'desc' => $this->t('Comment or document this display.'),
1125     ];
1126
1127     $title = strip_tags($this->getOption('title'));
1128     if (!$title) {
1129       $title = $this->t('None');
1130     }
1131
1132     $options['title'] = [
1133       'category' => 'title',
1134       'title' => $this->t('Title'),
1135       'value' => views_ui_truncate($title, 32),
1136       'desc' => $this->t('Change the title that this display will use.'),
1137     ];
1138
1139     $style_plugin_instance = $this->getPlugin('style');
1140     $style_summary = empty($style_plugin_instance->definition['title']) ? $this->t('Missing style plugin') : $style_plugin_instance->summaryTitle();
1141     $style_title = empty($style_plugin_instance->definition['title']) ? $this->t('Missing style plugin') : $style_plugin_instance->pluginTitle();
1142
1143     $options['style'] = [
1144       'category' => 'format',
1145       'title' => $this->t('Format'),
1146       'value' => $style_title,
1147       'setting' => $style_summary,
1148       'desc' => $this->t('Change the way content is formatted.'),
1149     ];
1150
1151     // This adds a 'Settings' link to the style_options setting if the style has
1152     // options.
1153     if ($style_plugin_instance->usesOptions()) {
1154       $options['style']['links']['style_options'] = $this->t('Change settings for this format');
1155     }
1156
1157     if ($style_plugin_instance->usesRowPlugin()) {
1158       $row_plugin_instance = $this->getPlugin('row');
1159       $row_summary = empty($row_plugin_instance->definition['title']) ? $this->t('Missing row plugin') : $row_plugin_instance->summaryTitle();
1160       $row_title = empty($row_plugin_instance->definition['title']) ? $this->t('Missing row plugin') : $row_plugin_instance->pluginTitle();
1161
1162       $options['row'] = [
1163         'category' => 'format',
1164         'title' => $this->t('Show'),
1165         'value' => $row_title,
1166         'setting' => $row_summary,
1167         'desc' => $this->t('Change the way each row in the view is styled.'),
1168       ];
1169       // This adds a 'Settings' link to the row_options setting if the row style
1170       // has options.
1171       if ($row_plugin_instance->usesOptions()) {
1172         $options['row']['links']['row_options'] = $this->t('Change settings for this style');
1173       }
1174     }
1175     if ($this->usesAJAX()) {
1176       $options['use_ajax'] = [
1177         'category' => 'other',
1178         'title' => $this->t('Use AJAX'),
1179         'value' => $this->getOption('use_ajax') ? $this->t('Yes') : $this->t('No'),
1180         'desc' => $this->t('Change whether or not this display will use AJAX.'),
1181       ];
1182     }
1183     if ($this->usesAttachments()) {
1184       $options['hide_attachment_summary'] = [
1185         'category' => 'other',
1186         'title' => $this->t('Hide attachments in summary'),
1187         'value' => $this->getOption('hide_attachment_summary') ? $this->t('Yes') : $this->t('No'),
1188         'desc' => $this->t('Change whether or not to display attachments when displaying a contextual filter summary.'),
1189       ];
1190     }
1191     if (!isset($this->definition['contextual links locations']) || !empty($this->definition['contextual links locations'])) {
1192       $options['show_admin_links'] = [
1193         'category' => 'other',
1194         'title' => $this->t('Contextual links'),
1195         'value' => $this->getOption('show_admin_links') ? $this->t('Shown') : $this->t('Hidden'),
1196         'desc' => $this->t('Change whether or not to display contextual links for this view.'),
1197       ];
1198     }
1199
1200     $pager_plugin = $this->getPlugin('pager');
1201     if (!$pager_plugin) {
1202       // Default to the no pager plugin.
1203       $pager_plugin = Views::pluginManager('pager')->createInstance('none');
1204     }
1205
1206     $pager_str = $pager_plugin->summaryTitle();
1207
1208     $options['pager'] = [
1209       'category' => 'pager',
1210       'title' => $this->t('Use pager'),
1211       'value' => $pager_plugin->pluginTitle(),
1212       'setting' => $pager_str,
1213       'desc' => $this->t("Change this display's pager setting."),
1214     ];
1215
1216     // If pagers aren't allowed, change the text of the item.
1217     if (!$this->usesPager()) {
1218       $options['pager']['title'] = $this->t('Items to display');
1219     }
1220
1221     if ($pager_plugin->usesOptions()) {
1222       $options['pager']['links']['pager_options'] = $this->t('Change settings for this pager type.');
1223     }
1224
1225     if ($this->usesMore()) {
1226       $options['use_more'] = [
1227         'category' => 'pager',
1228         'title' => $this->t('More link'),
1229         'value' => $this->getOption('use_more') ? $this->t('Yes') : $this->t('No'),
1230         'desc' => $this->t('Specify whether this display will provide a "more" link.'),
1231       ];
1232     }
1233
1234     $this->view->initQuery();
1235     if ($this->view->query->getAggregationInfo()) {
1236       $options['group_by'] = [
1237         'category' => 'other',
1238         'title' => $this->t('Use aggregation'),
1239         'value' => $this->getOption('group_by') ? $this->t('Yes') : $this->t('No'),
1240         'desc' => $this->t('Allow grouping and aggregation (calculation) of fields.'),
1241       ];
1242     }
1243
1244     $options['query'] = [
1245       'category' => 'other',
1246       'title' => $this->t('Query settings'),
1247       'value' => $this->t('Settings'),
1248       'desc' => $this->t('Allow to set some advanced settings for the query plugin'),
1249     ];
1250
1251     if (\Drupal::languageManager()->isMultilingual() && $this->isBaseTableTranslatable()) {
1252       $rendering_language_options = $this->buildRenderingLanguageOptions();
1253       $options['rendering_language'] = [
1254         'category' => 'language',
1255         'title' => $this->t('Rendering Language'),
1256         'value' => $rendering_language_options[$this->getOption('rendering_language')],
1257         'desc' => $this->t('All content that supports translations will be displayed in the selected language.'),
1258       ];
1259     }
1260
1261     $access_plugin = $this->getPlugin('access');
1262     if (!$access_plugin) {
1263       // Default to the no access control plugin.
1264       $access_plugin = Views::pluginManager('access')->createInstance('none');
1265     }
1266
1267     $access_str = $access_plugin->summaryTitle();
1268
1269     $options['access'] = [
1270       'category' => 'access',
1271       'title' => $this->t('Access'),
1272       'value' => $access_plugin->pluginTitle(),
1273       'setting' => $access_str,
1274       'desc' => $this->t('Specify access control type for this display.'),
1275     ];
1276
1277     if ($access_plugin->usesOptions()) {
1278       $options['access']['links']['access_options'] = $this->t('Change settings for this access type.');
1279     }
1280
1281     $cache_plugin = $this->getPlugin('cache');
1282     if (!$cache_plugin) {
1283       // Default to the no cache control plugin.
1284       $cache_plugin = Views::pluginManager('cache')->createInstance('none');
1285     }
1286
1287     $cache_str = $cache_plugin->summaryTitle();
1288
1289     $options['cache'] = [
1290       'category' => 'other',
1291       'title' => $this->t('Caching'),
1292       'value' => $cache_plugin->pluginTitle(),
1293       'setting' => $cache_str,
1294       'desc' => $this->t('Specify caching type for this display.'),
1295     ];
1296
1297     if ($cache_plugin->usesOptions()) {
1298       $options['cache']['links']['cache_options'] = $this->t('Change settings for this caching type.');
1299     }
1300
1301     if ($access_plugin->usesOptions()) {
1302       $options['access']['links']['access_options'] = $this->t('Change settings for this access type.');
1303     }
1304
1305     if ($this->usesLinkDisplay()) {
1306       $link_display_option = $this->getOption('link_display');
1307       $link_display = $this->t('None');
1308
1309       if ($link_display_option == 'custom_url') {
1310         $link_display = $this->t('Custom URL');
1311       }
1312       elseif (!empty($link_display_option)) {
1313         $display_id = $this->getLinkDisplay();
1314         $displays = $this->view->storage->get('display');
1315         if (!empty($displays[$display_id])) {
1316           $link_display = $displays[$display_id]['display_title'];
1317         }
1318       }
1319
1320       $options['link_display'] = [
1321         'category' => 'pager',
1322         'title' => $this->t('Link display'),
1323         'value' => $link_display,
1324         'desc' => $this->t('Specify which display or custom URL this display will link to.'),
1325       ];
1326     }
1327
1328     if ($this->usesExposedFormInBlock()) {
1329       $options['exposed_block'] = [
1330         'category' => 'exposed',
1331         'title' => $this->t('Exposed form in block'),
1332         'value' => $this->getOption('exposed_block') ? $this->t('Yes') : $this->t('No'),
1333         'desc' => $this->t('Allow the exposed form to appear in a block instead of the view.'),
1334       ];
1335     }
1336
1337     /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form_plugin */
1338     $exposed_form_plugin = $this->getPlugin('exposed_form');
1339     if (!$exposed_form_plugin) {
1340       // Default to the no cache control plugin.
1341       $exposed_form_plugin = Views::pluginManager('exposed_form')->createInstance('basic');
1342     }
1343
1344     $exposed_form_str = $exposed_form_plugin->summaryTitle();
1345
1346     $options['exposed_form'] = [
1347       'category' => 'exposed',
1348       'title' => $this->t('Exposed form style'),
1349       'value' => $exposed_form_plugin->pluginTitle(),
1350       'setting' => $exposed_form_str,
1351       'desc' => $this->t('Select the kind of exposed filter to use.'),
1352     ];
1353
1354     if ($exposed_form_plugin->usesOptions()) {
1355       $options['exposed_form']['links']['exposed_form_options'] = $this->t('Exposed form settings for this exposed form style.');
1356     }
1357
1358     $css_class = trim($this->getOption('css_class'));
1359     if (!$css_class) {
1360       $css_class = $this->t('None');
1361     }
1362
1363     $options['css_class'] = [
1364       'category' => 'other',
1365       'title' => $this->t('CSS class'),
1366       'value' => $css_class,
1367       'desc' => $this->t('Change the CSS class name(s) that will be added to this display.'),
1368     ];
1369
1370     foreach ($this->extenders as $extender) {
1371       $extender->optionsSummary($categories, $options);
1372     }
1373   }
1374
1375   /**
1376    * {@inheritdoc}
1377    */
1378   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
1379     parent::buildOptionsForm($form, $form_state);
1380     $section = $form_state->get('section');
1381     if ($this->defaultableSections($section)) {
1382       views_ui_standard_display_dropdown($form, $form_state, $section);
1383     }
1384     $form['#title'] = $this->display['display_title'] . ': ';
1385
1386     // Set the 'section' to highlight on the form.
1387     // If it's the item we're looking at is pulling from the default display,
1388     // reflect that. Don't use is_defaulted since we want it to show up even
1389     // on the default display.
1390     if (!empty($this->options['defaults'][$section])) {
1391       $form['#section'] = 'default-' . $section;
1392     }
1393     else {
1394       $form['#section'] = $this->display['id'] . '-' . $section;
1395     }
1396
1397     switch ($section) {
1398       case 'display_id':
1399         $form['#title'] .= $this->t('The machine name of this display');
1400         $form['display_id'] = [
1401           '#type' => 'textfield',
1402           '#title' => $this->t('Machine name of the display'),
1403           '#default_value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'],
1404           '#required' => TRUE,
1405           '#size' => 64,
1406         ];
1407         break;
1408       case 'display_title':
1409         $form['#title'] .= $this->t('The name and the description of this display');
1410         $form['display_title'] = [
1411           '#title' => $this->t('Administrative name'),
1412           '#type' => 'textfield',
1413           '#default_value' => $this->display['display_title'],
1414         ];
1415         $form['display_description'] = [
1416           '#title' => $this->t('Administrative description'),
1417           '#type' => 'textfield',
1418           '#default_value' => $this->getOption('display_description'),
1419         ];
1420         break;
1421       case 'display_comment':
1422         $form['#title'] .= $this->t('Administrative comment');
1423         $form['display_comment'] = [
1424           '#type' => 'textarea',
1425           '#title' => $this->t('Administrative comment'),
1426           '#description' => $this->t('This description will only be seen within the administrative interface and can be used to document this display.'),
1427           '#default_value' => $this->getOption('display_comment'),
1428         ];
1429         break;
1430       case 'title':
1431         $form['#title'] .= $this->t('The title of this view');
1432         $form['title'] = [
1433           '#title' => $this->t('Title'),
1434           '#type' => 'textfield',
1435           '#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.'),
1436           '#default_value' => $this->getOption('title'),
1437           '#maxlength' => 255,
1438         ];
1439         break;
1440       case 'css_class':
1441         $form['#title'] .= $this->t('CSS class');
1442         $form['css_class'] = [
1443           '#type' => 'textfield',
1444           '#title' => $this->t('CSS class name(s)'),
1445           '#description' => $this->t('Separate multiple classes by spaces.'),
1446           '#default_value' => $this->getOption('css_class'),
1447         ];
1448         break;
1449       case 'use_ajax':
1450         $form['#title'] .= $this->t('AJAX');
1451         $form['use_ajax'] = [
1452           '#description' => $this->t('Options such as paging, table sorting, and exposed filters will not initiate a page refresh.'),
1453           '#type' => 'checkbox',
1454           '#title' => $this->t('Use AJAX'),
1455           '#default_value' => $this->getOption('use_ajax') ? 1 : 0,
1456         ];
1457         break;
1458       case 'hide_attachment_summary':
1459         $form['#title'] .= $this->t('Hide attachments when displaying a contextual filter summary');
1460         $form['hide_attachment_summary'] = [
1461           '#type' => 'checkbox',
1462           '#title' => $this->t('Hide attachments in summary'),
1463           '#default_value' => $this->getOption('hide_attachment_summary') ? 1 : 0,
1464         ];
1465         break;
1466       case 'show_admin_links':
1467         $form['#title'] .= $this->t('Show contextual links on this view.');
1468         $form['show_admin_links'] = [
1469           '#type' => 'checkbox',
1470           '#title' => $this->t('Show contextual links'),
1471           '#default_value' => $this->getOption('show_admin_links'),
1472         ];
1473         break;
1474       case 'use_more':
1475         $form['#title'] .= $this->t('Add a more link to the bottom of the display.');
1476         $form['use_more'] = [
1477           '#type' => 'checkbox',
1478           '#title' => $this->t('Create more link'),
1479           '#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."),
1480           '#default_value' => $this->getOption('use_more'),
1481         ];
1482         $form['use_more_always'] = [
1483           '#type' => 'checkbox',
1484           '#title' => $this->t('Always display the more link'),
1485           '#description' => $this->t('Check this to display the more link even if there are no more items to display.'),
1486           '#default_value' => $this->getOption('use_more_always'),
1487           '#states' => [
1488             'visible' => [
1489               ':input[name="use_more"]' => ['checked' => TRUE],
1490             ],
1491           ],
1492         ];
1493         $form['use_more_text'] = [
1494           '#type' => 'textfield',
1495           '#title' => $this->t('More link text'),
1496           '#description' => $this->t('The text to display for the more link.'),
1497           '#default_value' => $this->getOption('use_more_text'),
1498           '#states' => [
1499             'visible' => [
1500               ':input[name="use_more"]' => ['checked' => TRUE],
1501             ],
1502           ],
1503         ];
1504         break;
1505       case 'group_by':
1506         $form['#title'] .= $this->t('Allow grouping and aggregation (calculation) of fields.');
1507         $form['group_by'] = [
1508           '#type' => 'checkbox',
1509           '#title' => $this->t('Aggregate'),
1510           '#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.'),
1511           '#default_value' => $this->getOption('group_by'),
1512         ];
1513         break;
1514       case 'access':
1515         $form['#title'] .= $this->t('Access restrictions');
1516         $form['access'] = [
1517           '#prefix' => '<div class="clearfix">',
1518           '#suffix' => '</div>',
1519           '#tree' => TRUE,
1520         ];
1521
1522         $access = $this->getOption('access');
1523         $form['access']['type'] = [
1524           '#title' => $this->t('Access'),
1525           '#title_display' => 'invisible',
1526           '#type' => 'radios',
1527           '#options' => Views::fetchPluginNames('access', $this->getType(), [$this->view->storage->get('base_table')]),
1528           '#default_value' => $access['type'],
1529         ];
1530
1531         $access_plugin = $this->getPlugin('access');
1532         if ($access_plugin->usesOptions()) {
1533           $form['markup'] = [
1534             '#prefix' => '<div class="js-form-item form-item description">',
1535             '#markup' => $this->t('You may also adjust the @settings for the currently selected access restriction.', ['@settings' => $this->optionLink($this->t('settings'), 'access_options')]),
1536             '#suffix' => '</div>',
1537           ];
1538         }
1539
1540         break;
1541       case 'access_options':
1542         $plugin = $this->getPlugin('access');
1543         $form['#title'] .= $this->t('Access options');
1544         if ($plugin) {
1545           $form['access_options'] = [
1546             '#tree' => TRUE,
1547           ];
1548           $plugin->buildOptionsForm($form['access_options'], $form_state);
1549         }
1550         break;
1551       case 'cache':
1552         $form['#title'] .= $this->t('Caching');
1553         $form['cache'] = [
1554           '#prefix' => '<div class="clearfix">',
1555           '#suffix' => '</div>',
1556           '#tree' => TRUE,
1557         ];
1558
1559         $cache = $this->getOption('cache');
1560         $form['cache']['type'] = [
1561           '#title' => $this->t('Caching'),
1562           '#title_display' => 'invisible',
1563           '#type' => 'radios',
1564           '#options' => Views::fetchPluginNames('cache', $this->getType(), [$this->view->storage->get('base_table')]),
1565           '#default_value' => $cache['type'],
1566         ];
1567
1568         $cache_plugin = $this->getPlugin('cache');
1569         if ($cache_plugin->usesOptions()) {
1570           $form['markup'] = [
1571             '#prefix' => '<div class="js-form-item form-item description">',
1572             '#suffix' => '</div>',
1573             '#markup' => $this->t('You may also adjust the @settings for the currently selected cache mechanism.', ['@settings' => $this->optionLink($this->t('settings'), 'cache_options')]),
1574           ];
1575         }
1576         break;
1577       case 'cache_options':
1578         $plugin = $this->getPlugin('cache');
1579         $form['#title'] .= $this->t('Caching options');
1580         if ($plugin) {
1581           $form['cache_options'] = [
1582             '#tree' => TRUE,
1583           ];
1584           $plugin->buildOptionsForm($form['cache_options'], $form_state);
1585         }
1586         break;
1587       case 'query':
1588         $query_options = $this->getOption('query');
1589         $plugin_name = $query_options['type'];
1590
1591         $form['#title'] .= $this->t('Query options');
1592         $this->view->initQuery();
1593         if ($this->view->query) {
1594           $form['query'] = [
1595             '#tree' => TRUE,
1596             'type' => [
1597               '#type' => 'value',
1598               '#value' => $plugin_name,
1599             ],
1600             'options' => [
1601               '#tree' => TRUE,
1602             ],
1603           ];
1604
1605           $this->view->query->buildOptionsForm($form['query']['options'], $form_state);
1606         }
1607         break;
1608       case 'rendering_language':
1609         $form['#title'] .= $this->t('Rendering language');
1610         if (\Drupal::languageManager()->isMultilingual() && $this->isBaseTableTranslatable()) {
1611           $options = $this->buildRenderingLanguageOptions();
1612           $form['rendering_language'] = [
1613             '#type' => 'select',
1614             '#options' => $options,
1615             '#title' => $this->t('Rendering language'),
1616             '#description' => $this->t('All content that supports translations will be displayed in the selected language.'),
1617             '#default_value' => $this->getOption('rendering_language'),
1618           ];
1619         }
1620         else {
1621           $form['rendering_language']['#markup'] = $this->t('The view is not based on a translatable entity type or the site is not multilingual.');
1622         }
1623         break;
1624       case 'style':
1625         $form['#title'] .= $this->t('How should this view be styled');
1626         $style_plugin = $this->getPlugin('style');
1627         $form['style'] = [
1628           '#prefix' => '<div class="clearfix">',
1629           '#suffix' => '</div>',
1630           '#tree' => TRUE,
1631         ];
1632         $form['style']['type'] = [
1633           '#title' => $this->t('Style'),
1634           '#title_display' => 'invisible',
1635           '#type' => 'radios',
1636           '#options' => Views::fetchPluginNames('style', $this->getType(), [$this->view->storage->get('base_table')]),
1637           '#default_value' => $style_plugin->definition['id'],
1638           '#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.'),
1639         ];
1640
1641         if ($style_plugin->usesOptions()) {
1642           $form['markup'] = [
1643             '#prefix' => '<div class="js-form-item form-item description">',
1644             '#suffix' => '</div>',
1645             '#markup' => $this->t('You may also adjust the @settings for the currently selected style.', ['@settings' => $this->optionLink($this->t('settings'), 'style_options')]),
1646           ];
1647         }
1648
1649         break;
1650       case 'style_options':
1651         $form['#title'] .= $this->t('Style options');
1652         $style = TRUE;
1653         $style_plugin = $this->getOption('style');
1654         $name = $style_plugin['type'];
1655
1656       case 'row_options':
1657         if (!isset($name)) {
1658           $row_plugin = $this->getOption('row');
1659           $name = $row_plugin['type'];
1660         }
1661         // If row, $style will be empty.
1662         if (empty($style)) {
1663           $form['#title'] .= $this->t('Row style options');
1664         }
1665         $plugin = $this->getPlugin(empty($style) ? 'row' : 'style', $name);
1666         if ($plugin) {
1667           $form[$section] = [
1668             '#tree' => TRUE,
1669           ];
1670           $plugin->buildOptionsForm($form[$section], $form_state);
1671         }
1672         break;
1673       case 'row':
1674         $form['#title'] .= $this->t('How should each row in this view be styled');
1675         $row_plugin_instance = $this->getPlugin('row');
1676         $form['row'] = [
1677           '#prefix' => '<div class="clearfix">',
1678           '#suffix' => '</div>',
1679           '#tree' => TRUE,
1680         ];
1681         $form['row']['type'] = [
1682           '#title' => $this->t('Row'),
1683           '#title_display' => 'invisible',
1684           '#type' => 'radios',
1685           '#options' => Views::fetchPluginNames('row', $this->getType(), [$this->view->storage->get('base_table')]),
1686           '#default_value' => $row_plugin_instance->definition['id'],
1687         ];
1688
1689         if ($row_plugin_instance->usesOptions()) {
1690           $form['markup'] = [
1691             '#prefix' => '<div class="js-form-item form-item description">',
1692             '#suffix' => '</div>',
1693             '#markup' => $this->t('You may also adjust the @settings for the currently selected row style.', ['@settings' => $this->optionLink($this->t('settings'), 'row_options')]),
1694           ];
1695         }
1696
1697         break;
1698       case 'link_display':
1699         $form['#title'] .= $this->t('Which display to use for path');
1700         $options = [FALSE => $this->t('None'), 'custom_url' => $this->t('Custom URL')];
1701
1702         foreach ($this->view->storage->get('display') as $display_id => $display) {
1703           if ($this->view->displayHandlers->get($display_id)->hasPath()) {
1704             $options[$display_id] = $display['display_title'];
1705           }
1706         }
1707
1708         $form['link_display'] = [
1709           '#type' => 'radios',
1710           '#options' => $options,
1711           '#description' => $this->t("Which display to use to get this display's path for things like summary links, rss feed links, more links, etc."),
1712           '#default_value' => $this->getOption('link_display'),
1713         ];
1714
1715         $options = [];
1716         $optgroup_arguments = (string) t('Arguments');
1717         foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
1718           $options[$optgroup_arguments]["{{ arguments.$arg }}"] = $this->t('@argument title', ['@argument' => $handler->adminLabel()]);
1719           $options[$optgroup_arguments]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', ['@argument' => $handler->adminLabel()]);
1720         }
1721
1722         // Default text.
1723         // We have some options, so make a list.
1724         $description = [];
1725         $description[] = [
1726           '#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.'),
1727         ];
1728         if (!empty($options)) {
1729           $description[] = [
1730             '#prefix' => '<p>',
1731             '#markup' => $this->t('The following tokens are available for this link. You may use Twig syntax in this field.'),
1732             '#suffix' => '</p>',
1733           ];
1734           foreach (array_keys($options) as $type) {
1735             if (!empty($options[$type])) {
1736               $items = [];
1737               foreach ($options[$type] as $key => $value) {
1738                 $items[] = $key . ' == ' . $value;
1739               }
1740               $item_list = [
1741                 '#theme' => 'item_list',
1742                 '#items' => $items,
1743               ];
1744               $description[] = $item_list;
1745             }
1746           }
1747         }
1748
1749         $form['link_url'] = [
1750           '#type' => 'textfield',
1751           '#title' => $this->t('Custom URL'),
1752           '#default_value' => $this->getOption('link_url'),
1753           '#description' => $description,
1754           '#states' => [
1755             'visible' => [
1756               ':input[name="link_display"]' => ['value' => 'custom_url'],
1757             ],
1758           ],
1759         ];
1760         break;
1761       case 'exposed_block':
1762         $form['#title'] .= $this->t('Put the exposed form in a block');
1763         $form['description'] = [
1764           '#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>',
1765         ];
1766         $form['exposed_block'] = [
1767           '#type' => 'radios',
1768           '#options' => [1 => $this->t('Yes'), 0 => $this->t('No')],
1769           '#default_value' => $this->getOption('exposed_block') ? 1 : 0,
1770         ];
1771         break;
1772       case 'exposed_form':
1773         $form['#title'] .= $this->t('Exposed Form');
1774         $form['exposed_form'] = [
1775           '#prefix' => '<div class="clearfix">',
1776           '#suffix' => '</div>',
1777           '#tree' => TRUE,
1778         ];
1779
1780         $exposed_form = $this->getOption('exposed_form');
1781         $form['exposed_form']['type'] = [
1782           '#title' => $this->t('Exposed form'),
1783           '#title_display' => 'invisible',
1784           '#type' => 'radios',
1785           '#options' => Views::fetchPluginNames('exposed_form', $this->getType(), [$this->view->storage->get('base_table')]),
1786           '#default_value' => $exposed_form['type'],
1787         ];
1788
1789         $exposed_form_plugin = $this->getPlugin('exposed_form');
1790         if ($exposed_form_plugin->usesOptions()) {
1791           $form['markup'] = [
1792             '#prefix' => '<div class="js-form-item form-item description">',
1793             '#suffix' => '</div>',
1794             '#markup' => $this->t('You may also adjust the @settings for the currently selected style.', ['@settings' => $this->optionLink($this->t('settings'), 'exposed_form_options')]),
1795           ];
1796         }
1797         break;
1798       case 'exposed_form_options':
1799         $plugin = $this->getPlugin('exposed_form');
1800         $form['#title'] .= $this->t('Exposed form options');
1801         if ($plugin) {
1802           $form['exposed_form_options'] = [
1803             '#tree' => TRUE,
1804           ];
1805           $plugin->buildOptionsForm($form['exposed_form_options'], $form_state);
1806         }
1807         break;
1808       case 'pager':
1809         $form['#title'] .= $this->t('Select pager');
1810         $form['pager'] = [
1811           '#prefix' => '<div class="clearfix">',
1812           '#suffix' => '</div>',
1813           '#tree' => TRUE,
1814         ];
1815
1816         $pager = $this->getOption('pager');
1817         $form['pager']['type'] = [
1818           '#title' => $this->t('Pager'),
1819           '#title_display' => 'invisible',
1820           '#type' => 'radios',
1821           '#options' => Views::fetchPluginNames('pager', !$this->usesPager() ? 'basic' : NULL, [$this->view->storage->get('base_table')]),
1822           '#default_value' => $pager['type'],
1823         ];
1824
1825         $pager_plugin = $this->getPlugin('pager');
1826         if ($pager_plugin->usesOptions()) {
1827           $form['markup'] = [
1828             '#prefix' => '<div class="js-form-item form-item description">',
1829             '#suffix' => '</div>',
1830             '#markup' => $this->t('You may also adjust the @settings for the currently selected pager.', ['@settings' => $this->optionLink($this->t('settings'), 'pager_options')]),
1831           ];
1832         }
1833
1834         break;
1835       case 'pager_options':
1836         $plugin = $this->getPlugin('pager');
1837         $form['#title'] .= $this->t('Pager options');
1838         if ($plugin) {
1839           $form['pager_options'] = [
1840             '#tree' => TRUE,
1841           ];
1842           $plugin->buildOptionsForm($form['pager_options'], $form_state);
1843         }
1844         break;
1845     }
1846
1847     foreach ($this->extenders as $extender) {
1848       $extender->buildOptionsForm($form, $form_state);
1849     }
1850   }
1851
1852   /**
1853    * {@inheritdoc}
1854    */
1855   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
1856     $section = $form_state->get('section');
1857     switch ($section) {
1858       case 'display_title':
1859         if ($form_state->isValueEmpty('display_title')) {
1860           $form_state->setError($form['display_title'], $this->t('Display title may not be empty.'));
1861         }
1862         break;
1863       case 'css_class':
1864         $css_class = $form_state->getValue('css_class');
1865         if (preg_match('/[^a-zA-Z0-9-_ ]/', $css_class)) {
1866           $form_state->setError($form['css_class'], $this->t('CSS classes must be alphanumeric or dashes only.'));
1867         }
1868         break;
1869       case 'display_id':
1870         if ($form_state->getValue('display_id')) {
1871           if (preg_match('/[^a-z0-9_]/', $form_state->getValue('display_id'))) {
1872             $form_state->setError($form['display_id'], $this->t('Display name must be letters, numbers, or underscores only.'));
1873           }
1874
1875           foreach ($this->view->displayHandlers as $id => $display) {
1876             if ($id != $this->view->current_display && ($form_state->getValue('display_id') == $id || (isset($display->new_id) && $form_state->getValue('display_id') == $display->new_id))) {
1877               $form_state->setError($form['display_id'], $this->t('Display id should be unique.'));
1878             }
1879           }
1880         }
1881         break;
1882       case 'query':
1883         if ($this->view->query) {
1884           $this->view->query->validateOptionsForm($form['query'], $form_state);
1885         }
1886         break;
1887     }
1888
1889     // Validate plugin options. Every section with "_options" in it, belongs to
1890     // a plugin type, like "style_options".
1891     if (strpos($section, '_options') !== FALSE) {
1892       $plugin_type = str_replace('_options', '', $section);
1893       // Load the plugin and let it handle the validation.
1894       if ($plugin = $this->getPlugin($plugin_type)) {
1895         $plugin->validateOptionsForm($form[$section], $form_state);
1896       }
1897     }
1898
1899     foreach ($this->extenders as $extender) {
1900       $extender->validateOptionsForm($form, $form_state);
1901     }
1902   }
1903
1904   /**
1905    * {@inheritdoc}
1906    */
1907   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
1908     // Not sure I like this being here, but it seems (?) like a logical place.
1909     $cache_plugin = $this->getPlugin('cache');
1910     if ($cache_plugin) {
1911       $cache_plugin->cacheFlush();
1912     }
1913
1914     $section = $form_state->get('section');
1915     switch ($section) {
1916       case 'display_id':
1917         if ($form_state->hasValue('display_id')) {
1918           $this->display['new_id'] = $form_state->getValue('display_id');
1919         }
1920         break;
1921       case 'display_title':
1922         $this->display['display_title'] = $form_state->getValue('display_title');
1923         $this->setOption('display_description', $form_state->getValue('display_description'));
1924         break;
1925       case 'query':
1926         $plugin = $this->getPlugin('query');
1927         if ($plugin) {
1928           $plugin->submitOptionsForm($form['query']['options'], $form_state);
1929           $this->setOption('query', $form_state->getValue($section));
1930         }
1931         break;
1932
1933       case 'link_display':
1934         $this->setOption('link_url', $form_state->getValue('link_url'));
1935       case 'title':
1936       case 'css_class':
1937       case 'display_comment':
1938       case 'distinct':
1939       case 'group_by':
1940         $this->setOption($section, $form_state->getValue($section));
1941         break;
1942       case 'rendering_language':
1943         $this->setOption('rendering_language', $form_state->getValue('rendering_language'));
1944         break;
1945       case 'use_ajax':
1946       case 'hide_attachment_summary':
1947       case 'show_admin_links':
1948       case 'exposed_block':
1949         $this->setOption($section, (bool) $form_state->getValue($section));
1950         break;
1951       case 'use_more':
1952         $this->setOption($section, intval($form_state->getValue($section)));
1953         $this->setOption('use_more_always', intval($form_state->getValue('use_more_always')));
1954         $this->setOption('use_more_text', $form_state->getValue('use_more_text'));
1955         break;
1956
1957       case 'access':
1958       case 'cache':
1959       case 'exposed_form':
1960       case 'pager':
1961       case 'row':
1962       case 'style':
1963         $plugin_type = $section;
1964         $plugin_options = $this->getOption($plugin_type);
1965         $type = $form_state->getValue([$plugin_type, 'type']);
1966         if ($plugin_options['type'] != $type) {
1967           /** @var \Drupal\views\Plugin\views\ViewsPluginInterface $plugin */
1968           $plugin = Views::pluginManager($plugin_type)->createInstance($type);
1969           if ($plugin) {
1970             $plugin->init($this->view, $this, $plugin_options['options']);
1971             $plugin_options = [
1972               'type' => $type,
1973               'options' => $plugin->options,
1974             ];
1975             $plugin->filterByDefinedOptions($plugin_options['options']);
1976             $this->setOption($plugin_type, $plugin_options);
1977             if ($plugin->usesOptions()) {
1978               $form_state->get('view')->addFormToStack('display', $this->display['id'], $plugin_type . '_options');
1979             }
1980           }
1981         }
1982         break;
1983
1984       case 'access_options':
1985       case 'cache_options':
1986       case 'exposed_form_options':
1987       case 'pager_options':
1988       case 'row_options':
1989       case 'style_options':
1990         // Submit plugin options. Every section with "_options" in it, belongs to
1991         // a plugin type, like "style_options".
1992         $plugin_type = str_replace('_options', '', $section);
1993         if ($plugin = $this->getPlugin($plugin_type)) {
1994           $plugin_options = $this->getOption($plugin_type);
1995           $plugin->submitOptionsForm($form[$plugin_type . '_options'], $form_state);
1996           $plugin_options['options'] = $form_state->getValue($section);
1997           $this->setOption($plugin_type, $plugin_options);
1998         }
1999         break;
2000     }
2001
2002     $extender_options = $this->getOption('display_extenders');
2003     foreach ($this->extenders as $extender) {
2004       $extender->submitOptionsForm($form, $form_state);
2005
2006       $plugin_id = $extender->getPluginId();
2007       $extender_options[$plugin_id] = $extender->options;
2008     }
2009     $this->setOption('display_extenders', $extender_options);
2010   }
2011
2012   /**
2013    * {@inheritdoc}
2014    */
2015   public function optionsOverride($form, FormStateInterface $form_state) {
2016     $this->setOverride($form_state->get('section'));
2017   }
2018
2019   /**
2020    * {@inheritdoc}
2021    */
2022   public function setOverride($section, $new_state = NULL) {
2023     $options = $this->defaultableSections($section);
2024     if (!$options) {
2025       return;
2026     }
2027
2028     if (!isset($new_state)) {
2029       $new_state = empty($this->options['defaults'][$section]);
2030     }
2031
2032     // For each option that is part of this group, fix our settings.
2033     foreach ($options as $option) {
2034       if ($new_state) {
2035         // Revert to defaults.
2036         unset($this->options[$option]);
2037         unset($this->display['display_options'][$option]);
2038       }
2039       else {
2040         // Copy existing values into our display.
2041         $this->options[$option] = $this->getOption($option);
2042         $this->display['display_options'][$option] = $this->options[$option];
2043       }
2044       $this->options['defaults'][$option] = $new_state;
2045       $this->display['display_options']['defaults'][$option] = $new_state;
2046     }
2047   }
2048
2049   /**
2050    * {@inheritdoc}
2051    */
2052   public function query() {
2053     foreach ($this->extenders as $extender) {
2054       $extender->query();
2055     }
2056   }
2057
2058   /**
2059    * {@inheritdoc}
2060    */
2061   public function renderFilters() {}
2062
2063   /**
2064    * {@inheritdoc}
2065    */
2066   public function renderPager() {
2067     return TRUE;
2068   }
2069
2070   /**
2071    * {@inheritdoc}
2072    */
2073   public function renderMoreLink() {
2074     if ($this->isMoreEnabled() && ($this->useMoreAlways() || (!empty($this->view->pager) && $this->view->pager->hasMoreRecords()))) {
2075       // If the user has supplied a custom "More" link path, replace any
2076       // argument tokens and use that for the URL.
2077       if ($this->getOption('link_display') == 'custom_url' && $override_path = $this->getOption('link_url')) {
2078         $tokens = $this->getArgumentsTokens();
2079         $path = $this->viewsTokenReplace($override_path, $tokens);
2080         // @todo Views should expect and store a leading /. See:
2081         //   https://www.drupal.org/node/2423913
2082         $url = Url::fromUserInput('/' . $path);
2083       }
2084       // Otherwise, use the URL for the display.
2085       else {
2086         $url = $this->view->getUrl(NULL, $this->display['id']);
2087       }
2088
2089       // If a URL is available (either from the display or a custom path),
2090       // render the "More" link.
2091       if ($url) {
2092         $url_options = [];
2093         if (!empty($this->view->exposed_raw_input)) {
2094           $url_options['query'] = $this->view->exposed_raw_input;
2095         }
2096         $url->setOptions($url_options);
2097
2098         return [
2099           '#type' => 'more_link',
2100           '#url' => $url,
2101           '#title' => $this->useMoreText(),
2102           '#view' => $this->view,
2103         ];
2104       }
2105     }
2106   }
2107
2108   /**
2109    * {@inheritdoc}
2110    */
2111   public function render() {
2112     $rows = (!empty($this->view->result) || $this->view->style_plugin->evenEmpty()) ? $this->view->style_plugin->render($this->view->result) : [];
2113
2114     $element = [
2115       '#theme' => $this->themeFunctions(),
2116       '#view' => $this->view,
2117       '#pre_render' => [[$this, 'elementPreRender']],
2118       '#rows' => $rows,
2119       // Assigned by reference so anything added in $element['#attached'] will
2120       // be available on the view.
2121       '#attached' => &$this->view->element['#attached'],
2122       '#cache' => &$this->view->element['#cache'],
2123     ];
2124
2125     $this->applyDisplayCacheabilityMetadata($this->view->element);
2126
2127     return $element;
2128   }
2129
2130   /**
2131    * Applies the cacheability of the current display to the given render array.
2132    *
2133    * @param array $element
2134    *   The render array with updated cacheability metadata.
2135    */
2136   protected function applyDisplayCacheabilityMetadata(array &$element) {
2137     /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
2138     $cache = $this->getPlugin('cache');
2139
2140     (new CacheableMetadata())
2141       ->setCacheTags(Cache::mergeTags($this->view->getCacheTags(), isset($this->display['cache_metadata']['tags']) ? $this->display['cache_metadata']['tags'] : []))
2142       ->setCacheContexts(isset($this->display['cache_metadata']['contexts']) ? $this->display['cache_metadata']['contexts'] : [])
2143       ->setCacheMaxAge(Cache::mergeMaxAges($cache->getCacheMaxAge(), isset($this->display['cache_metadata']['max-age']) ? $this->display['cache_metadata']['max-age'] : Cache::PERMANENT))
2144       ->merge(CacheableMetadata::createFromRenderArray($element))
2145       ->applyTo($element);
2146   }
2147
2148   /**
2149    * Applies the cacheability of the current display to the given render array.
2150    *
2151    * @param array $element
2152    *   The render array with updated cacheability metadata.
2153    *
2154    * @deprecated in Drupal 8.4.0, will be removed before Drupal 9.0. Use
2155    *   DisplayPluginBase::applyDisplayCacheabilityMetadata instead.
2156    *
2157    * @see \Drupal\views\Plugin\views\display\DisplayPluginBase::applyDisplayCacheabilityMetadata()
2158    */
2159   protected function applyDisplayCachablityMetadata(array &$element) {
2160     @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);
2161     $this->applyDisplayCacheabilityMetadata($element);
2162   }
2163
2164   /**
2165    * {@inheritdoc}
2166    */
2167   public function elementPreRender(array $element) {
2168     $view = $element['#view'];
2169     $empty = empty($view->result);
2170
2171     // Force a render array so CSS/JS can be attached.
2172     if (!is_array($element['#rows'])) {
2173       $element['#rows'] = ['#markup' => $element['#rows']];
2174     }
2175
2176     $element['#header'] = $view->display_handler->renderArea('header', $empty);
2177     $element['#footer'] = $view->display_handler->renderArea('footer', $empty);
2178     $element['#empty'] = $empty ? $view->display_handler->renderArea('empty', $empty) : [];
2179     $element['#exposed'] = !empty($view->exposed_widgets) ? $view->exposed_widgets : [];
2180     $element['#more'] = $view->display_handler->renderMoreLink();
2181     $element['#feed_icons'] = !empty($view->feedIcons) ? $view->feedIcons : [];
2182
2183     if ($view->display_handler->renderPager()) {
2184       $exposed_input = isset($view->exposed_raw_input) ? $view->exposed_raw_input : NULL;
2185       $element['#pager'] = $view->renderPager($exposed_input);
2186     }
2187
2188     if (!empty($view->attachment_before)) {
2189       $element['#attachment_before'] = $view->attachment_before;
2190     }
2191     if (!empty($view->attachment_after)) {
2192       $element['#attachment_after'] = $view->attachment_after;
2193     }
2194
2195     // If form fields were found in the view, reformat the view output as a form.
2196     if ($view->hasFormElements()) {
2197       // Only render row output if there are rows. Otherwise, render the empty
2198       // region.
2199       if (!empty($element['#rows'])) {
2200         $output = $element['#rows'];
2201       }
2202       else {
2203         $output = $element['#empty'];
2204       }
2205
2206       $form_object = ViewsForm::create(\Drupal::getContainer(), $view->storage->id(), $view->current_display, $view->args);
2207       $form = \Drupal::formBuilder()->getForm($form_object, $view, $output);
2208       // The form is requesting that all non-essential views elements be hidden,
2209       // usually because the rendered step is not a view result.
2210       if ($form['show_view_elements']['#value'] == FALSE) {
2211         $element['#header'] = [];
2212         $element['#exposed'] = [];
2213         $element['#pager'] = [];
2214         $element['#footer'] = [];
2215         $element['#more'] = [];
2216         $element['#feed_icons'] = [];
2217       }
2218
2219       $element['#rows'] = $form;
2220     }
2221
2222     return $element;
2223   }
2224
2225   /**
2226    * {@inheritdoc}
2227    */
2228   public function renderArea($area, $empty = FALSE) {
2229     $return = [];
2230     foreach ($this->getHandlers($area) as $key => $area_handler) {
2231       if ($area_render = $area_handler->render($empty)) {
2232         if (isset($area_handler->position)) {
2233           // Fix weight of area.
2234           $area_render['#weight'] = $area_handler->position;
2235         }
2236         $return[$key] = $area_render;
2237       }
2238     }
2239     return $return;
2240   }
2241
2242   /**
2243    * {@inheritdoc}
2244    */
2245   public function access(AccountInterface $account = NULL) {
2246     if (!isset($account)) {
2247       $account = \Drupal::currentUser();
2248     }
2249
2250     $plugin = $this->getPlugin('access');
2251     /** @var \Drupal\views\Plugin\views\access\AccessPluginBase $plugin */
2252     if ($plugin) {
2253       return $plugin->access($account);
2254     }
2255
2256     // Fallback to all access if no plugin.
2257     return TRUE;
2258   }
2259
2260   /**
2261    * {@inheritdoc}
2262    */
2263   public function preExecute() {
2264     $this->view->setAjaxEnabled($this->ajaxEnabled());
2265     if ($this->isMoreEnabled() && !$this->useMoreAlways()) {
2266       $this->view->get_total_rows = TRUE;
2267     }
2268     $this->view->initHandlers();
2269     if ($this->usesExposed()) {
2270       /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */
2271       $exposed_form = $this->getPlugin('exposed_form');
2272       $exposed_form->preExecute();
2273     }
2274
2275     foreach ($this->extenders as $extender) {
2276       $extender->preExecute();
2277     }
2278   }
2279
2280   /**
2281    * {@inheritdoc}
2282    */
2283   public function calculateCacheMetadata() {
2284     $cache_metadata = new CacheableMetadata();
2285
2286     // Iterate over ordinary views plugins.
2287     foreach (Views::getPluginTypes('plugin') as $plugin_type) {
2288       $plugin = $this->getPlugin($plugin_type);
2289       if ($plugin instanceof CacheableDependencyInterface) {
2290         $cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($plugin));
2291       }
2292     }
2293
2294     // Iterate over all handlers. Note that at least the argument handler will
2295     // need to ask all its subplugins.
2296     foreach (array_keys(Views::getHandlerTypes()) as $handler_type) {
2297       $handlers = $this->getHandlers($handler_type);
2298       foreach ($handlers as $handler) {
2299         if ($handler instanceof CacheableDependencyInterface) {
2300           $cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($handler));
2301         }
2302       }
2303     }
2304
2305     /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */
2306     if ($cache_plugin = $this->getPlugin('cache')) {
2307       $cache_plugin->alterCacheMetadata($cache_metadata);
2308     }
2309
2310     return $cache_metadata;
2311   }
2312
2313   /**
2314    * {@inheritdoc}
2315    */
2316   public function getCacheMetadata() {
2317     if (!isset($this->display['cache_metadata'])) {
2318       $cache_metadata = $this->calculateCacheMetadata();
2319       $this->display['cache_metadata']['max-age'] = $cache_metadata->getCacheMaxAge();
2320       $this->display['cache_metadata']['contexts'] = $cache_metadata->getCacheContexts();
2321       $this->display['cache_metadata']['tags'] = $cache_metadata->getCacheTags();
2322     }
2323     else {
2324       $cache_metadata = (new CacheableMetadata())
2325         ->setCacheMaxAge($this->display['cache_metadata']['max-age'])
2326         ->setCacheContexts($this->display['cache_metadata']['contexts'])
2327         ->setCacheTags($this->display['cache_metadata']['tags']);
2328     }
2329     return $cache_metadata;
2330   }
2331
2332   /**
2333    * {@inheritdoc}
2334    */
2335   public function execute() {}
2336
2337   /**
2338    * {@inheritdoc}
2339    */
2340   public function buildRenderable(array $args = [], $cache = TRUE) {
2341     $this->view->element += [
2342       '#type' => 'view',
2343       '#name' => $this->view->storage->id(),
2344       '#display_id' => $this->display['id'],
2345       '#arguments' => $args,
2346       '#embed' => FALSE,
2347       '#view' => $this->view,
2348       '#cache_properties' => ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'],
2349     ];
2350
2351     // When something passes $cache = FALSE, they're asking us not to create our
2352     // own render cache for it. However, we still need to include certain pieces
2353     // of cacheability metadata (e.g.: cache contexts), so they can bubble up.
2354     // Thus, we add the cacheability metadata first, then modify / remove the
2355     // cache keys depending on the $cache argument.
2356     $this->applyDisplayCacheabilityMetadata($this->view->element);
2357     if ($cache) {
2358       $this->view->element['#cache'] += ['keys' => []];
2359       // Places like \Drupal\views\ViewExecutable::setCurrentPage() set up an
2360       // additional cache context.
2361       $this->view->element['#cache']['keys'] = array_merge(['views', 'display', $this->view->element['#name'], $this->view->element['#display_id']], $this->view->element['#cache']['keys']);
2362     }
2363     else {
2364       // Remove the cache keys, to ensure render caching is not triggered. We
2365       // don't unset the other #cache values, to allow cacheability metadata to
2366       // still be bubbled.
2367       unset($this->view->element['#cache']['keys']);
2368     }
2369
2370     return $this->view->element;
2371   }
2372
2373   /**
2374    * {@inheritdoc}
2375    */
2376   public static function buildBasicRenderable($view_id, $display_id, array $args = []) {
2377     $build = [
2378       '#type' => 'view',
2379       '#name' => $view_id,
2380       '#display_id' => $display_id,
2381       '#arguments' => $args,
2382       '#embed' => FALSE,
2383       '#cache' => [
2384         'keys' => ['view', $view_id, 'display', $display_id],
2385       ],
2386     ];
2387
2388     if ($args) {
2389       $build['#cache']['keys'][] = 'args';
2390       $build['#cache']['keys'][] = implode(',', $args);
2391     }
2392
2393     $build['#cache_properties'] = ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'];
2394
2395     return $build;
2396
2397   }
2398
2399   /**
2400    * {@inheritdoc}
2401    */
2402   public function preview() {
2403     return $this->view->render();
2404   }
2405
2406   /**
2407    * {@inheritdoc}
2408    */
2409   public function getType() {
2410     return 'normal';
2411   }
2412
2413   /**
2414    * {@inheritdoc}
2415    */
2416   public function validate() {
2417     $errors = [];
2418     // Make sure displays that use fields HAVE fields.
2419     if ($this->usesFields()) {
2420       $fields = FALSE;
2421       foreach ($this->getHandlers('field') as $field) {
2422         if (empty($field->options['exclude'])) {
2423           $fields = TRUE;
2424         }
2425       }
2426
2427       if (!$fields) {
2428         $errors[] = $this->t('Display "@display" uses fields but there are none defined for it or all are excluded.', ['@display' => $this->display['display_title']]);
2429       }
2430     }
2431
2432     // Validate the more link.
2433     if ($this->isMoreEnabled() && $this->getOption('link_display') !== 'custom_url') {
2434       $routed_display = $this->getRoutedDisplay();
2435       if (!$routed_display || !$routed_display->isEnabled()) {
2436         $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']]);
2437       }
2438     }
2439
2440     if ($this->hasPath() && !$this->getOption('path')) {
2441       $errors[] = $this->t('Display "@display" uses a path but the path is undefined.', ['@display' => $this->display['display_title']]);
2442     }
2443
2444     // Validate style plugin.
2445     $style = $this->getPlugin('style');
2446     if (empty($style)) {
2447       $errors[] = $this->t('Display "@display" has an invalid style plugin.', ['@display' => $this->display['display_title']]);
2448     }
2449     else {
2450       $result = $style->validate();
2451       if (!empty($result) && is_array($result)) {
2452         $errors = array_merge($errors, $result);
2453       }
2454     }
2455
2456     // Validate query plugin.
2457     $query = $this->getPlugin('query');
2458     $result = $query->validate();
2459     if (!empty($result) && is_array($result)) {
2460       $errors = array_merge($errors, $result);
2461     }
2462
2463     // Check for missing relationships.
2464     $relationships = array_keys($this->getHandlers('relationship'));
2465     foreach (ViewExecutable::getHandlerTypes() as $type => $handler_type_info) {
2466       foreach ($this->getHandlers($type) as $handler_id => $handler) {
2467         if (!empty($handler->options['relationship']) && $handler->options['relationship'] != 'none' && !in_array($handler->options['relationship'], $relationships)) {
2468           $errors[] = $this->t('The %handler_type %handler uses a relationship that has been removed.', ['%handler_type' => $handler_type_info['lstitle'], '%handler' => $handler->adminLabel()]);
2469         }
2470       }
2471     }
2472
2473     // Validate handlers.
2474     foreach (ViewExecutable::getHandlerTypes() as $type => $info) {
2475       foreach ($this->getHandlers($type) as $handler) {
2476         $result = $handler->validate();
2477         if (!empty($result) && is_array($result)) {
2478           $errors = array_merge($errors, $result);
2479         }
2480       }
2481     }
2482
2483     return $errors;
2484   }
2485
2486   /**
2487    * {@inheritdoc}
2488    */
2489   public function newDisplay() {
2490   }
2491
2492   /**
2493    * {@inheritdoc}
2494    */
2495   public function isIdentifierUnique($id, $identifier) {
2496     foreach (ViewExecutable::getHandlerTypes() as $type => $info) {
2497       foreach ($this->getHandlers($type) as $key => $handler) {
2498         if ($handler->canExpose() && $handler->isExposed()) {
2499           if ($handler->isAGroup()) {
2500             if ($id != $key && $identifier == $handler->options['group_info']['identifier']) {
2501               return FALSE;
2502             }
2503           }
2504           else {
2505             if ($id != $key && $identifier == $handler->options['expose']['identifier']) {
2506               return FALSE;
2507             }
2508           }
2509         }
2510       }
2511     }
2512     return TRUE;
2513   }
2514
2515   /**
2516    * {@inheritdoc}
2517    */
2518   public function outputIsEmpty() {
2519     if (!empty($this->view->result)) {
2520       return FALSE;
2521     }
2522
2523     // Check whether all of the area handlers are empty.
2524     foreach (['empty', 'footer', 'header'] as $type) {
2525       $handlers = $this->getHandlers($type);
2526       foreach ($handlers as $handler) {
2527         // If one is not empty, return FALSE now.
2528         if (!$handler->isEmpty()) {
2529           return FALSE;
2530         }
2531       }
2532     }
2533
2534     return TRUE;
2535   }
2536
2537   /**
2538    * {@inheritdoc}
2539    */
2540   public function getSpecialBlocks() {
2541     $blocks = [];
2542
2543     if ($this->usesExposedFormInBlock()) {
2544       $delta = '-exp-' . $this->view->storage->id() . '-' . $this->display['id'];
2545       $desc = $this->t('Exposed form: @view-@display_id', ['@view' => $this->view->storage->id(), '@display_id' => $this->display['id']]);
2546
2547       $blocks[$delta] = [
2548         'info' => $desc,
2549       ];
2550     }
2551
2552     return $blocks;
2553   }
2554
2555   /**
2556    * {@inheritdoc}
2557    */
2558   public function viewExposedFormBlocks() {
2559     // Avoid interfering with the admin forms.
2560     $route_name = \Drupal::routeMatch()->getRouteName();
2561     if (strpos($route_name, 'views_ui.') === 0) {
2562       return;
2563     }
2564     $this->view->initHandlers();
2565
2566     if ($this->usesExposed() && $this->getOption('exposed_block')) {
2567       /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */
2568       $exposed_form = $this->getPlugin('exposed_form');
2569       return $exposed_form->renderExposedForm(TRUE);
2570     }
2571   }
2572
2573   /**
2574    * {@inheritdoc}
2575    */
2576   public function getArgumentText() {
2577     return [
2578       'filter value not present' => $this->t('When the filter value is <em>NOT</em> available'),
2579       'filter value present' => $this->t('When the filter value <em>IS</em> available or a default is provided'),
2580       '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'."),
2581     ];
2582   }
2583
2584   /**
2585    * {@inheritdoc}
2586    */
2587   public function getPagerText() {
2588     return [
2589       'items per page title' => $this->t('Items to display'),
2590       'items per page description' => $this->t('Enter 0 for no limit.')
2591     ];
2592   }
2593
2594   /**
2595    * {@inheritdoc}
2596    */
2597   public function mergeDefaults() {
2598     $defined_options = $this->defineOptions();
2599
2600     // Build a map of plural => singular for handler types.
2601     $type_map = [];
2602     foreach (ViewExecutable::getHandlerTypes() as $type => $info) {
2603       $type_map[$info['plural']] = $type;
2604     }
2605
2606     // Find all defined options, that have specified a merge_defaults callback.
2607     foreach ($defined_options as $type => $definition) {
2608       if (!isset($definition['merge_defaults']) || !is_callable($definition['merge_defaults'])) {
2609         continue;
2610       }
2611       // Switch the type to singular, if it's a plural handler.
2612       if (isset($type_map[$type])) {
2613         $type = $type_map[$type];
2614       }
2615
2616       call_user_func($definition['merge_defaults'], $type);
2617     }
2618   }
2619
2620   /**
2621    * {@inheritdoc}
2622    */
2623   public function remove() {
2624
2625   }
2626
2627   /**
2628    * Merges plugins default values.
2629    *
2630    * @param string $type
2631    *   The name of the plugin type option.
2632    */
2633   protected function mergePlugin($type) {
2634     if (($options = $this->getOption($type)) && isset($options['options'])) {
2635       $plugin = $this->getPlugin($type);
2636       $options['options'] = $options['options'] + $plugin->options;
2637       $this->setOption($type, $options);
2638     }
2639   }
2640
2641   /**
2642    * Merges handlers default values.
2643    *
2644    * @param string $type
2645    *   The name of the handler type option.
2646    */
2647   protected function mergeHandler($type) {
2648     $types = ViewExecutable::getHandlerTypes();
2649
2650     $options = $this->getOption($types[$type]['plural']);
2651     foreach ($this->getHandlers($type) as $id => $handler) {
2652       if (isset($options[$id])) {
2653         $options[$id] = $options[$id] + $handler->options;
2654       }
2655     }
2656
2657     $this->setOption($types[$type]['plural'], $options);
2658   }
2659
2660   /**
2661    * {@inheritdoc}
2662    */
2663   public function getExtenders() {
2664     return $this->extenders;
2665   }
2666
2667   /**
2668    * Returns the available rendering strategies for language-aware entities.
2669    *
2670    * @return array
2671    *   An array of available entity row renderers keyed by renderer identifiers.
2672    */
2673   protected function buildRenderingLanguageOptions() {
2674     // @todo Consider making these plugins. See
2675     //   https://www.drupal.org/node/2173811.
2676     // Pass the current rendering language (in this case a one element array) so
2677     // is not lost when there are language configuration changes.
2678     return $this->listLanguages(LanguageInterface::STATE_CONFIGURABLE | LanguageInterface::STATE_SITE_DEFAULT | PluginBase::INCLUDE_NEGOTIATED | PluginBase::INCLUDE_ENTITY, [$this->getOption('rendering_language')]);
2679   }
2680
2681   /**
2682    * Returns whether the base table is of a translatable entity type.
2683    *
2684    * @return bool
2685    *   TRUE if the base table is of a translatable entity type, FALSE otherwise.
2686    */
2687   protected function isBaseTableTranslatable() {
2688     if ($entity_type = $this->view->getBaseEntityType()) {
2689       return $entity_type->isTranslatable();
2690     }
2691     return FALSE;
2692   }
2693
2694 }
2695
2696 /**
2697  * @}
2698  */