Chris@0: '\Drupal\views_ui\Form\Ajax\AddItem', Chris@0: 'analyze' => '\Drupal\views_ui\Form\Ajax\Analyze', Chris@0: 'handler' => '\Drupal\views_ui\Form\Ajax\ConfigHandler', Chris@0: 'handler-extra' => '\Drupal\views_ui\Form\Ajax\ConfigHandlerExtra', Chris@0: 'handler-group' => '\Drupal\views_ui\Form\Ajax\ConfigHandlerGroup', Chris@0: 'display' => '\Drupal\views_ui\Form\Ajax\Display', Chris@0: 'edit-details' => '\Drupal\views_ui\Form\Ajax\EditDetails', Chris@0: 'rearrange' => '\Drupal\views_ui\Form\Ajax\Rearrange', Chris@0: 'rearrange-filter' => '\Drupal\views_ui\Form\Ajax\RearrangeFilter', Chris@0: 'reorder-displays' => '\Drupal\views_ui\Form\Ajax\ReorderDisplays', Chris@0: ]; Chris@0: Chris@0: /** Chris@0: * Whether the config is being created, updated or deleted through the Chris@0: * import process. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: private $isSyncing = FALSE; Chris@0: Chris@0: /** Chris@0: * Whether the config is being deleted through the uninstall process. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: private $isUninstalling = FALSE; Chris@0: Chris@0: /** Chris@0: * Constructs a View UI object. Chris@0: * Chris@0: * @param \Drupal\views\ViewEntityInterface $storage Chris@0: * The View storage object to wrap. Chris@0: */ Chris@0: public function __construct(ViewEntityInterface $storage) { Chris@0: $this->entityType = 'view'; Chris@0: $this->storage = $storage; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function get($property_name, $langcode = NULL) { Chris@0: if (property_exists($this->storage, $property_name)) { Chris@0: return $this->storage->get($property_name, $langcode); Chris@0: } Chris@0: Chris@0: return isset($this->{$property_name}) ? $this->{$property_name} : NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setStatus($status) { Chris@0: return $this->storage->setStatus($status); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function set($property_name, $value, $notify = TRUE) { Chris@0: if (property_exists($this->storage, $property_name)) { Chris@0: $this->storage->set($property_name, $value); Chris@0: } Chris@0: else { Chris@0: $this->{$property_name} = $value; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setSyncing($syncing) { Chris@0: $this->isSyncing = $syncing; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setUninstalling($isUninstalling) { Chris@0: $this->isUninstalling = $isUninstalling; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function isSyncing() { Chris@0: return $this->isSyncing; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function isUninstalling() { Chris@0: return $this->isUninstalling; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Basic submit handler applicable to all 'standard' forms. Chris@0: * Chris@0: * This submit handler determines whether the user wants the submitted changes Chris@0: * to apply to the default display or to the current display, and dispatches Chris@0: * control appropriately. Chris@0: */ Chris@0: public function standardSubmit($form, FormStateInterface $form_state) { Chris@0: // Determine whether the values the user entered are intended to apply to Chris@0: // the current display or the default display. Chris@0: Chris@0: list($was_defaulted, $is_defaulted, $revert) = $this->getOverrideValues($form, $form_state); Chris@0: Chris@0: // Based on the user's choice in the display dropdown, determine which display Chris@0: // these changes apply to. Chris@0: $display_id = $form_state->get('display_id'); Chris@0: if ($revert) { Chris@0: // If it's revert just change the override and return. Chris@0: $display = &$this->getExecutable()->displayHandlers->get($display_id); Chris@0: $display->optionsOverride($form, $form_state); Chris@0: Chris@0: // Don't execute the normal submit handling but still store the changed view into cache. Chris@0: $this->cacheSet(); Chris@0: return; Chris@0: } Chris@0: elseif ($was_defaulted === $is_defaulted) { Chris@0: // We're not changing which display these form values apply to. Chris@0: // Run the regular submit handler for this form. Chris@0: } Chris@0: elseif ($was_defaulted && !$is_defaulted) { Chris@0: // We were using the default display's values, but we're now overriding Chris@0: // the default display and saving values specific to this display. Chris@0: $display = &$this->getExecutable()->displayHandlers->get($display_id); Chris@0: // optionsOverride toggles the override of this section. Chris@0: $display->optionsOverride($form, $form_state); Chris@0: $display->submitOptionsForm($form, $form_state); Chris@0: } Chris@0: elseif (!$was_defaulted && $is_defaulted) { Chris@0: // We used to have an override for this display, but the user now wants Chris@0: // to go back to the default display. Chris@0: // Overwrite the default display with the current form values, and make Chris@0: // the current display use the new default values. Chris@0: $display = &$this->getExecutable()->displayHandlers->get($display_id); Chris@0: // optionsOverride toggles the override of this section. Chris@0: $display->optionsOverride($form, $form_state); Chris@0: $display->submitOptionsForm($form, $form_state); Chris@0: } Chris@0: Chris@0: $submit_handler = [$form_state->getFormObject(), 'submitForm']; Chris@0: call_user_func_array($submit_handler, [&$form, $form_state]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Submit handler for cancel button Chris@0: */ Chris@0: public function standardCancel($form, FormStateInterface $form_state) { Chris@0: if (!empty($this->changed) && isset($this->form_cache)) { Chris@0: unset($this->form_cache); Chris@0: $this->cacheSet(); Chris@0: } Chris@0: Chris@18: $form_state->setRedirectUrl($this->toUrl('edit-form')); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provide a standard set of Apply/Cancel/OK buttons for the forms. Also provide Chris@0: * a hidden op operator because the forms plugin doesn't seem to properly Chris@0: * provide which button was clicked. Chris@0: * Chris@0: * TODO: Is the hidden op operator still here somewhere, or is that part of the Chris@0: * docblock outdated? Chris@0: */ Chris@0: public function getStandardButtons(&$form, FormStateInterface $form_state, $form_id, $name = NULL) { Chris@0: $form['actions'] = [ Chris@0: '#type' => 'actions', Chris@0: ]; Chris@0: Chris@0: if (empty($name)) { Chris@0: $name = t('Apply'); Chris@0: if (!empty($this->stack) && count($this->stack) > 1) { Chris@0: $name = t('Apply and continue'); Chris@0: } Chris@0: $names = [t('Apply'), t('Apply and continue')]; Chris@0: } Chris@0: Chris@0: // Forms that are purely informational set an ok_button flag, so we know not Chris@0: // to create an "Apply" button for them. Chris@0: if (!$form_state->get('ok_button')) { Chris@0: $form['actions']['submit'] = [ Chris@0: '#type' => 'submit', Chris@0: '#value' => $name, Chris@0: '#id' => 'edit-submit-' . Html::getUniqueId($form_id), Chris@0: // The regular submit handler ($form_id . '_submit') does not apply if Chris@0: // we're updating the default display. It does apply if we're updating Chris@0: // the current display. Since we have no way of knowing at this point Chris@0: // which display the user wants to update, views_ui_standard_submit will Chris@0: // take care of running the regular submit handler as appropriate. Chris@0: '#submit' => [[$this, 'standardSubmit']], Chris@0: '#button_type' => 'primary', Chris@0: ]; Chris@0: // Form API button click detection requires the button's #value to be the Chris@0: // same between the form build of the initial page request, and the Chris@0: // initial form build of the request processing the form submission. Chris@0: // Ideally, the button's #value shouldn't change until the form rebuild Chris@0: // step. However, \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm() Chris@0: // implements a different multistep form workflow than the Form API does, Chris@0: // and adjusts $view->stack prior to form processing, so we compensate by Chris@0: // extending button click detection code to support any of the possible Chris@0: // button labels. Chris@0: if (isset($names)) { Chris@0: $form['actions']['submit']['#values'] = $names; Chris@0: $form['actions']['submit']['#process'] = array_merge(['views_ui_form_button_was_clicked'], \Drupal::service('element_info')->getInfoProperty($form['actions']['submit']['#type'], '#process', [])); Chris@0: } Chris@0: // If a validation handler exists for the form, assign it to this button. Chris@0: $form['actions']['submit']['#validate'][] = [$form_state->getFormObject(), 'validateForm']; Chris@0: } Chris@0: Chris@0: // Create a "Cancel" button. For purely informational forms, label it "OK". Chris@0: $cancel_submit = function_exists($form_id . '_cancel') ? $form_id . '_cancel' : [$this, 'standardCancel']; Chris@0: $form['actions']['cancel'] = [ Chris@0: '#type' => 'submit', Chris@0: '#value' => !$form_state->get('ok_button') ? t('Cancel') : t('Ok'), Chris@0: '#submit' => [$cancel_submit], Chris@0: '#validate' => [], Chris@0: '#limit_validation_errors' => [], Chris@0: ]; Chris@0: Chris@0: // Compatibility, to be removed later: // TODO: When is "later"? Chris@0: // We used to set these items on the form, but now we want them on the $form_state: Chris@0: if (isset($form['#title'])) { Chris@0: $form_state->set('title', $form['#title']); Chris@0: } Chris@0: if (isset($form['#section'])) { Chris@0: $form_state->set('#section', $form['#section']); Chris@0: } Chris@0: // Finally, we never want these cached -- our object cache does that for us. Chris@0: $form['#no_cache'] = TRUE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return the was_defaulted, is_defaulted and revert state of a form. Chris@0: */ Chris@0: public function getOverrideValues($form, FormStateInterface $form_state) { Chris@0: // Make sure the dropdown exists in the first place. Chris@0: if ($form_state->hasValue(['override', 'dropdown'])) { Chris@0: // #default_value is used to determine whether it was the default value or not. Chris@0: // So the available options are: $display, 'default' and 'default_revert', not 'defaults'. Chris@0: $was_defaulted = ($form['override']['dropdown']['#default_value'] === 'defaults'); Chris@0: $dropdown = $form_state->getValue(['override', 'dropdown']); Chris@0: $is_defaulted = ($dropdown === 'default'); Chris@0: $revert = ($dropdown === 'default_revert'); Chris@0: Chris@0: if ($was_defaulted !== $is_defaulted && isset($form['#section'])) { Chris@0: // We're changing which display these values apply to. Chris@0: // Update the #section so it knows what to mark changed. Chris@0: $form['#section'] = str_replace('default-', $form_state->get('display_id') . '-', $form['#section']); Chris@0: } Chris@0: } Chris@0: else { Chris@0: // The user didn't get the dropdown for overriding the default display. Chris@0: $was_defaulted = FALSE; Chris@0: $is_defaulted = FALSE; Chris@0: $revert = FALSE; Chris@0: } Chris@0: Chris@0: return [$was_defaulted, $is_defaulted, $revert]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add another form to the stack; clicking 'apply' will go to this form Chris@0: * rather than closing the ajax popup. Chris@0: */ Chris@0: public function addFormToStack($key, $display_id, $type, $id = NULL, $top = FALSE, $rebuild_keys = FALSE) { Chris@0: // Reset the cache of IDs. Drupal rather aggressively prevents ID Chris@0: // duplication but this causes it to remember IDs that are no longer even Chris@0: // being used. Chris@0: Html::resetSeenIds(); Chris@0: Chris@0: if (empty($this->stack)) { Chris@0: $this->stack = []; Chris@0: } Chris@0: Chris@0: $stack = [implode('-', array_filter([$key, $this->id(), $display_id, $type, $id])), $key, $display_id, $type, $id]; Chris@0: // If we're being asked to add this form to the bottom of the stack, no Chris@0: // special logic is required. Our work is equally easy if we were asked to add Chris@0: // to the top of the stack, but there's nothing in it yet. Chris@0: if (!$top || empty($this->stack)) { Chris@0: $this->stack[] = $stack; Chris@0: } Chris@0: // If we're adding to the top of an existing stack, we have to maintain the Chris@0: // existing integer keys, so they can be used for the "2 of 3" progress Chris@0: // indicator (which will now read "2 of 4"). Chris@0: else { Chris@0: $keys = array_keys($this->stack); Chris@0: $first = current($keys); Chris@0: $last = end($keys); Chris@0: for ($i = $last; $i >= $first; $i--) { Chris@0: if (!isset($this->stack[$i])) { Chris@0: continue; Chris@0: } Chris@0: // Move form number $i to the next position in the stack. Chris@0: $this->stack[$i + 1] = $this->stack[$i]; Chris@0: unset($this->stack[$i]); Chris@0: } Chris@0: // Now that the previously $first slot is free, move the new form into it. Chris@0: $this->stack[$first] = $stack; Chris@0: ksort($this->stack); Chris@0: Chris@0: // Start the keys from 0 again, if requested. Chris@0: if ($rebuild_keys) { Chris@0: $this->stack = array_values($this->stack); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Submit handler for adding new item(s) to a view. Chris@0: */ Chris@0: public function submitItemAdd($form, FormStateInterface $form_state) { Chris@0: $type = $form_state->get('type'); Chris@0: $types = ViewExecutable::getHandlerTypes(); Chris@0: $section = $types[$type]['plural']; Chris@0: $display_id = $form_state->get('display_id'); Chris@0: Chris@0: // Handle the override select. Chris@0: list($was_defaulted, $is_defaulted) = $this->getOverrideValues($form, $form_state); Chris@0: if ($was_defaulted && !$is_defaulted) { Chris@0: // We were using the default display's values, but we're now overriding Chris@0: // the default display and saving values specific to this display. Chris@0: $display = &$this->getExecutable()->displayHandlers->get($display_id); Chris@0: // setOverride toggles the override of this section. Chris@0: $display->setOverride($section); Chris@0: } Chris@0: elseif (!$was_defaulted && $is_defaulted) { Chris@0: // We used to have an override for this display, but the user now wants Chris@0: // to go back to the default display. Chris@0: // Overwrite the default display with the current form values, and make Chris@0: // the current display use the new default values. Chris@0: $display = &$this->getExecutable()->displayHandlers->get($display_id); Chris@0: // optionsOverride toggles the override of this section. Chris@0: $display->setOverride($section); Chris@0: } Chris@0: Chris@0: if (!$form_state->isValueEmpty('name') && is_array($form_state->getValue('name'))) { Chris@0: // Loop through each of the items that were checked and add them to the view. Chris@0: foreach (array_keys(array_filter($form_state->getValue('name'))) as $field) { Chris@0: list($table, $field) = explode('.', $field, 2); Chris@0: Chris@0: if ($cut = strpos($field, '$')) { Chris@0: $field = substr($field, 0, $cut); Chris@0: } Chris@0: $id = $this->getExecutable()->addHandler($display_id, $type, $table, $field); Chris@0: Chris@0: // check to see if we have group by settings Chris@0: $key = $type; Chris@0: // Footer,header and empty text have a different internal handler type(area). Chris@0: if (isset($types[$type]['type'])) { Chris@0: $key = $types[$type]['type']; Chris@0: } Chris@0: $item = [ Chris@0: 'table' => $table, Chris@0: 'field' => $field, Chris@0: ]; Chris@0: $handler = Views::handlerManager($key)->getHandler($item); Chris@0: if ($this->getExecutable()->displayHandlers->get('default')->useGroupBy() && $handler->usesGroupBy()) { Chris@0: $this->addFormToStack('handler-group', $display_id, $type, $id); Chris@0: } Chris@0: Chris@0: // check to see if this type has settings, if so add the settings form first Chris@0: if ($handler && $handler->hasExtraOptions()) { Chris@0: $this->addFormToStack('handler-extra', $display_id, $type, $id); Chris@0: } Chris@0: // Then add the form to the stack Chris@0: $this->addFormToStack('handler', $display_id, $type, $id); Chris@0: } Chris@0: } Chris@0: Chris@0: if (isset($this->form_cache)) { Chris@0: unset($this->form_cache); Chris@0: } Chris@0: Chris@0: // Store in cache Chris@0: $this->cacheSet(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set up query capturing. Chris@0: * Chris@0: * \Drupal\Core\Database\Database stores the queries that it runs, if logging Chris@0: * is enabled. Chris@0: * Chris@0: * @see ViewUI::endQueryCapture() Chris@0: */ Chris@0: public function startQueryCapture() { Chris@0: Database::startLog('views'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add the list of queries run during render to buildinfo. Chris@0: * Chris@0: * @see ViewUI::startQueryCapture() Chris@0: */ Chris@0: public function endQueryCapture() { Chris@0: $queries = Database::getLog('views'); Chris@0: Chris@0: $this->additionalQueries = $queries; Chris@0: } Chris@0: Chris@0: public function renderPreview($display_id, $args = []) { Chris@0: // Save the current path so it can be restored before returning from this function. Chris@0: $request_stack = \Drupal::requestStack(); Chris@0: $current_request = $request_stack->getCurrentRequest(); Chris@0: $executable = $this->getExecutable(); Chris@0: Chris@0: // Determine where the query and performance statistics should be output. Chris@0: $config = \Drupal::config('views.settings'); Chris@0: $show_query = $config->get('ui.show.sql_query.enabled'); Chris@0: $show_info = $config->get('ui.show.preview_information'); Chris@0: $show_location = $config->get('ui.show.sql_query.where'); Chris@0: Chris@0: $show_stats = $config->get('ui.show.performance_statistics'); Chris@0: if ($show_stats) { Chris@0: $show_stats = $config->get('ui.show.sql_query.where'); Chris@0: } Chris@0: Chris@0: $combined = $show_query && $show_stats; Chris@0: Chris@0: $rows = ['query' => [], 'statistics' => []]; Chris@0: Chris@0: $errors = $executable->validate(); Chris@0: $executable->destroy(); Chris@0: if (empty($errors)) { Chris@0: $this->ajax = TRUE; Chris@0: $executable->live_preview = TRUE; Chris@0: Chris@0: // AJAX happens via HTTP POST but everything expects exposed data to Chris@0: // be in GET. Copy stuff but remove ajax-framework specific keys. Chris@0: // If we're clicking on links in a preview, though, we could actually Chris@0: // have some input in the query parameters, so we merge request() and Chris@0: // query() to ensure we get it all. Chris@0: $exposed_input = array_merge(\Drupal::request()->request->all(), \Drupal::request()->query->all()); Chris@0: 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) { Chris@0: if (isset($exposed_input[$key])) { Chris@0: unset($exposed_input[$key]); Chris@0: } Chris@0: } Chris@0: $executable->setExposedInput($exposed_input); Chris@0: Chris@0: if (!$executable->setDisplay($display_id)) { Chris@0: return [ Chris@0: '#markup' => t('Invalid display id @display', ['@display' => $display_id]), Chris@0: ]; Chris@0: } Chris@0: Chris@0: $executable->setArguments($args); Chris@0: Chris@0: // Store the current view URL for later use: Chris@0: if ($executable->hasUrl() && $executable->display_handler->getOption('path')) { Chris@0: $path = $executable->getUrl(); Chris@0: } Chris@0: Chris@0: // Make view links come back to preview. Chris@0: Chris@0: // Also override the current path so we get the pager, and make sure the Chris@0: // Request object gets all of the proper values from $_SERVER. Chris@0: $request = Request::createFromGlobals(); Chris@0: $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'entity.view.preview_form'); Chris@0: $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, \Drupal::service('router.route_provider')->getRouteByName('entity.view.preview_form')); Chris@0: $request->attributes->set('view', $this->storage); Chris@0: $request->attributes->set('display_id', $display_id); Chris@0: $raw_parameters = new ParameterBag(); Chris@0: $raw_parameters->set('view', $this->id()); Chris@0: $raw_parameters->set('display_id', $display_id); Chris@0: $request->attributes->set('_raw_variables', $raw_parameters); Chris@0: Chris@0: foreach ($args as $key => $arg) { Chris@0: $request->attributes->set('arg_' . $key, $arg); Chris@0: } Chris@0: $request_stack->push($request); Chris@0: Chris@0: // Suppress contextual links of entities within the result set during a Chris@0: // Preview. Chris@0: // @todo We'll want to add contextual links specific to editing the View, so Chris@0: // the suppression may need to be moved deeper into the Preview pipeline. Chris@0: views_ui_contextual_links_suppress_push(); Chris@0: Chris@0: $show_additional_queries = $config->get('ui.show.additional_queries'); Chris@0: Chris@0: Timer::start('entity.view.preview_form'); Chris@0: Chris@0: if ($show_additional_queries) { Chris@0: $this->startQueryCapture(); Chris@0: } Chris@0: Chris@0: // Execute/get the view preview. Chris@0: $preview = $executable->preview($display_id, $args); Chris@0: Chris@0: if ($show_additional_queries) { Chris@0: $this->endQueryCapture(); Chris@0: } Chris@0: Chris@0: $this->render_time = Timer::stop('entity.view.preview_form')['time']; Chris@0: Chris@0: views_ui_contextual_links_suppress_pop(); Chris@0: Chris@0: // Prepare the query information and statistics to show either above or Chris@0: // below the view preview. Chris@0: // Initialise the empty rows arrays so we can safely merge them later. Chris@0: $rows['query'] = []; Chris@0: $rows['statistics'] = []; Chris@0: if ($show_info || $show_query || $show_stats) { Chris@0: // Get information from the preview for display. Chris@0: if (!empty($executable->build_info['query'])) { Chris@0: if ($show_query) { Chris@0: $query_string = $executable->build_info['query']; Chris@0: // Only the sql default class has a method getArguments. Chris@0: $quoted = []; Chris@0: Chris@0: if ($executable->query instanceof Sql) { Chris@0: $quoted = $query_string->getArguments(); Chris@0: $connection = Database::getConnection(); Chris@0: foreach ($quoted as $key => $val) { Chris@0: if (is_array($val)) { Chris@0: $quoted[$key] = implode(', ', array_map([$connection, 'quote'], $val)); Chris@0: } Chris@0: else { Chris@0: $quoted[$key] = $connection->quote($val); Chris@0: } Chris@0: } Chris@0: } Chris@0: $rows['query'][] = [ Chris@0: [ Chris@0: 'data' => [ Chris@0: '#type' => 'inline_template', Chris@0: '#template' => "{% trans 'Query' %}", Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: 'data' => [ Chris@0: '#type' => 'inline_template', Chris@0: '#template' => '
{{ query }}
', Chris@0: '#context' => ['query' => strtr($query_string, $quoted)], Chris@0: ], Chris@0: ], Chris@0: ]; Chris@0: if (!empty($this->additionalQueries)) { Chris@0: $queries[] = [ Chris@0: '#prefix' => '', Chris@0: '#markup' => t('These queries were run during view rendering:'), Chris@0: '#suffix' => '', Chris@0: ]; Chris@0: foreach ($this->additionalQueries as $query) { Chris@0: $query_string = strtr($query['query'], $query['args']); Chris@0: $queries[] = [ Chris@0: '#prefix' => "\n", Chris@0: '#markup' => t('[@time ms] @query', ['@time' => round($query['time'] * 100000, 1) / 100000.0, '@query' => $query_string]), Chris@0: ]; Chris@0: } Chris@0: Chris@0: $rows['query'][] = [ Chris@0: [ Chris@0: 'data' => [ Chris@0: '#type' => 'inline_template', Chris@0: '#template' => "{% trans 'Other queries' %}", Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: 'data' => [ Chris@0: '#prefix' => '
',
Chris@0:                      'queries' => $queries,
Chris@0:                      '#suffix' => '
', Chris@0: ], Chris@0: ], Chris@0: ]; Chris@0: } Chris@0: } Chris@0: if ($show_info) { Chris@0: $rows['query'][] = [ Chris@0: [ Chris@0: 'data' => [ Chris@0: '#type' => 'inline_template', Chris@0: '#template' => "{% trans 'Title' %}", Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: 'data' => [ Chris@0: '#markup' => $executable->getTitle(), Chris@0: ], Chris@0: ], Chris@0: ]; Chris@0: if (isset($path)) { Chris@0: // @todo Views should expect and store a leading /. See: Chris@0: // https://www.drupal.org/node/2423913 Chris@0: $path = \Drupal::l($path->toString(), $path); Chris@0: } Chris@0: else { Chris@0: $path = t('This display has no path.'); Chris@0: } Chris@0: $rows['query'][] = [ Chris@0: [ Chris@0: 'data' => [ Chris@0: '#prefix' => '', Chris@0: '#markup' => t('Path'), Chris@0: '#suffix' => '', Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: 'data' => [ Chris@0: '#markup' => $path, Chris@0: ], Chris@17: ], Chris@0: ]; Chris@0: } Chris@0: if ($show_stats) { Chris@0: $rows['statistics'][] = [ Chris@0: [ Chris@0: 'data' => [ Chris@0: '#type' => 'inline_template', Chris@0: '#template' => "{% trans 'Query build time' %}", Chris@0: ], Chris@0: ], Chris@0: t('@time ms', ['@time' => intval($executable->build_time * 100000) / 100]), Chris@0: ]; Chris@0: Chris@0: $rows['statistics'][] = [ Chris@0: [ Chris@0: 'data' => [ Chris@0: '#type' => 'inline_template', Chris@0: '#template' => "{% trans 'Query execute time' %}", Chris@0: ], Chris@0: ], Chris@0: t('@time ms', ['@time' => intval($executable->execute_time * 100000) / 100]), Chris@0: ]; Chris@0: Chris@0: $rows['statistics'][] = [ Chris@0: [ Chris@0: 'data' => [ Chris@0: '#type' => 'inline_template', Chris@0: '#template' => "{% trans 'View render time' %}", Chris@0: ], Chris@0: ], Chris@0: t('@time ms', ['@time' => intval($this->render_time * 100) / 100]), Chris@0: ]; Chris@0: } Chris@0: \Drupal::moduleHandler()->alter('views_preview_info', $rows, $executable); Chris@0: } Chris@0: else { Chris@0: // No query was run. Display that information in place of either the Chris@0: // query or the performance statistics, whichever comes first. Chris@0: if ($combined || ($show_location === 'above')) { Chris@0: $rows['query'][] = [ Chris@0: [ Chris@0: 'data' => [ Chris@0: '#prefix' => '', Chris@0: '#markup' => t('Query'), Chris@0: '#suffix' => '', Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: 'data' => [ Chris@0: '#markup' => t('No query was run'), Chris@0: ], Chris@0: ], Chris@0: ]; Chris@0: } Chris@0: else { Chris@0: $rows['statistics'][] = [ Chris@0: [ Chris@0: 'data' => [ Chris@0: '#prefix' => '', Chris@0: '#markup' => t('Query'), Chris@0: '#suffix' => '', Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: 'data' => [ Chris@0: '#markup' => t('No query was run'), Chris@0: ], Chris@0: ], Chris@0: ]; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: else { Chris@0: foreach ($errors as $display_errors) { Chris@0: foreach ($display_errors as $error) { Chris@17: \Drupal::messenger()->addError($error); Chris@0: } Chris@0: } Chris@0: $preview = ['#markup' => t('Unable to preview due to validation errors.')]; Chris@0: } Chris@0: Chris@0: // Assemble the preview, the query info, and the query statistics in the Chris@0: // requested order. Chris@0: $table = [ Chris@0: '#type' => 'table', Chris@0: '#prefix' => '
', Chris@0: '#suffix' => '
', Chris@0: '#rows' => array_merge($rows['query'], $rows['statistics']), Chris@0: ]; Chris@0: Chris@0: if ($show_location == 'above') { Chris@0: $output = [ Chris@0: 'table' => $table, Chris@0: 'preview' => $preview, Chris@0: ]; Chris@0: } Chris@0: else { Chris@0: $output = [ Chris@0: 'preview' => $preview, Chris@0: 'table' => $table, Chris@0: ]; Chris@0: } Chris@0: Chris@0: // Ensure that we just remove an additional request we pushed earlier. Chris@0: // This could happen if $errors was not empty. Chris@0: if ($request_stack->getCurrentRequest() != $current_request) { Chris@0: $request_stack->pop(); Chris@0: } Chris@0: return $output; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the user's current progress through the form stack. Chris@0: * Chris@0: * @return Chris@0: * FALSE if the user is not currently in a multiple-form stack. Otherwise, Chris@0: * an associative array with the following keys: Chris@0: * - current: The number of the current form on the stack. Chris@0: * - total: The total number of forms originally on the stack. Chris@0: */ Chris@0: public function getFormProgress() { Chris@0: $progress = FALSE; Chris@0: if (!empty($this->stack)) { Chris@0: // The forms on the stack have integer keys that don't change as the forms Chris@0: // are completed, so we can see which ones are still left. Chris@0: $keys = array_keys($this->stack); Chris@0: // Add 1 to the array keys for the benefit of humans, who start counting Chris@0: // from 1 and not 0. Chris@0: $current = reset($keys) + 1; Chris@0: $total = end($keys) + 1; Chris@0: if ($total > 1) { Chris@0: $progress = []; Chris@0: $progress['current'] = $current; Chris@0: $progress['total'] = $total; Chris@0: } Chris@0: } Chris@0: return $progress; Chris@0: } Chris@0: Chris@0: /** Chris@14: * Sets a cached view object in the shared tempstore. Chris@0: */ Chris@0: public function cacheSet() { Chris@0: if ($this->isLocked()) { Chris@17: \Drupal::messenger()->addError(t('Changes cannot be made to a locked view.')); Chris@0: return; Chris@0: } Chris@0: Chris@0: // Let any future object know that this view has changed. Chris@0: $this->changed = TRUE; Chris@0: Chris@0: $executable = $this->getExecutable(); Chris@0: if (isset($executable->current_display)) { Chris@0: // Add the knowledge of the changed display, too. Chris@0: $this->changed_display[$executable->current_display] = TRUE; Chris@0: $executable->current_display = NULL; Chris@0: } Chris@0: Chris@0: // Unset handlers. We don't want to write these into the cache. Chris@0: $executable->display_handler = NULL; Chris@0: $executable->default_display = NULL; Chris@0: $executable->query = NULL; Chris@0: $executable->displayHandlers = NULL; Chris@14: \Drupal::service('tempstore.shared')->get('views')->set($this->id(), $this); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns whether the current view is locked. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the view is locked, FALSE otherwise. Chris@0: */ Chris@0: public function isLocked() { Chris@18: $lock = $this->getLock(); Chris@18: return $lock && $lock->getOwnerId() != \Drupal::currentUser()->id(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Passes through all unknown calls onto the storage object. Chris@0: */ Chris@0: public function __call($method, $args) { Chris@0: return call_user_func_array([$this->storage, $method], $args); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function &getDisplay($display_id) { Chris@0: return $this->storage->getDisplay($display_id); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function id() { Chris@0: return $this->storage->id(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function uuid() { Chris@0: return $this->storage->uuid(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function isNew() { Chris@0: return $this->storage->isNew(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getEntityTypeId() { Chris@0: return $this->storage->getEntityTypeId(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function bundle() { Chris@0: return $this->storage->bundle(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getEntityType() { Chris@0: return $this->storage->getEntityType(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function createDuplicate() { Chris@0: return $this->storage->createDuplicate(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function load($id) { Chris@0: return View::load($id); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function loadMultiple(array $ids = NULL) { Chris@0: return View::loadMultiple($ids); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function create(array $values = []) { Chris@0: return View::create($values); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function delete() { Chris@0: return $this->storage->delete(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function save() { Chris@0: return $this->storage->save(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function urlInfo($rel = 'edit-form', array $options = []) { Chris@18: return $this->storage->toUrl($rel, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function toUrl($rel = 'edit-form', array $options = []) { Chris@0: return $this->storage->toUrl($rel, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function link($text = NULL, $rel = 'edit-form', array $options = []) { Chris@0: return $this->storage->link($text, $rel, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function toLink($text = NULL, $rel = 'edit-form', array $options = []) { Chris@0: return $this->storage->toLink($text, $rel, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function label() { Chris@0: return $this->storage->label(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function enforceIsNew($value = TRUE) { Chris@0: return $this->storage->enforceIsNew($value); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function toArray() { Chris@0: return $this->storage->toArray(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function language() { Chris@0: return $this->storage->language(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) { Chris@0: return $this->storage->access($operation, $account, $return_as_object); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function enable() { Chris@0: return $this->storage->enable(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function disable() { Chris@0: return $this->storage->disable(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function status() { Chris@0: return $this->storage->status(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getOriginalId() { Chris@0: return $this->storage->getOriginalId(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setOriginalId($id) { Chris@0: return $this->storage->setOriginalId($id); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function preSave(EntityStorageInterface $storage) { Chris@0: $this->storage->presave($storage); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function postSave(EntityStorageInterface $storage, $update = TRUE) { Chris@0: $this->storage->postSave($storage, $update); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function preCreate(EntityStorageInterface $storage, array &$values) { Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function postCreate(EntityStorageInterface $storage) { Chris@0: $this->storage->postCreate($storage); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function preDelete(EntityStorageInterface $storage, array $entities) { Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function postDelete(EntityStorageInterface $storage, array $entities) { Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function postLoad(EntityStorageInterface $storage, array &$entities) { Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getExecutable() { Chris@0: return $this->storage->getExecutable(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function duplicateDisplayAsType($old_display_id, $new_display_type) { Chris@0: return $this->storage->duplicateDisplayAsType($old_display_id, $new_display_type); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function mergeDefaultDisplaysOptions() { Chris@0: $this->storage->mergeDefaultDisplaysOptions(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function uriRelationships() { Chris@0: return $this->storage->uriRelationships(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function referencedEntities() { Chris@0: return $this->storage->referencedEntities(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function url($rel = 'edit-form', $options = []) { Chris@0: return $this->storage->url($rel, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function hasLinkTemplate($key) { Chris@0: return $this->storage->hasLinkTemplate($key); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function calculateDependencies() { Chris@0: $this->storage->calculateDependencies(); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getConfigDependencyKey() { Chris@0: return $this->storage->getConfigDependencyKey(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getConfigDependencyName() { Chris@0: return $this->storage->getConfigDependencyName(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getConfigTarget() { Chris@0: return $this->storage->getConfigTarget(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function onDependencyRemoval(array $dependencies) { Chris@0: return $this->storage->onDependencyRemoval($dependencies); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getDependencies() { Chris@0: return $this->storage->getDependencies(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheContexts() { Chris@0: return $this->storage->getCacheContexts(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheTags() { Chris@0: return $this->storage->getCacheTags(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheMaxAge() { Chris@0: return $this->storage->getCacheMaxAge(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getTypedData() { Chris@0: $this->storage->getTypedData(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addDisplay($plugin_id = 'page', $title = NULL, $id = NULL) { Chris@0: return $this->storage->addDisplay($plugin_id, $title, $id); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function isInstallable() { Chris@0: return $this->storage->isInstallable(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setThirdPartySetting($module, $key, $value) { Chris@0: return $this->storage->setThirdPartySetting($module, $key, $value); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getThirdPartySetting($module, $key, $default = NULL) { Chris@0: return $this->storage->getThirdPartySetting($module, $key, $default); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getThirdPartySettings($module) { Chris@0: return $this->storage->getThirdPartySettings($module); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function unsetThirdPartySetting($module, $key) { Chris@0: return $this->storage->unsetThirdPartySetting($module, $key); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getThirdPartyProviders() { Chris@0: return $this->storage->getThirdPartyProviders(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function trustData() { Chris@0: return $this->storage->trustData(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function hasTrustedData() { Chris@0: return $this->storage->hasTrustedData(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addCacheableDependency($other_object) { Chris@0: $this->storage->addCacheableDependency($other_object); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addCacheContexts(array $cache_contexts) { Chris@0: return $this->storage->addCacheContexts($cache_contexts); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function mergeCacheMaxAge($max_age) { Chris@0: return $this->storage->mergeCacheMaxAge($max_age); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheTagsToInvalidate() { Chris@0: return $this->storage->getCacheTagsToInvalidate(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addCacheTags(array $cache_tags) { Chris@0: return $this->storage->addCacheTags($cache_tags); Chris@0: } Chris@0: Chris@18: /** Chris@18: * Gets the lock on this View. Chris@18: * Chris@18: * @return \Drupal\Core\TempStore\Lock|null Chris@18: * The lock, if one exists. Chris@18: */ Chris@18: public function getLock() { Chris@18: return $this->lock; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Sets a lock on this View. Chris@18: * Chris@18: * @param \Drupal\Core\TempStore\Lock $lock Chris@18: * The lock object. Chris@18: * Chris@18: * @return $this Chris@18: */ Chris@18: public function setLock(Lock $lock) { Chris@18: $this->lock = $lock; Chris@18: return $this; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Unsets the lock on this View. Chris@18: * Chris@18: * @return $this Chris@18: */ Chris@18: public function unsetLock() { Chris@18: $this->lock = NULL; Chris@18: return $this; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function __set($name, $value) { Chris@18: if ($name === 'lock') { Chris@18: @trigger_error('Using the "lock" public property of a View is deprecated in Drupal 8.7.0 and will not be allowed in Drupal 9.0.0. Use \Drupal\views_ui\ViewUI::setLock() instead. See https://www.drupal.org/node/3025869.', E_USER_DEPRECATED); Chris@18: if ($value instanceof \stdClass && property_exists($value, 'owner') && property_exists($value, 'updated')) { Chris@18: $value = new Lock($value->owner, $value->updated); Chris@18: } Chris@18: $this->setLock($value); Chris@18: } Chris@18: else { Chris@18: $this->{$name} = $value; Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function __get($name) { Chris@18: if ($name === 'lock') { Chris@18: @trigger_error('Using the "lock" public property of a View is deprecated in Drupal 8.7.0 and will not be allowed in Drupal 9.0.0. Use \Drupal\views_ui\ViewUI::getLock() instead. See https://www.drupal.org/node/3025869.', E_USER_DEPRECATED); Chris@18: return $this->getLock(); Chris@18: } Chris@18: } Chris@18: Chris@0: }