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