clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
<?php
namespace Drupal\views_ui\Ajax;
use Drupal\Core\Ajax\CommandInterface;
/**
* Provides an AJAX command for setting a form submit URL in modal forms.
*
* This command is implemented in Drupal.AjaxCommands.prototype.viewsSetForm.
*/
class SetFormCommand implements CommandInterface {
/**
* The URL of the form.
*
* @var string
*/
protected $url;
/**
* Constructs a SetFormCommand object.
*
* @param string $url
* The URL of the form.
*/
public function __construct($url) {
$this->url = $url;
}
/**
* {@inheritdoc}
*/
public function render() {
return [
'command' => 'viewsSetForm',
'url' => $this->url,
];
}
}

View File

@@ -0,0 +1,225 @@
<?php
namespace Drupal\views_ui\Controller;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\views\ViewExecutable;
use Drupal\views\ViewEntityInterface;
use Drupal\views\Views;
use Drupal\views_ui\ViewUI;
use Drupal\views\ViewsData;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Component\Utility\Html;
/**
* Returns responses for Views UI routes.
*/
class ViewsUIController extends ControllerBase {
/**
* Stores the Views data cache object.
*
* @var \Drupal\views\ViewsData
*/
protected $viewsData;
/**
* Constructs a new \Drupal\views_ui\Controller\ViewsUIController object.
*
* @param \Drupal\views\ViewsData $views_data
* The Views data cache object.
*/
public function __construct(ViewsData $views_data) {
$this->viewsData = $views_data;
}
/**
* Lists all instances of fields on any views.
*
* @return array
* The Views fields report page.
*/
public function reportFields() {
$views = $this->entityTypeManager()->getStorage('view')->loadMultiple();
// Fetch all fieldapi fields which are used in views
// Therefore search in all views, displays and handler-types.
$fields = [];
$handler_types = ViewExecutable::getHandlerTypes();
foreach ($views as $view) {
$executable = $view->getExecutable();
$executable->initDisplay();
foreach ($executable->displayHandlers as $display_id => $display) {
if ($executable->setDisplay($display_id)) {
foreach ($handler_types as $type => $info) {
foreach ($executable->getHandlers($type, $display_id) as $item) {
$table_data = $this->viewsData->get($item['table']);
if (isset($table_data[$item['field']]) && isset($table_data[$item['field']][$type])
&& $field_data = $table_data[$item['field']][$type]) {
// The final check that we have a fieldapi field now.
if (isset($field_data['field_name'])) {
$fields[$field_data['field_name']][$view->id()] = $view->id();
}
}
}
}
}
}
}
$header = [$this->t('Field name'), $this->t('Used in')];
$rows = [];
foreach ($fields as $field_name => $views) {
$rows[$field_name]['data'][0]['data']['#plain_text'] = $field_name;
foreach ($views as $view) {
$rows[$field_name]['data'][1][] = Link::fromTextAndUrl($view, new Url('entity.view.edit_form', ['view' => $view]))->toString();
}
$item_list = [
'#theme' => 'item_list',
'#items' => $rows[$field_name]['data'][1],
'#context' => ['list_style' => 'comma-list'],
];
$rows[$field_name]['data'][1] = ['data' => $item_list];
}
// Sort rows by field name.
ksort($rows);
$output = [
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => $this->t('No fields have been used in views yet.'),
];
return $output;
}
/**
* Lists all plugins and what enabled Views use them.
*
* @return array
* The Views plugins report page.
*/
public function reportPlugins() {
$rows = Views::pluginList();
foreach ($rows as &$row) {
$views = [];
// Link each view name to the view itself.
foreach ($row['views'] as $view) {
$views[] = Link::fromTextAndUrl($view, new Url('entity.view.edit_form', ['view' => $view]))->toString();
}
unset($row['views']);
$row['views']['data'] = [
'#theme' => 'item_list',
'#items' => $views,
'#context' => ['list_style' => 'comma-list'],
];
}
// Sort rows by field name.
ksort($rows);
return [
'#type' => 'table',
'#header' => [$this->t('Type'), $this->t('Name'), $this->t('Provided by'), $this->t('Used in')],
'#rows' => $rows,
'#empty' => $this->t('There are no enabled views.'),
];
}
/**
* Calls a method on a view and reloads the listing page.
*
* @param \Drupal\views\ViewEntityInterface $view
* The view being acted upon.
* @param string $op
* The operation to perform, e.g., 'enable' or 'disable'.
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse
* Either returns a rebuilt listing page as an AJAX response, or redirects
* back to the listing page.
*/
public function ajaxOperation(ViewEntityInterface $view, $op, Request $request) {
// Perform the operation.
$view->$op()->save();
// If the request is via AJAX, return the rendered list as JSON.
if ($request->request->get('js')) {
$list = $this->entityTypeManager()->getListBuilder('view')->render();
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand('#views-entity-list', $list));
return $response;
}
// Otherwise, redirect back to the page.
return $this->redirect('entity.view.collection');
}
/**
* Menu callback for Views tag autocompletion.
*
* Like other autocomplete functions, this function inspects the 'q' query
* parameter for the string to use to search for suggestions.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A JSON response containing the autocomplete suggestions for Views tags.
*/
public function autocompleteTag(Request $request) {
$matches = [];
$string = $request->query->get('q');
// Get matches from default views.
$views = $this->entityTypeManager()->getStorage('view')->loadMultiple();
// Keep track of previously processed tags so they can be skipped.
$tags = [];
foreach ($views as $view) {
$view_tag = $view->get('tag');
foreach (Tags::explode($view_tag) as $tag) {
if ($tag && !in_array($tag, $tags, TRUE)) {
$tags[] = $tag;
if (mb_stripos($tag, $string) !== FALSE) {
$matches[] = ['value' => $tag, 'label' => Html::escape($tag)];
if (count($matches) >= 10) {
break 2;
}
}
}
}
}
return new JsonResponse($matches);
}
/**
* Returns the form to edit a view.
*
* @param \Drupal\views_ui\ViewUI $view
* The view to be edited.
* @param string|null $display_id
* (optional) The display ID being edited. Defaults to NULL, which will load
* the first available display.
*
* @return array
* An array containing the Views edit and preview forms.
*/
public function edit(ViewUI $view, $display_id = NULL) {
$name = $view->label();
$data = $this->viewsData->get($view->get('base_table'));
if (isset($data['table']['base']['title'])) {
$name .= ' (' . $data['table']['base']['title'] . ')';
}
$build['#title'] = $name;
$build['edit'] = $this->entityFormBuilder()->getForm($view, 'edit', ['display_id' => $display_id]);
$build['preview'] = $this->entityFormBuilder()->getForm($view, 'preview', ['display_id' => $display_id]);
return $build;
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Drupal\views_ui\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Views;
/**
* Form builder for the advanced admin settings page.
*
* @internal
*/
class AdvancedSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_admin_settings_advanced';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['views.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$config = $this->config('views.settings');
$form['cache'] = [
'#type' => 'details',
'#title' => $this->t('Caching'),
'#open' => TRUE,
];
$form['cache']['clear_cache'] = [
'#type' => 'submit',
'#value' => $this->t("Clear Views' cache"),
'#submit' => ['::cacheSubmit'],
];
$form['debug'] = [
'#type' => 'details',
'#title' => $this->t('Debugging'),
'#open' => TRUE,
];
$form['debug']['sql_signature'] = [
'#type' => 'checkbox',
'#title' => $this->t('Add Views signature to all SQL queries'),
'#description' => $this->t("All Views-generated queries will include the name of the views and display 'view-name:display-name' as a string at the end of the SELECT clause. This makes identifying Views queries in database server logs simpler, but should only be used when troubleshooting."),
'#default_value' => $config->get('sql_signature'),
];
$options = Views::fetchPluginNames('display_extender');
if (!empty($options)) {
$form['extenders'] = [
'#type' => 'details',
'#title' => $this->t('Display extenders'),
'#open' => TRUE,
];
$form['extenders']['display_extenders'] = [
'#default_value' => array_filter($config->get('display_extenders')),
'#options' => $options,
'#type' => 'checkboxes',
'#description' => $this->t('Select extensions of the views interface.'),
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('views.settings')
->set('sql_signature', $form_state->getValue('sql_signature'))
->set('display_extenders', $form_state->getValue('display_extenders', []))
->save();
parent::submitForm($form, $form_state);
}
/**
* Submission handler to clear the Views cache.
*/
public function cacheSubmit() {
views_invalidate_cache();
$this->messenger()->addStatus($this->t('The cache has been cleared.'));
}
}

View File

@@ -0,0 +1,189 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\ViewEntityInterface;
use Drupal\views\Views;
/**
* Provides a form for adding an item in the Views UI.
*
* @internal
*/
class AddHandler extends ViewsFormBase {
/**
* Constructs a new AddHandler object.
*/
public function __construct($type = NULL) {
$this->setType($type);
}
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'add-handler';
}
/**
* {@inheritdoc}
*/
public function getForm(ViewEntityInterface $view, $display_id, $js, $type = NULL) {
$this->setType($type);
return parent::getForm($view, $display_id, $js);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_add_handler_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$type = $form_state->get('type');
$form = [
'options' => [
'#theme_wrappers' => ['container'],
'#attributes' => ['class' => ['scroll'], 'data-drupal-views-scroll' => TRUE],
],
];
$executable = $view->getExecutable();
if (!$executable->setDisplay($display_id)) {
$form['markup'] = ['#markup' => $this->t('Invalid display id @display', ['@display' => $display_id])];
return $form;
}
$display = &$executable->displayHandlers->get($display_id);
$types = ViewExecutable::getHandlerTypes();
$ltitle = $types[$type]['ltitle'];
$section = $types[$type]['plural'];
if (!empty($types[$type]['type'])) {
$type = $types[$type]['type'];
}
$form['#title'] = $this->t('Add @type', ['@type' => $ltitle]);
$form['#section'] = $display_id . 'add-handler';
// Add the display override dropdown.
views_ui_standard_display_dropdown($form, $form_state, $section);
// Figure out all the base tables allowed based upon what the relationships provide.
$base_tables = $executable->getBaseTables();
$options = Views::viewsDataHelper()->fetchFields(array_keys($base_tables), $type, $display->useGroupBy(), $form_state->get('type'));
if (!empty($options)) {
$form['override']['controls'] = [
'#theme_wrappers' => ['container'],
'#id' => 'views-filterable-options-controls',
'#attributes' => ['class' => ['form--inline', 'views-filterable-options-controls']],
];
$form['override']['controls']['options_search'] = [
'#type' => 'textfield',
'#title' => $this->t('Search'),
];
$groups = ['all' => $this->t('- All -')];
$form['override']['controls']['group'] = [
'#type' => 'select',
'#title' => $this->t('Category'),
'#options' => [],
];
$form['options']['name'] = [
'#prefix' => '<div class="views-radio-box form-checkboxes views-filterable-options">',
'#suffix' => '</div>',
'#type' => 'tableselect',
'#header' => [
'title' => $this->t('Title'),
'group' => $this->t('Category'),
'help' => $this->t('Description'),
],
'#js_select' => FALSE,
];
$grouped_options = [];
foreach ($options as $key => $option) {
$group = preg_replace('/[^a-z0-9]/', '-', strtolower($option['group']));
$groups[$group] = $option['group'];
$grouped_options[$group][$key] = $option;
if (!empty($option['aliases']) && is_array($option['aliases'])) {
foreach ($option['aliases'] as $id => $alias) {
if (empty($alias['base']) || !empty($base_tables[$alias['base']])) {
$copy = $option;
$copy['group'] = $alias['group'];
$copy['title'] = $alias['title'];
if (isset($alias['help'])) {
$copy['help'] = $alias['help'];
}
$group = preg_replace('/[^a-z0-9]/', '-', strtolower($copy['group']));
$groups[$group] = $copy['group'];
$grouped_options[$group][$key . '$' . $id] = $copy;
}
}
}
}
foreach ($grouped_options as $group => $group_options) {
foreach ($group_options as $key => $option) {
$form['options']['name']['#options'][$key] = [
'#attributes' => [
'class' => ['filterable-option', $group],
],
'title' => [
'data' => [
'#title' => $option['title'],
'#plain_text' => $option['title'],
],
'class' => ['title'],
],
'group' => $option['group'],
'help' => [
'data' => $option['help'],
'class' => ['description'],
],
];
}
}
$form['override']['controls']['group']['#options'] = $groups;
}
else {
$form['options']['markup'] = [
'#markup' => '<div class="js-form-item form-item">' . $this->t('There are no @types available to add.', ['@types' => $ltitle]) . '</div>',
];
}
// Add a div to show the selected items
$form['selected'] = [
'#type' => 'item',
'#markup' => '<span class="views-ui-view-title">' . $this->t('Selected:') . '</span> ' . '<div class="views-selected-options"></div>',
'#theme_wrappers' => ['form_element', 'views_ui_container'],
'#attributes' => [
'class' => ['container-inline', 'views-add-form-selected', 'views-offset-bottom'],
'data-drupal-views-offset' => 'bottom',
],
];
$view->getStandardButtons($form, $form_state, 'views_ui_add_handler_form', $this->t('Add and configure @types', ['@types' => $ltitle]));
// Remove the default submit function.
$form['actions']['submit']['#submit'] = array_filter($form['actions']['submit']['#submit'], function ($var) {
return !(is_array($var) && isset($var[1]) && $var[1] == 'standardSubmit');
});
$form['actions']['submit']['#submit'][] = [$view, 'submitItemAdd'];
return $form;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Views;
/**
* Displays analysis information for a view.
*
* @internal
*/
class Analyze extends ViewsFormBase {
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'analyze';
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_analyze_view_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$form['#title'] = $this->t('View analysis');
$form['#section'] = 'analyze';
$analyzer = Views::analyzer();
$messages = $analyzer->getMessages($view->getExecutable());
$form['analysis'] = [
'#prefix' => '<div class="js-form-item form-item">',
'#suffix' => '</div>',
'#markup' => $analyzer->formatMessages($messages),
];
// Inform the standard button function that we want an OK button.
$form_state->set('ok_button', TRUE);
$view->getStandardButtons($form, $form_state, 'views_ui_analyze_view_form');
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\views_ui\ViewUI $view */
$view = $form_state->get('view');
$form_state->setRedirectUrl($view->toUrl('edit-form'));
}
}

View File

@@ -0,0 +1,281 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewEntityInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a form for configuring an item in the Views UI.
*
* @internal
*/
class ConfigHandler extends ViewsFormBase {
/**
* Constructs a new ConfigHandler object.
*/
public function __construct($type = NULL, $id = NULL) {
$this->setType($type);
$this->setID($id);
}
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'handler';
}
/**
* {@inheritdoc}
*/
public function getForm(ViewEntityInterface $view, $display_id, $js, $type = NULL, $id = NULL) {
$this->setType($type);
$this->setID($id);
return parent::getForm($view, $display_id, $js);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_config_item_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ?Request $request = NULL) {
/** @var \Drupal\views\Entity\View $view */
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$type = $form_state->get('type');
$id = $form_state->get('id');
$form = [
'options' => [
'#tree' => TRUE,
'#theme_wrappers' => ['container'],
'#attributes' => ['class' => ['scroll'], 'data-drupal-views-scroll' => TRUE],
],
];
$executable = $view->getExecutable();
$save_ui_cache = FALSE;
if (!$executable->setDisplay($display_id)) {
$form['markup'] = ['#markup' => $this->t('Invalid display id @display', ['@display' => $display_id])];
return $form;
}
$item = $executable->getHandler($display_id, $type, $id);
if ($item) {
$handler = $executable->display_handler->getHandler($type, $id);
if (empty($handler)) {
$form['markup'] = ['#markup' => $this->t("Error: handler for @table > @field doesn't exist!", ['@table' => $item['table'], '@field' => $item['field']])];
}
else {
$types = ViewExecutable::getHandlerTypes();
$form['#title'] = $this->t('Configure @type: @item', ['@type' => $types[$type]['lstitle'], '@item' => $handler->adminLabel()]);
// If this item can come from the default display, show a dropdown
// that lets the user choose which display the changes should apply to.
if ($executable->display_handler->defaultableSections($types[$type]['plural'])) {
$section = $types[$type]['plural'];
$form_state->set('section', $section);
views_ui_standard_display_dropdown($form, $form_state, $section);
}
// A whole bunch of code to figure out what relationships are valid for
// this item.
$relationships = $executable->display_handler->getOption('relationships');
$relationship_options = [];
foreach ($relationships as $relationship) {
// Relationships can't link back to self. But also, due to ordering,
// relationships can only link to prior relationships.
if ($type == 'relationship' && $id == $relationship['id']) {
break;
}
$relationship_handler = Views::handlerManager('relationship')->getHandler($relationship);
// Ignore invalid/broken relationships.
if (empty($relationship_handler)) {
continue;
}
// If this relationship is valid for this type, add it to the list.
$data = Views::viewsData()->get($relationship['table']);
if (isset($data[$relationship['field']]['relationship']['base']) && $base = $data[$relationship['field']]['relationship']['base']) {
$base_fields = Views::viewsDataHelper()->fetchFields($base, $type, $executable->display_handler->useGroupBy());
if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
$relationship_handler->init($executable, $executable->display_handler, $relationship);
$relationship_options[$relationship['id']] = $relationship_handler->adminLabel();
}
}
}
if (!empty($relationship_options)) {
// Make sure the existing relationship is even valid. If not, force
// it to none.
$base_fields = Views::viewsDataHelper()->fetchFields($view->get('base_table'), $type, $executable->display_handler->useGroupBy());
if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
$relationship_options = array_merge(['none' => $this->t('Do not use a relationship')], $relationship_options);
}
$rel = empty($item['relationship']) ? 'none' : $item['relationship'];
if (empty($relationship_options[$rel])) {
// Pick the first relationship.
$rel = key($relationship_options);
// We want this relationship option to get saved even if the user
// skips submitting the form.
$executable->setHandlerOption($display_id, $type, $id, 'relationship', $rel);
$save_ui_cache = TRUE;
// Re-initialize with new relationship.
$item['relationship'] = $rel;
$handler->init($executable, $executable->display_handler, $item);
}
$form['options']['relationship'] = [
'#type' => 'select',
'#title' => $this->t('Relationship'),
'#options' => $relationship_options,
'#default_value' => $rel,
'#weight' => -500,
];
}
else {
$form['options']['relationship'] = [
'#type' => 'value',
'#value' => 'none',
];
}
if (!empty($handler->definition['help'])) {
$form['options']['form_description'] = [
'#markup' => $handler->definition['help'],
'#theme_wrappers' => ['container'],
'#attributes' => ['class' => ['js-form-item form-item description']],
'#weight' => -1000,
];
}
$form['#section'] = $display_id . '-' . $type . '-' . $id;
// Get form from the handler.
$handler->buildOptionsForm($form['options'], $form_state);
$form_state->set('handler', $handler);
}
$name = $form_state->get('update_name');
$view->getStandardButtons($form, $form_state, 'views_ui_config_item_form', $name);
// Add a 'remove' button.
$form['actions']['remove'] = [
'#type' => 'submit',
'#value' => $this->t('Remove'),
'#submit' => [[$this, 'remove']],
'#limit_validation_errors' => [['override']],
'#button_type' => 'danger',
];
}
if ($save_ui_cache) {
$view->cacheSet();
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$form_state->get('handler')->validateOptionsForm($form['options'], $form_state);
if ($form_state->getErrors()) {
$form_state->set('rerender', TRUE);
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$id = $form_state->get('id');
$handler = $form_state->get('handler');
// Run it through the handler's submit function.
$handler->submitOptionsForm($form['options'], $form_state);
$item = $handler->options;
$types = ViewExecutable::getHandlerTypes();
// For footer/header $handler_type is area but $type is footer/header.
// For all other handle types it's the same.
$handler_type = $type = $form_state->get('type');
if (!empty($types[$type]['type'])) {
$handler_type = $types[$type]['type'];
}
$override = NULL;
$executable = $view->getExecutable();
if ($executable->display_handler->useGroupBy() && !empty($item['group_type'])) {
if (empty($executable->query)) {
$executable->initQuery();
}
$aggregate = $executable->query->getAggregationInfo();
if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
$override = $aggregate[$item['group_type']]['handler'][$type];
}
}
// Create a new handler and unpack the options from the form onto it. We
// can use that for storage.
$handler = Views::handlerManager($handler_type)->getHandler($item, $override);
$handler->init($executable, $executable->display_handler, $item);
// Add the incoming options to existing options because items using
// the extra form may not have everything in the form here.
$options = $handler->submitFormCalculateOptions($handler->options, $form_state->getValue('options', []));
// This unpacks only options that are in the definition, ensuring random
// extra stuff on the form is not sent through.
$handler->unpackOptions($handler->options, $options, NULL, FALSE);
// Store the item back on the view
$executable->setHandler($display_id, $type, $id, $handler->options);
// Ensure any temporary options are removed.
if (isset($view->temporary_options[$type][$id])) {
unset($view->temporary_options[$type][$id]);
}
// Write to cache
$view->cacheSet();
}
/**
* Submit handler for removing an item from a view.
*/
public function remove(&$form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$type = $form_state->get('type');
$id = $form_state->get('id');
// Store the item back on the view
[$was_defaulted, $is_defaulted] = $view->getOverrideValues($form, $form_state);
$executable = $view->getExecutable();
// If the display selection was changed toggle the override value.
if ($was_defaulted != $is_defaulted) {
$display = &$executable->displayHandlers->get($display_id);
$display->optionsOverride($form, $form_state);
}
$executable->removeHandler($display_id, $type, $id);
// Write to cache
$view->cacheSet();
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewEntityInterface;
use Drupal\views\ViewExecutable;
/**
* Provides a form for configuring extra information for a Views UI item.
*
* @internal
*/
class ConfigHandlerExtra extends ViewsFormBase {
/**
* Constructs a new ConfigHandlerExtra object.
*/
public function __construct($type = NULL, $id = NULL) {
$this->setType($type);
$this->setID($id);
}
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'handler-extra';
}
/**
* {@inheritdoc}
*/
public function getForm(ViewEntityInterface $view, $display_id, $js, $type = NULL, $id = NULL) {
$this->setType($type);
$this->setID($id);
return parent::getForm($view, $display_id, $js);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_config_item_extra_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$type = $form_state->get('type');
$id = $form_state->get('id');
$form = [
'options' => [
'#tree' => TRUE,
'#theme_wrappers' => ['container'],
'#attributes' => ['class' => ['scroll'], 'data-drupal-views-scroll' => TRUE],
],
];
$executable = $view->getExecutable();
if (!$executable->setDisplay($display_id)) {
$form['markup'] = ['#markup' => $this->t('Invalid display id @display', ['@display' => $display_id])];
return $form;
}
$item = $executable->getHandler($display_id, $type, $id);
if ($item) {
$handler = $executable->display_handler->getHandler($type, $id);
if (empty($handler)) {
$form['markup'] = ['#markup' => $this->t("Error: handler for @table > @field doesn't exist!", ['@table' => $item['table'], '@field' => $item['field']])];
}
else {
$handler->init($executable, $executable->display_handler, $item);
$types = ViewExecutable::getHandlerTypes();
$form['#title'] = $this->t('Configure extra settings for @type %item', ['@type' => $types[$type]['lstitle'], '%item' => $handler->adminLabel()]);
$form['#section'] = $display_id . '-' . $type . '-' . $id;
// Get form from the handler.
$handler->buildExtraOptionsForm($form['options'], $form_state);
$form_state->set('handler', $handler);
}
$view->getStandardButtons($form, $form_state, 'views_ui_config_item_extra_form');
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$form_state->get('handler')->validateExtraOptionsForm($form['options'], $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$handler = $form_state->get('handler');
// Run it through the handler's submit function.
$handler->submitExtraOptionsForm($form['options'], $form_state);
$item = $handler->options;
// Store the data we're given.
foreach ($form_state->getValue('options') as $key => $value) {
$item[$key] = $value;
}
// Store the item back on the view
$view->getExecutable()->setHandler($form_state->get('display_id'), $form_state->get('type'), $form_state->get('id'), $item);
// Write to cache
$view->cacheSet();
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Views;
use Drupal\views\ViewEntityInterface;
use Drupal\views\ViewExecutable;
/**
* Provides a form for configuring grouping information for a Views UI handler.
*
* @internal
*/
class ConfigHandlerGroup extends ViewsFormBase {
/**
* Constructs a new ConfigHandlerGroup object.
*/
public function __construct($type = NULL, $id = NULL) {
$this->setType($type);
$this->setID($id);
}
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'handler-group';
}
/**
* {@inheritdoc}
*/
public function getForm(ViewEntityInterface $view, $display_id, $js, $type = NULL, $id = NULL) {
$this->setType($type);
$this->setID($id);
return parent::getForm($view, $display_id, $js);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_config_item_group_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$type = $form_state->get('type');
$id = $form_state->get('id');
$form = [
'options' => [
'#tree' => TRUE,
'#theme_wrappers' => ['container'],
'#attributes' => ['class' => ['scroll'], 'data-drupal-views-scroll' => TRUE],
],
];
$executable = $view->getExecutable();
if (!$executable->setDisplay($display_id)) {
$form['markup'] = ['#markup' => $this->t('Invalid display id @display', ['@display' => $display_id])];
return $form;
}
$executable->initQuery();
$item = $executable->getHandler($display_id, $type, $id);
if ($item) {
$handler = $executable->display_handler->getHandler($type, $id);
if (empty($handler)) {
$form['markup'] = ['#markup' => $this->t("Error: handler for @table > @field doesn't exist!", ['@table' => $item['table'], '@field' => $item['field']])];
}
else {
$handler->init($executable, $executable->display_handler, $item);
$types = ViewExecutable::getHandlerTypes();
$form['#title'] = $this->t('Configure aggregation settings for @type %item', ['@type' => $types[$type]['lstitle'], '%item' => $handler->adminLabel()]);
$handler->buildGroupByForm($form['options'], $form_state);
$form_state->set('handler', $handler);
}
$view->getStandardButtons($form, $form_state, 'views_ui_config_item_group_form');
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$item = &$form_state->get('handler')->options;
$type = $form_state->get('type');
$handler = Views::handlerManager($type)->getHandler($item);
$executable = $view->getExecutable();
$handler->init($executable, $executable->display_handler, $item);
$handler->submitGroupByForm($form, $form_state);
// Store the item back on the view
$executable->setHandler($form_state->get('display_id'), $form_state->get('type'), $form_state->get('id'), $item);
// Write to cache
$view->cacheSet();
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewEntityInterface;
/**
* Provides a form for editing the Views display.
*
* @internal
*/
class Display extends ViewsFormBase {
/**
* Constructs a new Display object.
*/
public function __construct($type = NULL) {
$this->setType($type);
}
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'display';
}
/**
* {@inheritdoc}
*
* @todo Remove this and switch all usage of $form_state->get('section') to
* $form_state->get('type').
*/
public function getFormState(ViewEntityInterface $view, $display_id, $js) {
$form_state = parent::getFormState($view, $display_id, $js);
$form_state->set('section', $this->type);
return $form_state;
}
/**
* {@inheritdoc}
*/
public function getForm(ViewEntityInterface $view, $display_id, $js, $type = NULL) {
$this->setType($type);
return parent::getForm($view, $display_id, $js);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_edit_display_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$executable = $view->getExecutable();
if (!$executable->setDisplay($display_id)) {
$form['markup'] = ['#markup' => $this->t('Invalid display id @display', ['@display' => $display_id])];
return $form;
}
// Get form from the handler.
$form['options'] = [
'#theme_wrappers' => ['container'],
'#attributes' => ['class' => ['scroll'], 'data-drupal-views-scroll' => TRUE],
];
$executable->display_handler->buildOptionsForm($form['options'], $form_state);
// The handler options form sets $form['#title'], which we need on the entire
// $form instead of just the ['options'] section.
$form['#title'] = $form['options']['#title'];
unset($form['options']['#title']);
// Move the override dropdown out of the scrollable section of the form.
if (isset($form['options']['override'])) {
$form['override'] = $form['options']['override'];
unset($form['options']['override']);
}
$name = $form_state->get('update_name');
$view->getStandardButtons($form, $form_state, 'views_ui_edit_display_form', $name);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$view->getExecutable()->displayHandlers->get($display_id)->validateOptionsForm($form['options'], $form_state);
if ($form_state->getErrors()) {
$form_state->set('rerender', TRUE);
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$view->getExecutable()->displayHandlers->get($display_id)->submitOptionsForm($form['options'], $form_state);
$view->cacheSet();
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Views;
/**
* Provides a form for editing the details of a View.
*
* @internal
*/
class EditDetails extends ViewsFormBase {
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'edit-details';
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_edit_details_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$form['#title'] = $this->t('Name and description');
$form['#section'] = 'details';
$form['details'] = [
'#theme_wrappers' => ['container'],
'#attributes' => ['class' => ['scroll'], 'data-drupal-views-scroll' => TRUE],
];
$form['details']['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Administrative name'),
'#default_value' => $view->label(),
];
$form['details']['langcode'] = [
'#type' => 'language_select',
'#title' => $this->t('View language'),
'#description' => $this->t('Language of labels and other textual elements in this view.'),
'#default_value' => $view->get('langcode'),
];
$form['details']['description'] = [
'#type' => 'textfield',
'#title' => $this->t('Administrative description'),
'#default_value' => $view->get('description'),
];
$form['details']['tag'] = [
'#type' => 'textfield',
'#title' => $this->t('Administrative tags'),
'#description' => $this->t('Enter a comma-separated list of words to describe your view.'),
'#default_value' => $view->get('tag'),
'#autocomplete_route_name' => 'views_ui.autocomplete',
];
$view->getStandardButtons($form, $form_state, 'views_ui_edit_details_form');
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->get('view');
foreach ($form_state->getValues() as $key => $value) {
// Only save values onto the view if they're actual view properties
// (as opposed to 'op' or 'form_build_id').
if (isset($form['details'][$key])) {
$view->set($key, $value);
}
}
$bases = Views::viewsData()->fetchBaseTables();
$page_title = $view->label();
if (isset($bases[$view->get('base_table')])) {
$page_title .= ' (' . $bases[$view->get('base_table')]['title'] . ')';
}
$form_state->set('page_title', $page_title);
$view->cacheSet();
}
}

View File

@@ -0,0 +1,186 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\Markup;
use Drupal\Core\Url;
use Drupal\views\ViewEntityInterface;
use Drupal\views\ViewExecutable;
/**
* Provides a rearrange form for Views handlers.
*
* @internal
*/
class Rearrange extends ViewsFormBase {
/**
* Constructs a new Rearrange object.
*/
public function __construct($type = NULL) {
$this->setType($type);
}
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'rearrange';
}
/**
* {@inheritdoc}
*/
public function getForm(ViewEntityInterface $view, $display_id, $js, $type = NULL) {
$this->setType($type);
return parent::getForm($view, $display_id, $js);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_rearrange_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$type = $form_state->get('type');
$types = ViewExecutable::getHandlerTypes();
$executable = $view->getExecutable();
if (!$executable->setDisplay($display_id)) {
$form['markup'] = ['#markup' => $this->t('Invalid display id @display', ['@display' => $display_id])];
return $form;
}
$display = &$executable->displayHandlers->get($display_id);
$form['#title'] = $this->t('Rearrange @type', ['@type' => $types[$type]['ltitle']]);
$form['#section'] = $display_id . 'rearrange-item';
if ($display->defaultableSections($types[$type]['plural'])) {
$section = $types[$type]['plural'];
$form_state->set('section', $section);
views_ui_standard_display_dropdown($form, $form_state, $section);
}
$count = 0;
// Get relationship labels
$relationships = [];
foreach ($display->getHandlers('relationship') as $id => $handler) {
$relationships[$id] = $handler->adminLabel();
}
$form['fields'] = [
'#type' => 'table',
'#header' => ['', $this->t('Weight'), $this->t('Remove')],
'#empty' => $this->t('No fields available.'),
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'weight',
],
],
'#tree' => TRUE,
'#prefix' => '<div class="scroll" data-drupal-views-scroll>',
'#suffix' => '</div>',
];
foreach ($display->getOption($types[$type]['plural']) as $id => $field) {
$form['fields'][$id] = [];
$form['fields'][$id]['#attributes'] = ['class' => ['draggable'], 'id' => 'views-row-' . $id];
$handler = $display->getHandler($type, $id);
if ($handler) {
$name = $handler->adminLabel() . ' ' . $handler->adminSummary();
if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
$name = '(' . $relationships[$field['relationship']] . ') ' . $name;
}
$markup = $name;
}
else {
$name = $id;
$markup = $this->t('Broken field @id', ['@id' => $id]);
}
$form['fields'][$id]['name'] = ['#markup' => $markup];
$form['fields'][$id]['weight'] = [
'#type' => 'textfield',
'#default_value' => ++$count,
'#attributes' => ['class' => ['weight']],
'#title' => $this->t('Weight for @title', ['@title' => $name]),
'#title_display' => 'invisible',
];
$form['fields'][$id]['removed'] = [
'#type' => 'checkbox',
'#title' => $this->t('Remove @title', ['@title' => $name]),
'#title_display' => 'invisible',
'#id' => 'views-removed-' . $id,
'#attributes' => ['class' => ['views-remove-checkbox']],
'#default_value' => 0,
'#prefix' => '<div class="js-hide">',
'#suffix' => Markup::create('</div>' . Link::fromTextAndUrl(new FormattableMarkup('<span>@text</span>', ['@text' => $this->t('Remove')]),
Url::fromRoute('<none>', [], [
'attributes' => [
'id' => 'views-remove-link-' . $id,
'class' => ['views-hidden', 'views-button-remove', 'views-remove-link'],
'alt' => $this->t('Remove this item'),
'title' => $this->t('Remove this item'),
],
])
)->toString()),
];
}
$view->getStandardButtons($form, $form_state, 'views_ui_rearrange_form');
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$type = $form_state->get('type');
$types = ViewExecutable::getHandlerTypes();
$display = &$view->getExecutable()->displayHandlers->get($display_id);
$old_fields = $display->getOption($types[$type]['plural']);
$new_fields = $order = [];
// Make an array with the weights
foreach ($form_state->getValue('fields') as $field => $info) {
// Add each value that is a field with a weight to our list, but only if
// it has had its 'removed' checkbox checked.
if (is_array($info) && isset($info['weight']) && empty($info['removed'])) {
$order[$field] = $info['weight'];
}
}
// Sort the array
asort($order);
// Create a new list of fields in the new order.
foreach (array_keys($order) as $field) {
$new_fields[$field] = $old_fields[$field];
}
$display->setOption($types[$type]['plural'], $new_fields);
// Store in cache
$view->cacheSet();
}
}

View File

@@ -0,0 +1,358 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;
/**
* Provides a rearrange form for Views filters.
*
* @internal
*/
class RearrangeFilter extends ViewsFormBase {
/**
* Constructs a new RearrangeFilter object.
*/
public function __construct($type = NULL) {
$this->setType($type);
}
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'rearrange-filter';
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_rearrange_filter_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$type = 'filter';
$types = ViewExecutable::getHandlerTypes();
$executable = $view->getExecutable();
if (!$executable->setDisplay($display_id)) {
$form['markup'] = ['#markup' => $this->t('Invalid display id @display', ['@display' => $display_id])];
return $form;
}
$display = $executable->displayHandlers->get($display_id);
$form['#title'] = Html::escape($display->display['display_title']) . ': ';
$form['#title'] .= $this->t('Rearrange @type', ['@type' => $types[$type]['ltitle']]);
$form['#section'] = $display_id . 'rearrange-item';
if ($display->defaultableSections($types[$type]['plural'])) {
$section = $types[$type]['plural'];
$form_state->set('section', $section);
views_ui_standard_display_dropdown($form, $form_state, $section);
}
if (!empty($view->form_cache)) {
$groups = $view->form_cache['groups'];
$handlers = $view->form_cache['handlers'];
}
else {
$groups = $display->getOption('filter_groups');
$handlers = $display->getOption($types[$type]['plural']);
}
$count = 0;
// Get relationship labels
$relationships = [];
foreach ($display->getHandlers('relationship') as $id => $handler) {
$relationships[$id] = $handler->adminLabel();
}
$group_options = [];
/**
* Filter groups is an array that contains:
* [
* 'operator' => 'and' || 'or',
* 'groups' => [
* $group_id => 'and' || 'or',
* ],
* ];
*/
$grouping = count(array_keys($groups['groups'])) > 1;
$form['filter_groups']['#tree'] = TRUE;
$form['filter_groups']['operator'] = [
'#type' => 'select',
'#options' => [
'AND' => $this->t('And'),
'OR' => $this->t('Or'),
],
'#default_value' => $groups['operator'],
'#attributes' => [
'class' => ['warning-on-change'],
],
'#title' => $this->t('Operator to use on all groups'),
'#description' => $this->t('Either "group 0 AND group 1 AND group 2" or "group 0 OR group 1 OR group 2", etc'),
'#access' => $grouping,
];
$form['remove_groups']['#tree'] = TRUE;
foreach ($groups['groups'] as $id => $group) {
$form['filter_groups']['groups'][$id] = [
'#title' => $this->t('Operator'),
'#type' => 'select',
'#options' => [
'AND' => $this->t('And'),
'OR' => $this->t('Or'),
],
'#default_value' => $group,
'#attributes' => [
'class' => ['warning-on-change'],
],
];
// To prevent a notice.
$form['remove_groups'][$id] = [];
if ($id != 1) {
$form['remove_groups'][$id] = [
'#type' => 'submit',
'#value' => $this->t('Remove group @group', ['@group' => $id]),
'#id' => "views-remove-group-$id",
'#attributes' => [
'class' => ['views-remove-group'],
],
'#group' => $id,
'#ajax' => ['url' => NULL],
];
}
$group_options[$id] = $id == 1 ? $this->t('Default group') : $this->t('Group @group', ['@group' => $id]);
$form['#group_renders'][$id] = [];
}
$form['#group_options'] = $group_options;
$form['#groups'] = $groups;
// We don't use getHandlers() because we want items without handlers to
// appear and show up as 'broken' so that the user can see them.
$form['filters'] = ['#tree' => TRUE];
foreach ($handlers as $id => $field) {
// If the group does not exist, move the filters to the default group.
if (empty($field['group']) || empty($groups['groups'][$field['group']])) {
$field['group'] = 1;
}
$handler = $display->getHandler($type, $id);
if ($grouping && $handler && !$handler->canGroup()) {
$field['group'] = 'ungroupable';
}
// If not grouping and the handler is set ungroupable, move it back to
// the default group to prevent weird errors from having it be in its
// own group:
if (!$grouping && $field['group'] == 'ungroupable') {
$field['group'] = 1;
}
// Place this item into the proper group for rendering.
$form['#group_renders'][$field['group']][] = $id;
$form['filters'][$id]['weight'] = [
'#title' => $this->t('Weight for @id', ['@id' => $id]),
'#title_display' => 'invisible',
'#type' => 'textfield',
'#default_value' => ++$count,
'#size' => 8,
];
$form['filters'][$id]['group'] = [
'#title' => $this->t('Group for @id', ['@id' => $id]),
'#title_display' => 'invisible',
'#type' => 'select',
'#options' => $group_options,
'#default_value' => $field['group'],
'#attributes' => [
'class' => ['views-region-select', 'views-region-' . $id],
],
'#access' => $field['group'] !== 'ungroupable',
];
if ($handler) {
$name = $handler->adminLabel() . ' ' . $handler->adminSummary();
if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
$name = '(' . $relationships[$field['relationship']] . ') ' . $name;
}
$form['filters'][$id]['name'] = [
'#markup' => $name,
];
}
else {
$form['filters'][$id]['name'] = ['#markup' => $this->t('Broken field @id', ['@id' => $id])];
}
$form['filters'][$id]['removed'] = [
'#title' => $this->t('Remove @id', ['@id' => $id]),
'#title_display' => 'invisible',
'#type' => 'checkbox',
'#id' => 'views-removed-' . $id,
'#attributes' => ['class' => ['views-remove-checkbox']],
'#default_value' => 0,
];
}
$view->getStandardButtons($form, $form_state, 'views_ui_rearrange_filter_form');
$form['actions']['add_group'] = [
'#type' => 'submit',
'#value' => $this->t('Create new filter group'),
'#id' => 'views-add-group',
'#group' => 'add',
'#attributes' => [
'class' => ['views-add-group'],
],
'#ajax' => ['url' => NULL],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$types = ViewExecutable::getHandlerTypes();
$view = $form_state->get('view');
$display = &$view->getExecutable()->displayHandlers->get($form_state->get('display_id'));
$remember_groups = [];
if (!empty($view->form_cache)) {
$old_fields = $view->form_cache['handlers'];
}
else {
$old_fields = $display->getOption($types['filter']['plural']);
}
$groups = $form_state->getValue('filter_groups');
// Whatever button was clicked, re-calculate field information.
$new_fields = $order = [];
// Make an array with the weights
foreach ($form_state->getValue('filters') as $field => $info) {
// Add each value that is a field with a weight to our list, but only if
// it has had its 'removed' checkbox checked.
if (is_array($info) && empty($info['removed'])) {
if (isset($info['weight'])) {
$order[$field] = $info['weight'];
}
if (isset($info['group'])) {
$old_fields[$field]['group'] = $info['group'];
$remember_groups[$info['group']][] = $field;
}
}
}
// Sort the array
asort($order);
// Create a new list of fields in the new order.
foreach (array_keys($order) as $field) {
$new_fields[$field] = $old_fields[$field];
}
// If the #group property is set on the clicked button, that means we are
// either adding or removing a group, not actually updating the filters.
$triggering_element = $form_state->getTriggeringElement();
if (!empty($triggering_element['#group'])) {
if ($triggering_element['#group'] == 'add') {
// Add a new group
$groups['groups'][] = 'AND';
}
else {
// Renumber groups above the removed one down.
foreach (array_keys($groups['groups']) as $group_id) {
if ($group_id >= $triggering_element['#group']) {
$old_group = $group_id + 1;
if (isset($groups['groups'][$old_group])) {
$groups['groups'][$group_id] = $groups['groups'][$old_group];
if (isset($remember_groups[$old_group])) {
foreach ($remember_groups[$old_group] as $id) {
$new_fields[$id]['group'] = $group_id;
}
}
}
else {
// If this is the last one, just unset it.
unset($groups['groups'][$group_id]);
}
}
}
}
// Update our cache with values so that cancel still works the way
// people expect.
$view->form_cache = [
'key' => 'rearrange-filter',
'groups' => $groups,
'handlers' => $new_fields,
];
// Return to this form except on actual Update.
$view->addFormToStack('rearrange-filter', $form_state->get('display_id'), 'filter');
}
else {
// The actual update button was clicked. Remove the empty groups, and
// renumber them sequentially.
ksort($remember_groups);
$groups['groups'] = static::arrayKeyPlus(array_values(array_intersect_key($groups['groups'], $remember_groups)));
// Change the 'group' key on each field to match. Here, $mapping is an
// array whose keys are the old group numbers and whose values are the new
// (sequentially numbered) ones.
$mapping = array_flip(static::arrayKeyPlus(array_keys($remember_groups)));
foreach ($new_fields as &$new_field) {
$new_field['group'] = $mapping[$new_field['group']];
}
// Write the changed handler values.
$display->setOption($types['filter']['plural'], $new_fields);
$display->setOption('filter_groups', $groups);
if (isset($view->form_cache)) {
unset($view->form_cache);
}
}
// Store in cache.
$view->cacheSet();
}
/**
* Adds one to each key of an array.
*
* For example [0 => 'foo'] would be [1 => 'foo'].
*
* @param array $array
* The array to increment keys on.
*
* @return array
* The array with incremented keys.
*/
public static function arrayKeyPlus($array) {
$keys = array_keys($array);
// Sort the keys in reverse order so incrementing them doesn't overwrite any
// existing keys.
rsort($keys);
foreach ($keys as $key) {
$array[$key + 1] = $array[$key];
unset($array[$key]);
}
// Sort the keys back to ascending order.
ksort($array);
return $array;
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Displays the display reorder form.
*
* @internal
*/
class ReorderDisplays extends ViewsFormBase {
/**
* {@inheritdoc}
*/
public function getFormKey() {
return 'reorder-displays';
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_reorder_displays_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
/** @var \Drupal\views\ViewEntityInterface $view */
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$form['#title'] = $this->t('Reorder displays');
$form['#section'] = 'reorder';
$form['#action'] = Url::fromRoute('views_ui.form_reorder_displays', [
'js' => 'nojs',
'view' => $view->id(),
'display_id' => $display_id,
])->toString();
$form['view'] = [
'#type' => 'value',
'#value' => $view,
];
$displays = $view->get('display');
$count = count($displays);
// Sort the displays.
uasort($displays, function ($display1, $display2) {
return $display1['position'] <=> $display2['position'];
});
$form['displays'] = [
'#type' => 'table',
'#id' => 'reorder-displays',
'#header' => [$this->t('Display'), $this->t('Weight'), $this->t('Remove')],
'#empty' => $this->t('No displays available.'),
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'weight',
],
],
'#tree' => TRUE,
'#prefix' => '<div class="scroll" data-drupal-views-scroll>',
'#suffix' => '</div>',
];
foreach ($displays as $id => $display) {
$form['displays'][$id] = [
'#display' => $display,
'#attributes' => [
'id' => 'display-row-' . $id,
],
'#weight' => $display['position'],
];
// Only make row draggable if it's not the default display.
if ($id !== 'default') {
$form['displays'][$id]['#attributes']['class'][] = 'draggable';
}
$form['displays'][$id]['title'] = [
'#markup' => $display['display_title'],
];
$form['displays'][$id]['weight'] = [
'#type' => 'weight',
'#value' => $display['position'],
'#delta' => $count,
'#title' => $this->t('Weight for @display', ['@display' => $display['display_title']]),
'#title_display' => 'invisible',
'#attributes' => [
'class' => ['weight'],
],
];
$form['displays'][$id]['removed'] = [
'checkbox' => [
'#title' => $this->t('Remove @id', ['@id' => $id]),
'#title_display' => 'invisible',
'#type' => 'checkbox',
'#id' => 'display-removed-' . $id,
'#attributes' => [
'class' => ['views-remove-checkbox'],
],
'#default_value' => !empty($display['deleted']),
],
'link' => [
'#type' => 'link',
'#title' => new FormattableMarkup('<span>@text</span>', ['@text' => $this->t('Remove')]),
'#url' => Url::fromRoute('<none>'),
'#attributes' => [
'id' => 'display-remove-link-' . $id,
'class' => ['views-button-remove', 'display-remove-link'],
'alt' => $this->t('Remove this display'),
'title' => $this->t('Remove this display'),
],
],
'#access' => ($id !== 'default'),
];
if (!empty($display['deleted'])) {
$form['displays'][$id]['deleted'] = [
'#type' => 'value',
'#value' => TRUE,
];
$form['displays'][$id]['#attributes']['class'][] = 'hidden';
}
}
$view->getStandardButtons($form, $form_state, 'views_ui_reorder_displays_form');
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\views_ui\ViewUI $view */
$view = $form_state->get('view');
$order = [];
$user_input = $form_state->getUserInput();
foreach ($user_input['displays'] as $display => $info) {
// Add each value that is a field with a weight to our list, but only if
// it has had its 'removed' checkbox checked.
if (is_array($info) && isset($info['weight']) && empty($info['removed']['checkbox'])) {
$order[$display] = $info['weight'];
}
}
// Sort the order array.
asort($order);
// Remove the default display from ordering.
unset($order['default']);
// Increment up positions.
$position = 1;
foreach (array_keys($order) as $display) {
$order[$display] = $position++;
}
// Setting up position and removing deleted displays.
$displays = $view->get('display');
foreach ($displays as $display_id => &$display) {
// Don't touch the default.
if ($display_id === 'default') {
$display['position'] = 0;
continue;
}
if (isset($order[$display_id])) {
$display['position'] = $order[$display_id];
}
else {
$display['deleted'] = TRUE;
}
}
$view->set('display', $displays);
// Store in cache.
$view->cacheSet();
$url = $view->toUrl('edit-form')
->setOption('fragment', 'views-tab-default');
$form_state->setRedirectUrl($url);
}
}

View File

@@ -0,0 +1,283 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Component\Utility\Html;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Url;
use Drupal\views\Ajax\HighlightCommand;
use Drupal\views\Ajax\ReplaceTitleCommand;
use Drupal\views\Ajax\ShowButtonsCommand;
use Drupal\views\Ajax\TriggerPreviewCommand;
use Drupal\views\ViewEntityInterface;
use Drupal\views_ui\Ajax\SetFormCommand;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Provides a base class for Views UI AJAX forms.
*/
abstract class ViewsFormBase extends FormBase implements ViewsFormInterface {
/**
* The ID of the item this form is manipulating.
*
* @var string
*/
protected $id;
/**
* The type of item this form is manipulating.
*
* @var string
*/
protected $type;
/**
* Sets the ID for this form.
*
* @param string $id
* The ID of the item this form is manipulating.
*/
protected function setID($id) {
if ($id) {
$this->id = $id;
}
}
/**
* Sets the type for this form.
*
* @param string $type
* The type of the item this form is manipulating.
*/
protected function setType($type) {
if ($type) {
$this->type = $type;
}
}
/**
* {@inheritdoc}
*/
public function getFormState(ViewEntityInterface $view, $display_id, $js) {
// $js may already have been converted to a Boolean.
$ajax = is_string($js) ? $js === 'ajax' : $js;
return (new FormState())
->set('form_id', $this->getFormId())
->set('form_key', $this->getFormKey())
->set('ajax', $ajax)
->set('display_id', $display_id)
->set('view', $view)
->set('type', $this->type)
->set('id', $this->id)
->disableRedirect()
->addBuildInfo('callback_object', $this);
}
/**
* {@inheritdoc}
*/
public function getForm(ViewEntityInterface $view, $display_id, $js) {
/** @var \Drupal\Core\Form\FormStateInterface $form_state */
$form_state = $this->getFormState($view, $display_id, $js);
$view = $form_state->get('view');
$form_key = $form_state->get('form_key');
// @todo Remove the need for this.
\Drupal::moduleHandler()->loadInclude('views_ui', 'inc', 'admin');
// Reset the cache of IDs. Drupal rather aggressively prevents ID
// duplication but this causes it to remember IDs that are no longer even
// being used.
Html::resetSeenIds();
// Check to see if this is the top form of the stack. If it is, pop
// it off; if it isn't, the user clicked somewhere else and the stack is
// now irrelevant.
if (!empty($view->stack)) {
$identifier = implode('-', array_filter([$form_key, $view->id(), $display_id, $form_state->get('type'), $form_state->get('id')]));
// Retrieve the first form from the stack without changing the integer keys,
// as they're being used for the "2 of 3" progress indicator.
reset($view->stack);
$stack_key = key($view->stack);
$top = current($view->stack);
next($view->stack);
unset($view->stack[$stack_key]);
if (array_shift($top) != $identifier) {
$view->stack = [];
}
}
// Automatically remove the form cache if it is set and the key does
// not match. This way navigating away from the form without hitting
// update will work.
if (isset($view->form_cache) && $view->form_cache['key'] !== $form_key) {
unset($view->form_cache);
}
$form_class = get_class($form_state->getFormObject());
$response = $this->ajaxFormWrapper($form_class, $form_state);
// If the form has not been submitted, or was not set for rerendering, stop.
if (!$form_state->isSubmitted() || $form_state->get('rerender')) {
return $response;
}
// Sometimes we need to re-generate the form for multi-step type operations.
if (!empty($view->stack)) {
$stack = $view->stack;
$top = array_shift($stack);
// Build the new form state for the next form in the stack.
$reflection = new \ReflectionClass($view::$forms[$top[1]]);
$form_state = $reflection->newInstanceArgs(array_slice($top, 3, 2))->getFormState($view, $top[2], $form_state->get('ajax'));
$form_class = get_class($form_state->getFormObject());
$form_state->setUserInput([]);
$form_url = views_ui_build_form_url($form_state);
if (!$form_state->get('ajax')) {
return new RedirectResponse($form_url->setAbsolute()->toString());
}
$form_state->set('url', $form_url);
$response = $this->ajaxFormWrapper($form_class, $form_state);
}
elseif (!$form_state->get('ajax')) {
// If nothing on the stack, non-js forms just go back to the main view editor.
$display_id = $form_state->get('display_id');
return new RedirectResponse(Url::fromRoute('entity.view.edit_display_form', ['view' => $view->id(), 'display_id' => $display_id], ['absolute' => TRUE])->toString());
}
else {
$response = new AjaxResponse();
$response->addCommand(new CloseModalDialogCommand());
$response->addCommand(new ShowButtonsCommand(!empty($view->changed)));
$response->addCommand(new TriggerPreviewCommand());
if ($page_title = $form_state->get('page_title')) {
$response->addCommand(new ReplaceTitleCommand($page_title));
}
}
// If this form was for view-wide changes, there's no need to regenerate
// the display section of the form.
if ($display_id !== '') {
\Drupal::entityTypeManager()->getFormObject('view', 'edit')->rebuildCurrentTab($view, $response, $display_id);
}
return $response;
}
/**
* Wrapper for handling AJAX forms.
*
* Wrapper around \Drupal\Core\Form\FormBuilderInterface::buildForm() to
* handle some AJAX stuff automatically.
* This makes some assumptions about the client.
*
* @param \Drupal\Core\Form\FormInterface|string $form_class
* The value must be one of the following:
* - The name of a class that implements \Drupal\Core\Form\FormInterface.
* - An instance of a class that implements \Drupal\Core\Form\FormInterface.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse|string|array
* Returns one of three possible values:
* - A \Drupal\Core\Ajax\AjaxResponse object.
* - The rendered form, as a string.
* - A render array with the title in #title and the rendered form in the
* #markup array.
*/
protected function ajaxFormWrapper($form_class, FormStateInterface &$form_state) {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
// This won't override settings already in.
if (!$form_state->has('rerender')) {
$form_state->set('rerender', FALSE);
}
$ajax = $form_state->get('ajax');
// Do not overwrite if the redirect has been disabled.
if (!$form_state->isRedirectDisabled()) {
$form_state->disableRedirect($ajax);
}
$form_state->disableCache();
// Builds the form in a render context in order to ensure that cacheable
// metadata is bubbled up.
$render_context = new RenderContext();
$callable = function () use ($form_class, &$form_state) {
return \Drupal::formBuilder()->buildForm($form_class, $form_state);
};
$form = $renderer->executeInRenderContext($render_context, $callable);
if (!$render_context->isEmpty()) {
BubbleableMetadata::createFromRenderArray($form)
->merge($render_context->pop())
->applyTo($form);
}
$output = $renderer->renderRoot($form);
// These forms have the title built in, so set the title here:
$title = $form_state->get('title') ?: '';
if ($ajax && (!$form_state->isExecuted() || $form_state->get('rerender'))) {
// If the form didn't execute and we're using ajax, build up an
// Ajax command list to execute.
$response = new AjaxResponse();
// Attach the library necessary for using the OpenModalDialogCommand and
// set the attachments for this Ajax response.
$form['#attached']['library'][] = 'core/drupal.dialog.ajax';
$response->setAttachments($form['#attached']);
$display = '';
$status_messages = ['#type' => 'status_messages'];
if ($messages = $renderer->renderRoot($status_messages)) {
$display = '<div class="views-messages">' . $messages . '</div>';
}
$display .= $output;
$options = [
'classes' => [
'ui-dialog' => 'views-ui-dialog js-views-ui-dialog',
],
'width' => '75%',
];
$response->addCommand(new OpenModalDialogCommand($title, $display, $options));
// Views provides its own custom handling of AJAX form submissions.
// Usually this happens at the same path, but custom paths may be
// specified in $form_state.
$form_url = $form_state->has('url') ? $form_state->get('url')->toString() : Url::fromRoute('<current>')->toString();
$response->addCommand(new SetFormCommand($form_url));
if ($section = $form_state->get('#section')) {
$response->addCommand(new HighlightCommand('.' . Html::cleanCssIdentifier($section)));
}
return $response;
}
return $title ? ['#title' => $title, '#markup' => $output] : $output;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Drupal\views_ui\Form\Ajax;
use Drupal\Core\Form\FormInterface;
use Drupal\views\ViewEntityInterface;
interface ViewsFormInterface extends FormInterface {
/**
* Returns the key that represents this form.
*
* @return string
* The form key used in the URL, e.g., the string 'add-handler' in
* 'admin/structure/views/%/add-handler/%/%/%'.
*/
public function getFormKey();
/**
* Gets the form state for this form.
*
* @param \Drupal\views\ViewEntityInterface $view
* The view being edited.
* @param string|null $display_id
* The display ID being edited, or NULL to load the first available display.
* @param string $js
* If this is an AJAX form, it will be the string 'ajax'. Otherwise, it will
* be 'nojs'. This determines the response.
*
* @return \Drupal\Core\Form\FormStateInterface
* The current state of the form.
*/
public function getFormState(ViewEntityInterface $view, $display_id, $js);
/**
* Creates a new instance of this form.
*
* @param \Drupal\views\ViewEntityInterface $view
* The view being edited.
* @param string|null $display_id
* The display ID being edited, or NULL to load the first available display.
* @param string $js
* If this is an AJAX form, it will be the string 'ajax'. Otherwise, it will
* be 'nojs'. This determines the response.
*
* @return array
* A form for a specific operation in the Views UI, or an array of AJAX
* commands to render a form.
*
* @todo When https://www.drupal.org/node/1843224 is in, this will return
* \Drupal\Core\Ajax\AjaxResponse instead of the array of AJAX commands.
*/
public function getForm(ViewEntityInterface $view, $display_id, $js);
}

View File

@@ -0,0 +1,169 @@
<?php
namespace Drupal\views_ui\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\RedundantEditableConfigNamesTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form builder for the admin display defaults page.
*
* @internal
*/
class BasicSettingsForm extends ConfigFormBase {
use RedundantEditableConfigNamesTrait;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Constructs a \Drupal\views_ui\Form\BasicSettingsForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
* The typed config manager.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ThemeHandlerInterface $theme_handler) {
parent::__construct($config_factory, $typedConfigManager);
$this->themeHandler = $theme_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('config.typed'),
$container->get('theme_handler')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_admin_settings_basic';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$options = [];
foreach ($this->themeHandler->listInfo() as $name => $theme) {
if ($theme->status) {
$options[$name] = $theme->info['name'];
}
}
// This is not currently a fieldset but we may want it to be later,
// so this will make it easier to change if we do.
$form['basic'] = [];
$form['basic']['ui_show_default_display'] = [
'#type' => 'checkbox',
'#title' => $this->t('Always show the default display'),
'#config_target' => 'views.settings:ui.show.default_display',
];
$form['basic']['ui_show_advanced_column'] = [
'#type' => 'checkbox',
'#title' => $this->t('Always show advanced display settings'),
'#config_target' => 'views.settings:ui.show.advanced_column',
];
$form['basic']['ui_show_display_embed'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow embedded displays'),
'#description' => $this->t('Embedded displays can be used in code via views_embed_view().'),
'#config_target' => 'views.settings:ui.show.display_embed',
];
$form['basic']['ui_exposed_filter_any_label'] = [
'#type' => 'select',
'#title' => $this->t('Label for "Any" value on non-required single-select exposed filters'),
'#options' => ['old_any' => '<Any>', 'new_any' => $this->t('- Any -')],
'#config_target' => 'views.settings:ui.exposed_filter_any_label',
];
$form['live_preview'] = [
'#type' => 'details',
'#title' => $this->t('Live preview settings'),
'#open' => TRUE,
];
$form['live_preview']['ui_always_live_preview'] = [
'#type' => 'checkbox',
'#title' => $this->t('Automatically update preview on changes'),
'#config_target' => 'views.settings:ui.always_live_preview',
];
$form['live_preview']['ui_show_preview_information'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show information and statistics about the view during live preview'),
'#config_target' => 'views.settings:ui.show.preview_information',
];
$form['live_preview']['options'] = [
'#type' => 'container',
'#states' => [
'visible' => [
':input[name="ui_show_preview_information"]' => ['checked' => TRUE],
],
],
];
$form['live_preview']['options']['ui_show_sql_query_enabled'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show the SQL query'),
'#config_target' => 'views.settings:ui.show.sql_query.enabled',
];
$form['live_preview']['options']['ui_show_sql_query_where'] = [
'#type' => 'radios',
'#states' => [
'visible' => [
':input[name="ui_show_sql_query_enabled"]' => ['checked' => TRUE],
],
],
'#title' => $this->t('Show SQL query'),
'#options' => [
'above' => $this->t('Above the preview'),
'below' => $this->t('Below the preview'),
],
'#config_target' => 'views.settings:ui.show.sql_query.where',
];
$form['live_preview']['options']['ui_show_performance_statistics'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show performance statistics'),
'#config_target' => 'views.settings:ui.show.performance_statistics',
];
$form['live_preview']['options']['ui_show_additional_queries'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show other queries run during render during live preview'),
'#description' => $this->t("Drupal has the potential to run many queries while a view is being rendered. Checking this box will display every query run during view render as part of the live preview."),
'#config_target' => 'views.settings:ui.show.additional_queries',
];
return $form;
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Drupal\views_ui\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TempStore\SharedTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Builds the form to break the lock of an edited view.
*
* @internal
*/
class BreakLockForm extends EntityConfirmFormBase {
/**
* Stores the entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Stores the shared tempstore.
*
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
/**
* Constructs a \Drupal\views_ui\Form\BreakLockForm object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
* The factory for the temp store object.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, SharedTempStoreFactory $temp_store_factory) {
$this->entityTypeManager = $entity_type_manager;
$this->tempStore = $temp_store_factory->get('views');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('tempstore.shared')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_ui_break_lock_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Do you want to break the lock on view %name?', ['%name' => $this->entity->id()]);
}
/**
* {@inheritdoc}
*/
public function getDescription() {
$locked = $this->tempStore->getMetadata($this->entity->id());
$account = $this->entityTypeManager->getStorage('user')->load($locked->getOwnerId());
$username = [
'#theme' => 'username',
'#account' => $account,
];
return $this->t('By breaking this lock, any unsaved changes made by @user will be lost.', ['@user' => \Drupal::service('renderer')->render($username)]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return $this->entity->toUrl('edit-form');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Break lock');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
if (!$this->tempStore->getMetadata($this->entity->id())) {
$form['message']['#markup'] = $this->t('There is no lock on view %name to break.', ['%name' => $this->entity->id()]);
return $form;
}
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->tempStore->delete($this->entity->id());
$form_state->setRedirectUrl($this->entity->toUrl('edit-form'));
$this->messenger()->addStatus($this->t('The lock has been broken and you may now edit this view.'));
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Drupal\views_ui\ParamConverter;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\ParamConverter\AdminPathConfigEntityConverter;
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Drupal\Core\Routing\AdminContext;
use Drupal\Core\TempStore\SharedTempStoreFactory;
use Drupal\views_ui\ViewUI;
use Symfony\Component\Routing\Route;
/**
* Provides upcasting for a view entity to be used in the Views UI.
*
* Example:
*
* pattern: '/some/{view}/and/{bar}'
* options:
* parameters:
* view:
* type: 'entity:view'
* tempstore: TRUE
*
* The value for {view} will be converted to a view entity prepared for the
* Views UI and loaded from the views temp store, but it will not touch the
* value for {bar}.
*/
class ViewUIConverter extends AdminPathConfigEntityConverter implements ParamConverterInterface {
/**
* Stores the tempstore factory.
*
* @var \Drupal\Core\TempStore\SharedTempStoreFactory
*/
protected $tempStoreFactory;
/**
* Constructs a new ViewUIConverter.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
* The factory for the temp store object.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Routing\AdminContext $admin_context
* The route admin context service.
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, SharedTempStoreFactory $temp_store_factory, ConfigFactoryInterface $config_factory, AdminContext $admin_context, EntityRepositoryInterface $entity_repository) {
parent::__construct($entity_type_manager, $config_factory, $admin_context, $entity_repository);
$this->tempStoreFactory = $temp_store_factory;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
if (!$entity = parent::convert($value, $definition, $name, $defaults)) {
return;
}
// Get the temp store for this variable if it needs one. Attempt to load the
// view from the temp store, synchronize its status with the existing view,
// and store the lock metadata.
$store = $this->tempStoreFactory->get('views');
if ($view = $store->get($value)) {
if ($entity->status()) {
$view->enable();
}
else {
$view->disable();
}
$view->setLock($store->getMetadata($value));
}
// Otherwise, decorate the existing view for use in the UI.
else {
$view = new ViewUI($entity);
}
return $view;
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, Route $route) {
if (parent::applies($definition, $name, $route)) {
return !empty($definition['tempstore']) && $definition['type'] === 'entity:view';
}
return FALSE;
}
}

View File

@@ -0,0 +1,88 @@
<?php
// phpcs:ignoreFile
/**
* This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\views_ui\ParamConverter\ViewUIConverter' "core/modules/views_ui/src".
*/
namespace Drupal\views_ui\ProxyClass\ParamConverter {
/**
* Provides a proxy class for \Drupal\views_ui\ParamConverter\ViewUIConverter.
*
* @see \Drupal\Component\ProxyBuilder
*/
class ViewUIConverter implements \Drupal\Core\ParamConverter\ParamConverterInterface
{
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* The id of the original proxied service.
*
* @var string
*/
protected $drupalProxyOriginalServiceId;
/**
* The real proxied service, after it was lazy loaded.
*
* @var \Drupal\views_ui\ParamConverter\ViewUIConverter
*/
protected $service;
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Constructs a ProxyClass Drupal proxy object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The container.
* @param string $drupal_proxy_original_service_id
* The service ID of the original service.
*/
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
{
$this->container = $container;
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
}
/**
* Lazy loads the real service from the container.
*
* @return object
* Returns the constructed real service.
*/
protected function lazyLoadItself()
{
if (!isset($this->service)) {
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
}
return $this->service;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults)
{
return $this->lazyLoadItself()->convert($value, $definition, $name, $defaults);
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, \Symfony\Component\Routing\Route $route)
{
return $this->lazyLoadItself()->applies($definition, $name, $route);
}
}
}

View File

@@ -0,0 +1,228 @@
<?php
namespace Drupal\views_ui;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
use Drupal\views\Plugin\views\wizard\WizardException;
use Drupal\views\Plugin\ViewsPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Form controller for the Views add form.
*
* @internal
*/
class ViewAddForm extends ViewFormBase {
/**
* The wizard plugin manager.
*
* @var \Drupal\views\Plugin\ViewsPluginManager
*/
protected $wizardManager;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a new ViewAddForm object.
*
* @param \Drupal\views\Plugin\ViewsPluginManager $wizard_manager
* The wizard plugin manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
*/
public function __construct(ViewsPluginManager $wizard_manager, ModuleHandlerInterface $module_handler) {
$this->wizardManager = $wizard_manager;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.views.wizard'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
protected function prepareEntity() {
// Do not prepare the entity while it is being added.
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form['#attached']['library'][] = 'views_ui/views_ui.admin';
$form['#attributes']['class'] = ['views-admin'];
$form['name'] = [
'#type' => 'fieldset',
'#title' => $this->t('View basic information'),
'#attributes' => ['class' => ['fieldset-no-legend']],
];
$form['name']['label'] = [
'#type' => 'textfield',
'#title' => $this->t('View name'),
'#required' => TRUE,
'#size' => 32,
'#default_value' => '',
'#maxlength' => 255,
];
$form['name']['id'] = [
'#type' => 'machine_name',
'#maxlength' => 128,
'#machine_name' => [
'exists' => '\Drupal\views\Views::getView',
'source' => ['name', 'label'],
],
'#description' => $this->t('A unique machine-readable name for this View. It must only contain lowercase letters, numbers, and underscores.'),
];
$form['name']['description_enable'] = [
'#type' => 'checkbox',
'#title' => $this->t('Description'),
];
$form['name']['description'] = [
'#type' => 'textfield',
'#title' => $this->t('Provide description'),
'#title_display' => 'invisible',
'#size' => 64,
'#default_value' => '',
'#states' => [
'visible' => [
':input[name="description_enable"]' => ['checked' => TRUE],
],
],
];
// Create a wrapper for the entire dynamic portion of the form. Everything
// that can be updated by AJAX goes somewhere inside here. For example, this
// is needed by "Show" dropdown (below); it changes the base table of the
// view and therefore potentially requires all options on the form to be
// dynamically updated.
$form['displays'] = [];
// Create the part of the form that allows the user to select the basic
// properties of what the view will display.
$form['displays']['show'] = [
'#type' => 'fieldset',
'#title' => $this->t('View settings'),
'#tree' => TRUE,
'#attributes' => ['class' => ['container-inline']],
];
// Create the "Show" dropdown, which allows the base table of the view to be
// selected.
$wizard_plugins = $this->wizardManager->getDefinitions();
$options = [];
foreach ($wizard_plugins as $key => $wizard) {
$options[$key] = $wizard['title'];
}
$form['displays']['show']['wizard_key'] = [
'#type' => 'select',
'#title' => $this->t('Show'),
'#options' => $options,
'#sort_options' => TRUE,
];
$show_form = &$form['displays']['show'];
$default_value = $this->moduleHandler->moduleExists('node') ? 'node' : 'users';
$show_form['wizard_key']['#default_value'] = WizardPluginBase::getSelected($form_state, ['show', 'wizard_key'], $default_value, $show_form['wizard_key']);
// Changing this dropdown updates the entire content of $form['displays'] via
// AJAX.
views_ui_add_ajax_trigger($show_form, 'wizard_key', ['displays']);
// Build the rest of the form based on the currently selected wizard plugin.
$wizard_key = $show_form['wizard_key']['#default_value'];
$wizard_instance = $this->wizardManager->createInstance($wizard_key);
$form = $wizard_instance->buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$actions['submit']['#value'] = $this->t('Save and edit');
// Remove EntityFormController::save() form the submission handlers.
$actions['submit']['#submit'] = [[$this, 'submitForm']];
$actions['cancel'] = [
'#type' => 'submit',
'#value' => $this->t('Cancel'),
'#submit' => ['::cancel'],
'#limit_validation_errors' => [],
];
return $actions;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$wizard_type = $form_state->getValue(['show', 'wizard_key']);
$wizard_instance = $this->wizardManager->createInstance($wizard_type);
$form_state->set('wizard', $wizard_instance->getPluginDefinition());
$form_state->set('wizard_instance', $wizard_instance);
$path = &$form_state->getValue(['page', 'path']);
if (!empty($path)) {
// @todo https://www.drupal.org/node/2423913 Views should expect and store
// a leading /.
$path = ltrim($path, '/ ');
}
$errors = $wizard_instance->validateView($form, $form_state);
foreach ($errors as $display_errors) {
foreach ($display_errors as $name => $message) {
$form_state->setErrorByName($name, $message);
}
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
try {
/** @var \Drupal\views\Plugin\views\wizard\WizardInterface $wizard */
$wizard = $form_state->get('wizard_instance');
$this->entity = $wizard->createView($form, $form_state);
}
// @todo Figure out whether it really makes sense to throw and catch exceptions on the wizard.
catch (WizardException $e) {
$this->messenger()->addError($e->getMessage());
$form_state->setRedirect('entity.view.collection');
return;
}
$this->entity->save();
$this->messenger()->addStatus($this->t('The view %name has been saved.', ['%name' => $form_state->getValue('label')]));
$form_state->setRedirectUrl($this->entity->toUrl('edit-form'));
}
/**
* Form submission handler for the 'cancel' action.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function cancel(array $form, FormStateInterface $form_state) {
$form_state->setRedirect('entity.view.collection');
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Drupal\views_ui;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for the Views duplicate form.
*
* @internal
*/
class ViewDuplicateForm extends ViewFormBase {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected LanguageManagerInterface $languageManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('module_handler'),
$container->get('language_manager')
);
}
/**
* Constructs a ViewDuplicateForm.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* Drupal's module handler.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(ModuleHandlerInterface $moduleHandler, LanguageManagerInterface $language_manager) {
$this->setModuleHandler($moduleHandler);
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
protected function prepareEntity() {
// Do not prepare the entity while it is being added.
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
parent::form($form, $form_state);
$form['#title'] = $this->t('Duplicate of @label', ['@label' => $this->entity->label()]);
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('View name'),
'#required' => TRUE,
'#size' => 32,
'#maxlength' => 255,
'#default_value' => $this->t('Duplicate of @label', ['@label' => $this->entity->label()]),
];
$form['id'] = [
'#type' => 'machine_name',
'#maxlength' => 128,
'#machine_name' => [
'exists' => '\Drupal\views\Views::getView',
'source' => ['label'],
],
'#default_value' => '',
'#description' => $this->t('A unique machine-readable name for this View. It must only contain lowercase letters, numbers, and underscores.'),
];
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Duplicate'),
];
return $actions;
}
/**
* Form submission handler for the 'clone' action.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* A reference to a keyed array containing the current state of the form.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// The original ID gets set to NULL when duplicating, so we need to store it
// here.
$original_id = $this->entity->id();
$this->entity = $this->entity->createDuplicate();
$this->entity->set('label', $form_state->getValue('label'));
$this->entity->set('id', $form_state->getValue('id'));
$this->entity->save();
$this->copyTranslations($original_id);
// Redirect the user to the view admin form.
$form_state->setRedirectUrl($this->entity->toUrl('edit-form'));
}
/**
* Copies all translations that existed on the original View.
*
* @param string $original_id
* The original View ID.
*/
private function copyTranslations(string $original_id): void {
if (!$this->moduleHandler->moduleExists('config_translation')) {
return;
}
$current_langcode = $this->languageManager->getConfigOverrideLanguage()
->getId();
$languages = $this->languageManager->getLanguages();
$original_name = 'views.view.' . $original_id;
$duplicate_name = 'views.view.' . $this->entity->id();
foreach ($languages as $language) {
$langcode = $language->getId();
if ($langcode !== $current_langcode) {
$original_translation = $this->languageManager->getLanguageConfigOverride($langcode, $original_name)
->get();
if ($original_translation) {
$duplicate_translation = $this->languageManager->getLanguageConfigOverride($langcode, $duplicate_name);
$duplicate_translation->setData($original_translation);
$duplicate_translation->save();
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,174 @@
<?php
namespace Drupal\views_ui;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Base form for Views forms.
*/
abstract class ViewFormBase extends EntityForm {
/**
* The name of the display used by the form.
*
* @var string
*/
protected $displayID;
/**
* {@inheritdoc}
*/
public function init(FormStateInterface $form_state) {
parent::init($form_state);
// @todo Remove the need for this.
$form_state->loadInclude('views_ui', 'inc', 'admin');
$form_state->set('view', $this->entity);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $display_id = NULL) {
if (isset($display_id) && $form_state->has('display_id') && ($display_id !== $form_state->get('display_id'))) {
throw new \InvalidArgumentException('Mismatch between $form_state->get(\'display_id\') and $display_id.');
}
$this->displayID = $form_state->has('display_id') ? $form_state->get('display_id') : $display_id;
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
protected function prepareEntity() {
// Determine the displays available for editing.
if ($tabs = $this->getDisplayTabs($this->entity)) {
if (empty($this->displayID)) {
// If a display isn't specified, use the first one after sorting by
// #weight.
uasort($tabs, 'Drupal\Component\Utility\SortArray::sortByWeightProperty');
foreach ($tabs as $id => $tab) {
if (!isset($tab['#access']) || $tab['#access']) {
$this->displayID = $id;
break;
}
}
}
// If a display is specified, but we don't have access to it, return
// an access denied page.
if ($this->displayID && !isset($tabs[$this->displayID])) {
throw new NotFoundHttpException();
}
elseif ($this->displayID && (isset($tabs[$this->displayID]['#access']) && !$tabs[$this->displayID]['#access'])) {
throw new AccessDeniedHttpException();
}
}
elseif ($this->displayID) {
throw new NotFoundHttpException();
}
}
/**
* Adds tabs for navigating across Displays when editing a View.
*
* This function can be called from hook_menu_local_tasks_alter() to implement
* these tabs as secondary local tasks, or it can be called from elsewhere if
* having them as secondary local tasks isn't desired. The caller is responsible
* for setting the active tab's #active property to TRUE.
*
* @param \Drupal\views_ui\ViewUI $view
* The ViewUI entity.
*
* @return array
* An array of tab definitions.
*/
public function getDisplayTabs(ViewUI $view) {
$executable = $view->getExecutable();
$executable->initDisplay();
$display_id = $this->displayID;
$tabs = [];
// Create a tab for each display.
foreach ($view->get('display') as $id => $display) {
// Get an instance of the display plugin, to make sure it will work in the
// UI.
$display_plugin = $executable->displayHandlers->get($id);
if (empty($display_plugin)) {
continue;
}
$tabs[$id] = [
'#theme' => 'menu_local_task',
'#weight' => $display['position'],
'#link' => [
'title' => $this->getDisplayLabel($view, $id),
'localized_options' => [],
'url' => $view->toUrl('edit-display-form')->setRouteParameter('display_id', $id),
],
];
if (!empty($display['deleted'])) {
$tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-deleted-link';
}
if (isset($display['display_options']['enabled']) && !$display['display_options']['enabled']) {
$tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-disabled-link';
}
}
// If the default display isn't supposed to be shown, don't display its tab, unless it's the only display.
if ((!$this->isDefaultDisplayShown($view) && $display_id != 'default') && count($tabs) > 1) {
$tabs['default']['#access'] = FALSE;
}
// Mark the display tab as red to show validation errors.
$errors = $executable->validate();
foreach ($view->get('display') as $id => $display) {
if (!empty($errors[$id])) {
// Always show the tab.
$tabs[$id]['#access'] = TRUE;
// Add a class to mark the error and a title to make a hover tip.
$tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'error';
$tabs[$id]['#link']['localized_options']['attributes']['title'] = $this->t('This display has one or more validation errors.');
}
}
return $tabs;
}
/**
* Controls whether or not the default display should have its own tab on edit.
*/
public function isDefaultDisplayShown(ViewUI $view) {
// Always show the default display for advanced users who prefer that mode.
$advanced_mode = \Drupal::config('views.settings')->get('ui.show.default_display');
// For other users, show the default display only if there are no others, and
// hide it if there's at least one "real" display.
$additional_displays = (count($view->getExecutable()->displayHandlers) == 1);
return $advanced_mode || $additional_displays;
}
/**
* Placeholder function for overriding $display['display_title'].
*
* @todo Remove this function once editing the display title is possible.
*/
public function getDisplayLabel(ViewUI $view, $display_id, $check_changed = TRUE) {
$display = $view->get('display');
$title = $display_id == 'default' ? $this->t('Default') : $display[$display_id]['display_title'];
$title = Unicode::truncate($title, 25, FALSE, TRUE);
if ($check_changed && !empty($view->changed_display[$display_id])) {
$changed = '*';
$title = $title . $changed;
}
return $title;
}
}

View File

@@ -0,0 +1,291 @@
<?php
namespace Drupal\views_ui;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
/**
* Defines a class to build a listing of view entities.
*
* @see \Drupal\views\Entity\View
*/
class ViewListBuilder extends ConfigEntityListBuilder {
/**
* The views display plugin manager to use.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $displayManager;
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity_type.manager')->getStorage($entity_type->id()),
$container->get('plugin.manager.views.display')
);
}
/**
* Constructs a new ViewListBuilder object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage class.
* @param \Drupal\Component\Plugin\PluginManagerInterface $display_manager
* The views display plugin manager to use.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, PluginManagerInterface $display_manager) {
parent::__construct($entity_type, $storage);
$this->displayManager = $display_manager;
// This list builder uses client-side filters which requires all entities to
// be listed, disable the pager.
// @todo https://www.drupal.org/node/2536826 change the filtering to support
// a pager.
$this->limit = FALSE;
}
/**
* {@inheritdoc}
*/
public function load() {
$entities = [
'enabled' => [],
'disabled' => [],
];
foreach (parent::load() as $entity) {
if ($entity->status()) {
$entities['enabled'][] = $entity;
}
else {
$entities['disabled'][] = $entity;
}
}
return $entities;
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $view) {
$row = parent::buildRow($view);
return [
'data' => [
'view_name' => [
'data' => [
'#plain_text' => $view->label(),
],
],
'machine_name' => [
'data' => [
'#plain_text' => $view->id(),
],
],
'description' => [
'data' => [
'#plain_text' => $view->get('description'),
],
],
'displays' => [
'data' => [
'#theme' => 'views_ui_view_displays_list',
'#displays' => $this->getDisplaysList($view),
],
],
'operations' => $row['operations'],
],
'#attributes' => [
'class' => [$view->status() ? 'views-ui-list-enabled' : 'views-ui-list-disabled'],
],
];
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
return [
'view_name' => [
'data' => $this->t('View name'),
'#attributes' => [
'class' => ['views-ui-name'],
],
],
'machine_name' => [
'data' => $this->t('Machine name'),
'#attributes' => [
'class' => ['views-ui-machine-name'],
],
],
'description' => [
'data' => $this->t('Description'),
'#attributes' => [
'class' => ['views-ui-description'],
],
],
'displays' => [
'data' => $this->t('Displays'),
'#attributes' => [
'class' => ['views-ui-displays'],
],
],
'operations' => [
'data' => $this->t('Operations'),
'#attributes' => [
'class' => ['views-ui-operations'],
],
],
];
}
/**
* {@inheritdoc}
*/
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
// Remove destination redirect for Edit operation.
$operations['edit']['url'] = $entity->toUrl('edit-form');
if ($entity->hasLinkTemplate('duplicate-form')) {
$operations['duplicate'] = [
'title' => $this->t('Duplicate'),
'weight' => 15,
'url' => $entity->toUrl('duplicate-form'),
];
}
// Add AJAX functionality to enable/disable operations.
foreach (['enable', 'disable'] as $op) {
if (isset($operations[$op])) {
$operations[$op]['url'] = $entity->toUrl($op);
// Enable and disable operations should use AJAX.
$operations[$op]['attributes']['class'][] = 'use-ajax';
}
}
// ajax.js focuses automatically on the data-drupal-selector element. When
// enabling the view again, focusing on the disable link doesn't work, as it
// is hidden. We assign data-drupal-selector to every link, so it focuses
// on the edit link.
foreach ($operations as &$operation) {
$operation['attributes']['data-drupal-selector'] = 'views-listing-' . $entity->id();
}
return $operations;
}
/**
* {@inheritdoc}
*/
public function render() {
$entities = $this->load();
$list['#type'] = 'container';
$list['#attributes']['id'] = 'views-entity-list';
$list['#attached']['library'][] = 'core/drupal.ajax';
$list['#attached']['library'][] = 'views_ui/views_ui.listing';
$list['filters'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['table-filter', 'js-show'],
],
];
$list['filters']['text'] = [
'#type' => 'search',
'#title' => $this->t('Filter'),
'#title_display' => 'invisible',
'#size' => 60,
'#placeholder' => $this->t('Filter by view name, machine name, description, or display path'),
'#attributes' => [
'class' => ['views-filter-text'],
'data-table' => '.views-listing-table',
'autocomplete' => 'off',
'title' => $this->t('Enter a part of the view name, machine name, description, or display path to filter by.'),
],
];
$list['enabled']['heading']['#markup'] = '<h2>' . $this->t('Enabled', [], ['context' => 'Plural']) . '</h2>';
$list['disabled']['heading']['#markup'] = '<h2>' . $this->t('Disabled', [], ['context' => 'Plural']) . '</h2>';
foreach (['enabled', 'disabled'] as $status) {
$list[$status]['#type'] = 'container';
$list[$status]['#attributes'] = ['class' => ['views-list-section', $status]];
$list[$status]['table'] = [
'#theme' => 'views_ui_views_listing_table',
'#headers' => $this->buildHeader(),
'#attributes' => ['class' => ['views-listing-table', $status]],
];
foreach ($entities[$status] as $entity) {
$list[$status]['table']['#rows'][$entity->id()] = $this->buildRow($entity);
}
}
$list['enabled']['table']['#empty'] = $this->t('There are no enabled views.');
$list['disabled']['table']['#empty'] = $this->t('There are no disabled views.');
return $list;
}
/**
* Gets a list of displays included in the view.
*
* @param \Drupal\Core\Entity\EntityInterface $view
* The view entity instance to get a list of displays for.
*
* @return array
* An array of display types that this view includes.
*/
protected function getDisplaysList(EntityInterface $view) {
$displays = [];
$executable = $view->getExecutable();
$executable->initDisplay();
foreach ($executable->displayHandlers as $display) {
$rendered_path = FALSE;
$definition = $display->getPluginDefinition();
if (!empty($definition['admin'])) {
if ($display->hasPath()) {
$path = $display->getPath();
if ($view->status() && !str_contains($path, '%')) {
// Wrap this in a try/catch as trying to generate links to some
// routes may throw an exception, for example if they do not
// respond to HTML, such as RESTExports.
try {
// @todo Views should expect and store a leading /. See:
// https://www.drupal.org/node/2423913
$rendered_path = Link::fromTextAndUrl('/' . $path, Url::fromUserInput('/' . $path))->toString();
}
catch (BadRequestException | NotAcceptableHttpException $e) {
$rendered_path = '/' . $path;
}
}
else {
$rendered_path = '/' . $path;
}
}
$displays[] = [
'display' => $definition['admin'],
'path' => $rendered_path,
];
}
}
sort($displays);
return $displays;
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Drupal\views_ui;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\WorkspaceSafeFormInterface;
use Drupal\Core\Url;
/**
* Form controller for the Views preview form.
*
* @internal
*/
class ViewPreviewForm extends ViewFormBase implements WorkspaceSafeFormInterface {
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$view = $this->entity;
$form['#prefix'] = '<div id="views-preview-wrapper" class="views-preview-wrapper views-admin clearfix">';
$form['#suffix'] = '</div>';
$form['#id'] = 'views-ui-preview-form';
$form_state->disableCache();
$form['controls']['#attributes'] = ['class' => ['clearfix']];
$form['controls']['title'] = [
'#prefix' => '<h2 class="view-preview-form__title">',
'#markup' => $this->t('Preview'),
'#suffix' => '</h2>',
];
// Add a checkbox controlling whether or not this display auto-previews.
$form['controls']['live_preview'] = [
'#type' => 'checkbox',
'#id' => 'edit-displays-live-preview',
'#title' => $this->t('Auto preview'),
'#default_value' => \Drupal::config('views.settings')->get('ui.always_live_preview'),
];
// Add the arguments textfield.
$form['controls']['view_args'] = [
'#type' => 'textfield',
'#title' => $this->t('Preview with contextual filters:'),
'#description' => $this->t('Separate contextual filter values with a "/". For example, %example.', ['%example' => '40/12/10']),
'#id' => 'preview-args',
];
$args = [];
if ($form_state->getValue('view_args', '') !== '') {
$args = explode('/', $form_state->getValue('view_args'));
}
$user_input = $form_state->getUserInput();
if ($form_state->get('show_preview') || !empty($user_input['js'])) {
$form['preview'] = [
'#weight' => 110,
'#theme_wrappers' => ['container'],
'#attributes' => ['id' => 'views-live-preview', 'class' => ['views-live-preview']],
'preview' => $view->renderPreview($this->displayID, $args),
];
}
$uri = $view->toUrl('preview-form');
$uri->setRouteParameter('display_id', $this->displayID);
$form['#action'] = $uri->toString();
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$view = $this->entity;
return [
'#attributes' => [
'id' => 'preview-submit-wrapper',
'class' => ['preview-submit-wrapper'],
],
'button' => [
'#type' => 'submit',
'#value' => $this->t('Update preview'),
'#attributes' => ['class' => ['arguments-preview']],
'#submit' => ['::submitPreview'],
'#id' => 'preview-submit',
'#ajax' => [
'url' => Url::fromRoute('entity.view.preview_form', ['view' => $view->id(), 'display_id' => $this->displayID]),
'wrapper' => 'views-preview-wrapper',
'event' => 'click',
'progress' => ['type' => 'fullscreen'],
'method' => 'replaceWith',
'disable-refocus' => TRUE,
],
],
];
}
/**
* Form submission handler for the Preview button.
*/
public function submitPreview($form, FormStateInterface $form_state) {
$form_state->set('show_preview', TRUE);
$form_state->setRebuild();
}
}

File diff suppressed because it is too large Load Diff