diff options
Diffstat (limited to 'core/modules/views')
35 files changed, 365 insertions, 63 deletions
diff --git a/core/modules/views/config/schema/views.schema.yml b/core/modules/views/config/schema/views.schema.yml index 8ddd73931f2c..12187c6b9465 100644 --- a/core/modules/views/config/schema/views.schema.yml +++ b/core/modules/views/config/schema/views.schema.yml @@ -140,16 +140,29 @@ views.view.*: views_block: type: block_settings label: 'View block' + constraints: + FullyValidatable: ~ mapping: views_label: type: label label: 'Title' + requiredKey: false items_per_page: - type: string + type: integer label: 'Items per block' + constraints: + Range: + min: 1 + # Will only be respected if the associated View is configured to allow this to be overridden. + # @see \Drupal\views\Plugin\views\display\Block::blockForm() + requiredKey: false + # NULL to use the default defined by the view. + nullable: true block.settings.views_block:*: type: views_block + constraints: + FullyValidatable: ~ block.settings.views_exposed_filter_block:*: type: views_block diff --git a/core/modules/views/src/FieldViewsDataProvider.php b/core/modules/views/src/FieldViewsDataProvider.php index c0b9d50b2d9c..fad34ff460e2 100644 --- a/core/modules/views/src/FieldViewsDataProvider.php +++ b/core/modules/views/src/FieldViewsDataProvider.php @@ -139,8 +139,8 @@ class FieldViewsDataProvider { if (!empty($translatable_configs) && empty($untranslatable_configs)) { $translation_join_type = 'language'; } - // If the field is translatable only on certain bundles, there will be a join - // on langcode OR bundle name. + // If the field is translatable only on certain bundles, there will be a + // join on langcode OR bundle name. elseif (!empty($translatable_configs) && !empty($untranslatable_configs)) { foreach ($untranslatable_configs as $config) { $untranslatable_config_bundles[] = $config->getTargetBundle(); @@ -268,8 +268,8 @@ class FieldViewsDataProvider { 'help' => $this->t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]), ]; - // Go through and create a list of aliases for all possible combinations of - // entity type + name. + // Go through and create a list of aliases for all possible combinations + // of entity type + name. $aliases = []; $also_known = []; foreach ($all_labels as $label_name => $true) { @@ -296,15 +296,15 @@ class FieldViewsDataProvider { } if ($aliases) { $data[$table_alias][$field_alias]['aliases'] = $aliases; - // The $also_known variable contains markup that is HTML escaped and that - // loses safeness when imploded. The help text is used in #description - // and therefore XSS admin filtered by default. Escaped HTML is not - // altered by XSS filtering, therefore it is safe to just concatenate the - // strings. Afterwards we mark the entire string as safe, so it won't be - // escaped, no matter where it is used. + // The $also_known variable contains markup that is HTML escaped and + // that loses safeness when imploded. The help text is used in + // #description and therefore XSS admin filtered by default. Escaped + // HTML is not altered by XSS filtering, therefore it is safe to just + // concatenate the strings. Afterwards we mark the entire string as + // safe, so it won't be escaped, no matter where it is used. // Considering the dual use of this help data (both as metadata and as - // help text), other patterns such as use of #markup would not be correct - // here. + // help text), other patterns such as use of #markup would not be + // correct here. $data[$table_alias][$field_alias]['help'] = Markup::create($data[$table_alias][$field_alias]['help'] . ' ' . $this->t('Also known as:') . ' ' . implode(', ', $also_known)); } @@ -328,7 +328,8 @@ class FieldViewsDataProvider { foreach ($field_columns as $column => $attributes) { $allow_sort = TRUE; - // Identify likely filters and arguments for each column based on field type. + // Identify likely filters and arguments for each column based on field + // type. switch ($attributes['type']) { case 'int': case 'mediumint': @@ -387,8 +388,8 @@ class FieldViewsDataProvider { 'help' => $this->t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]), ]; - // Go through and create a list of aliases for all possible combinations of - // entity type + name. + // Go through and create a list of aliases for all possible combinations + // of entity type + name. $aliases = []; $also_known = []; foreach ($all_labels as $label_name => $true) { diff --git a/core/modules/views/src/Hook/ViewsHooks.php b/core/modules/views/src/Hook/ViewsHooks.php index 6facc63de6de..b309887723c3 100644 --- a/core/modules/views/src/Hook/ViewsHooks.php +++ b/core/modules/views/src/Hook/ViewsHooks.php @@ -2,6 +2,7 @@ namespace Drupal\views\Hook; +use Drupal\block\BlockInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\views\ViewsConfigUpdater; use Drupal\views\ViewEntityInterface; @@ -378,4 +379,19 @@ class ViewsHooks { $config_updater->updateAll($view); } + /** + * Implements hook_ENTITY_TYPE_presave() for blocks. + */ + #[Hook('block_presave')] + public function blockPresave(BlockInterface $block): void { + if (str_starts_with($block->getPluginId(), 'views_block:')) { + $settings = $block->get('settings'); + if (isset($settings['items_per_page']) && $settings['items_per_page'] === 'none') { + @trigger_error('Saving a views block with "none" items per page is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. To use the items per page defined by the view, use NULL. See https://www.drupal.org/node/3522240', E_USER_DEPRECATED); + $settings['items_per_page'] = NULL; + $block->set('settings', $settings); + } + } + } + } diff --git a/core/modules/views/src/Hook/ViewsViewsHooks.php b/core/modules/views/src/Hook/ViewsViewsHooks.php index a54decce9e62..4f10f689646c 100644 --- a/core/modules/views/src/Hook/ViewsViewsHooks.php +++ b/core/modules/views/src/Hook/ViewsViewsHooks.php @@ -143,8 +143,9 @@ class ViewsViewsHooks { } } // Registers an action bulk form per entity. + $all_actions = \Drupal::entityTypeManager()->getStorage('action')->loadMultiple(); foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type => $entity_info) { - $actions = array_filter(\Drupal::entityTypeManager()->getStorage('action')->loadMultiple(), function (ActionConfigEntityInterface $action) use ($entity_type) { + $actions = array_filter($all_actions, function (ActionConfigEntityInterface $action) use ($entity_type) { return $action->getType() == $entity_type; }); if (empty($actions)) { diff --git a/core/modules/views/src/Plugin/views/argument/LanguageArgument.php b/core/modules/views/src/Plugin/views/argument/LanguageArgument.php index 23980568f764..81a2181dd5a2 100644 --- a/core/modules/views/src/Plugin/views/argument/LanguageArgument.php +++ b/core/modules/views/src/Plugin/views/argument/LanguageArgument.php @@ -15,21 +15,19 @@ use Drupal\views\Attribute\ViewsArgument; class LanguageArgument extends ArgumentPluginBase { /** - * Overrides \Drupal\views\Plugin\views\argument\ArgumentPluginBase::summaryName(). - * - * Gets the user-friendly version of the language name. + * {@inheritdoc} */ public function summaryName($data) { + // Gets the user-friendly version of the language name. return $this->language($data->{$this->name_alias}); } /** - * Overrides \Drupal\views\Plugin\views\argument\ArgumentPluginBase::title(). - * - * Gets the user friendly version of the language name for display as a - * title placeholder. + * {@inheritdoc} */ public function title() { + // Gets the user friendly version of the language name for display as a + // title placeholder. return $this->language($this->argument); } diff --git a/core/modules/views/src/Plugin/views/display/Block.php b/core/modules/views/src/Plugin/views/display/Block.php index 6532990b8d3e..5692ee1df013 100644 --- a/core/modules/views/src/Plugin/views/display/Block.php +++ b/core/modules/views/src/Plugin/views/display/Block.php @@ -120,7 +120,7 @@ class Block extends DisplayPluginBase { * @see \Drupal\views\Plugin\Block\ViewsBlock::defaultConfiguration() */ public function blockSettings(array $settings) { - $settings['items_per_page'] = 'none'; + $settings['items_per_page'] = NULL; return $settings; } @@ -315,7 +315,7 @@ class Block extends DisplayPluginBase { 40 => 40, 48 => 48, ], - '#default_value' => $block_configuration['items_per_page'], + '#default_value' => $block_configuration['items_per_page'] ?? 'none', ]; break; } @@ -353,7 +353,7 @@ class Block extends DisplayPluginBase { */ public function blockSubmit(ViewsBlock $block, $form, FormStateInterface $form_state) { if ($items_per_page = $form_state->getValue(['override', 'items_per_page'])) { - $block->setConfigurationValue('items_per_page', $items_per_page); + $block->setConfigurationValue('items_per_page', $items_per_page === 'none' ? NULL : intval($items_per_page)); } $form_state->unsetValue(['override', 'items_per_page']); } @@ -366,8 +366,9 @@ class Block extends DisplayPluginBase { */ public function preBlockBuild(ViewsBlock $block) { $config = $block->getConfiguration(); - if ($config['items_per_page'] !== 'none') { - $this->view->setItemsPerPage($config['items_per_page']); + if (is_numeric($config['items_per_page']) && $config['items_per_page'] > 0) { + // @todo Delete the intval() in https://www.drupal.org/project/drupal/issues/3521221 + $this->view->setItemsPerPage(intval($config['items_per_page'])); } } diff --git a/core/modules/views/src/Plugin/views/field/Boolean.php b/core/modules/views/src/Plugin/views/field/Boolean.php index f2eb8f639b87..0c91fdc59509 100644 --- a/core/modules/views/src/Plugin/views/field/Boolean.php +++ b/core/modules/views/src/Plugin/views/field/Boolean.php @@ -16,8 +16,9 @@ use Drupal\views\Plugin\views\display\DisplayPluginBase; * Allows for display of true/false, yes/no, on/off, enabled/disabled. * * Definition terms: - * - output formats: An array where the first entry is displayed on boolean true - * and the second is displayed on boolean false. An example for sticky is: + * - output formats: An array where the first entry is displayed on boolean + * true and the second is displayed on boolean false. An example for sticky + * is: * @code * 'output formats' => [ * 'sticky' => [t('Sticky'), ''], diff --git a/core/modules/views/src/Plugin/views/field/Url.php b/core/modules/views/src/Plugin/views/field/Url.php index 18e40a61f0e9..7f5dd0a33653 100644 --- a/core/modules/views/src/Plugin/views/field/Url.php +++ b/core/modules/views/src/Plugin/views/field/Url.php @@ -9,7 +9,7 @@ use Drupal\views\Attribute\ViewsField; use Drupal\views\ResultRow; /** - * Field handler to provide simple renderer that turns a URL into a clickable link. + * Field handler to provide a renderer that turns a URL into a clickable link. * * @ingroup views_field_handlers */ diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php index e3b5b87ac2ef..0f526735d6e3 100644 --- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php +++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php @@ -889,7 +889,7 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen * (optional) The form element to set any errors on. * * @return string - * Returns an error message if validation fails, or NULL if validation passes. + * The error message if validation fails, or NULL if validation passes. */ protected function validateIdentifier($identifier, ?FormStateInterface $form_state = NULL, &$form_group = []) { $error = ''; @@ -1226,9 +1226,11 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen continue; } // Each rows contains three widgets: - // a) The title, where users define how they identify a pair of operator | value - // b) The operator - // c) The value (or values) to use in the filter with the selected operator + // - The title, where users define how they identify a pair of + // operator | value. + // - The operator. + // - The value (or values) to use in the filter with the selected + // operator. // In each row, we have to display the operator form and the value from // $row acts as a fake form to render each widget in a row. diff --git a/core/modules/views/src/Plugin/views/style/Table.php b/core/modules/views/src/Plugin/views/style/Table.php index 48adbf427ede..561628ac6820 100644 --- a/core/modules/views/src/Plugin/views/style/Table.php +++ b/core/modules/views/src/Plugin/views/style/Table.php @@ -71,7 +71,7 @@ class Table extends StylePluginBase implements CacheableDependencyInterface { $options = parent::defineOptions(); $options['columns'] = ['default' => []]; - $options['class'] = ['default' => []]; + $options['class'] = ['default' => '']; $options['default'] = ['default' => '']; $options['info'] = ['default' => []]; $options['override'] = ['default' => TRUE]; diff --git a/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php index ef8568203cd4..d53e93cecec1 100644 --- a/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php +++ b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php @@ -208,7 +208,8 @@ abstract class WizardPluginBase extends PluginBase implements WizardInterface { * Gets the availableSorts property. * * @return array - * An array of available sorts, keyed by sort ID, containing sort information. + * An array whose keys are the available sort options and whose + * corresponding values are human readable labels. */ public function getAvailableSorts() { return $this->availableSorts; @@ -483,7 +484,7 @@ abstract class WizardPluginBase extends PluginBase implements WizardInterface { } /** - * Gets the current value of a #select element, from within a form constructor function. + * Gets the current value of a #select element. * * This function is intended for use in highly dynamic forms (in particular * the add view wizard) which are rebuilt in different ways depending on which diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php index 55e3a8d91453..4867ba48f9a6 100644 --- a/core/modules/views/src/ViewExecutable.php +++ b/core/modules/views/src/ViewExecutable.php @@ -348,7 +348,7 @@ class ViewExecutable { public $footer; /** - * Stores the area handlers for the empty text which are initialized on this view. + * The area handlers for the empty text which are initialized on this view. * * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects. * diff --git a/core/modules/views/src/ViewsConfigUpdater.php b/core/modules/views/src/ViewsConfigUpdater.php index 3b2709cc60c2..9c5d38e10ac5 100644 --- a/core/modules/views/src/ViewsConfigUpdater.php +++ b/core/modules/views/src/ViewsConfigUpdater.php @@ -134,6 +134,9 @@ class ViewsConfigUpdater implements ContainerInjectionInterface { if ($this->processRememberRolesUpdate($handler, $handler_type)) { $changed = TRUE; } + if ($this->processTableCssClassUpdate($view)) { + $changed = TRUE; + } return $changed; }); } @@ -335,6 +338,7 @@ class ViewsConfigUpdater implements ContainerInjectionInterface { if ( isset($display['display_options']['style']) && $display['display_options']['style']['type'] === 'table' && + isset($display['display_options']['style']['options']) && !isset($display['display_options']['style']['options']['class']) ) { $display['display_options']['style']['options']['class'] = ''; @@ -346,6 +350,12 @@ class ViewsConfigUpdater implements ContainerInjectionInterface { $view->set('display', $displays); } + $deprecations_triggered = &$this->triggeredDeprecations['table_css_class'][$view->id()]; + if ($this->deprecationsEnabled && $changed && !$deprecations_triggered) { + $deprecations_triggered = TRUE; + @trigger_error(sprintf('The update to add a default table CSS class for view "%s" is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Profile, module and theme provided configuration should be updated. See https://www.drupal.org/node/3499943', $view->id()), E_USER_DEPRECATED); + } + return $changed; } diff --git a/core/modules/views/tests/fixtures/update/views-block-items-per-page.php b/core/modules/views/tests/fixtures/update/views-block-items-per-page.php new file mode 100644 index 000000000000..542992a24df2 --- /dev/null +++ b/core/modules/views/tests/fixtures/update/views-block-items-per-page.php @@ -0,0 +1,48 @@ +<?php + +/** + * @file + * Creates a Views block with an `items_per_page` setting of `none`. + */ + +declare(strict_types=1); + +use Drupal\Core\Database\Database; +use Drupal\Core\Serialization\Yaml; + +$block_data = Yaml::decode(<<<END +uuid: ecdad54d-8165-4ed3-a678-8ad20b388282 +langcode: en +status: true +dependencies: + config: + - views.view.who_s_online + module: + - views + theme: + - olivero +id: olivero_who_s_online +theme: olivero +region: header +weight: 0 +provider: null +plugin: 'views_block:who_s_online-who_s_online_block' +settings: + id: 'views_block:who_s_online-who_s_online_block' + label: '' + label_display: visible + provider: views + views_label: '' + items_per_page: none +visibility: { } +END +); + +Database::getConnection() + ->insert('config') + ->fields([ + 'collection' => '', + 'name' => 'block.block.olivero_who_s_online', + 'data' => serialize($block_data), + ]) + ->execute(); diff --git a/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml b/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml index cba11476ae8b..573705f502da 100644 --- a/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml +++ b/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml @@ -126,6 +126,7 @@ display: type: table options: grouping: { } + class: '' row_class: '' default_row_class: true columns: diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml index 3ab5ecb66894..077b4bb4ce16 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml @@ -42,6 +42,7 @@ display: style: type: table options: + class: '' info: id: sortable: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml index bf65e0eaf379..1157a4e60bca 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml @@ -43,6 +43,15 @@ display: style: type: table options: + grouping: { } + class: '' + row_class: '' + default_row_class: true + override: true + sticky: true + caption: '' + summary: '' + description: '' info: id: sortable: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml index db3fd0693bf7..2036ee77c5fb 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml @@ -57,6 +57,7 @@ display: type: table options: grouping: { } + class: '' row_class: '' default_row_class: true override: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_distinct_click_sorting.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_distinct_click_sorting.yml index 55813bc9c49a..fe6c14f744bf 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_distinct_click_sorting.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_distinct_click_sorting.yml @@ -65,6 +65,7 @@ display: type: table options: grouping: { } + class: '' row_class: '' default_row_class: true override: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml index a19cea01b9f3..5ed73c2a1178 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml @@ -133,6 +133,7 @@ display: type: table options: grouping: { } + class: '' row_class: '' default_row_class: true override: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml index 091c4375635e..a8c84a7a6497 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml @@ -20,6 +20,8 @@ display: element_label_type: h2 style: type: table + options: + class: '' display_extenders: { } display_plugin: default display_title: Default diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_placeholder_text.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_placeholder_text.yml index 1f33e75b61fb..1217b6c86eba 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_placeholder_text.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_placeholder_text.yml @@ -52,6 +52,7 @@ display: type: table options: grouping: { } + class: '' row_class: '' default_row_class: true override: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml index ddb305fb162e..dffcbe3c0b72 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml @@ -40,6 +40,7 @@ display: type: table options: grouping: { } + class: '' row_class: '' default_row_class: true override: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml index 21ac6945808e..aa3e387ee905 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml @@ -286,6 +286,7 @@ display: override: true sticky: false grouping: { } + class: '' row_class: '' default_row_class: true uses_fields: false diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml index b52621ee9908..a4bda56b5437 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml @@ -68,6 +68,7 @@ display: type: table options: grouping: { } + class: '' row_class: '' default_row_class: true override: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache_none.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache_none.yml index aea7b9040134..91382cd03900 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache_none.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache_none.yml @@ -68,6 +68,7 @@ display: type: table options: grouping: { } + class: '' row_class: '' default_row_class: true override: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml index 10d215d29b51..77cca5f518f3 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml @@ -63,6 +63,7 @@ display: type: table options: grouping: { } + class: '' row_class: '' default_row_class: true override: true diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml index cec43f7c7dc7..98fa2836be96 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml @@ -48,6 +48,8 @@ display: plugin_id: standard style: type: table + options: + class: '' display_plugin: default display_title: Default id: default diff --git a/core/modules/views/tests/src/Functional/Plugin/ContextualFiltersBlockContextTest.php b/core/modules/views/tests/src/Functional/Plugin/ContextualFiltersBlockContextTest.php index 955226497f95..0384c917083e 100644 --- a/core/modules/views/tests/src/Functional/Plugin/ContextualFiltersBlockContextTest.php +++ b/core/modules/views/tests/src/Functional/Plugin/ContextualFiltersBlockContextTest.php @@ -122,7 +122,7 @@ class ContextualFiltersBlockContextTest extends ViewTestBase { 'provider' => 'views', 'label_display' => 'visible', 'views_label' => '', - 'items_per_page' => 'none', + 'items_per_page' => NULL, 'context_mapping' => ['nid' => '@node.node_route_context:node'], ]; $this->assertEquals($expected_settings, $block->getPlugin()->getConfiguration(), 'Block settings are correct.'); diff --git a/core/modules/views/tests/src/Functional/Update/BlockItemsPerPageUpdateTest.php b/core/modules/views/tests/src/Functional/Update/BlockItemsPerPageUpdateTest.php new file mode 100644 index 000000000000..bd250cc2736e --- /dev/null +++ b/core/modules/views/tests/src/Functional/Update/BlockItemsPerPageUpdateTest.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\views\Functional\Update; + +use Drupal\block\Entity\Block; +use Drupal\FunctionalTests\Update\UpdatePathTestBase; + +/** + * @group Update + * @covers views_post_update_block_items_per_page + */ +final class BlockItemsPerPageUpdateTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles(): void { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gz', + __DIR__ . '/../../../fixtures/update/views-block-items-per-page.php', + ]; + } + + /** + * Tests changing an `items_per_page` setting of `none` to NULL. + */ + public function testUpdateItemsPerPage(): void { + $settings = Block::load('olivero_who_s_online')?->get('settings'); + $this->assertIsArray($settings); + $this->assertSame('none', $settings['items_per_page']); + + $this->runUpdates(); + + $settings = Block::load('olivero_who_s_online')?->get('settings'); + $this->assertIsArray($settings); + $this->assertNull($settings['items_per_page']); + } + +} diff --git a/core/modules/views/tests/src/Functional/Wizard/ItemsPerPageTest.php b/core/modules/views/tests/src/Functional/Wizard/ItemsPerPageTest.php index 5f9cd364dac8..d290a27cd191 100644 --- a/core/modules/views/tests/src/Functional/Wizard/ItemsPerPageTest.php +++ b/core/modules/views/tests/src/Functional/Wizard/ItemsPerPageTest.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Drupal\Tests\views\Functional\Wizard; +use Drupal\views\Entity\View; + /** * Tests that the views wizard can specify the number of items per page. * @@ -19,6 +21,16 @@ class ItemsPerPageTest extends WizardTestBase { /** * {@inheritdoc} */ + protected static $configSchemaCheckerExclusions = [ + // To be able to test with the now invalid: + // - `items_per_page: 'none'` + // - `items_per_page: '5'` + 'block.block.views_block_items_per_page_test_with_historical_override', + ]; + + /** + * {@inheritdoc} + */ protected function setUp($import_test_views = TRUE, $modules = []): void { parent::setUp($import_test_views, $modules); @@ -27,6 +39,12 @@ class ItemsPerPageTest extends WizardTestBase { /** * Tests the number of items per page. + * + * This should be removed from the `legacy` group in + * https://drupal.org/i/3521221; see + * \Drupal\views\Hook\ViewsHooks::blockPresave(). + * + * @group legacy */ public function testItemsPerPage(): void { $this->drupalCreateContentType(['type' => 'article']); @@ -83,7 +101,7 @@ class ItemsPerPageTest extends WizardTestBase { $this->drupalGet($view['page[path]']); $this->assertSession()->statusCodeEquals(200); - // Make sure the page display shows the nodes we expect, and that they + // Make sure the page display shows the 4 nodes we expect, and that they // appear in the expected order. $this->assertSession()->addressEquals($view['page[path]']); $this->assertSession()->pageTextContains($view['page[title]']); @@ -109,21 +127,94 @@ class ItemsPerPageTest extends WizardTestBase { // Place the block, visit a page that displays the block, and check that the // nodes we expect appear in the correct order. - $this->drupalPlaceBlock("views_block:{$view['id']}-block_1"); - + $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1"); + + // Asserts that the 3 newest articles are listed, which is the configuration + // for the `block` display in the view. In other words: the `items_per_page` + // setting in the `View` config entity is respected. + $assert_3_newest_nodes = function () use ($node5, $node4, $node3, $node2, $node1, $page_node) { + $this->drupalGet('user'); + $content = $this->getSession()->getPage()->getContent(); + $this->assertSession()->pageTextContains($node5->label()); + $this->assertSession()->pageTextContains($node4->label()); + $this->assertSession()->pageTextContains($node3->label()); + $this->assertSession()->pageTextNotContains($node2->label()); + $this->assertSession()->pageTextNotContains($node1->label()); + $this->assertSession()->pageTextNotContains($page_node->label()); + $pos5 = strpos($content, $node5->label()); + $pos4 = strpos($content, $node4->label()); + $pos3 = strpos($content, $node3->label()); + $this->assertGreaterThan($pos5, $pos4); + $this->assertGreaterThan($pos4, $pos3); + }; + self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']); + self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']); + self::assertArrayNotHasKey('items_per_page', $block->get('settings')); + $assert_3_newest_nodes(); + $block->delete(); + + // Because the `allow[items_per_page]` checkbox is checked, it is allowed to + // override the `items_per_page` setting for the Views's `block` display, + // and is actually respected. Valid values are `null` ("do not override") + // and a positive integer. + $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1", [ + 'items_per_page' => NULL, + ]); + self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']); + self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']); + self::assertNull($block->get('settings')['items_per_page']); + $assert_3_newest_nodes(); + $block->delete(); + + $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1", [ + 'items_per_page' => 5, + ]); + self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']); + self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']); + self::assertSame(5, $block->get('settings')['items_per_page']); $this->drupalGet('user'); - $content = $this->getSession()->getPage()->getContent(); - $this->assertSession()->pageTextContains($node5->label()); - $this->assertSession()->pageTextContains($node4->label()); - $this->assertSession()->pageTextContains($node3->label()); - $this->assertSession()->pageTextNotContains($node2->label()); - $this->assertSession()->pageTextNotContains($node1->label()); - $this->assertSession()->pageTextNotContains($page_node->label()); - $pos5 = strpos($content, $node5->label()); - $pos4 = strpos($content, $node4->label()); - $pos3 = strpos($content, $node3->label()); - $this->assertGreaterThan($pos5, $pos4); - $this->assertGreaterThan($pos4, $pos3); + foreach ([$node5, $node4, $node3, $node2, $node1] as $node) { + $this->assertSession()->pageTextContains($node->label()); + } + $block->delete(); + + // Finally: set `items_per_page: 'none'`, which is the predecessor of + // `items_per_page: null`. This must continue to work as before even if the + // configuration is no longer considered valid, because otherwise we risk + // breaking e.g. blocks placed using Layout Builder. + // @todo Delete in https://www.drupal.org/project/drupal/issues/3521221. + $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1", [ + 'id' => 'views_block_items_per_page_test_with_historical_override', + ]); + // Explicitly set the `items_per_page` setting to a string without casting. + // It should be changed to NULL by the pre-save hook. + // @see \Drupal\views\Hook\ViewsHooks::blockPresave() + $block->set('settings', [ + 'items_per_page' => 'none', + ])->trustData()->save(); + $this->expectDeprecation('Saving a views block with "none" items per page is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. To use the items per page defined by the view, use NULL. See https://www.drupal.org/node/3522240'); + self::assertNull($block->get('settings')['items_per_page']); + self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']); + self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']); + $assert_3_newest_nodes(); + $block->delete(); + + // Truly finally: set `items_per_page: '5'`, because for the same reason as + // above, blocks placed using Layout Builder may still have stale settings. + $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1", [ + 'id' => 'views_block_items_per_page_test_with_historical_override', + ]); + // Explicitly set the `items_per_page` setting to a string without casting. + $block->set('settings', [ + 'items_per_page' => '5', + ])->trustData()->save(); + self::assertSame('5', $block->get('settings')['items_per_page']); + self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']); + self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']); + $this->drupalGet('user'); + foreach ([$node5, $node4, $node3, $node2, $node1] as $node) { + $this->assertSession()->pageTextContains($node->label()); + } } } diff --git a/core/modules/views/tests/src/Kernel/Plugin/ViewsBlockTest.php b/core/modules/views/tests/src/Kernel/Plugin/ViewsBlockTest.php index b336584c74d6..ebd9df73e01d 100644 --- a/core/modules/views/tests/src/Kernel/Plugin/ViewsBlockTest.php +++ b/core/modules/views/tests/src/Kernel/Plugin/ViewsBlockTest.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Drupal\Tests\views\Kernel\Plugin; +use Drupal\Core\Extension\ThemeInstallerInterface; +use Drupal\Tests\block\Traits\BlockCreationTrait; use Drupal\views\Plugin\Block\ViewsBlock; use Drupal\views\Tests\ViewTestData; use Drupal\Tests\views\Kernel\ViewsKernelTestBase; @@ -16,6 +18,8 @@ use Drupal\views\Views; */ class ViewsBlockTest extends ViewsKernelTestBase { + use BlockCreationTrait; + /** * {@inheritdoc} */ @@ -142,4 +146,22 @@ class ViewsBlockTest extends ViewsKernelTestBase { $this->assertEquals('"test_view_block::block_1" views block', $views_block->getPreviewFallbackString()); } + /** + * Tests that saving a Views block with items_per_page = `none` is deprecated. + * + * @covers \Drupal\views\Hook\ViewsHooks::blockPresave + * + * @group legacy + */ + public function testSaveBlockWithDeprecatedItemsPerPageSetting(): void { + $this->container->get(ThemeInstallerInterface::class)->install(['stark']); + + $this->expectDeprecation('Saving a views block with "none" items per page is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. To use the items per page defined by the view, use NULL. See https://www.drupal.org/node/3522240'); + $block = $this->placeBlock('views_block:test_view_block-block_1', [ + 'items_per_page' => 'none', + ]); + $settings = $block->get('settings'); + $this->assertNull($settings['items_per_page']); + } + } diff --git a/core/modules/views/tests/src/Unit/Plugin/views/display/BlockTest.php b/core/modules/views/tests/src/Unit/Plugin/views/display/BlockTest.php index 0d72bf27b4ac..6988a04d8b48 100644 --- a/core/modules/views/tests/src/Unit/Plugin/views/display/BlockTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/views/display/BlockTest.php @@ -62,29 +62,37 @@ class BlockTest extends UnitTestCase { /** * Tests the build method with no overriding. + * + * @testWith [null] + * ["none"] + * [0] + * @todo Delete the last two cases in https://www.drupal.org/project/drupal/issues/3521221. The last one is `intval('none')`. */ - public function testBuildNoOverride(): void { + public function testBuildNoOverride($items_per_page_setting): void { $this->executable->expects($this->never()) ->method('setItemsPerPage'); $this->blockPlugin->expects($this->once()) ->method('getConfiguration') - ->willReturn(['items_per_page' => 'none']); + ->willReturn(['items_per_page' => $items_per_page_setting]); $this->blockDisplay->preBlockBuild($this->blockPlugin); } /** * Tests the build method with overriding items per page. + * + * @testWith [5, 5] + * ["5", 5] */ - public function testBuildOverride(): void { + public function testBuildOverride(mixed $input, int $expected): void { $this->executable->expects($this->once()) ->method('setItemsPerPage') - ->with(5); + ->with($expected); $this->blockPlugin->expects($this->once()) ->method('getConfiguration') - ->willReturn(['items_per_page' => 5]); + ->willReturn(['items_per_page' => $input]); $this->blockDisplay->preBlockBuild($this->blockPlugin); } diff --git a/core/modules/views/views.api.php b/core/modules/views/views.api.php index 4fbcad6730ea..6579f93199bb 100644 --- a/core/modules/views/views.api.php +++ b/core/modules/views/views.api.php @@ -532,8 +532,9 @@ function hook_views_data_alter(array &$data) { * When collecting the views data, views_views_data() invokes this hook for each * field storage definition, on the module that provides the field storage * definition. If the return value is empty, the result of - * FieldViewsDataProvider::defaultFieldImplementation() is used instead. Then the result is altered - * by invoking hook_field_views_data_alter() on all modules. + * FieldViewsDataProvider::defaultFieldImplementation() is used instead. Then + * the result is altered by invoking hook_field_views_data_alter() on all + * modules. * * If no hook implementation exists, hook_views_data() falls back to * FieldViewsDataProvider::defaultFieldImplementation(). diff --git a/core/modules/views/views.post_update.php b/core/modules/views/views.post_update.php index c3f352e99a42..48623fc593e0 100644 --- a/core/modules/views/views.post_update.php +++ b/core/modules/views/views.post_update.php @@ -5,6 +5,7 @@ * Post update functions for Views. */ +use Drupal\block\BlockInterface; use Drupal\Core\Config\Entity\ConfigEntityUpdater; use Drupal\views\ViewEntityInterface; use Drupal\views\ViewsConfigUpdater; @@ -83,7 +84,29 @@ function views_post_update_update_remember_role_empty(?array &$sandbox = NULL): function views_post_update_table_css_class(?array &$sandbox = NULL): void { /** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */ $view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class); + $view_config_updater->setDeprecationsEnabled(FALSE); \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater): bool { return $view_config_updater->needsTableCssClassUpdate($view); }); } + +/** + * Defaults `items_per_page` to NULL in Views blocks. + */ +function views_post_update_block_items_per_page(?array &$sandbox = NULL): void { + if (!\Drupal::moduleHandler()->moduleExists('block')) { + return; + } + \Drupal::classResolver(ConfigEntityUpdater::class) + ->update($sandbox, 'block', function (BlockInterface $block): bool { + if (str_starts_with($block->getPluginId(), 'views_block:')) { + $settings = $block->get('settings'); + if ($settings['items_per_page'] === 'none') { + $settings['items_per_page'] = NULL; + $block->set('settings', $settings); + return TRUE; + } + } + return FALSE; + }); +} |