Newer
Older
<?php
namespace Drupal\views_ui;
use Drupal\Component\Utility\Html;
catch
committed
use Drupal\Component\Utility\SafeMarkup;
catch
committed
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\ReplaceCommand;
Alex Pott
committed
use Drupal\Core\Datetime\DateFormatterInterface;
Dries Buytaert
committed
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Url;
Angie Byron
committed
use Drupal\user\SharedTempStoreFactory;
catch
committed
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;
catch
committed
use Symfony\Component\HttpFoundation\RequestStack;
Lee Rowlands
committed
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
/**
* Form controller for the Views edit form.
*
* @internal
*/
Alex Pott
committed
class ViewEditForm extends ViewFormBase {
/**
* The views temp store.
*
Angie Byron
committed
* @var \Drupal\user\SharedTempStore
*/
protected $tempStore;
/**
* The request object.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The date formatter service.
*
Alex Pott
committed
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The element info manager.
*
* @var \Drupal\Core\Render\ElementInfoManagerInterface
*/
protected $elementInfo;
/**
Alex Pott
committed
* Constructs a new ViewEditForm object.
*
Angie Byron
committed
* @param \Drupal\user\SharedTempStoreFactory $temp_store_factory
* The factory for the temp store object.
* @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
* The request stack object.
Alex Pott
committed
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date Formatter service.
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
* The element info manager.
*/
Alex Pott
committed
public function __construct(SharedTempStoreFactory $temp_store_factory, RequestStack $requestStack, DateFormatterInterface $date_formatter, ElementInfoManagerInterface $element_info) {
$this->tempStore = $temp_store_factory->get('views');
$this->requestStack = $requestStack;
$this->dateFormatter = $date_formatter;
$this->elementInfo = $element_info;
}
/**
* {@inheritdoc}
*/
Alex Pott
committed
public static function create(ContainerInterface $container) {
return new static(
Angie Byron
committed
$container->get('user.shared_tempstore'),
$container->get('request_stack'),
$container->get('date.formatter'),
$container->get('element_info')
);
}
/**
* {@inheritdoc}
*/
Dries Buytaert
committed
public function form(array $form, FormStateInterface $form_state) {
catch
committed
$display_id = $this->displayID;
Angie Byron
committed
// 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:
Angie Byron
committed
// - Change $form_state->get('view') to $form_state->getTemporary()['view'].
// - Add a #process function to initialize $form_state->getTemporary()['view']
// on cached form submissions.
Angie Byron
committed
// - Use \Drupal\Core\Form\FormStateInterface::loadInclude().
Angie Byron
committed
$form_state->disableCache();
if ($display_id) {
Alex Pott
committed
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';
Alex Pott
committed
$form['#attached']['library'][] = 'views_ui/views_ui.admin';
$form['#attached']['library'][] = 'views_ui/admin.styling';
$form += [
'#prefix' => '',
'#suffix' => '',
];
Alex Pott
committed
$view_status = $view->status() ? 'enabled' : 'disabled';
$form['#prefix'] .= '<div class="views-edit-view views-admin ' . $view_status . ' clearfix">';
$form['#suffix'] = '</div>' . $form['#suffix'];
$form['#attributes']['class'] = ['form-edit'];
Alex Pott
committed
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 <a href=":url">break this lock</a>.', $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' => '<h1 class="unit-title clearfix">' . $this->t('Displays') . '</h1>',
'#type' => 'container',
'#attributes' => [
'class' => [
'views-displays',
],
],
];
$form['displays']['top'] = $this->renderDisplayTop($view);
// The rest requires a display to be selected.
if ($display_id) {
Angie Byron
committed
$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.
Alex Pott
committed
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.
Alex Pott
committed
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}
*/
Dries Buytaert
committed
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' => [],
];
Angie Byron
committed
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;
Angie Byron
committed
if ($view->isLocked()) {
$form_state->setErrorByName('', $this->t('Changes cannot be made to a locked view.'));
Angie Byron
committed
}
Alex Pott
committed
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;
catch
committed
$executable = $view->getExecutable();
Alex Pott
committed
$executable->initDisplay();
catch
committed
// 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);
}
}
catch
committed
$executable->displayHandlers->remove($id);
unset($displays[$id]);
}
}
catch
committed
// Rename display ids if needed.
catch
committed
foreach ($executable->displayHandlers as $id => $display) {
catch
committed
if (!empty($display->display['new_id']) && $display->display['new_id'] !== $display->display['id'] && empty($display->display['deleted'])) {
$new_id = $display->display['new_id'];
catch
committed
$display->display['id'] = $new_id;
unset($display->display['new_id']);
$executable->displayHandlers->set($new_id, $display);
$displays[$new_id] = $displays[$id];
unset($displays[$id]);
catch
committed
// 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', [
Alex Pott
committed
'view' => $view->id(),
'display_id' => $new_id,
]);
}
catch
committed
elseif (isset($display->display['new_id'])) {
unset($display->display['new_id']);
}
}
$view->set('display', $displays);
Jess
committed
// @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.
catch
committed
$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;
}
Alex Pott
committed
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.
Angie Byron
committed
$form_state->setRedirectUrl(Url::fromUri("base:$destination"));
}
$view->save();
drupal_set_message($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.
Dries Buytaert
committed
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
Dries Buytaert
committed
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());
Alex Pott
committed
$form_state->setRedirectUrl($this->entity->urlInfo('collection'));
}
/**
* Returns a renderable array representing the edit page for one display.
*/
public function getDisplayTab($view) {
$build = [];
catch
committed
$display_id = $this->displayID;
Alex Pott
committed
$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.
Alex Pott
committed
\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.
Alex Pott
committed
$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 <li> and the whole list in <ul>.
$build['top']['actions']['prefix']['#markup'] = '<ul class="dropbutton">';
if (!$is_display_deleted) {
if (!$is_enabled) {
$build['top']['actions']['enable'] = [
'#type' => 'submit',
catch
committed
'#value' => $this->t('Enable @display_title', ['@display_title' => $display_title]),
'#limit_validation_errors' => [],
'#submit' => ['::submitDisplayEnable', '::submitDelayDestination'],
'#prefix' => '<li class="enable">',
"#suffix" => '</li>',
];
}
// Add a link to view the page unless the view is disabled or has no
// path.
Alex Pott
committed
elseif ($view->status() && $view->getExecutable()->displayHandlers->get($display['id'])->hasPath()) {
$path = $view->getExecutable()->displayHandlers->get($display['id'])->getPath();
Lee Rowlands
committed
if ($path && (strpos($path, '%') === FALSE)) {
Lee Rowlands
committed
// Wrap this in a try/catch as trying to generate links to some
// routes may throw a NotAcceptableHttpException if they do not
// respond to HTML, such as RESTExports.
try {
if (!parse_url($path, PHP_URL_SCHEME)) {
// @todo Views should expect and store a leading /. See:
// https://www.drupal.org/node/2423913
$url = Url::fromUserInput('/' . ltrim($path, '/'));
}
else {
$url = Url::fromUri("base:$path");
}
Angie Byron
committed
}
Lee Rowlands
committed
catch (NotAcceptableHttpException $e) {
$url = '/' . $path;
Angie Byron
committed
}
Lee Rowlands
committed
$build['top']['actions']['path'] = [
'#type' => 'link',
catch
committed
'#title' => $this->t('View @display_title', ['@display_title' => $display_title]),
'#options' => ['alt' => [$this->t("Go to the real page for this display")]],
Angie Byron
committed
'#url' => $url,
'#prefix' => '<li class="view">',
"#suffix" => '</li>',
];
}
}
if (!$is_default) {
$build['top']['actions']['duplicate'] = [
'#type' => 'submit',
catch
committed
'#value' => $this->t('Duplicate @display_title', ['@display_title' => $display_title]),
'#limit_validation_errors' => [],
'#submit' => ['::submitDisplayDuplicate', '::submitDelayDestination'],
'#prefix' => '<li class="duplicate">',
"#suffix" => '</li>',
];
}
// Always allow a display to be deleted.
$build['top']['actions']['delete'] = [
'#type' => 'submit',
catch
committed
'#value' => $this->t('Delete @display_title', ['@display_title' => $display_title]),
'#limit_validation_errors' => [],
'#submit' => ['::submitDisplayDelete', '::submitDelayDestination'],
'#prefix' => '<li class="delete">',
"#suffix" => '</li>',
];
foreach (Views::fetchPluginNames('display', NULL, [$view->get('storage')->get('base_table')]) as $type => $label) {
if ($type == $display['display_plugin']) {
continue;
}
$build['top']['actions']['duplicate_as'][$type] = [
'#type' => 'submit',
catch
committed
'#value' => $this->t('Duplicate as @type', ['@type' => $label]),
'#limit_validation_errors' => [],
'#submit' => ['::submitDuplicateDisplayAsType', '::submitDelayDestination'],
'#prefix' => '<li class="duplicate">',
'#suffix' => '</li>',
];
}
}
else {
$build['top']['actions']['undo_delete'] = [
'#type' => 'submit',
catch
committed
'#value' => $this->t('Undo delete of @display_title', ['@display_title' => $display_title]),
'#limit_validation_errors' => [],
'#submit' => ['::submitDisplayUndoDelete', '::submitDelayDestination'],
'#prefix' => '<li class="undo-delete">',
"#suffix" => '</li>',
];
}
if ($is_enabled) {
$build['top']['actions']['disable'] = [
'#type' => 'submit',
catch
committed
'#value' => $this->t('Disable @display_title', ['@display_title' => $display_title]),
'#limit_validation_errors' => [],
'#submit' => ['::submitDisplayDisable', '::submitDelayDestination'],
'#prefix' => '<li class="disable">',
"#suffix" => '</li>',
];
}
$build['top']['actions']['suffix']['#markup'] = '</ul>';
// The area above the three columns.
$build['top']['display_title'] = [
'#theme' => 'views_ui_display_tab_setting',
'#description' => $this->t('Display name'),
'#link' => $view->getExecutable()->displayHandlers->get($display['id'])->optionLink($display_title, 'display_title'),
];
}
$build['columns'] = [];
$build['columns']['#theme_wrappers'] = ['container'];
$build['columns']['#attributes'] = ['id' => 'edit-display-settings-main', 'class' => ['clearfix', 'views-display-columns']];
$build['columns']['first']['#theme_wrappers'] = ['container'];
$build['columns']['first']['#attributes'] = ['class' => ['views-display-column', 'first']];
$build['columns']['second']['#theme_wrappers'] = ['container'];
$build['columns']['second']['#attributes'] = ['class' => ['views-display-column', 'second']];
$build['columns']['second']['settings'] = [];
$build['columns']['second']['header'] = [];
$build['columns']['second']['footer'] = [];
$build['columns']['second']['empty'] = [];
$build['columns']['second']['pager'] = [];
// The third column buckets are wrapped in details.
$build['columns']['third'] = [
'#type' => 'details',
'#title' => $this->t('Advanced'),
'#theme_wrappers' => ['details'],
'#attributes' => [
'class' => [
'views-display-column',
'third',
],
],
];
// Collapse the details by default.
Angie Byron
committed
$build['columns']['third']['#open'] = \Drupal::config('views.settings')->get('ui.show.advanced_column');
// Each option (e.g. title, access, display as grid/table/list) fits into one
// of several "buckets," or boxes (Format, Fields, Sort, and so on).
$buckets = [];
// Fetch options from the display plugin, with a list of buckets they go into.
$options = [];
Alex Pott
committed
$view->getExecutable()->displayHandlers->get($display['id'])->optionsSummary($buckets, $options);
// Place each option into its bucket.
foreach ($options as $id => $option) {
// Each option self-identifies as belonging in a particular bucket.
$buckets[$option['category']]['build'][$id] = $this->buildOptionForm($view, $id, $option, $display);
}
// Place each bucket into the proper column.
foreach ($buckets as $id => $bucket) {
// Let buckets identify themselves as belonging in a column.
if (isset($bucket['column']) && isset($build['columns'][$bucket['column']])) {
$column = $bucket['column'];
}
// If a bucket doesn't pick one of our predefined columns to belong to, put
// it in the last one.
else {
$column = 'third';
}
if (isset($bucket['build']) && is_array($bucket['build'])) {
$build['columns'][$column][$id] = $bucket['build'];
$build['columns'][$column][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
$build['columns'][$column][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
$build['columns'][$column][$id]['#name'] = $id;
}
}
$build['columns']['first']['fields'] = $this->getFormBucket($view, 'field', $display);
$build['columns']['first']['filters'] = $this->getFormBucket($view, 'filter', $display);
$build['columns']['first']['sorts'] = $this->getFormBucket($view, 'sort', $display);
$build['columns']['second']['header'] = $this->getFormBucket($view, 'header', $display);
$build['columns']['second']['footer'] = $this->getFormBucket($view, 'footer', $display);
Angie Byron
committed
$build['columns']['second']['empty'] = $this->getFormBucket($view, 'empty', $display);
$build['columns']['third']['arguments'] = $this->getFormBucket($view, 'argument', $display);
$build['columns']['third']['relationships'] = $this->getFormBucket($view, 'relationship', $display);
return $build;
}
/**
* Submit handler to add a restore a removed display to a view.
*/
Dries Buytaert
committed
public function submitDisplayUndoDelete($form, FormStateInterface $form_state) {
$view = $this->entity;
// Create the new display
Angie Byron
committed
$id = $form_state->get('display_id');
$displays = $view->get('display');
$displays[$id]['deleted'] = FALSE;
$view->set('display', $displays);
// Store in cache
Alex Pott
committed
$view->cacheSet();
// Redirect to the top-level edit page.
$form_state->setRedirect('entity.view.edit_display_form', [
Alex Pott
committed
'view' => $view->id(),
'display_id' => $id,
]);
}
/**
* Submit handler to enable a disabled display.
*/
Dries Buytaert
committed
public function submitDisplayEnable($form, FormStateInterface $form_state) {
$view = $this->entity;
Angie Byron
committed
$id = $form_state->get('display_id');
// setOption doesn't work because this would might affect upper displays
Alex Pott
committed
$view->getExecutable()->displayHandlers->get($id)->setOption('enabled', TRUE);
// Store in cache
Alex Pott
committed
$view->cacheSet();
// Redirect to the top-level edit page.
$form_state->setRedirect('entity.view.edit_display_form', [
Alex Pott
committed
'view' => $view->id(),
'display_id' => $id,
]);
}
/**
* Submit handler to disable display.
*/
Dries Buytaert
committed
public function submitDisplayDisable($form, FormStateInterface $form_state) {
$view = $this->entity;
Angie Byron
committed
$id = $form_state->get('display_id');
Alex Pott
committed
$view->getExecutable()->displayHandlers->get($id)->setOption('enabled', FALSE);
// Store in cache
Alex Pott
committed
$view->cacheSet();
// Redirect to the top-level edit page.
$form_state->setRedirect('entity.view.edit_display_form', [
Alex Pott
committed
'view' => $view->id(),
'display_id' => $id,
]);
}
/**
* Submit handler to delete a display from a view.
*/
Dries Buytaert
committed
public function submitDisplayDelete($form, FormStateInterface $form_state) {
$view = $this->entity;
Angie Byron
committed
$display_id = $form_state->get('display_id');
// Mark the display for deletion.
$displays = $view->get('display');
$displays[$display_id]['deleted'] = TRUE;
$view->set('display', $displays);
Alex Pott
committed
$view->cacheSet();
// Redirect to the top-level edit page. The first remaining display will
// become the active display.
Alex Pott
committed
$form_state->setRedirectUrl($view->urlInfo('edit-form'));
}
/**
* Regenerate the current tab for AJAX updates.
catch
committed
*
* @param \Drupal\views_ui\ViewUI $view
* The view to regenerate its tab.
* @param \Drupal\Core\Ajax\AjaxResponse $response
* The response object to add new commands to.
* @param string $display_id
* The display ID of the tab to regenerate.
*/
catch
committed
public function rebuildCurrentTab(ViewUI $view, AjaxResponse $response, $display_id) {
catch
committed
$this->displayID = $display_id;
Alex Pott
committed
if (!$view->getExecutable()->setDisplay('default')) {
return;
}
// Regenerate the main display area.
$build = $this->getDisplayTab($view);
$response->addCommand(new HtmlCommand('#views-tab-' . $display_id, $build));
// Regenerate the top area so changes to display names and order will appear.
$build = $this->renderDisplayTop($view);
$response->addCommand(new ReplaceCommand('#views-display-top', $build));
}
/**
* Render the top of the display so it can be updated during ajax operations.
*/
public function renderDisplayTop(ViewUI $view) {
catch
committed
$display_id = $this->displayID;
Angie Byron
committed
$element['#theme_wrappers'][] = 'views_ui_container';
$element['#attributes']['class'] = ['views-display-top', 'clearfix'];
$element['#attributes']['id'] = ['views-display-top'];
// Extra actions for the display
$element['extra_actions'] = [
'#type' => 'dropbutton',
'#attributes' => [
'id' => 'views-display-extra-actions',
],
'#links' => [
'edit-details' => [
'title' => $this->t('Edit view name/description'),
'url' => Url::fromRoute('views_ui.form_edit_details', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
'attributes' => ['class' => ['views-ajax-link']],
],
'analyze' => [
'title' => $this->t('Analyze view'),
'url' => Url::fromRoute('views_ui.form_analyze', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
'attributes' => ['class' => ['views-ajax-link']],
],
'duplicate' => [
'title' => $this->t('Duplicate view'),
'url' => $view->urlInfo('duplicate-form'),
],
'reorder' => [
'title' => $this->t('Reorder displays'),
'url' => Url::fromRoute('views_ui.form_reorder_displays', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
'attributes' => ['class' => ['views-ajax-link']],
],
],
];
Alex Pott
committed
if ($view->access('delete')) {
$element['extra_actions']['#links']['delete'] = [
Alex Pott
committed
'title' => $this->t('Delete view'),
'url' => $view->urlInfo('delete-form'),
];
Alex Pott
committed
}
// Let other modules add additional links here.
Alex Pott
committed
\Drupal::moduleHandler()->alter('views_ui_display_top_links', $element['extra_actions']['#links'], $view, $display_id);
if (isset($view->type) && $view->type != $this->t('Default')) {
if ($view->type == $this->t('Overridden')) {
$element['extra_actions']['#links']['revert'] = [
'title' => $this->t('Revert view'),
Angie Byron
committed
'href' => "admin/structure/views/view/{$view->id()}/revert",
'query' => ['destination' => $view->url('edit-form')],
];
}
else {
$element['extra_actions']['#links']['delete'] = [
'title' => $this->t('Delete view'),
'url' => $view->urlInfo('delete-form'),
];
}
}
// Determine the displays available for editing.
if ($tabs = $this->getDisplayTabs($view)) {
if ($display_id) {
$tabs[$display_id]['#active'] = TRUE;
}
$tabs['#prefix'] = '<h2 class="visually-hidden">' . $this->t('Secondary tabs') . '</h2><ul id = "views-display-menu-tabs" class="tabs secondary">';
$tabs['#suffix'] = '</ul>';
$element['tabs'] = $tabs;
}
// Buttons for adding a new display.
foreach (Views::fetchPluginNames('display', NULL, [$view->get('base_table')]) as $type => $label) {
$element['add_display'][$type] = [
'#type' => 'submit',
'#value' => $this->t('Add @display', ['@display' => $label]),
'#limit_validation_errors' => [],
'#submit' => ['::submitDisplayAdd', '::submitDelayDestination'],
'#attributes' => ['class' => ['add-display']],
// Allow JavaScript to remove the 'Add ' prefix from the button label when
// placing the button in a "Add" dropdown menu.
'#process' => array_merge(['views_ui_form_button_was_clicked'], $this->elementInfo->getInfoProperty('submit', '#process', [])),
'#values' => [$this->t('Add @display', ['@display' => $label]), $label],
];
}
return $element;
}
/**
* Submit handler for form buttons that do not complete a form workflow.
*
* The Edit View form is a multistep form workflow, but with state managed by
Angie Byron
committed
* the SharedTempStore rather than $form_state->setRebuild(). Without this
* submit handler, buttons that add or remove displays would redirect to the
* destination parameter (e.g., when the Edit View form is linked to from a
* contextual link). This handler can be added to buttons whose form submission
* should not yet redirect to the destination.
*/
Dries Buytaert
committed
public function submitDelayDestination($form, FormStateInterface $form_state) {
$request = $this->requestStack->getCurrentRequest();
$destination = $request->query->get('destination');
$redirect = $form_state->getRedirect();
// If there is a destination, and redirects are not explicitly disabled, add
// the destination as a query string to the redirect and suppress it for the
// current request.
if (isset($destination) && $redirect !== FALSE) {
// Create a valid redirect if one does not exist already.
if (!($redirect instanceof Url)) {
$redirect = Url::createFromRequest($request);
}
// Add the current destination to the redirect unless one exists already.
$options = $redirect->getOptions();
if (!isset($options['query']['destination'])) {
$options['query']['destination'] = $destination;
$redirect->setOptions($options);
}
$form_state->setRedirectUrl($redirect);
$request->query->remove('destination');
}
}
/**
* Submit handler to duplicate a display for a view.
*/
Dries Buytaert
committed
public function submitDisplayDuplicate($form, FormStateInterface $form_state) {
$view = $this->entity;
catch
committed
$display_id = $this->displayID;
// Create the new display.
$displays = $view->get('display');
Angie Byron
committed
$display = $view->getExecutable()->newDisplay($displays[$display_id]['display_plugin']);
$new_display_id = $display->display['id'];
$displays[$new_display_id] = $displays[$display_id];
$displays[$new_display_id]['id'] = $new_display_id;
$view->set('display', $displays);
// By setting the current display the changed marker will appear on the new
// display.
Alex Pott
committed
$view->getExecutable()->current_display = $new_display_id;
Alex Pott
committed
$view->cacheSet();
// Redirect to the new display's edit page.
$form_state->setRedirect('entity.view.edit_display_form', [
Alex Pott
committed
'view' => $view->id(),
'display_id' => $new_display_id,
]);
}
/**
* Submit handler to add a display to a view.
*/
Dries Buytaert
committed
public function submitDisplayAdd($form, FormStateInterface $form_state) {
$view = $this->entity;
// Create the new display.
Angie Byron
committed
$parents = $form_state->getTriggeringElement()['#parents'];
$display_type = array_pop($parents);
Angie Byron
committed
$display = $view->getExecutable()->newDisplay($display_type);
$display_id = $display->display['id'];
// A new display got added so the asterisks symbol should appear on the new
// display.
Alex Pott
committed
$view->getExecutable()->current_display = $display_id;
Alex Pott
committed
$view->cacheSet();
// Redirect to the new display's edit page.
$form_state->setRedirect('entity.view.edit_display_form', [
Alex Pott
committed
'view' => $view->id(),
'display_id' => $display_id,
]);
}
/**
* Submit handler to Duplicate a display as another display type.
*/
Dries Buytaert
committed
public function submitDuplicateDisplayAsType($form, FormStateInterface $form_state) {
/** @var \Drupal\views\ViewEntityInterface $view */
$view = $this->entity;
catch
committed
$display_id = $this->displayID;
// Create the new display.
Angie Byron
committed
$parents = $form_state->getTriggeringElement()['#parents'];
$display_type = array_pop($parents);
Alex Pott
committed
$new_display_id = $view->duplicateDisplayAsType($display_id, $display_type);
// By setting the current display the changed marker will appear on the new
// display.
Alex Pott
committed
$view->getExecutable()->current_display = $new_display_id;
Alex Pott
committed
$view->cacheSet();
// Redirect to the new display's edit page.
$form_state->setRedirect('entity.view.edit_display_form', [
Alex Pott
committed
'view' => $view->id(),
'display_id' => $new_display_id,
]);
}
/**
* Build a renderable array representing one option on the edit form.
*
* This function might be more logical as a method on an object, if a suitable
* object emerges out of refactoring.
*/
public function buildOptionForm(ViewUI $view, $id, $option, $display) {
$option_build = [];
$option_build['#theme'] = 'views_ui_display_tab_setting';
$option_build['#description'] = $option['title'];
Alex Pott
committed
$option_build['#link'] = $view->getExecutable()->displayHandlers->get($display['id'])->optionLink($option['value'], $id, '', empty($option['desc']) ? '' : $option['desc']);
$option_build['#links'] = [];
if (!empty($option['links']) && is_array($option['links'])) {
foreach ($option['links'] as $link_id => $link_value) {
Alex Pott
committed
$option_build['#settings_links'][] = $view->getExecutable()->displayHandlers->get($display['id'])->optionLink($option['setting'], $link_id, 'views-button-configure', $link_value);
}
}
Alex Pott
committed
if (!empty($view->getExecutable()->displayHandlers->get($display['id'])->options['defaults'][$id])) {
$display_id = 'default';
$option_build['#defaulted'] = TRUE;
}
else {
$display_id = $display['id'];
Alex Pott
committed
if (!$view->getExecutable()->displayHandlers->get($display['id'])->isDefaultDisplay()) {
if ($view->getExecutable()->displayHandlers->get($display['id'])->defaultableSections($id)) {
$option_build['#overridden'] = TRUE;
}
}
}
$option_build['#attributes']['class'][] = Html::cleanCssIdentifier($display_id . '-' . $id);
return $option_build;
}
/**
* Add information about a section to a display.
*/
public function getFormBucket(ViewUI $view, $type, $display) {
Alex Pott
committed
$executable = $view->getExecutable();
Angie Byron
committed
$executable->setDisplay($display['id']);
$executable->initStyle();
catch
committed
$types = $executable->getHandlerTypes();
Angie Byron
committed
$build = [
'#theme_wrappers' => ['views_ui_display_tab_bucket'],
];
$build['#overridden'] = FALSE;
$build['#defaulted'] = FALSE;
$build['#name'] = $type;
$build['#title'] = $types[$type]['title'];
$rearrange_url = Url::fromRoute('views_ui.form_rearrange', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id'], 'type' => $type]);
$class = 'icon compact rearrange';
// Different types now have different rearrange forms, so we use this switch
// to get the right one.
switch ($type) {
case 'filter':
// The rearrange form for filters contains the and/or UI, so override
// the used path.
$rearrange_url = Url::fromRoute('views_ui.form_rearrange_filter', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id']]);
// TODO: Add another class to have another symbol for filter rearrange.
$class = 'icon compact rearrange';
break;
case 'field':
// Fetch the style plugin info so we know whether to list fields or not.
Angie Byron
committed
$style_plugin = $executable->style_plugin;
$uses_fields = $style_plugin && $style_plugin->usesFields();
if (!$uses_fields) {
$build['fields'][] = [
Alex Pott
committed
'#markup' => $this->t('The selected style or row format does not use fields.'),
'#theme_wrappers' => ['views_ui_container'],
'#attributes' => ['class' => ['views-display-setting']],
];
return $build;
}
break;
Angie Byron
committed
case 'header':
case 'footer':
case 'empty':
Angie Byron
committed
if (!$executable->display_handler->usesAreas()) {
$build[$type][] = [
'#markup' => $this->t('The selected display type does not use @type plugins', ['@type' => $type]),
'#theme_wrappers' => ['views_ui_container'],
'#attributes' => ['class' => ['views-display-setting']],
];
Angie Byron
committed
return $build;
}
break;
}
// Create an array of actions to pass to links template.
$actions = [];
Angie Byron
committed
$count_handlers = count($executable->display_handler->getHandlers($type));
// Create the add text variable for the add action.
$add_text = $this->t('Add <span class="visually-hidden">@type</span>', ['@type' => $types[$type]['ltitle']]);