b6841a84bc6da859f7b1a91656777e858cb6bed3
[yaffs-website] / web / core / modules / views_ui / src / ViewUI.php
1 <?php
2
3 namespace Drupal\views_ui;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Utility\Timer;
7 use Drupal\Component\Utility\Xss;
8 use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
9 use Drupal\Core\Form\FormStateInterface;
10 use Drupal\views\Views;
11 use Drupal\Core\Entity\EntityStorageInterface;
12 use Drupal\views\ViewExecutable;
13 use Drupal\Core\Database\Database;
14 use Drupal\Core\Session\AccountInterface;
15 use Drupal\views\Plugin\views\query\Sql;
16 use Drupal\views\Entity\View;
17 use Drupal\views\ViewEntityInterface;
18 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
19 use Symfony\Component\HttpFoundation\ParameterBag;
20 use Symfony\Component\HttpFoundation\Request;
21
22 /**
23  * Stores UI related temporary settings.
24  */
25 class ViewUI implements ViewEntityInterface {
26
27   /**
28    * Indicates if a view is currently being edited.
29    *
30    * @var bool
31    */
32   public $editing = FALSE;
33
34   /**
35    * Stores an array of displays that have been changed.
36    *
37    * @var array
38    */
39   public $changed_display;
40
41   /**
42    * How long the view takes to render in microseconds.
43    *
44    * @var float
45    */
46   public $render_time;
47
48   /**
49    * If this view is locked for editing.
50    *
51    * If this view is locked it will contain the result of
52    * \Drupal\user\SharedTempStore::getMetadata(). Which can be a stdClass or
53    * NULL.
54    *
55    * @var stdClass
56    */
57   public $lock;
58
59   /**
60    * If this view has been changed.
61    *
62    * @var bool
63    */
64   public $changed;
65
66   /**
67    * Stores options temporarily while editing.
68    *
69    * @var array
70    */
71   public $temporary_options;
72
73   /**
74    * Stores a stack of UI forms to display.
75    *
76    * @var array
77    */
78   public $stack;
79
80   /**
81    * Is the view run in a context of the preview in the admin interface.
82    *
83    * @var bool
84    */
85   public $live_preview;
86
87   public $renderPreview = FALSE;
88
89   /**
90    * The View storage object.
91    *
92    * @var \Drupal\views\ViewEntityInterface
93    */
94   protected $storage;
95
96   /**
97    * Stores a list of database queries run beside the main one from views.
98    *
99    * @var array
100    *
101    * @see \Drupal\Core\Database\Log
102    */
103   protected $additionalQueries;
104
105   /**
106    * Contains an array of form keys and their respective classes.
107    *
108    * @var array
109    */
110   public static $forms = [
111     'add-handler' => '\Drupal\views_ui\Form\Ajax\AddItem',
112     'analyze' => '\Drupal\views_ui\Form\Ajax\Analyze',
113     'handler' => '\Drupal\views_ui\Form\Ajax\ConfigHandler',
114     'handler-extra' => '\Drupal\views_ui\Form\Ajax\ConfigHandlerExtra',
115     'handler-group' => '\Drupal\views_ui\Form\Ajax\ConfigHandlerGroup',
116     'display' => '\Drupal\views_ui\Form\Ajax\Display',
117     'edit-details' => '\Drupal\views_ui\Form\Ajax\EditDetails',
118     'rearrange' => '\Drupal\views_ui\Form\Ajax\Rearrange',
119     'rearrange-filter' => '\Drupal\views_ui\Form\Ajax\RearrangeFilter',
120     'reorder-displays' => '\Drupal\views_ui\Form\Ajax\ReorderDisplays',
121   ];
122
123   /**
124    * Whether the config is being created, updated or deleted through the
125    * import process.
126    *
127    * @var bool
128    */
129   private $isSyncing = FALSE;
130
131   /**
132    * Whether the config is being deleted through the uninstall process.
133    *
134    * @var bool
135    */
136   private $isUninstalling = FALSE;
137
138   /**
139    * Constructs a View UI object.
140    *
141    * @param \Drupal\views\ViewEntityInterface $storage
142    *   The View storage object to wrap.
143    */
144   public function __construct(ViewEntityInterface $storage) {
145     $this->entityType = 'view';
146     $this->storage = $storage;
147   }
148
149   /**
150    * {@inheritdoc}
151    */
152   public function get($property_name, $langcode = NULL) {
153     if (property_exists($this->storage, $property_name)) {
154       return $this->storage->get($property_name, $langcode);
155     }
156
157     return isset($this->{$property_name}) ? $this->{$property_name} : NULL;
158   }
159
160   /**
161    * {@inheritdoc}
162    */
163   public function setStatus($status) {
164     return $this->storage->setStatus($status);
165   }
166
167   /**
168    * {@inheritdoc}
169    */
170   public function set($property_name, $value, $notify = TRUE) {
171     if (property_exists($this->storage, $property_name)) {
172       $this->storage->set($property_name, $value);
173     }
174     else {
175       $this->{$property_name} = $value;
176     }
177   }
178
179   /**
180    * {@inheritdoc}
181    */
182   public function setSyncing($syncing) {
183     $this->isSyncing = $syncing;
184   }
185
186   /**
187    * {@inheritdoc}
188    */
189   public function setUninstalling($isUninstalling) {
190     $this->isUninstalling = $isUninstalling;
191   }
192
193   /**
194    * {@inheritdoc}
195    */
196   public function isSyncing() {
197     return $this->isSyncing;
198   }
199
200   /**
201    * {@inheritdoc}
202    */
203   public function isUninstalling() {
204     return $this->isUninstalling;
205   }
206
207   /**
208    * Basic submit handler applicable to all 'standard' forms.
209    *
210    * This submit handler determines whether the user wants the submitted changes
211    * to apply to the default display or to the current display, and dispatches
212    * control appropriately.
213    */
214   public function standardSubmit($form, FormStateInterface $form_state) {
215     // Determine whether the values the user entered are intended to apply to
216     // the current display or the default display.
217
218     list($was_defaulted, $is_defaulted, $revert) = $this->getOverrideValues($form, $form_state);
219
220     // Based on the user's choice in the display dropdown, determine which display
221     // these changes apply to.
222     $display_id = $form_state->get('display_id');
223     if ($revert) {
224       // If it's revert just change the override and return.
225       $display = &$this->getExecutable()->displayHandlers->get($display_id);
226       $display->optionsOverride($form, $form_state);
227
228       // Don't execute the normal submit handling but still store the changed view into cache.
229       $this->cacheSet();
230       return;
231     }
232     elseif ($was_defaulted === $is_defaulted) {
233       // We're not changing which display these form values apply to.
234       // Run the regular submit handler for this form.
235     }
236     elseif ($was_defaulted && !$is_defaulted) {
237       // We were using the default display's values, but we're now overriding
238       // the default display and saving values specific to this display.
239       $display = &$this->getExecutable()->displayHandlers->get($display_id);
240       // optionsOverride toggles the override of this section.
241       $display->optionsOverride($form, $form_state);
242       $display->submitOptionsForm($form, $form_state);
243     }
244     elseif (!$was_defaulted && $is_defaulted) {
245       // We used to have an override for this display, but the user now wants
246       // to go back to the default display.
247       // Overwrite the default display with the current form values, and make
248       // the current display use the new default values.
249       $display = &$this->getExecutable()->displayHandlers->get($display_id);
250       // optionsOverride toggles the override of this section.
251       $display->optionsOverride($form, $form_state);
252       $display->submitOptionsForm($form, $form_state);
253     }
254
255     $submit_handler = [$form_state->getFormObject(), 'submitForm'];
256     call_user_func_array($submit_handler, [&$form, $form_state]);
257   }
258
259   /**
260    * Submit handler for cancel button
261    */
262   public function standardCancel($form, FormStateInterface $form_state) {
263     if (!empty($this->changed) && isset($this->form_cache)) {
264       unset($this->form_cache);
265       $this->cacheSet();
266     }
267
268     $form_state->setRedirectUrl($this->urlInfo('edit-form'));
269   }
270
271   /**
272    * Provide a standard set of Apply/Cancel/OK buttons for the forms. Also provide
273    * a hidden op operator because the forms plugin doesn't seem to properly
274    * provide which button was clicked.
275    *
276    * TODO: Is the hidden op operator still here somewhere, or is that part of the
277    * docblock outdated?
278    */
279   public function getStandardButtons(&$form, FormStateInterface $form_state, $form_id, $name = NULL) {
280     $form['actions'] = [
281       '#type' => 'actions',
282     ];
283
284     if (empty($name)) {
285       $name = t('Apply');
286       if (!empty($this->stack) && count($this->stack) > 1) {
287         $name = t('Apply and continue');
288       }
289       $names = [t('Apply'), t('Apply and continue')];
290     }
291
292     // Forms that are purely informational set an ok_button flag, so we know not
293     // to create an "Apply" button for them.
294     if (!$form_state->get('ok_button')) {
295       $form['actions']['submit'] = [
296         '#type' => 'submit',
297         '#value' => $name,
298         '#id' => 'edit-submit-' . Html::getUniqueId($form_id),
299         // The regular submit handler ($form_id . '_submit') does not apply if
300         // we're updating the default display. It does apply if we're updating
301         // the current display. Since we have no way of knowing at this point
302         // which display the user wants to update, views_ui_standard_submit will
303         // take care of running the regular submit handler as appropriate.
304         '#submit' => [[$this, 'standardSubmit']],
305         '#button_type' => 'primary',
306       ];
307       // Form API button click detection requires the button's #value to be the
308       // same between the form build of the initial page request, and the
309       // initial form build of the request processing the form submission.
310       // Ideally, the button's #value shouldn't change until the form rebuild
311       // step. However, \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm()
312       // implements a different multistep form workflow than the Form API does,
313       // and adjusts $view->stack prior to form processing, so we compensate by
314       // extending button click detection code to support any of the possible
315       // button labels.
316       if (isset($names)) {
317         $form['actions']['submit']['#values'] = $names;
318         $form['actions']['submit']['#process'] = array_merge(['views_ui_form_button_was_clicked'], \Drupal::service('element_info')->getInfoProperty($form['actions']['submit']['#type'], '#process', []));
319       }
320       // If a validation handler exists for the form, assign it to this button.
321       $form['actions']['submit']['#validate'][] = [$form_state->getFormObject(), 'validateForm'];
322     }
323
324     // Create a "Cancel" button. For purely informational forms, label it "OK".
325     $cancel_submit = function_exists($form_id . '_cancel') ? $form_id . '_cancel' : [$this, 'standardCancel'];
326     $form['actions']['cancel'] = [
327       '#type' => 'submit',
328       '#value' => !$form_state->get('ok_button') ? t('Cancel') : t('Ok'),
329       '#submit' => [$cancel_submit],
330       '#validate' => [],
331       '#limit_validation_errors' => [],
332     ];
333
334     // Compatibility, to be removed later: // TODO: When is "later"?
335     // We used to set these items on the form, but now we want them on the $form_state:
336     if (isset($form['#title'])) {
337       $form_state->set('title', $form['#title']);
338     }
339     if (isset($form['#section'])) {
340       $form_state->set('#section', $form['#section']);
341     }
342     // Finally, we never want these cached -- our object cache does that for us.
343     $form['#no_cache'] = TRUE;
344   }
345
346   /**
347    * Return the was_defaulted, is_defaulted and revert state of a form.
348    */
349   public function getOverrideValues($form, FormStateInterface $form_state) {
350     // Make sure the dropdown exists in the first place.
351     if ($form_state->hasValue(['override', 'dropdown'])) {
352       // #default_value is used to determine whether it was the default value or not.
353       // So the available options are: $display, 'default' and 'default_revert', not 'defaults'.
354       $was_defaulted = ($form['override']['dropdown']['#default_value'] === 'defaults');
355       $dropdown = $form_state->getValue(['override', 'dropdown']);
356       $is_defaulted = ($dropdown === 'default');
357       $revert = ($dropdown === 'default_revert');
358
359       if ($was_defaulted !== $is_defaulted && isset($form['#section'])) {
360         // We're changing which display these values apply to.
361         // Update the #section so it knows what to mark changed.
362         $form['#section'] = str_replace('default-', $form_state->get('display_id') . '-', $form['#section']);
363       }
364     }
365     else {
366       // The user didn't get the dropdown for overriding the default display.
367       $was_defaulted = FALSE;
368       $is_defaulted = FALSE;
369       $revert = FALSE;
370     }
371
372     return [$was_defaulted, $is_defaulted, $revert];
373   }
374
375   /**
376    * Add another form to the stack; clicking 'apply' will go to this form
377    * rather than closing the ajax popup.
378    */
379   public function addFormToStack($key, $display_id, $type, $id = NULL, $top = FALSE, $rebuild_keys = FALSE) {
380     // Reset the cache of IDs. Drupal rather aggressively prevents ID
381     // duplication but this causes it to remember IDs that are no longer even
382     // being used.
383     Html::resetSeenIds();
384
385     if (empty($this->stack)) {
386       $this->stack = [];
387     }
388
389     $stack = [implode('-', array_filter([$key, $this->id(), $display_id, $type, $id])), $key, $display_id, $type, $id];
390     // If we're being asked to add this form to the bottom of the stack, no
391     // special logic is required. Our work is equally easy if we were asked to add
392     // to the top of the stack, but there's nothing in it yet.
393     if (!$top || empty($this->stack)) {
394       $this->stack[] = $stack;
395     }
396     // If we're adding to the top of an existing stack, we have to maintain the
397     // existing integer keys, so they can be used for the "2 of 3" progress
398     // indicator (which will now read "2 of 4").
399     else {
400       $keys = array_keys($this->stack);
401       $first = current($keys);
402       $last = end($keys);
403       for ($i = $last; $i >= $first; $i--) {
404         if (!isset($this->stack[$i])) {
405           continue;
406         }
407         // Move form number $i to the next position in the stack.
408         $this->stack[$i + 1] = $this->stack[$i];
409         unset($this->stack[$i]);
410       }
411       // Now that the previously $first slot is free, move the new form into it.
412       $this->stack[$first] = $stack;
413       ksort($this->stack);
414
415       // Start the keys from 0 again, if requested.
416       if ($rebuild_keys) {
417         $this->stack = array_values($this->stack);
418       }
419     }
420   }
421
422   /**
423    * Submit handler for adding new item(s) to a view.
424    */
425   public function submitItemAdd($form, FormStateInterface $form_state) {
426     $type = $form_state->get('type');
427     $types = ViewExecutable::getHandlerTypes();
428     $section = $types[$type]['plural'];
429     $display_id = $form_state->get('display_id');
430
431     // Handle the override select.
432     list($was_defaulted, $is_defaulted) = $this->getOverrideValues($form, $form_state);
433     if ($was_defaulted && !$is_defaulted) {
434       // We were using the default display's values, but we're now overriding
435       // the default display and saving values specific to this display.
436       $display = &$this->getExecutable()->displayHandlers->get($display_id);
437       // setOverride toggles the override of this section.
438       $display->setOverride($section);
439     }
440     elseif (!$was_defaulted && $is_defaulted) {
441       // We used to have an override for this display, but the user now wants
442       // to go back to the default display.
443       // Overwrite the default display with the current form values, and make
444       // the current display use the new default values.
445       $display = &$this->getExecutable()->displayHandlers->get($display_id);
446       // optionsOverride toggles the override of this section.
447       $display->setOverride($section);
448     }
449
450     if (!$form_state->isValueEmpty('name') && is_array($form_state->getValue('name'))) {
451       // Loop through each of the items that were checked and add them to the view.
452       foreach (array_keys(array_filter($form_state->getValue('name'))) as $field) {
453         list($table, $field) = explode('.', $field, 2);
454
455         if ($cut = strpos($field, '$')) {
456           $field = substr($field, 0, $cut);
457         }
458         $id = $this->getExecutable()->addHandler($display_id, $type, $table, $field);
459
460         // check to see if we have group by settings
461         $key = $type;
462         // Footer,header and empty text have a different internal handler type(area).
463         if (isset($types[$type]['type'])) {
464           $key = $types[$type]['type'];
465         }
466         $item = [
467           'table' => $table,
468           'field' => $field,
469         ];
470         $handler = Views::handlerManager($key)->getHandler($item);
471         if ($this->getExecutable()->displayHandlers->get('default')->useGroupBy() && $handler->usesGroupBy()) {
472           $this->addFormToStack('handler-group', $display_id, $type, $id);
473         }
474
475         // check to see if this type has settings, if so add the settings form first
476         if ($handler && $handler->hasExtraOptions()) {
477           $this->addFormToStack('handler-extra', $display_id, $type, $id);
478         }
479         // Then add the form to the stack
480         $this->addFormToStack('handler', $display_id, $type, $id);
481       }
482     }
483
484     if (isset($this->form_cache)) {
485       unset($this->form_cache);
486     }
487
488     // Store in cache
489     $this->cacheSet();
490   }
491
492   /**
493    * Set up query capturing.
494    *
495    * \Drupal\Core\Database\Database stores the queries that it runs, if logging
496    * is enabled.
497    *
498    * @see ViewUI::endQueryCapture()
499    */
500   public function startQueryCapture() {
501     Database::startLog('views');
502   }
503
504   /**
505    * Add the list of queries run during render to buildinfo.
506    *
507    * @see ViewUI::startQueryCapture()
508    */
509   public function endQueryCapture() {
510     $queries = Database::getLog('views');
511
512     $this->additionalQueries = $queries;
513   }
514
515   public function renderPreview($display_id, $args = []) {
516     // Save the current path so it can be restored before returning from this function.
517     $request_stack = \Drupal::requestStack();
518     $current_request = $request_stack->getCurrentRequest();
519     $executable = $this->getExecutable();
520
521     // Determine where the query and performance statistics should be output.
522     $config = \Drupal::config('views.settings');
523     $show_query = $config->get('ui.show.sql_query.enabled');
524     $show_info = $config->get('ui.show.preview_information');
525     $show_location = $config->get('ui.show.sql_query.where');
526
527     $show_stats = $config->get('ui.show.performance_statistics');
528     if ($show_stats) {
529       $show_stats = $config->get('ui.show.sql_query.where');
530     }
531
532     $combined = $show_query && $show_stats;
533
534     $rows = ['query' => [], 'statistics' => []];
535
536     $errors = $executable->validate();
537     $executable->destroy();
538     if (empty($errors)) {
539       $this->ajax = TRUE;
540       $executable->live_preview = TRUE;
541
542       // AJAX happens via HTTP POST but everything expects exposed data to
543       // be in GET. Copy stuff but remove ajax-framework specific keys.
544       // If we're clicking on links in a preview, though, we could actually
545       // have some input in the query parameters, so we merge request() and
546       // query() to ensure we get it all.
547       $exposed_input = array_merge(\Drupal::request()->request->all(), \Drupal::request()->query->all());
548       foreach (['view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER, 'ajax_page_state', 'form_id', 'form_build_id', 'form_token'] as $key) {
549         if (isset($exposed_input[$key])) {
550           unset($exposed_input[$key]);
551         }
552       }
553       $executable->setExposedInput($exposed_input);
554
555       if (!$executable->setDisplay($display_id)) {
556         return [
557           '#markup' => t('Invalid display id @display', ['@display' => $display_id]),
558         ];
559       }
560
561       $executable->setArguments($args);
562
563       // Store the current view URL for later use:
564       if ($executable->hasUrl() && $executable->display_handler->getOption('path')) {
565         $path = $executable->getUrl();
566       }
567
568       // Make view links come back to preview.
569
570       // Also override the current path so we get the pager, and make sure the
571       // Request object gets all of the proper values from $_SERVER.
572       $request = Request::createFromGlobals();
573       $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'entity.view.preview_form');
574       $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, \Drupal::service('router.route_provider')->getRouteByName('entity.view.preview_form'));
575       $request->attributes->set('view', $this->storage);
576       $request->attributes->set('display_id', $display_id);
577       $raw_parameters = new ParameterBag();
578       $raw_parameters->set('view', $this->id());
579       $raw_parameters->set('display_id', $display_id);
580       $request->attributes->set('_raw_variables', $raw_parameters);
581
582       foreach ($args as $key => $arg) {
583         $request->attributes->set('arg_' . $key, $arg);
584       }
585       $request_stack->push($request);
586
587       // Suppress contextual links of entities within the result set during a
588       // Preview.
589       // @todo We'll want to add contextual links specific to editing the View, so
590       //   the suppression may need to be moved deeper into the Preview pipeline.
591       views_ui_contextual_links_suppress_push();
592
593       $show_additional_queries = $config->get('ui.show.additional_queries');
594
595       Timer::start('entity.view.preview_form');
596
597       if ($show_additional_queries) {
598         $this->startQueryCapture();
599       }
600
601       // Execute/get the view preview.
602       $preview = $executable->preview($display_id, $args);
603
604       if ($show_additional_queries) {
605         $this->endQueryCapture();
606       }
607
608       $this->render_time = Timer::stop('entity.view.preview_form')['time'];
609
610       views_ui_contextual_links_suppress_pop();
611
612       // Prepare the query information and statistics to show either above or
613       // below the view preview.
614       // Initialise the empty rows arrays so we can safely merge them later.
615       $rows['query'] = [];
616       $rows['statistics'] = [];
617       if ($show_info || $show_query || $show_stats) {
618         // Get information from the preview for display.
619         if (!empty($executable->build_info['query'])) {
620           if ($show_query) {
621             $query_string = $executable->build_info['query'];
622             // Only the sql default class has a method getArguments.
623             $quoted = [];
624
625             if ($executable->query instanceof Sql) {
626               $quoted = $query_string->getArguments();
627               $connection = Database::getConnection();
628               foreach ($quoted as $key => $val) {
629                 if (is_array($val)) {
630                   $quoted[$key] = implode(', ', array_map([$connection, 'quote'], $val));
631                 }
632                 else {
633                   $quoted[$key] = $connection->quote($val);
634                 }
635               }
636             }
637             $rows['query'][] = [
638               [
639                 'data' => [
640                   '#type' => 'inline_template',
641                   '#template' => "<strong>{% trans 'Query' %}</strong>",
642                 ],
643               ],
644               [
645                 'data' => [
646                   '#type' => 'inline_template',
647                   '#template' => '<pre>{{ query }}</pre>',
648                   '#context' => ['query' => strtr($query_string, $quoted)],
649                 ],
650               ],
651             ];
652             if (!empty($this->additionalQueries)) {
653               $queries[] = [
654                 '#prefix' => '<strong>',
655                 '#markup' => t('These queries were run during view rendering:'),
656                 '#suffix' => '</strong>',
657               ];
658               foreach ($this->additionalQueries as $query) {
659                 $query_string = strtr($query['query'], $query['args']);
660                 $queries[] = [
661                   '#prefix' => "\n",
662                   '#markup' => t('[@time ms] @query', ['@time' => round($query['time'] * 100000, 1) / 100000.0, '@query' => $query_string]),
663                 ];
664               }
665
666               $rows['query'][] = [
667                 [
668                   'data' => [
669                     '#type' => 'inline_template',
670                     '#template' => "<strong>{% trans 'Other queries' %}</strong>",
671                   ],
672                 ],
673                 [
674                   'data' => [
675                     '#prefix' => '<pre>',
676                      'queries' => $queries,
677                      '#suffix' => '</pre>',
678                     ],
679                 ],
680               ];
681             }
682           }
683           if ($show_info) {
684             $rows['query'][] = [
685               [
686                 'data' => [
687                   '#type' => 'inline_template',
688                   '#template' => "<strong>{% trans 'Title' %}</strong>",
689                 ],
690               ],
691               Xss::filterAdmin($executable->getTitle()),
692             ];
693             if (isset($path)) {
694               // @todo Views should expect and store a leading /. See:
695               //   https://www.drupal.org/node/2423913
696               $path = \Drupal::l($path->toString(), $path);
697             }
698             else {
699               $path = t('This display has no path.');
700             }
701             $rows['query'][] = [
702               [
703                 'data' => [
704                   '#prefix' => '<strong>',
705                   '#markup' => t('Path'),
706                   '#suffix' => '</strong>',
707                 ],
708               ],
709               [
710                 'data' => [
711                   '#markup' => $path,
712                 ],
713               ]
714             ];
715           }
716           if ($show_stats) {
717             $rows['statistics'][] = [
718               [
719                 'data' => [
720                   '#type' => 'inline_template',
721                   '#template' => "<strong>{% trans 'Query build time' %}</strong>",
722                 ],
723               ],
724               t('@time ms', ['@time' => intval($executable->build_time * 100000) / 100]),
725             ];
726
727             $rows['statistics'][] = [
728               [
729                 'data' => [
730                   '#type' => 'inline_template',
731                   '#template' => "<strong>{% trans 'Query execute time' %}</strong>",
732                 ],
733               ],
734               t('@time ms', ['@time' => intval($executable->execute_time * 100000) / 100]),
735             ];
736
737             $rows['statistics'][] = [
738               [
739                 'data' => [
740                   '#type' => 'inline_template',
741                   '#template' => "<strong>{% trans 'View render time' %}</strong>",
742                 ],
743               ],
744               t('@time ms', ['@time' => intval($this->render_time * 100) / 100]),
745             ];
746           }
747           \Drupal::moduleHandler()->alter('views_preview_info', $rows, $executable);
748         }
749         else {
750           // No query was run. Display that information in place of either the
751           // query or the performance statistics, whichever comes first.
752           if ($combined || ($show_location === 'above')) {
753             $rows['query'][] = [
754               [
755                 'data' => [
756                   '#prefix' => '<strong>',
757                   '#markup' => t('Query'),
758                   '#suffix' => '</strong>',
759                 ],
760               ],
761               [
762                 'data' => [
763                   '#markup' => t('No query was run'),
764                 ],
765               ],
766             ];
767           }
768           else {
769             $rows['statistics'][] = [
770               [
771                 'data' => [
772                   '#prefix' => '<strong>',
773                   '#markup' => t('Query'),
774                   '#suffix' => '</strong>',
775                 ],
776               ],
777               [
778                 'data' => [
779                   '#markup' => t('No query was run'),
780                 ],
781               ],
782             ];
783           }
784         }
785       }
786     }
787     else {
788       foreach ($errors as $display_errors) {
789         foreach ($display_errors as $error) {
790           drupal_set_message($error, 'error');
791         }
792       }
793       $preview = ['#markup' => t('Unable to preview due to validation errors.')];
794     }
795
796     // Assemble the preview, the query info, and the query statistics in the
797     // requested order.
798     $table = [
799       '#type' => 'table',
800       '#prefix' => '<div class="views-query-info">',
801       '#suffix' => '</div>',
802       '#rows' => array_merge($rows['query'], $rows['statistics']),
803     ];
804
805     if ($show_location == 'above') {
806       $output = [
807         'table' => $table,
808         'preview' => $preview,
809       ];
810     }
811     else {
812       $output = [
813         'preview' => $preview,
814         'table' => $table,
815       ];
816     }
817
818     // Ensure that we just remove an additional request we pushed earlier.
819     // This could happen if $errors was not empty.
820     if ($request_stack->getCurrentRequest() != $current_request) {
821       $request_stack->pop();
822     }
823     return $output;
824   }
825
826   /**
827    * Get the user's current progress through the form stack.
828    *
829    * @return
830    *   FALSE if the user is not currently in a multiple-form stack. Otherwise,
831    *   an associative array with the following keys:
832    *   - current: The number of the current form on the stack.
833    *   - total: The total number of forms originally on the stack.
834    */
835   public function getFormProgress() {
836     $progress = FALSE;
837     if (!empty($this->stack)) {
838       // The forms on the stack have integer keys that don't change as the forms
839       // are completed, so we can see which ones are still left.
840       $keys = array_keys($this->stack);
841       // Add 1 to the array keys for the benefit of humans, who start counting
842       // from 1 and not 0.
843       $current = reset($keys) + 1;
844       $total = end($keys) + 1;
845       if ($total > 1) {
846         $progress = [];
847         $progress['current'] = $current;
848         $progress['total'] = $total;
849       }
850     }
851     return $progress;
852   }
853
854   /**
855    * Sets a cached view object in the user tempstore.
856    */
857   public function cacheSet() {
858     if ($this->isLocked()) {
859       drupal_set_message(t('Changes cannot be made to a locked view.'), 'error');
860       return;
861     }
862
863     // Let any future object know that this view has changed.
864     $this->changed = TRUE;
865
866     $executable = $this->getExecutable();
867     if (isset($executable->current_display)) {
868       // Add the knowledge of the changed display, too.
869       $this->changed_display[$executable->current_display] = TRUE;
870       $executable->current_display = NULL;
871     }
872
873     // Unset handlers. We don't want to write these into the cache.
874     $executable->display_handler = NULL;
875     $executable->default_display = NULL;
876     $executable->query = NULL;
877     $executable->displayHandlers = NULL;
878     \Drupal::service('user.shared_tempstore')->get('views')->set($this->id(), $this);
879   }
880
881   /**
882    * Returns whether the current view is locked.
883    *
884    * @return bool
885    *   TRUE if the view is locked, FALSE otherwise.
886    */
887   public function isLocked() {
888     return is_object($this->lock) && ($this->lock->owner != \Drupal::currentUser()->id());
889   }
890
891   /**
892    * Passes through all unknown calls onto the storage object.
893    */
894   public function __call($method, $args) {
895     return call_user_func_array([$this->storage, $method], $args);
896   }
897
898   /**
899    * {@inheritdoc}
900    */
901   public function &getDisplay($display_id) {
902     return $this->storage->getDisplay($display_id);
903   }
904
905   /**
906    * {@inheritdoc}
907    */
908   public function id() {
909     return $this->storage->id();
910   }
911
912   /**
913    * {@inheritdoc}
914    */
915   public function uuid() {
916     return $this->storage->uuid();
917   }
918
919   /**
920    * {@inheritdoc}
921    */
922   public function isNew() {
923     return $this->storage->isNew();
924   }
925
926   /**
927    * {@inheritdoc}
928    */
929   public function getEntityTypeId() {
930     return $this->storage->getEntityTypeId();
931   }
932
933   /**
934    * {@inheritdoc}
935    */
936   public function bundle() {
937     return $this->storage->bundle();
938   }
939
940   /**
941    * {@inheritdoc}
942    */
943   public function getEntityType() {
944     return $this->storage->getEntityType();
945   }
946
947   /**
948    * {@inheritdoc}
949    */
950   public function createDuplicate() {
951     return $this->storage->createDuplicate();
952   }
953
954   /**
955    * {@inheritdoc}
956    */
957   public static function load($id) {
958     return View::load($id);
959   }
960
961   /**
962    * {@inheritdoc}
963    */
964   public static function loadMultiple(array $ids = NULL) {
965     return View::loadMultiple($ids);
966   }
967
968   /**
969    * {@inheritdoc}
970    */
971   public static function create(array $values = []) {
972     return View::create($values);
973   }
974
975   /**
976    * {@inheritdoc}
977    */
978   public function delete() {
979     return $this->storage->delete();
980   }
981
982   /**
983    * {@inheritdoc}
984    */
985   public function save() {
986     return $this->storage->save();
987   }
988
989   /**
990    * {@inheritdoc}
991    */
992   public function urlInfo($rel = 'edit-form', array $options = []) {
993     return $this->storage->urlInfo($rel, $options);
994   }
995
996   /**
997    * {@inheritdoc}
998    */
999   public function toUrl($rel = 'edit-form', array $options = []) {
1000     return $this->storage->toUrl($rel, $options);
1001   }
1002
1003   /**
1004    * {@inheritdoc}
1005    */
1006   public function link($text = NULL, $rel = 'edit-form', array $options = []) {
1007     return $this->storage->link($text, $rel, $options);
1008   }
1009
1010   /**
1011    * {@inheritdoc}
1012    */
1013   public function toLink($text = NULL, $rel = 'edit-form', array $options = []) {
1014     return $this->storage->toLink($text, $rel, $options);
1015   }
1016
1017   /**
1018    * {@inheritdoc}
1019    */
1020   public function label() {
1021     return $this->storage->label();
1022   }
1023
1024   /**
1025    * {@inheritdoc}
1026    */
1027   public function enforceIsNew($value = TRUE) {
1028     return $this->storage->enforceIsNew($value);
1029   }
1030
1031   /**
1032    * {@inheritdoc}
1033    */
1034   public function toArray() {
1035     return $this->storage->toArray();
1036   }
1037
1038   /**
1039    * {@inheritdoc}
1040    */
1041   public function language() {
1042     return $this->storage->language();
1043   }
1044
1045   /**
1046    * {@inheritdoc}
1047    */
1048   public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
1049     return $this->storage->access($operation, $account, $return_as_object);
1050   }
1051
1052   /**
1053    * {@inheritdoc}
1054    */
1055   public function enable() {
1056     return $this->storage->enable();
1057   }
1058
1059   /**
1060    * {@inheritdoc}
1061    */
1062   public function disable() {
1063     return $this->storage->disable();
1064   }
1065
1066   /**
1067    * {@inheritdoc}
1068    */
1069   public function status() {
1070     return $this->storage->status();
1071   }
1072
1073   /**
1074    * {@inheritdoc}
1075    */
1076   public function getOriginalId() {
1077     return $this->storage->getOriginalId();
1078   }
1079
1080   /**
1081    * {@inheritdoc}
1082    */
1083   public function setOriginalId($id) {
1084     return $this->storage->setOriginalId($id);
1085   }
1086
1087   /**
1088    * {@inheritdoc}
1089    */
1090   public function preSave(EntityStorageInterface $storage) {
1091     $this->storage->presave($storage);
1092   }
1093
1094   /**
1095    * {@inheritdoc}
1096    */
1097   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
1098     $this->storage->postSave($storage, $update);
1099   }
1100
1101   /**
1102    * {@inheritdoc}
1103    */
1104   public static function preCreate(EntityStorageInterface $storage, array &$values) {
1105   }
1106
1107   /**
1108    * {@inheritdoc}
1109    */
1110   public function postCreate(EntityStorageInterface $storage) {
1111     $this->storage->postCreate($storage);
1112   }
1113
1114   /**
1115    * {@inheritdoc}
1116    */
1117   public static function preDelete(EntityStorageInterface $storage, array $entities) {
1118   }
1119
1120   /**
1121    * {@inheritdoc}
1122    */
1123   public static function postDelete(EntityStorageInterface $storage, array $entities) {
1124   }
1125
1126   /**
1127    * {@inheritdoc}
1128    */
1129   public static function postLoad(EntityStorageInterface $storage, array &$entities) {
1130   }
1131
1132   /**
1133    * {@inheritdoc}
1134    */
1135   public function getExecutable() {
1136     return $this->storage->getExecutable();
1137   }
1138
1139   /**
1140    * {@inheritdoc}
1141    */
1142   public function duplicateDisplayAsType($old_display_id, $new_display_type) {
1143     return $this->storage->duplicateDisplayAsType($old_display_id, $new_display_type);
1144   }
1145
1146   /**
1147    * {@inheritdoc}
1148    */
1149   public function mergeDefaultDisplaysOptions() {
1150     $this->storage->mergeDefaultDisplaysOptions();
1151   }
1152
1153   /**
1154    * {@inheritdoc}
1155    */
1156   public function uriRelationships() {
1157     return $this->storage->uriRelationships();
1158   }
1159
1160   /**
1161    * {@inheritdoc}
1162    */
1163   public function referencedEntities() {
1164     return $this->storage->referencedEntities();
1165   }
1166
1167   /**
1168    * {@inheritdoc}
1169    */
1170   public function url($rel = 'edit-form', $options = []) {
1171     return $this->storage->url($rel, $options);
1172   }
1173
1174   /**
1175    * {@inheritdoc}
1176    */
1177   public function hasLinkTemplate($key) {
1178     return $this->storage->hasLinkTemplate($key);
1179   }
1180
1181   /**
1182    * {@inheritdoc}
1183    */
1184   public function calculateDependencies() {
1185     $this->storage->calculateDependencies();
1186     return $this;
1187   }
1188
1189   /**
1190    * {@inheritdoc}
1191    */
1192   public function getConfigDependencyKey() {
1193     return $this->storage->getConfigDependencyKey();
1194   }
1195
1196   /**
1197    * {@inheritdoc}
1198    */
1199   public function getConfigDependencyName() {
1200     return $this->storage->getConfigDependencyName();
1201   }
1202
1203   /**
1204    * {@inheritdoc}
1205    */
1206   public function getConfigTarget() {
1207     return $this->storage->getConfigTarget();
1208   }
1209
1210   /**
1211    * {@inheritdoc}
1212    */
1213   public function onDependencyRemoval(array $dependencies) {
1214     return $this->storage->onDependencyRemoval($dependencies);
1215   }
1216
1217   /**
1218    * {@inheritdoc}
1219    */
1220   public function getDependencies() {
1221     return $this->storage->getDependencies();
1222   }
1223
1224   /**
1225    * {@inheritdoc}
1226    */
1227   public function getCacheContexts() {
1228     return $this->storage->getCacheContexts();
1229   }
1230
1231   /**
1232    * {@inheritdoc}
1233    */
1234   public function getCacheTags() {
1235     return $this->storage->getCacheTags();
1236   }
1237
1238   /**
1239    * {@inheritdoc}
1240    */
1241   public function getCacheMaxAge() {
1242     return $this->storage->getCacheMaxAge();
1243   }
1244
1245   /**
1246    * {@inheritdoc}
1247    */
1248   public function getTypedData() {
1249     $this->storage->getTypedData();
1250   }
1251
1252   /**
1253    * {@inheritdoc}
1254    */
1255   public function addDisplay($plugin_id = 'page', $title = NULL, $id = NULL) {
1256     return $this->storage->addDisplay($plugin_id, $title, $id);
1257   }
1258
1259   /**
1260    * {@inheritdoc}
1261    */
1262   public function isInstallable() {
1263     return $this->storage->isInstallable();
1264   }
1265
1266   /**
1267    * {@inheritdoc}
1268    */
1269   public function setThirdPartySetting($module, $key, $value) {
1270     return $this->storage->setThirdPartySetting($module, $key, $value);
1271   }
1272
1273   /**
1274    * {@inheritdoc}
1275    */
1276   public function getThirdPartySetting($module, $key, $default = NULL) {
1277     return $this->storage->getThirdPartySetting($module, $key, $default);
1278   }
1279
1280   /**
1281    * {@inheritdoc}
1282    */
1283   public function getThirdPartySettings($module) {
1284     return $this->storage->getThirdPartySettings($module);
1285   }
1286
1287   /**
1288    * {@inheritdoc}
1289    */
1290   public function unsetThirdPartySetting($module, $key) {
1291     return $this->storage->unsetThirdPartySetting($module, $key);
1292   }
1293
1294   /**
1295    * {@inheritdoc}
1296    */
1297   public function getThirdPartyProviders() {
1298     return $this->storage->getThirdPartyProviders();
1299   }
1300
1301   /**
1302    * {@inheritdoc}
1303    */
1304   public function trustData() {
1305     return $this->storage->trustData();
1306   }
1307
1308   /**
1309    * {@inheritdoc}
1310    */
1311   public function hasTrustedData() {
1312     return $this->storage->hasTrustedData();
1313   }
1314
1315   /**
1316    * {@inheritdoc}
1317    */
1318   public function addCacheableDependency($other_object) {
1319     $this->storage->addCacheableDependency($other_object);
1320     return $this;
1321   }
1322
1323   /**
1324    * {@inheritdoc}
1325    */
1326   public function addCacheContexts(array $cache_contexts) {
1327     return $this->storage->addCacheContexts($cache_contexts);
1328   }
1329
1330   /**
1331    * {@inheritdoc}
1332    */
1333   public function mergeCacheMaxAge($max_age) {
1334     return $this->storage->mergeCacheMaxAge($max_age);
1335   }
1336
1337   /**
1338    * {@inheritdoc}
1339    */
1340   public function getCacheTagsToInvalidate() {
1341     return $this->storage->getCacheTagsToInvalidate();
1342   }
1343
1344   /**
1345    * {@inheritdoc}
1346    */
1347   public function addCacheTags(array $cache_tags) {
1348     return $this->storage->addCacheTags($cache_tags);
1349   }
1350
1351 }