Newer
Older
<?php
/**
* @file
* Contains Drupal\views_ui\ViewEditFormController.
*/
namespace Drupal\views_ui;
catch
committed
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Component\Utility\NestedArray;
use Drupal\user\TempStoreFactory;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for the Views edit form.
*/
Alex Pott
committed
class ViewEditFormController extends ViewFormControllerBase {
/**
* The views temp store.
*
* @var \Drupal\user\TempStore
*/
protected $tempStore;
/**
* The request object.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* Constructs a new ViewEditFormController object.
*
* @param \Drupal\user\TempStoreFactory $temp_store_factory
* The factory for the temp store object.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*/
Alex Pott
committed
public function __construct(TempStoreFactory $temp_store_factory, Request $request) {
$this->tempStore = $temp_store_factory->get('views');
$this->request = $request;
}
/**
* {@inheritdoc}
*/
Alex Pott
committed
public static function create(ContainerInterface $container) {
return new static(
$container->get('user.tempstore'),
$container->get('request')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, array &$form_state) {
$view = $this->entity;
catch
committed
$display_id = $this->displayID;
// Do not allow the form to be cached, because $form_state['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['view'] to $form_state['temporary']['view'].
// - Add a #process function to initialize $form_state['temporary']['view']
// on cached form submissions.
// - Use form_load_include().
$form_state['no_cache'] = TRUE;
if ($display_id) {
Alex Pott
committed
if (!$view->getExecutable()->setDisplay($display_id)) {
$form['#markup'] = $this->t('Invalid display id @display', array('@display' => $display_id));
return $form;
}
}
$form['#tree'] = TRUE;
$form['#attached']['library'][] = array('system', 'jquery.ui.tabs');
$form['#attached']['library'][] = array('system', 'jquery.ui.dialog');
$form['#attached']['library'][] = array('system', 'drupal.states');
$form['#attached']['library'][] = array('system', 'drupal.tabledrag');
Angie Byron
committed
if (!\Drupal::config('views.settings')->get('no_javascript')) {
catch
committed
$form['#attached']['library'][] = array('views_ui', 'views_ui.admin');
}
$form['#attached']['css'] = static::getAdminCSS();
$form['#attached']['js'][] = array(
'data' => array('views' => array('ajax' => array(
'id' => '#views-ajax-body',
'title' => '#views-ajax-title',
'popup' => '#views-ajax-popup',
'defaultForm' => $view->getDefaultAJAXMessage(),
))),
'type' => 'setting',
);
$form += array(
'#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'] = array('form-edit');
Alex Pott
committed
if ($view->isLocked()) {
$username = array(
'#theme' => 'username',
'#account' => user_load($view->lock->owner),
);
$lock_message_substitutions = array(
'!user' => drupal_render($username),
'!age' => format_interval(REQUEST_TIME - $view->lock->updated),
'!break' => url('admin/structure/views/view/' . $view->id() . '/break-lock'),
);
$form['locked'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('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="!break">break this lock</a>.', $lock_message_substitutions),
'#weight' => -10,
);
}
else {
$form['changed'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('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'] = array(
'#prefix' => '<h1 class="unit-title clearfix">' . $this->t('Displays') . '</h1>',
'#type' => 'container',
'#attributes' => array(
'class' => array(
'views-displays',
),
),
);
$form['displays']['top'] = $this->renderDisplayTop($view);
// The rest requires a display to be selected.
if ($display_id) {
$form_state['display_id'] = $display_id;
// The part of the page where editing will take place.
$form['displays']['settings'] = array(
'#type' => 'container',
'#id' => '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'] = array('container');
$tab_content['#attributes'] = array('class' => array('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'] = array(
'#type' => 'container',
'tab_content' => $tab_content,
);
// The content of the popup dialog.
$form['ajax-area'] = array(
'#type' => 'container',
'#id' => 'views-ajax-popup',
);
$form['ajax-area']['ajax-title'] = array(
catch
committed
'#markup' => '<div id="views-ajax-title"></div>',
);
$form['ajax-area']['ajax-body'] = array(
'#type' => 'container',
'#id' => 'views-ajax-body',
'#children' => $view->getDefaultAJAXMessage(),
);
}
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, array &$form_state) {
$actions = parent::actions($form, $form_state);
unset($actions['delete']);
$actions['cancel'] = array(
'#value' => $this->t('Cancel'),
'#submit' => array(
array($this, 'cancel'),
),
);
return $actions;
}
/**
* {@inheritdoc}
*/
public function validate(array $form, array &$form_state) {
parent::validate($form, $form_state);
$view = $this->entity;
Alex Pott
committed
foreach ($view->getExecutable()->validate() as $display_errors) {
foreach ($display_errors as $error) {
$this->setFormError('', $form_state, $error);
}
}
}
/**
* {@inheritdoc}
*/
public function submit(array $form, array &$form_state) {
parent::submit($form, $form_state);
$view = $this->entity;
catch
committed
$executable = $view->getExecutable();
// Go through and remove displayed scheduled for removal.
$displays = $view->get('display');
foreach ($displays as $id => $display) {
if (!empty($display['deleted'])) {
catch
committed
$executable->displayHandlers->remove($id);
unset($displays[$id]);
}
}
catch
committed
// Rename display ids if needed.
catch
committed
foreach ($executable->displayHandlers as $id => $display) {
if (!empty($display->display['new_id'])) {
$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.
Angie Byron
committed
$form_state['redirect_route'] = array(
'route_name' => 'views_ui.edit_display',
catch
committed
'route_parameters' => array('view' => $view->id(), 'display_id' => $new_id),
Angie Byron
committed
);
}
}
$view->set('display', $displays);
// @todo: Revisit this when http://drupal.org/node/1668866 is in.
$query = $this->request->query;
$destination = $query->get('destination');
if (!empty($destination)) {
// Find out the first display which has a changed path and redirect to this url.
Angie Byron
committed
$old_view = views_get_view($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');
}
}
$form_state['redirect'] = $destination;
}
$view->save();
drupal_set_message($this->t('The view %name has been saved.', array('%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 array $form_state
* A reference to a keyed array containing the current state of the form.
*/
public function cancel(array $form, array &$form_state) {
// Remove this view from cache so edits will be lost.
$view = $this->entity;
$this->tempStore->delete($view->id());
Angie Byron
committed
$form_state['redirect_route']['route_name'] = 'views_ui.list';
}
/**
* Returns a renderable array representing the edit page for one display.
*/
public function getDisplayTab($view) {
$build = array();
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.", array('@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 = array(
'#theme_wrappers' => array('container'),
'#attributes' => array('id' => 'edit-display-settings-details'),
);
$is_display_deleted = !empty($display['deleted']);
// The master display cannot be cloned.
$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'] = array('container');
$build['top']['#attributes']['id'] = 'edit-display-settings-top';
$build['top']['#attributes']['class'] = array('views-ui-display-tab-actions', 'views-ui-display-tab-bucket', 'clearfix');
// The Delete, Duplicate and Undo Delete buttons.
$build['top']['actions'] = array(
'#theme_wrappers' => array('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'] = array(
'#type' => 'submit',
'#value' => $this->t('Enable @display_title', array('@display_title' => $display_title)),
'#limit_validation_errors' => array(),
'#submit' => array(array($this, 'submitDisplayEnable'), array($this, '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();
if ($path && (strpos($path, '%') === FALSE)) {
$build['top']['actions']['path'] = array(
'#type' => 'link',
'#title' => $this->t('View @display', array('@display' => $display['display_title'])),
'#options' => array('alt' => array($this->t("Go to the real page for this display"))),
'#href' => $path,
'#prefix' => '<li class="view">',
"#suffix" => '</li>',
);
}
}
if (!$is_default) {
$build['top']['actions']['duplicate'] = array(
'#type' => 'submit',
'#value' => $this->t('Clone @display_title', array('@display_title' => $display_title)),
'#limit_validation_errors' => array(),
'#submit' => array(array($this, 'submitDisplayDuplicate'), array($this, 'submitDelayDestination')),
'#prefix' => '<li class="duplicate">',
"#suffix" => '</li>',
);
}
// Always allow a display to be deleted.
$build['top']['actions']['delete'] = array(
'#type' => 'submit',
'#value' => $this->t('Delete @display_title', array('@display_title' => $display_title)),
'#limit_validation_errors' => array(),
'#submit' => array(array($this, 'submitDisplayDelete'), array($this, 'submitDelayDestination')),
'#prefix' => '<li class="delete">',
"#suffix" => '</li>',
);
foreach (views_fetch_plugin_names('display', NULL, array($view->get('storage')->get('base_table'))) as $type => $label) {
if ($type == $display['display_plugin']) {
continue;
}
$build['top']['actions']['clone_as'][$type] = array(
'#type' => 'submit',
'#value' => $this->t('Clone as @type', array('@type' => $label)),
'#limit_validation_errors' => array(),
'#submit' => array(array($this, 'submitCloneDisplayAsType'), array($this, 'submitDelayDestination')),
'#prefix' => '<li class="duplicate">',
'#suffix' => '</li>',
);
}
}
else {
$build['top']['actions']['undo_delete'] = array(
'#type' => 'submit',
'#value' => $this->t('Undo delete of @display_title', array('@display_title' => $display_title)),
'#limit_validation_errors' => array(),
'#submit' => array(array($this, 'submitDisplayUndoDelete'), array($this, 'submitDelayDestination')),
'#prefix' => '<li class="undo-delete">',
"#suffix" => '</li>',
);
}
if ($is_enabled) {
$build['top']['actions']['disable'] = array(
'#type' => 'submit',
'#value' => $this->t('Disable @display_title', array('@display_title' => $display_title)),
'#limit_validation_errors' => array(),
'#submit' => array(array($this, 'submitDisplayDisable'), array($this, 'submitDelayDestination')),
'#prefix' => '<li class="disable">',
"#suffix" => '</li>',
);
}
$build['top']['actions']['suffix']['#markup'] = '</ul>';
// The area above the three columns.
$build['top']['display_title'] = array(
'#theme' => 'views_ui_display_tab_setting',
'#description' => $this->t('Display name'),
Alex Pott
committed
'#link' => $view->getExecutable()->displayHandlers->get($display['id'])->optionLink(check_plain($display_title), 'display_title'),
);
}
$build['columns'] = array();
$build['columns']['#theme_wrappers'] = array('container');
$build['columns']['#attributes'] = array('id' => 'edit-display-settings-main', 'class' => array('clearfix', 'views-display-columns'));
$build['columns']['first']['#theme_wrappers'] = array('container');
$build['columns']['first']['#attributes'] = array('class' => array('views-display-column', 'first'));
$build['columns']['second']['#theme_wrappers'] = array('container');
$build['columns']['second']['#attributes'] = array('class' => array('views-display-column', 'second'));
$build['columns']['second']['settings'] = array();
$build['columns']['second']['header'] = array();
$build['columns']['second']['footer'] = array();
Angie Byron
committed
$build['columns']['second']['empty'] = array();
$build['columns']['second']['pager'] = array();
// The third column buckets are wrapped in details.
$build['columns']['third'] = array(
'#type' => 'details',
'#title' => $this->t('Advanced'),
'#collapsed' => TRUE,
Alex Pott
committed
'#theme_wrappers' => array('details'),
'#attributes' => array(
'class' => array(
'views-display-column',
'third',
),
),
);
// Collapse the details by default.
Angie Byron
committed
if (\Drupal::config('views.settings')->get('ui.show.advanced_column')) {
$build['columns']['third']['#collapsed'] = FALSE;
}
// 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 = array();
// Fetch options from the display plugin, with a list of buckets they go into.
$options = array();
Alex Pott
committed
$view->getExecutable()->displayHandlers->get($display['id'])->optionsSummary($buckets, $options);
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
// 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'] = !empty($bucket['title']) ? $bucket['title'] : $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.
*/
public function submitDisplayUndoDelete($form, &$form_state) {
$view = $this->entity;
// Create the new display
$id = $form_state['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.
Angie Byron
committed
$form_state['redirect_route'] = array(
'route_name' => 'views_ui.edit_display',
'route_parameters' => array('view' => $view->id(), 'display_id' => $id),
);
}
/**
* Submit handler to enable a disabled display.
*/
public function submitDisplayEnable($form, &$form_state) {
$view = $this->entity;
$id = $form_state['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.
Angie Byron
committed
$form_state['redirect_route'] = array(
'route_name' => 'views_ui.edit_display',
'route_parameters' => array('view' => $view->id(), 'display_id' => $id),
);
}
/**
* Submit handler to disable display.
*/
public function submitDisplayDisable($form, &$form_state) {
$view = $this->entity;
$id = $form_state['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.
Angie Byron
committed
$form_state['redirect_route'] = array(
'route_name' => 'views_ui.edit_display',
'route_parameters' => array('view' => $view->id(), 'display_id' => $id),
);
}
/**
* Submit handler to delete a display from a view.
*/
public function submitDisplayDelete($form, &$form_state) {
$view = $this->entity;
$display_id = $form_state['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.
Angie Byron
committed
$form_state['redirect_route'] = array(
'route_name' => 'views_ui.edit',
'route_parameters' => array('view' => $view->id()),
);
}
/**
* 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);
static::addMicroweights($build);
catch
committed
$response->addCommand(new HtmlCommand('#views-tab-' . $display_id, drupal_render($build)));
// Regenerate the top area so changes to display names and order will appear.
$build = $this->renderDisplayTop($view);
static::addMicroweights($build);
catch
committed
$response->addCommand(new ReplaceCommand('#views-display-top', drupal_render($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'] = array('views-display-top', 'clearfix');
$element['#attributes']['id'] = array('views-display-top');
// Extra actions for the display
$element['extra_actions'] = array(
'#type' => 'dropbutton',
'#attributes' => array(
'id' => 'views-display-extra-actions',
),
'#links' => array(
'edit-details' => array(
'title' => $this->t('Edit view name/description'),
'href' => "admin/structure/views/nojs/edit-details/{$view->id()}/$display_id",
'attributes' => array('class' => array('views-ajax-link')),
),
'analyze' => array(
'title' => $this->t('Analyze view'),
Angie Byron
committed
'href' => "admin/structure/views/nojs/analyze/{$view->id()}/$display_id",
'attributes' => array('class' => array('views-ajax-link')),
),
'clone' => array(
'title' => $this->t('Clone view'),
Angie Byron
committed
'href' => "admin/structure/views/view/{$view->id()}/clone",
),
'reorder' => array(
'title' => $this->t('Reorder displays'),
Angie Byron
committed
'href' => "admin/structure/views/nojs/reorder-displays/{$view->id()}/$display_id",
'attributes' => array('class' => array('views-ajax-link')),
),
),
);
Alex Pott
committed
if ($view->access('delete')) {
$element['extra_actions']['#links']['delete'] = array(
'title' => $this->t('Delete view'),
'href' => "admin/structure/views/view/{$view->id()}/delete",
);
}
// 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'] = array(
'title' => $this->t('Revert view'),
Angie Byron
committed
'href' => "admin/structure/views/view/{$view->id()}/revert",
'query' => array('destination' => "admin/structure/views/view/{$view->id()}"),
);
}
else {
$element['extra_actions']['#links']['delete'] = array(
'title' => $this->t('Delete view'),
Angie Byron
committed
'href' => "admin/structure/views/view/{$view->id()}/delete",
);
}
}
// 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_fetch_plugin_names('display', NULL, array($view->get('base_table'))) as $type => $label) {
$element['add_display'][$type] = array(
'#type' => 'submit',
'#value' => $this->t('Add !display', array('!display' => $label)),
'#limit_validation_errors' => array(),
'#submit' => array(array($this, 'submitDisplayAdd'), array($this, 'submitDelayDestination')),
'#attributes' => array('class' => array('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(array('views_ui_form_button_was_clicked'), element_info_property('submit', '#process', array())),
'#values' => array($this->t('Add !display', array('!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
* the TempStore rather than $form_state['rebuild']. 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.
*/
public function submitDelayDestination($form, &$form_state) {
$query = $this->request->query;
// @todo: Revisit this when http://drupal.org/node/1668866 is in.
$destination = $query->get('destination');
if (isset($destination) && $form_state['redirect'] !== FALSE) {
if (!isset($form_state['redirect'])) {
$form_state['redirect'] = current_path();
}
if (is_string($form_state['redirect'])) {
$form_state['redirect'] = array($form_state['redirect']);
}
$options = isset($form_state['redirect'][1]) ? $form_state['redirect'][1] : array();
if (!isset($options['query']['destination'])) {
$options['query']['destination'] = $destination;
}
$form_state['redirect'][1] = $options;
$query->remove('destination');
}
}
/**
* Submit handler to duplicate a display for a view.
*/
public function submitDisplayDuplicate($form, &$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.
Angie Byron
committed
$form_state['redirect_route'] = array(
'route_name' => 'views_ui.edit_display',
'route_parameters' => array('view' => $view->id(), 'display_id' => $new_display_id),
);
}
/**
* Submit handler to add a display to a view.
*/
public function submitDisplayAdd($form, &$form_state) {
$view = $this->entity;
// Create the new display.
$parents = $form_state['triggering_element']['#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.
Angie Byron
committed
$form_state['redirect_route'] = array(
'route_name' => 'views_ui.edit_display',
'route_parameters' => array('view' => $view->id(), 'display_id' => $display_id),
);
}
/**
* Submit handler to clone a display as another display type.
*/
public function submitCloneDisplayAsType($form, &$form_state) {
$view = $this->entity;
catch
committed
$display_id = $this->displayID;
// Create the new display.
$parents = $form_state['triggering_element']['#parents'];
$display_type = array_pop($parents);
Angie Byron
committed
$display = $view->getExecutable()->newDisplay($display_type);
$new_display_id = $display->display['id'];
$displays = $view->get('display');
// Let the display title be generated by the addDisplay method and set the
// right display plugin, but keep the rest from the original display.
$display_clone = $displays[$display_id];
unset($display_clone['display_title']);
unset($display_clone['display_plugin']);
$displays[$new_display_id] = NestedArray::mergeDeep($displays[$new_display_id], $display_clone);
$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.
Angie Byron
committed
$form_state['redirect_route'] = array(
'route_name' => 'views_ui.edit_display',
'route_parameters' => array('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 = array();
$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'] = array();
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'][] = drupal_clean_css_identifier($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();
$types = $executable->viewsHandlerTypes();
$build = array(
'#theme_wrappers' => array('views_ui_display_tab_bucket'),
);
$build['#overridden'] = FALSE;
$build['#defaulted'] = FALSE;
$build['#name'] = $build['#title'] = $types[$type]['title'];
Angie Byron
committed
$rearrange_url = "admin/structure/views/nojs/rearrange/{$view->id()}/{$display['id']}/$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.
Angie Byron
committed
$rearrange_url = "admin/structure/views/nojs/rearrange-filter/{$view->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'][] = array(
'#markup' => $this->t('The selected style or row format does not utilize fields.'),
'#theme_wrappers' => array('views_ui_container'),
'#attributes' => array('class' => array('views-display-setting')),
);
return $build;
}
break;
Angie Byron
committed
case 'header':
case 'footer':
case 'empty':
Angie Byron
committed
if (!$executable->display_handler->usesAreas()) {
Angie Byron
committed
$build[$type][] = array(
'#markup' => $this->t('The selected display type does not utilize @type plugins', array('@type' => $type)),
Angie Byron
committed
'#theme_wrappers' => array('views_ui_container'),
'#attributes' => array('class' => array('views-display-setting')),
);
return $build;
}
break;
}
// Create an array of actions to pass to theme_links
$actions = array();
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>', array('@type' => $types[$type]['ltitle']));
$actions['add'] = array(
'title' => $add_text,
Angie Byron
committed
'href' => "admin/structure/views/nojs/add-handler/{$view->id()}/{$display['id']}/$type",
'attributes' => array('class' => array('icon compact add', 'views-ajax-link'), 'id' => 'views-add-' . $type),
'html' => TRUE,
);
if ($count_handlers > 0) {
// Create the rearrange text variable for the rearrange action.
$rearrange_text = $type == 'filter' ? $this->t('And/Or Rearrange <span class="visually-hidden">filter criteria</span>') : $this->t('Rearrange <span class="visually-hidden">@type</span>', array('@type' => $types[$type]['ltitle']));
$actions['rearrange'] = array(
'title' => $rearrange_text,
'href' => $rearrange_url,
'attributes' => array('class' => array($class, 'views-ajax-link'), 'id' => 'views-rearrange-' . $type),
'html' => TRUE,
);
}
// Render the array of links
$build['#actions'] = array(
'#type' => 'dropbutton',
'#links' => $actions,
'#attributes' => array(
'class' => array('views-ui-settings-bucket-operations'),
),
);
Angie Byron
committed
if (!$executable->display_handler->isDefaultDisplay()) {
if (!$executable->display_handler->isDefaulted($types[$type]['plural'])) {