Chris@0: tempStore = $temp_store_factory->get('views'); Chris@0: $this->requestStack = $requestStack; Chris@0: $this->dateFormatter = $date_formatter; Chris@0: $this->elementInfo = $element_info; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function create(ContainerInterface $container) { Chris@0: return new static( Chris@14: $container->get('tempstore.shared'), Chris@0: $container->get('request_stack'), Chris@0: $container->get('date.formatter'), Chris@0: $container->get('element_info') Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function form(array $form, FormStateInterface $form_state) { Chris@18: /** @var \Drupal\views_ui\ViewUI $view */ Chris@0: $view = $this->entity; Chris@0: $display_id = $this->displayID; Chris@0: // Do not allow the form to be cached, because $form_state->get('view') can become Chris@0: // stale between page requests. Chris@0: // See views_ui_ajax_get_form() for how this affects #ajax. Chris@0: // @todo To remove this and allow the form to be cacheable: Chris@0: // - Change $form_state->get('view') to $form_state->getTemporary()['view']. Chris@0: // - Add a #process function to initialize $form_state->getTemporary()['view'] Chris@0: // on cached form submissions. Chris@0: // - Use \Drupal\Core\Form\FormStateInterface::loadInclude(). Chris@0: $form_state->disableCache(); Chris@0: Chris@0: if ($display_id) { Chris@0: if (!$view->getExecutable()->setDisplay($display_id)) { Chris@0: $form['#markup'] = $this->t('Invalid display id @display', ['@display' => $display_id]); Chris@0: return $form; Chris@0: } Chris@0: } Chris@0: Chris@0: $form['#tree'] = TRUE; Chris@0: Chris@0: $form['#attached']['library'][] = 'core/jquery.ui.tabs'; Chris@0: $form['#attached']['library'][] = 'core/jquery.ui.dialog'; Chris@0: $form['#attached']['library'][] = 'core/drupal.states'; Chris@0: $form['#attached']['library'][] = 'core/drupal.tabledrag'; Chris@0: $form['#attached']['library'][] = 'views_ui/views_ui.admin'; Chris@0: $form['#attached']['library'][] = 'views_ui/admin.styling'; Chris@0: Chris@0: $form += [ Chris@0: '#prefix' => '', Chris@0: '#suffix' => '', Chris@0: ]; Chris@0: Chris@0: $view_status = $view->status() ? 'enabled' : 'disabled'; Chris@0: $form['#prefix'] .= '
'; Chris@0: $form['#suffix'] = '
' . $form['#suffix']; Chris@0: Chris@0: $form['#attributes']['class'] = ['form-edit']; Chris@0: Chris@0: if ($view->isLocked()) { Chris@0: $form['locked'] = [ Chris@0: '#type' => 'container', Chris@0: '#attributes' => ['class' => ['view-locked', 'messages', 'messages--warning']], Chris@0: '#weight' => -10, Chris@18: 'message' => [ Chris@18: '#type' => 'break_lock_link', Chris@18: '#label' => $view->getEntityType()->getSingularLabel(), Chris@18: '#lock' => $view->getLock(), Chris@18: '#url' => $view->toUrl('break-lock-form'), Chris@18: ], Chris@0: ]; Chris@0: } Chris@0: else { Chris@0: $form['changed'] = [ Chris@0: '#type' => 'container', Chris@0: '#attributes' => ['class' => ['view-changed', 'messages', 'messages--warning']], Chris@0: '#children' => $this->t('You have unsaved changes.'), Chris@0: '#weight' => -10, Chris@0: ]; Chris@0: if (empty($view->changed)) { Chris@0: $form['changed']['#attributes']['class'][] = 'js-hide'; Chris@0: } Chris@0: } Chris@0: Chris@0: $form['displays'] = [ Chris@0: '#prefix' => '

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

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