tempStore = $temp_store_factory->get('views'); $this->requestStack = $requestStack; $this->dateFormatter = $date_formatter; $this->elementInfo = $element_info; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('tempstore.shared'), $container->get('request_stack'), $container->get('date.formatter'), $container->get('element_info') ); } /** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { $view = $this->entity; $display_id = $this->displayID; // Do not allow the form to be cached, because $form_state->get('view') can become // stale between page requests. // See views_ui_ajax_get_form() for how this affects #ajax. // @todo To remove this and allow the form to be cacheable: // - Change $form_state->get('view') to $form_state->getTemporary()['view']. // - Add a #process function to initialize $form_state->getTemporary()['view'] // on cached form submissions. // - Use \Drupal\Core\Form\FormStateInterface::loadInclude(). $form_state->disableCache(); if ($display_id) { if (!$view->getExecutable()->setDisplay($display_id)) { $form['#markup'] = $this->t('Invalid display id @display', ['@display' => $display_id]); return $form; } } $form['#tree'] = TRUE; $form['#attached']['library'][] = 'core/jquery.ui.tabs'; $form['#attached']['library'][] = 'core/jquery.ui.dialog'; $form['#attached']['library'][] = 'core/drupal.states'; $form['#attached']['library'][] = 'core/drupal.tabledrag'; $form['#attached']['library'][] = 'views_ui/views_ui.admin'; $form['#attached']['library'][] = 'views_ui/admin.styling'; $form += [ '#prefix' => '', '#suffix' => '', ]; $view_status = $view->status() ? 'enabled' : 'disabled'; $form['#prefix'] .= '
'; $form['#suffix'] = '
' . $form['#suffix']; $form['#attributes']['class'] = ['form-edit']; if ($view->isLocked()) { $username = [ '#theme' => 'username', '#account' => $this->entityManager->getStorage('user')->load($view->lock->owner), ]; $lock_message_substitutions = [ '@user' => \Drupal::service('renderer')->render($username), '@age' => $this->dateFormatter->formatTimeDiffSince($view->lock->updated), ':url' => $view->url('break-lock-form'), ]; $form['locked'] = [ '#type' => 'container', '#attributes' => ['class' => ['view-locked', 'messages', 'messages--warning']], '#children' => $this->t('This view is being edited by user @user, and is therefore locked from editing by others. This lock is @age old. Click here to break this lock.', $lock_message_substitutions), '#weight' => -10, ]; } else { $form['changed'] = [ '#type' => 'container', '#attributes' => ['class' => ['view-changed', 'messages', 'messages--warning']], '#children' => $this->t('You have unsaved changes.'), '#weight' => -10, ]; if (empty($view->changed)) { $form['changed']['#attributes']['class'][] = 'js-hide'; } } $form['displays'] = [ '#prefix' => '

' . $this->t('Displays') . '

', '#type' => 'container', '#attributes' => [ 'class' => [ 'views-displays', ], ], ]; $form['displays']['top'] = $this->renderDisplayTop($view); // The rest requires a display to be selected. if ($display_id) { $form_state->set('display_id', $display_id); // The part of the page where editing will take place. $form['displays']['settings'] = [ '#type' => 'container', '#id' => 'edit-display-settings', '#attributes' => [ 'class' => ['edit-display-settings'], ], ]; // Add a text that the display is disabled. if ($view->getExecutable()->displayHandlers->has($display_id)) { if (!$view->getExecutable()->displayHandlers->get($display_id)->isEnabled()) { $form['displays']['settings']['disabled']['#markup'] = $this->t('This display is disabled.'); } } // Add the edit display content $tab_content = $this->getDisplayTab($view); $tab_content['#theme_wrappers'] = ['container']; $tab_content['#attributes'] = ['class' => ['views-display-tab']]; $tab_content['#id'] = 'views-tab-' . $display_id; // Mark deleted displays as such. $display = $view->get('display'); if (!empty($display[$display_id]['deleted'])) { $tab_content['#attributes']['class'][] = 'views-display-deleted'; } // Mark disabled displays as such. if ($view->getExecutable()->displayHandlers->has($display_id) && !$view->getExecutable()->displayHandlers->get($display_id)->isEnabled()) { $tab_content['#attributes']['class'][] = 'views-display-disabled'; } $form['displays']['settings']['settings_content'] = [ '#type' => 'container', 'tab_content' => $tab_content, ]; } return $form; } /** * {@inheritdoc} */ protected function actions(array $form, FormStateInterface $form_state) { $actions = parent::actions($form, $form_state); unset($actions['delete']); $actions['cancel'] = [ '#type' => 'submit', '#value' => $this->t('Cancel'), '#submit' => ['::cancel'], '#limit_validation_errors' => [], ]; if ($this->entity->isLocked()) { $actions['submit']['#access'] = FALSE; $actions['cancel']['#access'] = FALSE; } return $actions; } /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); $view = $this->entity; if ($view->isLocked()) { $form_state->setErrorByName('', $this->t('Changes cannot be made to a locked view.')); } foreach ($view->getExecutable()->validate() as $display_errors) { foreach ($display_errors as $error) { $form_state->setErrorByName('', $error); } } } /** * {@inheritdoc} */ public function save(array $form, FormStateInterface $form_state) { $view = $this->entity; $executable = $view->getExecutable(); $executable->initDisplay(); // Go through and remove displayed scheduled for removal. $displays = $view->get('display'); foreach ($displays as $id => $display) { if (!empty($display['deleted'])) { // Remove view display from view attachment under the attachments // options. $display_handler = $executable->displayHandlers->get($id); if ($attachments = $display_handler->getAttachedDisplays()) { foreach ($attachments as $attachment) { $attached_options = $executable->displayHandlers->get($attachment)->getOption('displays'); unset($attached_options[$id]); $executable->displayHandlers->get($attachment)->setOption('displays', $attached_options); } } $executable->displayHandlers->remove($id); unset($displays[$id]); } } // Rename display ids if needed. foreach ($executable->displayHandlers as $id => $display) { if (!empty($display->display['new_id']) && $display->display['new_id'] !== $display->display['id'] && empty($display->display['deleted'])) { $new_id = $display->display['new_id']; $display->display['id'] = $new_id; unset($display->display['new_id']); $executable->displayHandlers->set($new_id, $display); $displays[$new_id] = $displays[$id]; unset($displays[$id]); // Redirect the user to the renamed display to be sure that the page itself exists and doesn't throw errors. $form_state->setRedirect('entity.view.edit_display_form', [ 'view' => $view->id(), 'display_id' => $new_id, ]); } elseif (isset($display->display['new_id'])) { unset($display->display['new_id']); } } $view->set('display', $displays); // @todo: Revisit this when https://www.drupal.org/node/1668866 is in. $query = $this->requestStack->getCurrentRequest()->query; $destination = $query->get('destination'); if (!empty($destination)) { // Find out the first display which has a changed path and redirect to this url. $old_view = Views::getView($view->id()); $old_view->initDisplay(); foreach ($old_view->displayHandlers as $id => $display) { // Only check for displays with a path. $old_path = $display->getOption('path'); if (empty($old_path)) { continue; } if (($display->getPluginId() == 'page') && ($old_path == $destination) && ($old_path != $view->getExecutable()->displayHandlers->get($id)->getOption('path'))) { $destination = $view->getExecutable()->displayHandlers->get($id)->getOption('path'); $query->remove('destination'); } } // @todo Use Url::fromPath() once https://www.drupal.org/node/2351379 is // resolved. $form_state->setRedirectUrl(Url::fromUri("base:$destination")); } $view->save(); $this->messenger()->addStatus($this->t('The view %name has been saved.', ['%name' => $view->label()])); // Remove this view from cache so we can edit it properly. $this->tempStore->delete($view->id()); } /** * Form submission handler for the 'cancel' action. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ public function cancel(array $form, FormStateInterface $form_state) { // Remove this view from cache so edits will be lost. $view = $this->entity; $this->tempStore->delete($view->id()); $form_state->setRedirectUrl($this->entity->urlInfo('collection')); } /** * Returns a renderable array representing the edit page for one display. */ public function getDisplayTab($view) { $build = []; $display_id = $this->displayID; $display = $view->getExecutable()->displayHandlers->get($display_id); // If the plugin doesn't exist, display an error message instead of an edit // page. if (empty($display)) { // @TODO: Improved UX for the case where a plugin is missing. $build['#markup'] = $this->t("Error: Display @display refers to a plugin named '@plugin', but that plugin is not available.", ['@display' => $display->display['id'], '@plugin' => $display->display['display_plugin']]); } // Build the content of the edit page. else { $build['details'] = $this->getDisplayDetails($view, $display->display); } // In AJAX context, ViewUI::rebuildCurrentTab() returns this outside of form // context, so hook_form_views_ui_edit_form_alter() is insufficient. \Drupal::moduleHandler()->alter('views_ui_display_tab', $build, $view, $display_id); return $build; } /** * Helper function to get the display details section of the edit UI. * * @param $display * * @return array * A renderable page build array. */ public function getDisplayDetails($view, $display) { $display_title = $this->getDisplayLabel($view, $display['id'], FALSE); $build = [ '#theme_wrappers' => ['container'], '#attributes' => ['id' => 'edit-display-settings-details'], ]; $is_display_deleted = !empty($display['deleted']); // The master display cannot be duplicated. $is_default = $display['id'] == 'default'; // @todo: Figure out why getOption doesn't work here. $is_enabled = $view->getExecutable()->displayHandlers->get($display['id'])->isEnabled(); if ($display['id'] != 'default') { $build['top']['#theme_wrappers'] = ['container']; $build['top']['#attributes']['id'] = 'edit-display-settings-top'; $build['top']['#attributes']['class'] = ['views-ui-display-tab-actions', 'edit-display-settings-top', 'views-ui-display-tab-bucket', 'clearfix']; // The Delete, Duplicate and Undo Delete buttons. $build['top']['actions'] = [ '#theme_wrappers' => ['dropbutton_wrapper'], ]; // Because some of the 'links' are actually submit buttons, we have to // manually wrap each item in
  • and the whole list in