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 .
Chris@0: $build['top']['actions']['prefix']['#markup'] = '';
Chris@0:
Chris@0: // The area above the three columns.
Chris@0: $build['top']['display_title'] = [
Chris@0: '#theme' => 'views_ui_display_tab_setting',
Chris@0: '#description' => $this->t('Display name'),
Chris@0: '#link' => $view->getExecutable()->displayHandlers->get($display['id'])->optionLink($display_title, 'display_title'),
Chris@0: ];
Chris@0: }
Chris@0:
Chris@0: $build['columns'] = [];
Chris@0: $build['columns']['#theme_wrappers'] = ['container'];
Chris@0: $build['columns']['#attributes'] = ['id' => 'edit-display-settings-main', 'class' => ['clearfix', 'views-display-columns']];
Chris@0:
Chris@0: $build['columns']['first']['#theme_wrappers'] = ['container'];
Chris@0: $build['columns']['first']['#attributes'] = ['class' => ['views-display-column', 'first']];
Chris@0:
Chris@0: $build['columns']['second']['#theme_wrappers'] = ['container'];
Chris@0: $build['columns']['second']['#attributes'] = ['class' => ['views-display-column', 'second']];
Chris@0:
Chris@0: $build['columns']['second']['settings'] = [];
Chris@0: $build['columns']['second']['header'] = [];
Chris@0: $build['columns']['second']['footer'] = [];
Chris@0: $build['columns']['second']['empty'] = [];
Chris@0: $build['columns']['second']['pager'] = [];
Chris@0:
Chris@0: // The third column buckets are wrapped in details.
Chris@0: $build['columns']['third'] = [
Chris@0: '#type' => 'details',
Chris@0: '#title' => $this->t('Advanced'),
Chris@0: '#theme_wrappers' => ['details'],
Chris@0: '#attributes' => [
Chris@0: 'class' => [
Chris@0: 'views-display-column',
Chris@0: 'third',
Chris@0: ],
Chris@0: ],
Chris@0: ];
Chris@0: // Collapse the details by default.
Chris@0: $build['columns']['third']['#open'] = \Drupal::config('views.settings')->get('ui.show.advanced_column');
Chris@0:
Chris@0: // Each option (e.g. title, access, display as grid/table/list) fits into one
Chris@0: // of several "buckets," or boxes (Format, Fields, Sort, and so on).
Chris@0: $buckets = [];
Chris@0:
Chris@0: // Fetch options from the display plugin, with a list of buckets they go into.
Chris@0: $options = [];
Chris@0: $view->getExecutable()->displayHandlers->get($display['id'])->optionsSummary($buckets, $options);
Chris@0:
Chris@0: // Place each option into its bucket.
Chris@0: foreach ($options as $id => $option) {
Chris@0: // Each option self-identifies as belonging in a particular bucket.
Chris@0: $buckets[$option['category']]['build'][$id] = $this->buildOptionForm($view, $id, $option, $display);
Chris@0: }
Chris@0:
Chris@0: // Place each bucket into the proper column.
Chris@0: foreach ($buckets as $id => $bucket) {
Chris@0: // Let buckets identify themselves as belonging in a column.
Chris@0: if (isset($bucket['column']) && isset($build['columns'][$bucket['column']])) {
Chris@0: $column = $bucket['column'];
Chris@0: }
Chris@0: // If a bucket doesn't pick one of our predefined columns to belong to, put
Chris@0: // it in the last one.
Chris@0: else {
Chris@0: $column = 'third';
Chris@0: }
Chris@0: if (isset($bucket['build']) && is_array($bucket['build'])) {
Chris@0: $build['columns'][$column][$id] = $bucket['build'];
Chris@0: $build['columns'][$column][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
Chris@0: $build['columns'][$column][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
Chris@0: $build['columns'][$column][$id]['#name'] = $id;
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: $build['columns']['first']['fields'] = $this->getFormBucket($view, 'field', $display);
Chris@0: $build['columns']['first']['filters'] = $this->getFormBucket($view, 'filter', $display);
Chris@0: $build['columns']['first']['sorts'] = $this->getFormBucket($view, 'sort', $display);
Chris@0: $build['columns']['second']['header'] = $this->getFormBucket($view, 'header', $display);
Chris@0: $build['columns']['second']['footer'] = $this->getFormBucket($view, 'footer', $display);
Chris@0: $build['columns']['second']['empty'] = $this->getFormBucket($view, 'empty', $display);
Chris@0: $build['columns']['third']['arguments'] = $this->getFormBucket($view, 'argument', $display);
Chris@0: $build['columns']['third']['relationships'] = $this->getFormBucket($view, 'relationship', $display);
Chris@0:
Chris@0: return $build;
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Submit handler to add a restore a removed display to a view.
Chris@0: */
Chris@0: public function submitDisplayUndoDelete($form, FormStateInterface $form_state) {
Chris@0: $view = $this->entity;
Chris@0: // Create the new display
Chris@0: $id = $form_state->get('display_id');
Chris@0: $displays = $view->get('display');
Chris@0: $displays[$id]['deleted'] = FALSE;
Chris@0: $view->set('display', $displays);
Chris@0:
Chris@0: // Store in cache
Chris@0: $view->cacheSet();
Chris@0:
Chris@0: // Redirect to the top-level edit page.
Chris@0: $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $id,
Chris@0: ]);
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Submit handler to enable a disabled display.
Chris@0: */
Chris@0: public function submitDisplayEnable($form, FormStateInterface $form_state) {
Chris@0: $view = $this->entity;
Chris@0: $id = $form_state->get('display_id');
Chris@0: // setOption doesn't work because this would might affect upper displays
Chris@0: $view->getExecutable()->displayHandlers->get($id)->setOption('enabled', TRUE);
Chris@0:
Chris@0: // Store in cache
Chris@0: $view->cacheSet();
Chris@0:
Chris@0: // Redirect to the top-level edit page.
Chris@0: $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $id,
Chris@0: ]);
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Submit handler to disable display.
Chris@0: */
Chris@0: public function submitDisplayDisable($form, FormStateInterface $form_state) {
Chris@0: $view = $this->entity;
Chris@0: $id = $form_state->get('display_id');
Chris@0: $view->getExecutable()->displayHandlers->get($id)->setOption('enabled', FALSE);
Chris@0:
Chris@0: // Store in cache
Chris@0: $view->cacheSet();
Chris@0:
Chris@0: // Redirect to the top-level edit page.
Chris@0: $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $id,
Chris@0: ]);
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Submit handler to delete a display from a view.
Chris@0: */
Chris@0: public function submitDisplayDelete($form, FormStateInterface $form_state) {
Chris@0: $view = $this->entity;
Chris@0: $display_id = $form_state->get('display_id');
Chris@0:
Chris@0: // Mark the display for deletion.
Chris@0: $displays = $view->get('display');
Chris@0: $displays[$display_id]['deleted'] = TRUE;
Chris@0: $view->set('display', $displays);
Chris@0: $view->cacheSet();
Chris@0:
Chris@0: // Redirect to the top-level edit page. The first remaining display will
Chris@0: // become the active display.
Chris@18: $form_state->setRedirectUrl($view->toUrl('edit-form'));
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Regenerate the current tab for AJAX updates.
Chris@0: *
Chris@0: * @param \Drupal\views_ui\ViewUI $view
Chris@0: * The view to regenerate its tab.
Chris@0: * @param \Drupal\Core\Ajax\AjaxResponse $response
Chris@0: * The response object to add new commands to.
Chris@0: * @param string $display_id
Chris@0: * The display ID of the tab to regenerate.
Chris@0: */
Chris@0: public function rebuildCurrentTab(ViewUI $view, AjaxResponse $response, $display_id) {
Chris@0: $this->displayID = $display_id;
Chris@0: if (!$view->getExecutable()->setDisplay('default')) {
Chris@0: return;
Chris@0: }
Chris@0:
Chris@0: // Regenerate the main display area.
Chris@0: $build = $this->getDisplayTab($view);
Chris@0: $response->addCommand(new HtmlCommand('#views-tab-' . $display_id, $build));
Chris@0:
Chris@0: // Regenerate the top area so changes to display names and order will appear.
Chris@0: $build = $this->renderDisplayTop($view);
Chris@0: $response->addCommand(new ReplaceCommand('#views-display-top', $build));
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Render the top of the display so it can be updated during ajax operations.
Chris@0: */
Chris@0: public function renderDisplayTop(ViewUI $view) {
Chris@0: $display_id = $this->displayID;
Chris@0: $element['#theme_wrappers'][] = 'views_ui_container';
Chris@0: $element['#attributes']['class'] = ['views-display-top', 'clearfix'];
Chris@0: $element['#attributes']['id'] = ['views-display-top'];
Chris@0:
Chris@0: // Extra actions for the display
Chris@0: $element['extra_actions'] = [
Chris@0: '#type' => 'dropbutton',
Chris@0: '#attributes' => [
Chris@0: 'id' => 'views-display-extra-actions',
Chris@0: ],
Chris@0: '#links' => [
Chris@0: 'edit-details' => [
Chris@0: 'title' => $this->t('Edit view name/description'),
Chris@0: 'url' => Url::fromRoute('views_ui.form_edit_details', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
Chris@0: 'attributes' => ['class' => ['views-ajax-link']],
Chris@0: ],
Chris@0: 'analyze' => [
Chris@0: 'title' => $this->t('Analyze view'),
Chris@0: 'url' => Url::fromRoute('views_ui.form_analyze', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
Chris@0: 'attributes' => ['class' => ['views-ajax-link']],
Chris@0: ],
Chris@0: 'duplicate' => [
Chris@0: 'title' => $this->t('Duplicate view'),
Chris@18: 'url' => $view->toUrl('duplicate-form'),
Chris@0: ],
Chris@0: 'reorder' => [
Chris@0: 'title' => $this->t('Reorder displays'),
Chris@0: 'url' => Url::fromRoute('views_ui.form_reorder_displays', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
Chris@0: 'attributes' => ['class' => ['views-ajax-link']],
Chris@0: ],
Chris@0: ],
Chris@0: ];
Chris@0:
Chris@0: if ($view->access('delete')) {
Chris@0: $element['extra_actions']['#links']['delete'] = [
Chris@0: 'title' => $this->t('Delete view'),
Chris@18: 'url' => $view->toUrl('delete-form'),
Chris@0: ];
Chris@0: }
Chris@0:
Chris@0: // Let other modules add additional links here.
Chris@0: \Drupal::moduleHandler()->alter('views_ui_display_top_links', $element['extra_actions']['#links'], $view, $display_id);
Chris@0:
Chris@0: if (isset($view->type) && $view->type != $this->t('Default')) {
Chris@0: if ($view->type == $this->t('Overridden')) {
Chris@0: $element['extra_actions']['#links']['revert'] = [
Chris@0: 'title' => $this->t('Revert view'),
Chris@0: 'href' => "admin/structure/views/view/{$view->id()}/revert",
Chris@18: 'query' => ['destination' => $view->toUrl('edit-form')->toString()],
Chris@0: ];
Chris@0: }
Chris@0: else {
Chris@0: $element['extra_actions']['#links']['delete'] = [
Chris@0: 'title' => $this->t('Delete view'),
Chris@18: 'url' => $view->toUrl('delete-form'),
Chris@0: ];
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: // Determine the displays available for editing.
Chris@0: if ($tabs = $this->getDisplayTabs($view)) {
Chris@0: if ($display_id) {
Chris@0: $tabs[$display_id]['#active'] = TRUE;
Chris@0: }
Chris@0: $tabs['#prefix'] = '' . $this->t('Secondary tabs') . '
';
Chris@0: $element['tabs'] = $tabs;
Chris@0: }
Chris@0:
Chris@0: // Buttons for adding a new display.
Chris@0: foreach (Views::fetchPluginNames('display', NULL, [$view->get('base_table')]) as $type => $label) {
Chris@0: $element['add_display'][$type] = [
Chris@0: '#type' => 'submit',
Chris@0: '#value' => $this->t('Add @display', ['@display' => $label]),
Chris@0: '#limit_validation_errors' => [],
Chris@0: '#submit' => ['::submitDisplayAdd', '::submitDelayDestination'],
Chris@0: '#attributes' => ['class' => ['add-display']],
Chris@0: // Allow JavaScript to remove the 'Add ' prefix from the button label when
Chris@0: // placing the button in a "Add" dropdown menu.
Chris@0: '#process' => array_merge(['views_ui_form_button_was_clicked'], $this->elementInfo->getInfoProperty('submit', '#process', [])),
Chris@0: '#values' => [$this->t('Add @display', ['@display' => $label]), $label],
Chris@0: ];
Chris@0: }
Chris@0:
Chris@0: return $element;
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Submit handler for form buttons that do not complete a form workflow.
Chris@0: *
Chris@0: * The Edit View form is a multistep form workflow, but with state managed by
Chris@0: * the SharedTempStore rather than $form_state->setRebuild(). Without this
Chris@0: * submit handler, buttons that add or remove displays would redirect to the
Chris@0: * destination parameter (e.g., when the Edit View form is linked to from a
Chris@0: * contextual link). This handler can be added to buttons whose form submission
Chris@0: * should not yet redirect to the destination.
Chris@0: */
Chris@0: public function submitDelayDestination($form, FormStateInterface $form_state) {
Chris@0: $request = $this->requestStack->getCurrentRequest();
Chris@0: $destination = $request->query->get('destination');
Chris@0:
Chris@0: $redirect = $form_state->getRedirect();
Chris@0: // If there is a destination, and redirects are not explicitly disabled, add
Chris@0: // the destination as a query string to the redirect and suppress it for the
Chris@0: // current request.
Chris@0: if (isset($destination) && $redirect !== FALSE) {
Chris@0: // Create a valid redirect if one does not exist already.
Chris@0: if (!($redirect instanceof Url)) {
Chris@0: $redirect = Url::createFromRequest($request);
Chris@0: }
Chris@0:
Chris@0: // Add the current destination to the redirect unless one exists already.
Chris@0: $options = $redirect->getOptions();
Chris@0: if (!isset($options['query']['destination'])) {
Chris@0: $options['query']['destination'] = $destination;
Chris@0: $redirect->setOptions($options);
Chris@0: }
Chris@0:
Chris@0: $form_state->setRedirectUrl($redirect);
Chris@0: $request->query->remove('destination');
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Submit handler to duplicate a display for a view.
Chris@0: */
Chris@0: public function submitDisplayDuplicate($form, FormStateInterface $form_state) {
Chris@0: $view = $this->entity;
Chris@0: $display_id = $this->displayID;
Chris@0:
Chris@0: // Create the new display.
Chris@0: $displays = $view->get('display');
Chris@0: $display = $view->getExecutable()->newDisplay($displays[$display_id]['display_plugin']);
Chris@0: $new_display_id = $display->display['id'];
Chris@0: $displays[$new_display_id] = $displays[$display_id];
Chris@0: $displays[$new_display_id]['id'] = $new_display_id;
Chris@0: $view->set('display', $displays);
Chris@0:
Chris@0: // By setting the current display the changed marker will appear on the new
Chris@0: // display.
Chris@0: $view->getExecutable()->current_display = $new_display_id;
Chris@0: $view->cacheSet();
Chris@0:
Chris@0: // Redirect to the new display's edit page.
Chris@0: $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $new_display_id,
Chris@0: ]);
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Submit handler to add a display to a view.
Chris@0: */
Chris@0: public function submitDisplayAdd($form, FormStateInterface $form_state) {
Chris@0: $view = $this->entity;
Chris@0: // Create the new display.
Chris@0: $parents = $form_state->getTriggeringElement()['#parents'];
Chris@0: $display_type = array_pop($parents);
Chris@0: $display = $view->getExecutable()->newDisplay($display_type);
Chris@0: $display_id = $display->display['id'];
Chris@0: // A new display got added so the asterisks symbol should appear on the new
Chris@0: // display.
Chris@0: $view->getExecutable()->current_display = $display_id;
Chris@0: $view->cacheSet();
Chris@0:
Chris@0: // Redirect to the new display's edit page.
Chris@0: $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $display_id,
Chris@0: ]);
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Submit handler to Duplicate a display as another display type.
Chris@0: */
Chris@0: public function submitDuplicateDisplayAsType($form, FormStateInterface $form_state) {
Chris@0: /** @var \Drupal\views\ViewEntityInterface $view */
Chris@0: $view = $this->entity;
Chris@0: $display_id = $this->displayID;
Chris@0:
Chris@0: // Create the new display.
Chris@0: $parents = $form_state->getTriggeringElement()['#parents'];
Chris@0: $display_type = array_pop($parents);
Chris@0:
Chris@0: $new_display_id = $view->duplicateDisplayAsType($display_id, $display_type);
Chris@0:
Chris@0: // By setting the current display the changed marker will appear on the new
Chris@0: // display.
Chris@0: $view->getExecutable()->current_display = $new_display_id;
Chris@0: $view->cacheSet();
Chris@0:
Chris@0: // Redirect to the new display's edit page.
Chris@0: $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $new_display_id,
Chris@0: ]);
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Build a renderable array representing one option on the edit form.
Chris@0: *
Chris@0: * This function might be more logical as a method on an object, if a suitable
Chris@0: * object emerges out of refactoring.
Chris@0: */
Chris@0: public function buildOptionForm(ViewUI $view, $id, $option, $display) {
Chris@0: $option_build = [];
Chris@0: $option_build['#theme'] = 'views_ui_display_tab_setting';
Chris@0:
Chris@0: $option_build['#description'] = $option['title'];
Chris@0:
Chris@0: $option_build['#link'] = $view->getExecutable()->displayHandlers->get($display['id'])->optionLink($option['value'], $id, '', empty($option['desc']) ? '' : $option['desc']);
Chris@0:
Chris@0: $option_build['#links'] = [];
Chris@0: if (!empty($option['links']) && is_array($option['links'])) {
Chris@0: foreach ($option['links'] as $link_id => $link_value) {
Chris@0: $option_build['#settings_links'][] = $view->getExecutable()->displayHandlers->get($display['id'])->optionLink($option['setting'], $link_id, 'views-button-configure', $link_value);
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: if (!empty($view->getExecutable()->displayHandlers->get($display['id'])->options['defaults'][$id])) {
Chris@0: $display_id = 'default';
Chris@0: $option_build['#defaulted'] = TRUE;
Chris@0: }
Chris@0: else {
Chris@0: $display_id = $display['id'];
Chris@0: if (!$view->getExecutable()->displayHandlers->get($display['id'])->isDefaultDisplay()) {
Chris@0: if ($view->getExecutable()->displayHandlers->get($display['id'])->defaultableSections($id)) {
Chris@0: $option_build['#overridden'] = TRUE;
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0: $option_build['#attributes']['class'][] = Html::cleanCssIdentifier($display_id . '-' . $id);
Chris@0: return $option_build;
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * Add information about a section to a display.
Chris@0: */
Chris@0: public function getFormBucket(ViewUI $view, $type, $display) {
Chris@0: $executable = $view->getExecutable();
Chris@0: $executable->setDisplay($display['id']);
Chris@0: $executable->initStyle();
Chris@0:
Chris@0: $types = $executable->getHandlerTypes();
Chris@0:
Chris@0: $build = [
Chris@0: '#theme_wrappers' => ['views_ui_display_tab_bucket'],
Chris@0: ];
Chris@0:
Chris@0: $build['#overridden'] = FALSE;
Chris@0: $build['#defaulted'] = FALSE;
Chris@0:
Chris@0: $build['#name'] = $type;
Chris@0: $build['#title'] = $types[$type]['title'];
Chris@0:
Chris@0: $rearrange_url = Url::fromRoute('views_ui.form_rearrange', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id'], 'type' => $type]);
Chris@0: $class = 'icon compact rearrange';
Chris@0:
Chris@0: // Different types now have different rearrange forms, so we use this switch
Chris@0: // to get the right one.
Chris@0: switch ($type) {
Chris@0: case 'filter':
Chris@0: // The rearrange form for filters contains the and/or UI, so override
Chris@0: // the used path.
Chris@0: $rearrange_url = Url::fromRoute('views_ui.form_rearrange_filter', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id']]);
Chris@0: // TODO: Add another class to have another symbol for filter rearrange.
Chris@0: $class = 'icon compact rearrange';
Chris@0: break;
Chris@0: case 'field':
Chris@0: // Fetch the style plugin info so we know whether to list fields or not.
Chris@0: $style_plugin = $executable->style_plugin;
Chris@0: $uses_fields = $style_plugin && $style_plugin->usesFields();
Chris@0: if (!$uses_fields) {
Chris@0: $build['fields'][] = [
Chris@0: '#markup' => $this->t('The selected style or row format does not use fields.'),
Chris@0: '#theme_wrappers' => ['views_ui_container'],
Chris@0: '#attributes' => ['class' => ['views-display-setting']],
Chris@0: ];
Chris@0: return $build;
Chris@0: }
Chris@0: break;
Chris@0: case 'header':
Chris@0: case 'footer':
Chris@0: case 'empty':
Chris@0: if (!$executable->display_handler->usesAreas()) {
Chris@0: $build[$type][] = [
Chris@0: '#markup' => $this->t('The selected display type does not use @type plugins', ['@type' => $type]),
Chris@0: '#theme_wrappers' => ['views_ui_container'],
Chris@0: '#attributes' => ['class' => ['views-display-setting']],
Chris@0: ];
Chris@0: return $build;
Chris@0: }
Chris@0: break;
Chris@0: }
Chris@0:
Chris@0: // Create an array of actions to pass to links template.
Chris@0: $actions = [];
Chris@0: $count_handlers = count($executable->display_handler->getHandlers($type));
Chris@0:
Chris@0: // Create the add text variable for the add action.
Chris@0: $add_text = $this->t('Add @type', ['@type' => $types[$type]['ltitle']]);
Chris@0:
Chris@0: $actions['add'] = [
Chris@0: 'title' => $add_text,
Chris@0: 'url' => Url::fromRoute('views_ui.form_add_handler', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id'], 'type' => $type]),
Chris@0: 'attributes' => ['class' => ['icon compact add', 'views-ajax-link'], 'id' => 'views-add-' . $type],
Chris@0: ];
Chris@0: if ($count_handlers > 0) {
Chris@0: // Create the rearrange text variable for the rearrange action.
Chris@0: $rearrange_text = $type == 'filter' ? $this->t('And/Or Rearrange filter criteria') : $this->t('Rearrange @type', ['@type' => $types[$type]['ltitle']]);
Chris@0:
Chris@0: $actions['rearrange'] = [
Chris@0: 'title' => $rearrange_text,
Chris@0: 'url' => $rearrange_url,
Chris@0: 'attributes' => ['class' => [$class, 'views-ajax-link'], 'id' => 'views-rearrange-' . $type],
Chris@0: ];
Chris@0: }
Chris@0:
Chris@0: // Render the array of links
Chris@0: $build['#actions'] = [
Chris@0: '#type' => 'dropbutton',
Chris@0: '#links' => $actions,
Chris@0: '#attributes' => [
Chris@0: 'class' => ['views-ui-settings-bucket-operations'],
Chris@0: ],
Chris@0: ];
Chris@0:
Chris@0: if (!$executable->display_handler->isDefaultDisplay()) {
Chris@0: if (!$executable->display_handler->isDefaulted($types[$type]['plural'])) {
Chris@0: $build['#overridden'] = TRUE;
Chris@0: }
Chris@0: else {
Chris@0: $build['#defaulted'] = TRUE;
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: static $relationships = NULL;
Chris@0: if (!isset($relationships)) {
Chris@0: // Get relationship labels.
Chris@0: $relationships = [];
Chris@0: foreach ($executable->display_handler->getHandlers('relationship') as $id => $handler) {
Chris@0: $relationships[$id] = $handler->adminLabel();
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: // Filters can now be grouped so we do a little bit extra:
Chris@0: $groups = [];
Chris@0: $grouping = FALSE;
Chris@0: if ($type == 'filter') {
Chris@0: $group_info = $executable->display_handler->getOption('filter_groups');
Chris@0: // If there is only one group but it is using the "OR" filter, we still
Chris@0: // treat it as a group for display purposes, since we want to display the
Chris@0: // "OR" label next to items within the group.
Chris@0: if (!empty($group_info['groups']) && (count($group_info['groups']) > 1 || current($group_info['groups']) == 'OR')) {
Chris@0: $grouping = TRUE;
Chris@0: $groups = [0 => []];
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: $build['fields'] = [];
Chris@0:
Chris@0: foreach ($executable->display_handler->getOption($types[$type]['plural']) as $id => $field) {
Chris@0: // Build the option link for this handler ("Node: ID = article").
Chris@0: $build['fields'][$id] = [];
Chris@0: $build['fields'][$id]['#theme'] = 'views_ui_display_tab_setting';
Chris@0:
Chris@0: $handler = $executable->display_handler->getHandler($type, $id);
Chris@0: if ($handler->broken()) {
Chris@0: $build['fields'][$id]['#class'][] = 'broken';
Chris@0: $field_name = $handler->adminLabel();
Chris@0: $build['fields'][$id]['#link'] = $this->l($field_name, new Url('views_ui.form_handler', [
Chris@0: 'js' => 'nojs',
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $display['id'],
Chris@0: 'type' => $type,
Chris@0: 'id' => $id,
Chris@0: ], ['attributes' => ['class' => ['views-ajax-link']]]));
Chris@0: continue;
Chris@0: }
Chris@0:
Chris@0: $field_name = $handler->adminLabel(TRUE);
Chris@0: if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
Chris@0: $field_name = '(' . $relationships[$field['relationship']] . ') ' . $field_name;
Chris@0: }
Chris@0:
Chris@0: $description = $handler->adminSummary();
Chris@0: $link_text = $field_name . (empty($description) ? '' : " ($description)");
Chris@0: $link_attributes = ['class' => ['views-ajax-link']];
Chris@0: if (!empty($field['exclude'])) {
Chris@0: $link_attributes['class'][] = 'views-field-excluded';
Chris@0: // Add a [hidden] marker, if the field is excluded.
Chris@0: $link_text .= ' [' . $this->t('hidden') . ']';
Chris@0: }
Chris@0: $build['fields'][$id]['#link'] = $this->l($link_text, new Url('views_ui.form_handler', [
Chris@0: 'js' => 'nojs',
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $display['id'],
Chris@0: 'type' => $type,
Chris@0: 'id' => $id,
Chris@0: ], ['attributes' => $link_attributes]));
Chris@0: $build['fields'][$id]['#class'][] = Html::cleanCssIdentifier($display['id'] . '-' . $type . '-' . $id);
Chris@0:
Chris@0: if ($executable->display_handler->useGroupBy() && $handler->usesGroupBy()) {
Chris@17: $build['fields'][$id]['#settings_links'][] = $this->l(new FormattableMarkup('@text', ['@text' => $this->t('Aggregation settings')]), new Url('views_ui.form_handler_group', [
Chris@0: 'js' => 'nojs',
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $display['id'],
Chris@0: 'type' => $type,
Chris@0: 'id' => $id,
Chris@0: ], ['attributes' => ['class' => ['views-button-configure', 'views-ajax-link'], 'title' => $this->t('Aggregation settings')]]));
Chris@0: }
Chris@0:
Chris@0: if ($handler->hasExtraOptions()) {
Chris@17: $build['fields'][$id]['#settings_links'][] = $this->l(new FormattableMarkup('@text', ['@text' => $this->t('Settings')]), new Url('views_ui.form_handler_extra', [
Chris@0: 'js' => 'nojs',
Chris@0: 'view' => $view->id(),
Chris@0: 'display_id' => $display['id'],
Chris@0: 'type' => $type,
Chris@0: 'id' => $id,
Chris@0: ], ['attributes' => ['class' => ['views-button-configure', 'views-ajax-link'], 'title' => $this->t('Settings')]]));
Chris@0: }
Chris@0:
Chris@0: if ($grouping) {
Chris@0: $gid = $handler->options['group'];
Chris@0:
Chris@0: // Show in default group if the group does not exist.
Chris@0: if (empty($group_info['groups'][$gid])) {
Chris@0: $gid = 0;
Chris@0: }
Chris@0: $groups[$gid][] = $id;
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: // If using grouping, re-order fields so that they show up properly in the list.
Chris@0: if ($type == 'filter' && $grouping) {
Chris@0: $store = $build['fields'];
Chris@0: $build['fields'] = [];
Chris@0: foreach ($groups as $gid => $contents) {
Chris@0: // Display an operator between each group.
Chris@0: if (!empty($build['fields'])) {
Chris@0: $build['fields'][] = [
Chris@0: '#theme' => 'views_ui_display_tab_setting',
Chris@0: '#class' => ['views-group-text'],
Chris@0: '#link' => ($group_info['operator'] == 'OR' ? $this->t('OR') : $this->t('AND')),
Chris@0: ];
Chris@0: }
Chris@0: // Display an operator between each pair of filters within the group.
Chris@0: $keys = array_keys($contents);
Chris@0: $last = end($keys);
Chris@0: foreach ($contents as $key => $pid) {
Chris@0: if ($key != $last) {
Chris@0: $operator = $group_info['groups'][$gid] == 'OR' ? $this->t('OR') : $this->t('AND');
Chris@17: $store[$pid]['#link'] = new FormattableMarkup('@link @operator', ['@link' => $store[$pid]['#link'], '@operator' => $operator]);
Chris@0: }
Chris@0: $build['fields'][$pid] = $store[$pid];
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: return $build;
Chris@0: }
Chris@0:
Chris@0: }