ViewsUiThemeHooks.php
Namespace
Drupal\views_ui\HookFile
-
core/
modules/ views_ui/ src/ Hook/ ViewsUiThemeHooks.php
View source
<?php
namespace Drupal\views_ui\Hook;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\Checkboxes;
use Drupal\Core\Render\Element\Radios;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\views\ViewExecutable;
/**
* Hook implementations for views_ui.
*/
class ViewsUiThemeHooks {
use StringTranslationTrait;
public function __construct(protected ConfigFactoryInterface $configFactory, protected ModuleHandlerInterface $moduleHandler) {
}
/**
* Implements hook_theme().
*/
public function theme() : array {
return [
// Edit a view.
'views_ui_display_tab_setting' => [
'variables' => [
'description' => '',
'link' => '',
'settings_links' => [],
'overridden' => FALSE,
'defaulted' => FALSE,
'description_separator' => TRUE,
'class' => [],
],
'initial preprocess' => static::class . ':preprocessDisplayTabSetting',
],
'views_ui_display_tab_bucket' => [
'render element' => 'element',
'initial preprocess' => static::class . ':preprocessDisplayTabBucket',
],
'views_ui_rearrange_filter_form' => [
'render element' => 'form',
'initial preprocess' => static::class . ':preprocessRearrangeFilterForm',
],
'views_ui_expose_filter_form' => [
'render element' => 'form',
],
// Legacy theme hook for displaying views info.
'views_ui_view_info' => [
'variables' => [
'view' => NULL,
'displays' => NULL,
],
],
// List views.
'views_ui_views_listing_table' => [
'variables' => [
'headers' => NULL,
'rows' => NULL,
'attributes' => [],
],
'initial preprocess' => static::class . ':preprocessViewsListingTable',
],
'views_ui_view_displays_list' => [
'variables' => [
'displays' => [],
],
],
// Group of filters.
'views_ui_build_group_filter_form' => [
'render element' => 'form',
'initial preprocess' => static::class . ':preprocessBuildGroupFilterForm',
],
// On behalf of a plugin.
'views_ui_style_plugin_table' => [
'render element' => 'form',
'initial preprocess' => static::class . ':preprocessStylePluginTable',
],
// When previewing a view.
'views_ui_view_preview_section' => [
'variables' => [
'view' => NULL,
'section' => NULL,
'content' => NULL,
'links' => '',
],
'initial preprocess' => static::class . ':preprocessViewPreviewSection',
],
// Generic container wrapper, to use instead of theme_container when an id
// is not desired.
'views_ui_container' => [
'variables' => [
'children' => NULL,
'attributes' => [],
],
],
];
}
/**
* Prepares variables for Views UI display tab setting templates.
*
* Default template: views-ui-display-tab-setting.html.twig.
*
* @param array $variables
* An associative array containing:
* - link: The setting's primary link.
* - settings_links: An array of links for this setting.
* - defaulted: A boolean indicating the setting is in its default state.
* - overridden: A boolean indicating the setting has been overridden from
* the default.
* - description: The setting's description.
* - description_separator: A boolean indicating a separator colon should be
* appended to the setting's description.
*/
public function preprocessDisplayTabSetting(array &$variables) : void {
// Put the primary link to the left side.
array_unshift($variables['settings_links'], $variables['link']);
if (!empty($variables['overridden'])) {
$variables['attributes']['title'][] = $this->t('Overridden');
}
// Append a colon to the description, if requested.
if ($variables['description'] && $variables['description_separator']) {
$variables['description'] .= $this->t(':');
}
}
/**
* Prepares variables for Views UI view listing templates.
*
* Default template: views-ui-view-listing-table.html.twig.
*
* @param array $variables
* An associative array containing:
* - headers: An associative array containing the headers for the view
* listing table.
* - rows: An associative array containing the rows data for the view
* listing table.
*/
public function preprocessViewsListingTable(array &$variables) : void {
// Convert the attributes to valid attribute objects.
foreach ($variables['headers'] as $key => $header) {
$variables['headers'][$key]['attributes'] = new Attribute($header['#attributes']);
}
if (!empty($variables['rows'])) {
foreach ($variables['rows'] as $key => $row) {
$variables['rows'][$key]['attributes'] = new Attribute($row['#attributes']);
}
}
}
/**
* Prepares variables for Views UI display tab bucket templates.
*
* Default template: views-ui-display-tab-bucket.html.twig.
*
* @param array $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #name, #overridden, #children, #title, #actions.
*/
public function preprocessDisplayTabBucket(array &$variables) : void {
$element = $variables['element'];
if (!empty($element['#overridden'])) {
$variables['attributes']['title'][] = $this->t('Overridden');
}
$variables['name'] = $element['#name'] ?? NULL;
$variables['overridden'] = $element['#overridden'] ?? NULL;
$variables['content'] = $element['#children'];
$variables['title'] = $element['#title'];
$variables['actions'] = !empty($element['#actions']) ? $element['#actions'] : [];
}
/**
* Prepares variables for Views UI build group filter form templates.
*
* Default template: views-ui-build-group-filter-form.html.twig.
*
* @param array $variables
* An associative array containing:
* - form: A render element representing the form.
*/
public function preprocessBuildGroupFilterForm(array &$variables) : void {
$form = $variables['form'];
// Prepare table of options.
$header = [
$this->t('Default'),
$this->t('Weight'),
$this->t('Label'),
$this->t('Operator'),
$this->t('Value'),
$this->t('Operations'),
];
// Prepare default selectors.
$form_state = new FormState();
$form['default_group'] = Radios::processRadios($form['default_group'], $form_state, $form);
$form['default_group_multiple'] = Checkboxes::processCheckboxes($form['default_group_multiple'], $form_state, $form);
$form['default_group']['All']['#title'] = '';
$rows[] = [
[
'data' => $form['default_group']['All'],
],
'',
[
'data' => $this->configFactory
->get('views.settings')
->get('ui.exposed_filter_any_label') == 'old_any' ? $this->t('<Any>') : $this->t('- Any -'),
'colspan' => 4,
'class' => [
'class' => 'any-default-radios-row',
],
],
];
// Remove the 'All' default_group form element because it's added to the
// table row.
unset($variables['form']['default_group']['All']);
foreach (Element::children($form['group_items']) as $group_id) {
$form['group_items'][$group_id]['value']['#title'] = '';
$default = [
$form['default_group'][$group_id],
$form['default_group_multiple'][$group_id],
];
// Remove these fields from the form since they are moved into the table.
unset($variables['form']['default_group'][$group_id]);
unset($variables['form']['default_group_multiple'][$group_id]);
$link = [
'#type' => 'link',
'#url' => Url::fromRoute('<none>', [], [
'attributes' => [
'id' => 'views-remove-link-' . $group_id,
'class' => [
'views-hidden',
'views-button-remove',
'views-groups-remove-link',
'views-remove-link',
],
'alt' => $this->t('Remove this item'),
'title' => $this->t('Remove this item'),
],
]),
'#title' => new FormattableMarkup('<span>@text</span>', [
'@text' => $this->t('Remove'),
]),
];
$remove = [
$form['group_items'][$group_id]['remove'],
$link,
];
$data = [
'default' => [
'data' => $default,
],
'weight' => [
'data' => $form['group_items'][$group_id]['weight'],
],
'title' => [
'data' => $form['group_items'][$group_id]['title'],
],
'operator' => [
'data' => $form['group_items'][$group_id]['operator'],
],
'value' => [
'data' => $form['group_items'][$group_id]['value'],
],
'remove' => [
'data' => $remove,
],
];
$rows[] = [
'data' => $data,
'id' => 'views-row-' . $group_id,
'class' => [
'draggable',
],
];
}
$variables['table'] = [
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#attributes' => [
'class' => [
'views-filter-groups',
],
'id' => 'views-filter-groups',
],
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'weight',
],
],
];
// Hide fields used in table.
unset($variables['form']['group_items']);
}
/**
* Prepares variables for Views UI rearrange filter form templates.
*
* Default template: views-ui-rearrange-filter-form.html.twig.
*
* @param array $variables
* An associative array containing:
* - form: A render element representing the form.
*/
public function preprocessRearrangeFilterForm(array &$variables) : void {
$form =& $variables['form'];
$rows = $ungroupable_rows = [];
// Enable grouping only if > 1 group.
$variables['grouping'] = count(array_keys($form['#group_options'])) > 1;
foreach ($form['#group_renders'] as $group_id => $contents) {
// Header row for the group.
if ($group_id !== 'ungroupable') {
// Set up tabledrag so that it changes the group dropdown when rows are
// dragged between groups.
$options = [
'table_id' => 'views-rearrange-filters',
'action' => 'match',
'relationship' => 'sibling',
'group' => 'views-group-select',
'subgroup' => 'views-group-select-' . $group_id,
];
drupal_attach_tabledrag($form['override'], $options);
// Title row, spanning all columns.
$row = [];
// Add a cell to the first row, containing the group operator.
$row[] = [
'class' => [
'group',
'group-operator',
'container-inline',
],
'data' => $form['filter_groups']['groups'][$group_id],
'rowspan' => max([
2,
count($contents) + 1,
]),
];
// Title.
$row[] = [
'class' => [
'group',
'group-title',
],
'data' => [
'#prefix' => '<span>',
'#markup' => $form['#group_options'][$group_id],
'#suffix' => '</span>',
],
'colspan' => 4,
];
$rows[] = [
'class' => [
'views-group-title',
],
'data' => $row,
'id' => 'views-group-title-' . $group_id,
];
// Row which will only appear if the group has nothing in it.
$row = [];
$class = 'group-' . (count($contents) ? 'populated' : 'empty');
$instructions = '<span>' . $this->t('No filters have been added.') . '</span> <span class="js-only">' . $this->t('Drag to add filters.') . '</span>';
// When JavaScript is enabled, the button for removing the group (if
// it's present) should be hidden, since it will be replaced by a link
// on the client side.
if (!empty($form['remove_groups'][$group_id]['#type']) && $form['remove_groups'][$group_id]['#type'] == 'submit') {
$form['remove_groups'][$group_id]['#attributes']['class'][] = 'js-hide';
}
$row[] = [
'colspan' => 5,
'data' => [
[
'#markup' => $instructions,
],
$form['remove_groups'][$group_id],
],
];
$rows[] = [
'class' => [
'group-message',
'group-' . $group_id . '-message',
$class,
],
'data' => $row,
'id' => 'views-group-' . $group_id,
];
}
foreach ($contents as $id) {
if (isset($form['filters'][$id]['name'])) {
$row = [];
$row[]['data'] = $form['filters'][$id]['name'];
$form['filters'][$id]['weight']['#attributes']['class'] = [
'weight',
];
$row[]['data'] = $form['filters'][$id]['weight'];
$form['filters'][$id]['group']['#attributes']['class'] = [
'views-group-select views-group-select-' . $group_id,
];
$row[]['data'] = $form['filters'][$id]['group'];
$form['filters'][$id]['removed']['#attributes']['class'][] = 'js-hide';
$remove_link = [
'#type' => 'link',
'#url' => Url::fromRoute('<none>'),
'#title' => new FormattableMarkup('<span>@text</span>', [
'@text' => $this->t('Remove'),
]),
'#weight' => '1',
'#options' => [
'attributes' => [
'id' => 'views-remove-link-' . $id,
'class' => [
'views-hidden',
'views-button-remove',
'views-groups-remove-link',
'views-remove-link',
],
'alt' => $this->t('Remove this item'),
'title' => $this->t('Remove this item'),
],
],
];
$row[]['data'] = [
$form['filters'][$id]['removed'],
$remove_link,
];
$row = [
'data' => $row,
'class' => [
'draggable',
],
'id' => 'views-row-' . $id,
];
if ($group_id !== 'ungroupable') {
$rows[] = $row;
}
else {
$ungroupable_rows[] = $row;
}
}
}
}
if (!$variables['grouping']) {
$form['filter_groups']['groups'][0]['#title'] = $this->t('Operator');
}
if (!empty($ungroupable_rows)) {
$header = [
$this->t('Ungroupable filters'),
$this->t('Weight'),
[
'data' => $this->t('Group'),
'class' => [
'views-hide-label',
],
],
[
'data' => $this->t('Remove'),
'class' => [
'views-hide-label',
],
],
];
$variables['ungroupable_table'] = [
'#type' => 'table',
'#header' => $header,
'#rows' => $ungroupable_rows,
'#attributes' => [
'id' => 'views-rearrange-filters-ungroupable',
'class' => [
'arrange',
],
],
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'weight',
],
],
];
}
if (empty($rows)) {
$rows[] = [
[
'data' => $this->t('No fields available.'),
'colspan' => '2',
],
];
}
// Set up tabledrag so that the weights are changed when rows are dragged.
$variables['table'] = [
'#type' => 'table',
'#rows' => $rows,
'#attributes' => [
'id' => 'views-rearrange-filters',
'class' => [
'arrange',
],
],
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'weight',
],
],
];
// When JavaScript is enabled, the button for adding a new group should be
// hidden, since it will be replaced by a link on the client side.
$form['actions']['add_group']['#attributes']['class'][] = 'js-hide';
}
/**
* Prepares variables for style plugin table templates.
*
* Default template: views-ui-style-plugin-table.html.twig.
*
* @param array $variables
* An associative array containing:
* - form: A render element representing the form.
*/
public function preprocessStylePluginTable(array &$variables) : void {
$form = $variables['form'];
$header = [
$this->t('Field'),
$this->t('Column'),
$this->t('Align'),
$this->t('Separator'),
[
'data' => $this->t('Sortable'),
'align' => 'center',
],
[
'data' => $this->t('Default order'),
'align' => 'center',
],
[
'data' => $this->t('Default sort'),
'align' => 'center',
],
[
'data' => $this->t('Hide empty column'),
'align' => 'center',
],
[
'data' => $this->t('Responsive'),
'align' => 'center',
],
];
$rows = [];
foreach (Element::children($form['columns']) as $id) {
$row = [];
$row[]['data'] = $form['info'][$id]['name'];
$row[]['data'] = $form['columns'][$id];
$row[]['data'] = $form['info'][$id]['align'];
$row[]['data'] = $form['info'][$id]['separator'];
if (!empty($form['info'][$id]['sortable'])) {
$row[] = [
'data' => $form['info'][$id]['sortable'],
'align' => 'center',
];
$row[] = [
'data' => $form['info'][$id]['default_sort_order'],
'align' => 'center',
];
$row[] = [
'data' => $form['default'][$id],
'align' => 'center',
];
}
else {
$row[] = '';
$row[] = '';
$row[] = '';
}
$row[] = [
'data' => $form['info'][$id]['empty_column'],
'align' => 'center',
];
$row[] = [
'data' => $form['info'][$id]['responsive'],
'align' => 'center',
];
$rows[] = $row;
}
// Add the special 'None' row.
$rows[] = [
[
'data' => $this->t('None'),
'colspan' => 6,
],
[
'align' => 'center',
'data' => $form['default'][-1],
],
[
'colspan' => 2,
],
];
// Unset elements from the form array that are used to build the table so
// that they are not rendered twice.
unset($form['default']);
unset($form['info']);
unset($form['columns']);
$variables['table'] = [
'#type' => 'table',
'#theme' => 'table__views_ui_style_plugin_table',
'#header' => $header,
'#rows' => $rows,
];
$variables['form'] = $form;
}
/**
* Prepares variables for views UI view preview section templates.
*
* Default template: views-ui-view-preview-section.html.twig.
*
* @param array $variables
* An associative array containing:
* - view: The view object.
* - section: The section name of a View (e.g. title, rows or pager).
*/
public function preprocessViewPreviewSection(array &$variables) : void {
switch ($variables['section']) {
case 'title':
$variables['title'] = $this->t('Title');
$links = $this->viewPreviewSectionDisplayCategoryLinks($variables['view'], 'title', $variables['title']);
break;
case 'header':
$variables['title'] = $this->t('Header');
$links = $this->viewPreviewSectionHandlerLinks($variables['view'], $variables['section']);
break;
case 'empty':
$variables['title'] = $this->t('No results behavior');
$links = $this->viewPreviewSectionHandlerLinks($variables['view'], $variables['section']);
break;
case 'exposed':
// @todo Sorts can be exposed too, so we may need a better title.
$variables['title'] = $this->t('Exposed Filters');
$links = $this->viewPreviewSectionDisplayCategoryLinks($variables['view'], 'exposed_form_options', $variables['title']);
break;
case 'rows':
// @todo The title needs to depend on what is being viewed.
$variables['title'] = $this->t('Content');
$links = $this->viewPreviewSectionRowsLinks($variables['view']);
break;
case 'pager':
$variables['title'] = $this->t('Pager');
$links = $this->viewPreviewSectionDisplayCategoryLinks($variables['view'], 'pager_options', $variables['title']);
break;
case 'more':
$variables['title'] = $this->t('More');
$links = $this->viewPreviewSectionDisplayCategoryLinks($variables['view'], 'use_more', $variables['title']);
break;
case 'footer':
$variables['title'] = $this->t('Footer');
$links = $this->viewPreviewSectionHandlerLinks($variables['view'], $variables['section']);
break;
case 'attachment_before':
// @todo Add links to the attachment configuration page.
$variables['title'] = $this->t('Attachment before');
break;
case 'attachment_after':
// @todo Add links to the attachment configuration page.
$variables['title'] = $this->t('Attachment after');
break;
}
if (isset($links)) {
$build = [
'#theme' => 'links__contextual',
'#links' => $links,
'#attributes' => [
'class' => [
'contextual-links',
],
],
'#attached' => [
'library' => [
'contextual/drupal.contextual-links',
],
],
];
$variables['links'] = $build;
}
}
/**
* Implements hook_preprocess_HOOK() for views templates.
*/
public function preprocessViewsView(&$variables) : void {
$view = $variables['view'];
// Render title for the admin preview.
if (!empty($view->live_preview)) {
$variables['title'] = [
'#markup' => $view->getTitle(),
'#allowed_tags' => Xss::getHtmlTagList(),
];
}
if (!empty($view->live_preview) && $this->moduleHandler
->moduleExists('contextual')) {
$view->setShowAdminLinks(FALSE);
foreach ([
'title',
'header',
'exposed',
'rows',
'pager',
'more',
'footer',
'empty',
'attachment_after',
'attachment_before',
] as $section) {
if (!empty($variables[$section])) {
$variables[$section] = [
'#theme' => 'views_ui_view_preview_section',
'#view' => $view,
'#section' => $section,
'#content' => $variables[$section],
'#theme_wrappers' => [
'views_ui_container',
],
'#attributes' => [
'class' => [
'contextual-region',
],
],
];
}
}
}
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
public function themeSuggestionsViewsUiViewPreviewSection(array $variables) : array {
return [
'views_ui_view_preview_section__' . $variables['section'],
];
}
/**
* Returns contextual links for each handler of a certain section.
*
* @todo Bring in relationships.
* @todo Refactor this function to use much stuff of
* views_ui_edit_form_get_bucket.
*
* @param \Drupal\views\ViewExecutable $view
* The view executable.
* @param string $type
* The section type.
* @param string|\Stringable|false $title
* Add a bolded title of this section.
*/
protected function viewPreviewSectionHandlerLinks(ViewExecutable $view, string $type, string|\Stringable|false $title = FALSE) : array {
$display = $view->display_handler->display;
$handlers = $view->display_handler
->getHandlers($type);
$links = [];
$types = ViewExecutable::getHandlerTypes();
if ($title) {
$links[$type . '-title'] = [
'title' => $types[$type]['title'],
];
}
foreach ($handlers as $id => $handler) {
$field_name = $handler->adminLabel(TRUE);
$links[$type . '-edit-' . $id] = [
'title' => $this->t('Edit @section', [
'@section' => $field_name,
]),
'url' => Url::fromRoute('views_ui.form_handler', [
'js' => 'nojs',
'view' => $view->storage
->id(),
'display_id' => $display['id'],
'type' => $type,
'id' => $id,
]),
'attributes' => [
'class' => [
'views-ajax-link',
],
],
];
}
$links[$type . '-add'] = [
'title' => $this->t('Add new'),
'url' => Url::fromRoute('views_ui.form_add_handler', [
'js' => 'nojs',
'view' => $view->storage
->id(),
'display_id' => $display['id'],
'type' => $type,
]),
'attributes' => [
'class' => [
'views-ajax-link',
],
],
];
return $links;
}
/**
* Returns a link to editing a certain display setting.
*/
protected function viewPreviewSectionDisplayCategoryLinks(ViewExecutable $view, $type, $title) : array {
$display = $view->display_handler->display;
$links = [
$type . '-edit' => [
'title' => $this->t('Edit @section', [
'@section' => $title,
]),
'url' => Url::fromRoute('views_ui.form_display', [
'js' => 'nojs',
'view' => $view->storage
->id(),
'display_id' => $display['id'],
'type' => $type,
]),
'attributes' => [
'class' => [
'views-ajax-link',
],
],
],
];
return $links;
}
/**
* Returns all contextual links for the main content part of the view.
*/
protected function viewPreviewSectionRowsLinks(ViewExecutable $view) : array {
$links = [];
$links = array_merge($links, $this->viewPreviewSectionHandlerLinks($view, 'filter', TRUE));
$links = array_merge($links, $this->viewPreviewSectionHandlerLinks($view, 'field', TRUE));
$links = array_merge($links, $this->viewPreviewSectionHandlerLinks($view, 'sort', TRUE));
$links = array_merge($links, $this->viewPreviewSectionHandlerLinks($view, 'argument', TRUE));
$links = array_merge($links, $this->viewPreviewSectionHandlerLinks($view, 'relationship', TRUE));
return $links;
}
}
Classes
| Title | Deprecated | Summary |
|---|---|---|
| ViewsUiThemeHooks | Hook implementations for views_ui. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.