diff options
Diffstat (limited to 'core')
182 files changed, 1632 insertions, 716 deletions
diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php index de0cfedb16f1..21b7ee78a040 100644 --- a/core/.phpstan-baseline.php +++ b/core/.phpstan-baseline.php @@ -32453,12 +32453,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\theme_test\\\\EventSubscriber\\\\ThemeTestSubscriber\\:\\:onView\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\theme_test\\\\ThemeTestController\\:\\:generalSuggestionAlter\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -45069,31 +45063,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/EntityWorkspaceConflictConstraintValidatorTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:initializeWorkspacesModule\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:switchToWorkspace\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:assertWorkspaceAssociation\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:createWorkspaceHierarchy\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', -]; - -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\EntityWorkspaceConflictConstraintValidatorTest\\:\\:initializeWorkspacesModule\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -45226,6 +45195,30 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceEntityRepositoryTest.php', ]; $ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:assertWorkspaceAssociation\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:createWorkspaceHierarchy\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:initializeWorkspacesModule\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:switchToWorkspace\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', +]; +$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceInformationTest\\:\\:assertWorkspaceAssociation\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index a81dcc29d301..09256dab56e1 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -483,7 +483,6 @@ Testing Usability - Cristina Chumillas 'ckrina' https://www.drupal.org/u/ckrina -- Roy Scholten 'yoroy' https://www.drupal.org/u/yoroy - Bojhan Somers 'Bojhan' https://www.drupal.org/u/bojhan diff --git a/core/composer.json b/core/composer.json index 1ef99e7496b0..63ebe5803f1f 100644 --- a/core/composer.json +++ b/core/composer.json @@ -57,7 +57,8 @@ "conflict": { "drupal/automatic_updates": "<4", "drupal/project_browser": "<2.1", - "drush/drush": "<12.4.3" + "drush/drush": "<12.4.3", + "dealerdirect/phpcodesniffer-composer-installer": "1.1.0" }, "replace": { "drupal/core-annotation": "self.version", diff --git a/core/core.services.yml b/core/core.services.yml index 33e9498bbc4b..9f7c1a2d11cd 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -854,6 +854,9 @@ services: class: Drupal\Core\Menu\ContextualLinkManager arguments: ['@controller_resolver', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user', '@request_stack'] Drupal\Core\Menu\ContextualLinkManagerInterface: '@plugin.manager.menu.contextual_link' + Drupal\Core\Menu\MenuPreprocess: + class: Drupal\Core\Menu\MenuPreprocess + autowire: true plugin.manager.display_variant: class: Drupal\Core\Display\VariantManager parent: default_plugin_manager @@ -1535,6 +1538,9 @@ services: tags: - { name: service_collector, tag: breadcrumb_builder, call: addBuilder } Drupal\Core\Breadcrumb\ChainBreadcrumbBuilderInterface: '@breadcrumb' + Drupal\Core\Breadcrumb\BreadcrumbPreprocess: + class: Drupal\Core\Breadcrumb\BreadcrumbPreprocess + autowire: true token: class: Drupal\Core\Utility\Token arguments: ['@module_handler', '@cache.default', '@language_manager', '@cache_tags.invalidator', '@renderer'] @@ -1575,6 +1581,9 @@ services: Drupal\Core\Theme\ThemePreprocess: class: Drupal\Core\Theme\ThemePreprocess autowire: true + Drupal\Core\Theme\ImagePreprocess: + class: Drupal\Core\Theme\ImagePreprocess + autowire: true Drupal\Core\Datetime\DatePreprocess: class: Drupal\Core\Datetime\DatePreprocess autowire: true diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 6c5d563cb63a..587f9e70c200 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -11,16 +11,15 @@ use Drupal\Core\Datetime\DatePreprocess; use Drupal\Core\Field\FieldPreprocess; use Drupal\Core\Pager\PagerPreprocess; +use Drupal\Core\Breadcrumb\BreadcrumbPreprocess; +use Drupal\Core\Menu\MenuPreprocess; +use Drupal\Core\Theme\ImagePreprocess; use Drupal\Core\Theme\ThemePreprocess; use Drupal\Core\Config\Config; use Drupal\Core\Config\StorageException; -use Drupal\Core\Template\Attribute; use Drupal\Core\Template\AttributeHelper; use Drupal\Core\Theme\ThemeCommonElements; use Drupal\Core\Theme\ThemeSettings; -use Drupal\Core\Render\Element; -use Drupal\Core\Utility\TableSort; -use Drupal\Core\Installer\InstallerKernel; /** * @defgroup content_flags Content markers @@ -483,49 +482,15 @@ function template_preprocess_links(&$variables): void { * - sizes: The sizes attribute for viewport-based selection of images. * phpcs:ignore * - http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2 + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_image(&$variables): void { - /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ - $file_url_generator = \Drupal::service('file_url_generator'); - - if (!empty($variables['uri'])) { - $variables['attributes']['src'] = $file_url_generator->generateString($variables['uri']); - } - // Generate a srcset attribute conforming to the spec at - // https://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset - if (!empty($variables['srcset'])) { - $srcset = []; - foreach ($variables['srcset'] as $src) { - // URI is mandatory. - $source = $file_url_generator->generateString($src['uri']); - if (isset($src['width']) && !empty($src['width'])) { - $source .= ' ' . $src['width']; - } - elseif (isset($src['multiplier']) && !empty($src['multiplier'])) { - $source .= ' ' . $src['multiplier']; - } - $srcset[] = $source; - } - $variables['attributes']['srcset'] = implode(', ', $srcset); - } - - foreach (['width', 'height', 'alt', 'title', 'sizes'] as $key) { - if (isset($variables[$key])) { - // If the property has already been defined in the attributes, - // do not override, including NULL. - if (AttributeHelper::attributeExists($key, $variables['attributes'])) { - continue; - } - $variables['attributes'][$key] = $variables[$key]; - } - } - - // Without dimensions specified, layout shifts can occur, - // which are more noticeable on pages that take some time to load. - // As a result, only mark images as lazy load that have dimensions. - if (isset($variables['width'], $variables['height']) && !isset($variables['attributes']['loading'])) { - $variables['attributes']['loading'] = 'lazy'; - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ImagePreprocess::class)->preprocessImage($variables); } /** @@ -619,167 +584,15 @@ function template_preprocess_image(&$variables): void { * - sticky: Use a "sticky" table header. * - empty: The message to display in an extra row if table does not have any * rows. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_table(&$variables): void { - // Format the table columns: - if (!empty($variables['colgroups'])) { - foreach ($variables['colgroups'] as &$colgroup) { - // Check if we're dealing with a simple or complex column - if (isset($colgroup['data'])) { - $cols = $colgroup['data']; - unset($colgroup['data']); - $colgroup_attributes = $colgroup; - } - else { - $cols = $colgroup; - $colgroup_attributes = []; - } - $colgroup = []; - $colgroup['attributes'] = new Attribute($colgroup_attributes); - $colgroup['cols'] = []; - - // Build columns. - if (is_array($cols) && !empty($cols)) { - foreach ($cols as $col_key => $col) { - $colgroup['cols'][$col_key]['attributes'] = new Attribute($col); - } - } - } - } - - // Build an associative array of responsive classes keyed by column. - $responsive_classes = []; - - // Format the table header: - $ts = []; - $header_columns = 0; - if (!empty($variables['header'])) { - $ts = TableSort::getContextFromRequest($variables['header'], \Drupal::request()); - - // Use a separate index with responsive classes as headers - // may be associative. - $responsive_index = -1; - foreach ($variables['header'] as $col_key => $cell) { - // Increase the responsive index. - $responsive_index++; - - if (!is_array($cell)) { - $header_columns++; - $cell_content = $cell; - $cell_attributes = new Attribute(); - $is_header = TRUE; - } - else { - if (isset($cell['colspan'])) { - $header_columns += $cell['colspan']; - } - else { - $header_columns++; - } - $cell_content = ''; - if (isset($cell['data'])) { - $cell_content = $cell['data']; - unset($cell['data']); - } - // Flag the cell as a header or not and remove the flag. - $is_header = $cell['header'] ?? TRUE; - unset($cell['header']); - - // Track responsive classes for each column as needed. Only the header - // cells for a column are marked up with the responsive classes by a - // module developer or themer. The responsive classes on the header - // cells must be transferred to the content cells. - if (!empty($cell['class']) && is_array($cell['class'])) { - if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) { - $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM; - } - elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) { - $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW; - } - } - - TableSort::header($cell_content, $cell, $variables['header'], $ts); - - // TableSort::header() removes the 'sort', 'initial_click_sort' and - // 'field' keys. - $cell_attributes = new Attribute($cell); - } - $variables['header'][$col_key] = []; - $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td'; - $variables['header'][$col_key]['attributes'] = $cell_attributes; - $variables['header'][$col_key]['content'] = $cell_content; - } - } - $variables['header_columns'] = $header_columns; - - // Rows and footer have the same structure. - $sections = ['rows' , 'footer']; - foreach ($sections as $section) { - if (!empty($variables[$section])) { - foreach ($variables[$section] as $row_key => $row) { - $cells = $row; - $row_attributes = []; - - // Check if we're dealing with a simple or complex row - if (isset($row['data'])) { - $cells = $row['data']; - $variables['no_striping'] = $row['no_striping'] ?? FALSE; - - // Set the attributes array and exclude 'data' and 'no_striping'. - $row_attributes = $row; - unset($row_attributes['data']); - unset($row_attributes['no_striping']); - } - - // Build row. - $variables[$section][$row_key] = []; - $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes); - $variables[$section][$row_key]['cells'] = []; - if (!empty($cells)) { - // Reset the responsive index. - $responsive_index = -1; - foreach ($cells as $col_key => $cell) { - // Increase the responsive index. - $responsive_index++; - - if (!is_array($cell)) { - $cell_content = $cell; - $cell_attributes = []; - $is_header = FALSE; - } - else { - $cell_content = ''; - if (isset($cell['data'])) { - $cell_content = $cell['data']; - unset($cell['data']); - } - - // Flag the cell as a header or not and remove the flag. - $is_header = !empty($cell['header']); - unset($cell['header']); - - $cell_attributes = $cell; - } - // Active table sort information. - if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) { - $variables[$section][$row_key]['cells'][$col_key]['active_table_sort'] = TRUE; - } - // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM - // class from header to cell as needed. - if (isset($responsive_classes[$responsive_index])) { - $cell_attributes['class'][] = $responsive_classes[$responsive_index]; - } - $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td'; - $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes); - $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content; - } - } - } - } - } - if (empty($variables['no_striping'])) { - $variables['attributes']['data-striping'] = 1; - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessTable($variables); } /** @@ -799,56 +612,14 @@ function template_preprocess_table(&$variables): void { * - list_type: The type of list to return (e.g. "ul", "ol"). * - wrapper_attributes: HTML attributes to be applied to the list wrapper. * - * @see https://www.drupal.org/node/1842756 + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_item_list(&$variables): void { - $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']); - $variables['#attached']['library'][] = 'core/drupal.item-list'; - foreach ($variables['items'] as &$item) { - $attributes = []; - // If the item value is an array, then it is a render array. - if (is_array($item)) { - // List items support attributes via the '#wrapper_attributes' property. - if (isset($item['#wrapper_attributes'])) { - $attributes = $item['#wrapper_attributes']; - } - // Determine whether there are any child elements in the item that are not - // fully-specified render arrays. If there are any, then the child - // elements present nested lists and we automatically inherit the render - // array properties of the current list to them. - foreach (Element::children($item) as $key) { - $child = &$item[$key]; - // If this child element does not specify how it can be rendered, then - // we need to inherit the render properties of the current list. - if (!isset($child['#type']) && !isset($child['#theme']) && !isset($child['#markup'])) { - // Since item-list.html.twig supports both strings and render arrays - // as items, the items of the nested list may have been specified as - // the child elements of the nested list, instead of #items. For - // convenience, we automatically move them into #items. - if (!isset($child['#items'])) { - // This is the same condition as in - // \Drupal\Core\Render\Element::children(), which cannot be used - // here, since it triggers an error on string values. - foreach ($child as $child_key => $child_value) { - if (is_int($child_key) || $child_key === '' || $child_key[0] !== '#') { - $child['#items'][$child_key] = $child_value; - unset($child[$child_key]); - } - } - } - // Lastly, inherit the original theme variables of the current list. - $child['#theme'] = $variables['theme_hook_original']; - $child['#list_type'] = $variables['list_type']; - } - } - } - - // Set the item's value and attributes for the template. - $item = [ - 'value' => $item, - 'attributes' => new Attribute($attributes), - ]; - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessItemList($variables); } /** @@ -882,27 +653,15 @@ function template_preprocess_container(&$variables): void { * It's the caller's responsibility to ensure this array's items contain no * dangerous HTML such as <script> tags. * - active: The key for the currently active maintenance task. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_maintenance_task_list(&$variables): void { - $items = $variables['items']; - $active = $variables['active']; - - $done = isset($items[$active]) || $active == NULL; - foreach ($items as $k => $item) { - $variables['tasks'][$k]['item'] = $item; - $variables['tasks'][$k]['attributes'] = new Attribute(); - if ($active == $k) { - $variables['tasks'][$k]['attributes']->addClass('is-active'); - $variables['tasks'][$k]['status'] = t('active'); - $done = FALSE; - } - else { - if ($done) { - $variables['tasks'][$k]['attributes']->addClass('done'); - $variables['tasks'][$k]['status'] = t('done'); - } - } - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessMaintenanceTaskList($variables); } /** @@ -1052,9 +811,15 @@ function theme_get_suggestions($args, $base, $delimiter = '__'): array { * Prepares variables for tablesort indicators. * * Default template: tablesort-indicator.html.twig. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_tablesort_indicator(&$variables): void { - $variables['#attached']['library'][] = 'core/drupal.tablesort'; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessTablesortIndicator($variables); } /** @@ -1066,25 +831,14 @@ function template_preprocess_tablesort_indicator(&$variables): void { * An associative array containing: * - content - An array of page content. * - * @see system_page_attachments() + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_maintenance_page(&$variables): void { - // @todo Rename the templates to page--maintenance + page--install. - \Drupal::service(ThemePreprocess::class)->preprocessPage($variables); - - // @see system_page_attachments() - $variables['#attached']['library'][] = 'system/maintenance'; - - // Maintenance page and install page need branding info in variables because - // there is no blocks. - $site_config = \Drupal::config('system.site'); - $variables['logo'] = theme_get_setting('logo.url'); - $variables['site_name'] = $site_config->get('name'); - $variables['site_slogan'] = $site_config->get('slogan'); - - // Maintenance page and install page need page title in variable because there - // are no blocks. - $variables['title'] = $variables['page']['#title']; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessMaintenancePage($variables); } /** @@ -1096,21 +850,14 @@ function template_preprocess_maintenance_page(&$variables): void { * An associative array containing: * - content - An array of page content. * - * @see template_preprocess_maintenance_page() + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_install_page(&$variables): void { - $installer_active_task = NULL; - if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE === 'install' && InstallerKernel::installationAttempted()) { - $installer_active_task = $GLOBALS['install_state']['active_task']; - } - - template_preprocess_maintenance_page($variables); - - // Override the site name that is displayed on the page, since Drupal is - // still in the process of being installed. - $distribution_name = drupal_install_profile_distribution_name(); - $variables['site_name'] = $distribution_name; - $variables['site_version'] = $installer_active_task ? drupal_install_profile_distribution_version() : ''; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessInstallPage($variables); } /** @@ -1125,11 +872,15 @@ function template_preprocess_install_page(&$variables): void { * @param array $variables * An associative array containing: * - elements: An associative array containing properties of the region. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_region(&$variables): void { - // Create the $content variable that templates expect. - $variables['content'] = $variables['elements']['#children']; - $variables['region'] = $variables['elements']['#region']; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessRegion($variables); } /** @@ -1182,13 +933,15 @@ function template_preprocess_field_multiple_value_form(&$variables): void { * @param array $variables * An associative array containing: * - links: A list of \Drupal\Core\Link objects which should be rendered. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_breadcrumb(&$variables): void { - $variables['breadcrumb'] = []; - /** @var \Drupal\Core\Link $link */ - foreach ($variables['links'] as $key => $link) { - $variables['breadcrumb'][$key] = ['text' => $link->getText(), 'url' => $link->getUrl()->toString()]; - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(BreadcrumbPreprocess::class)->preprocessBreadcrumb($variables); } /** @@ -1212,7 +965,7 @@ function template_preprocess_breadcrumb(&$variables): void { * - #route_parameters: An associative array of the route parameters. * - #quantity: The number of pages in the list. * - * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Initial + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial * template_preprocess functions are registered directly in hook_theme(). * * @see https://www.drupal.org/node/3504125 @@ -1233,26 +986,15 @@ function template_preprocess_pager(&$variables): void { * - #link: A menu link array with 'title', 'url', and (optionally) * 'localized_options' keys. * - #active: A boolean indicating whether the local task is active. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_menu_local_task(&$variables): void { - $link = $variables['element']['#link']; - $link += [ - 'localized_options' => [], - ]; - $link_text = $link['title']; - - if (!empty($variables['element']['#active'])) { - $variables['is_active'] = TRUE; - } - - $link['localized_options']['set_active_class'] = TRUE; - - $variables['link'] = [ - '#type' => 'link', - '#title' => $link_text, - '#url' => $link['url'], - '#options' => $link['localized_options'], - ]; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(MenuPreprocess::class)->preprocessMenuLocalTask($variables); } /** @@ -1265,22 +1007,15 @@ function template_preprocess_menu_local_task(&$variables): void { * - element: A render element containing: * - #link: A menu link array with 'title', 'url', and (optionally) * 'localized_options' keys. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_menu_local_action(&$variables): void { - $link = $variables['element']['#link']; - $link += [ - 'localized_options' => [], - ]; - $link['localized_options']['attributes']['class'][] = 'button'; - $link['localized_options']['attributes']['class'][] = 'button-action'; - $link['localized_options']['set_active_class'] = TRUE; - - $variables['link'] = [ - '#type' => 'link', - '#title' => $link['title'], - '#options' => $link['localized_options'], - '#url' => $link['url'], - ]; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(MenuPreprocess::class)->preprocessMenuLocalAction($variables); } /** diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php new file mode 100644 index 000000000000..4de3fde48a45 --- /dev/null +++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php @@ -0,0 +1,32 @@ +<?php + +namespace Drupal\Core\Breadcrumb; + +/** + * Breadcrumb theme preprocess. + * + * @internal + */ +class BreadcrumbPreprocess { + + /** + * Prepares variables for breadcrumb templates. + * + * Default template: breadcrumb.html.twig. + * + * @param array $variables + * An associative array containing: + * - links: A list of \Drupal\Core\Link objects which should be rendered. + */ + public function preprocessBreadcrumb(array &$variables): void { + $variables['breadcrumb'] = []; + /** @var \Drupal\Core\Link $link */ + foreach ($variables['links'] as $key => $link) { + $variables['breadcrumb'][$key] = [ + 'text' => $link->getText(), + 'url' => $link->getUrl()->toString(), + ]; + } + } + +} diff --git a/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php b/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php index 617699bda15f..a0824c65f17f 100644 --- a/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php +++ b/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php @@ -34,6 +34,10 @@ trait AutowireTrait { } if (!$container->has($service)) { + if ($parameter->allowsNull()) { + $args[] = NULL; + continue; + } throw new AutowiringFailedException($service, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s::_construct()", you should configure its value explicitly.', $service, $parameter->getName(), static::class)); } diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php index 514cd9e9f550..13aa3dcf3f78 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php @@ -3,8 +3,10 @@ namespace Drupal\Core\DependencyInjection\Compiler; use Drupal\Component\Utility\Reflection; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; @@ -178,7 +180,8 @@ class TaggedHandlersPass implements CompilerPassInterface { foreach ($this->tagCache[$tag] ?? [] as $id => $attributes) { // Validate the interface. $handler = $container->getDefinition($id); - if (!is_a($handler->getClass(), $interface, TRUE)) { + $class = $this->resolveDefinitionClass($handler, $container); + if (!is_a($class, $interface, TRUE)) { throw new LogicException("Service '$id' for consumer '$consumer_id' does not implement $interface."); } $handlers[$id] = $attributes[0]['priority'] ?? 0; @@ -249,4 +252,33 @@ class TaggedHandlersPass implements CompilerPassInterface { $consumer->addArgument(array_keys($handlers)); } + /** + * Resolves the definition class. + * + * @param \Symfony\Component\DependencyInjection\Definition $definition + * The service definition. + * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container + * The service container. + * + * @return class-string|null + * The resolved class-string or null if the class cannot be resolved. + */ + protected function resolveDefinitionClass(Definition $definition, ContainerBuilder $container): ?string { + $class = $definition->getClass(); + if ($class) { + return $class; + } + + if (!$definition instanceof ChildDefinition) { + return NULL; + } + + if (!$container->hasDefinition($definition->getParent())) { + return NULL; + } + + $parent = $container->getDefinition($definition->getParent()); + return $this->resolveDefinitionClass($parent, $container); + } + } diff --git a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php index 30a2220777ee..ff3384063ef5 100644 --- a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php @@ -204,9 +204,9 @@ interface QueryInterface extends AlterableInterface { * * @param array $headers * An array of headers of the same structure as described in - * template_preprocess_table(). Use a 'specifier' in place of a 'field' to - * specify what to sort on. This can be an entity or a field as described - * in condition(). + * \Drupal\Core\Theme\ThemePreprocess::preprocessTable(). Use a 'specifier' + * in place of a 'field' to specify what to sort on. This can be an entity + * or a field as described in condition(). * * @return $this */ diff --git a/core/lib/Drupal/Core/Menu/MenuPreprocess.php b/core/lib/Drupal/Core/Menu/MenuPreprocess.php new file mode 100644 index 000000000000..a0834704c531 --- /dev/null +++ b/core/lib/Drupal/Core/Menu/MenuPreprocess.php @@ -0,0 +1,73 @@ +<?php + +namespace Drupal\Core\Menu; + +/** + * Menu theme preprocess. + * + * @internal + */ +class MenuPreprocess { + + /** + * Prepares variables for single local task link templates. + * + * Default template: menu-local-task.html.twig. + * + * @param array $variables + * An associative array containing: + * - element: A render element containing: + * - #link: A menu link array with 'title', 'url', and (optionally) + * 'localized_options' keys. + * - #active: A boolean indicating whether the local task is active. + */ + public function preprocessMenuLocalTask(array &$variables): void { + $link = $variables['element']['#link']; + $link += [ + 'localized_options' => [], + ]; + $link_text = $link['title']; + + if (!empty($variables['element']['#active'])) { + $variables['is_active'] = TRUE; + } + + $link['localized_options']['set_active_class'] = TRUE; + + $variables['link'] = [ + '#type' => 'link', + '#title' => $link_text, + '#url' => $link['url'], + '#options' => $link['localized_options'], + ]; + } + + /** + * Prepares variables for single local action link templates. + * + * Default template: menu-local-action.html.twig. + * + * @param array $variables + * An associative array containing: + * - element: A render element containing: + * - #link: A menu link array with 'title', 'url', and (optionally) + * 'localized_options' keys. + */ + public function preprocessMenuLocalAction(array &$variables): void { + $link = $variables['element']['#link']; + $link += [ + 'localized_options' => [], + ]; + $link['localized_options']['attributes']['class'][] = 'button'; + $link['localized_options']['attributes']['class'][] = 'button-action'; + $link['localized_options']['set_active_class'] = TRUE; + + $variables['link'] = [ + '#type' => 'link', + '#title' => $link['title'], + '#options' => $link['localized_options'], + '#url' => $link['url'], + ]; + } + +} diff --git a/core/lib/Drupal/Core/Recipe/InputConfigurator.php b/core/lib/Drupal/Core/Recipe/InputConfigurator.php index cec8e588611c..3d1f871abbb9 100644 --- a/core/lib/Drupal/Core/Recipe/InputConfigurator.php +++ b/core/lib/Drupal/Core/Recipe/InputConfigurator.php @@ -171,12 +171,17 @@ final class InputConfigurator { * Returns the default value for an input definition. * * @param array $definition - * An input definition. Must contain a `source` element, which can be either - * 'config' or 'value'. If `source` is 'config', then there must also be a - * `config` element, which is a two-element indexed array containing - * (in order) the name of an extant config object, and a property path - * within that object. If `source` is 'value', then there must be a `value` - * element, which will be returned as-is. + * An input definition. Must contain a `source` element, which can be one + * of `config`, `env`, or `value`: + * - If `source` is `config`, there must also be a `config` element, which + * is a two-element indexed array containing (in order) the name of an + * extant config object, and a property path within that object. + * - If `source` is `env`, there must also be an `env` element, which is + * the name of an environment variable to return. The value will always + * be returned as a string. If the environment variable is not set, an + * empty string will be returned. + * - If `source` is 'value', then there must be a `value` element, which + * will be returned as-is. * * @return mixed * The default value. @@ -192,6 +197,17 @@ final class InputConfigurator { } return $config->get($key); } + elseif ($settings['source'] === 'env') { + // getenv() accepts NULL to return an array of all environment variables, + // but this makes no sense in a recipe. There is no valid situation where + // the name of the environment variable should be empty. + if (empty($settings['env'])) { + throw new \RuntimeException("The name of the environment variable cannot be empty."); + } + // If the variable doesn't exist, getenv() returns FALSE; we can represent + // that as an empty string. + return (string) getenv($settings['env']); + } return $settings['value']; } diff --git a/core/lib/Drupal/Core/Recipe/Recipe.php b/core/lib/Drupal/Core/Recipe/Recipe.php index 888f54e4f42c..1eb42b79343b 100644 --- a/core/lib/Drupal/Core/Recipe/Recipe.php +++ b/core/lib/Drupal/Core/Recipe/Recipe.php @@ -237,7 +237,7 @@ final class Recipe { 'default' => new Required([ new Collection([ 'source' => new Required([ - new Choice(['value', 'config']), + new Choice(['value', 'config', 'env']), ]), 'value' => new Optional(), 'config' => new Optional([ @@ -250,6 +250,10 @@ final class Recipe { ]), ]), ]), + 'env' => new Optional([ + new Type('string'), + new NotBlank(), + ]), ]), new Callback(self::validateDefaultValueDefinition(...)), ]), diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php index 0c63ce2ac495..5ce90c946550 100644 --- a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php +++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php @@ -88,7 +88,8 @@ class BareHtmlPageRenderer implements BareHtmlPageRendererInterface { * The page to attach to. */ public function systemPageAttachments(array &$page): void { - // Ensure the same CSS is loaded in template_preprocess_maintenance_page(). + // Ensure the same CSS is loaded in + // \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage(). $page['#attached']['library'][] = 'system/base'; if (\Drupal::service('router.admin_context')->isAdminRoute()) { $page['#attached']['library'][] = 'system/admin'; diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php index c9af86146829..957b868ed63e 100644 --- a/core/lib/Drupal/Core/Render/Element/Table.php +++ b/core/lib/Drupal/Core/Render/Element/Table.php @@ -402,7 +402,7 @@ class Table extends FormElementBase { * @return array * Associative array of rendered child elements for a table. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments() * @see drupal_attach_tabledrag() */ diff --git a/core/lib/Drupal/Core/Theme/ImagePreprocess.php b/core/lib/Drupal/Core/Theme/ImagePreprocess.php new file mode 100644 index 000000000000..2f43ffe4c089 --- /dev/null +++ b/core/lib/Drupal/Core/Theme/ImagePreprocess.php @@ -0,0 +1,90 @@ +<?php + +namespace Drupal\Core\Theme; + +use Drupal\Core\File\FileUrlGeneratorInterface; +use Drupal\Core\Template\AttributeHelper; + +/** + * Image theme preprocess. + * + * @internal + */ +class ImagePreprocess { + + public function __construct(protected FileUrlGeneratorInterface $fileUrlGenerator) { + } + + /** + * Prepares variables for image templates. + * + * Default template: image.html.twig. + * + * @param array $variables + * An associative array containing: + * - uri: Either the path of the image file (relative to base_path()) or a + * full URL. + * - width: The width of the image (if known). + * - height: The height of the image (if known). + * - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0 + * always require an alt attribute. The HTML 5 draft allows the alt + * attribute to be omitted in some cases. Therefore, this variable + * defaults to an empty string, but can be set to NULL for the attribute + * to be omitted. Usually, neither omission nor an empty string satisfies + * accessibility requirements, so it is strongly encouraged for code + * building variables for image.html.twig templates to pass a meaningful + * value for this variable. + * - https://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8 + * - https://www.w3.org/TR/xhtml1/dtds.html + * - http://dev.w3.org/html5/spec/Overview.html#alt + * - title: The title text is displayed when the image is hovered in some + * popular browsers. + * - attributes: Associative array of attributes to be placed in the img + * tag. + * - srcset: Array of multiple URIs and sizes/multipliers. + * - sizes: The sizes attribute for viewport-based selection of images. + * phpcs:ignore + * - http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2 + */ + public function preprocessImage(array &$variables): void { + if (!empty($variables['uri'])) { + $variables['attributes']['src'] = $this->fileUrlGenerator->generateString($variables['uri']); + } + // Generate a srcset attribute conforming to the spec at + // https://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset + if (!empty($variables['srcset'])) { + $srcset = []; + foreach ($variables['srcset'] as $src) { + // URI is mandatory. + $source = $this->fileUrlGenerator->generateString($src['uri']); + if (isset($src['width']) && !empty($src['width'])) { + $source .= ' ' . $src['width']; + } + elseif (isset($src['multiplier']) && !empty($src['multiplier'])) { + $source .= ' ' . $src['multiplier']; + } + $srcset[] = $source; + } + $variables['attributes']['srcset'] = implode(', ', $srcset); + } + + foreach (['width', 'height', 'alt', 'title', 'sizes'] as $key) { + if (isset($variables[$key])) { + // If the property has already been defined in the attributes, + // do not override, including NULL. + if (AttributeHelper::attributeExists($key, $variables['attributes'])) { + continue; + } + $variables['attributes'][$key] = $variables[$key]; + } + } + + // Without dimensions specified, layout shifts can occur, + // which are more noticeable on pages that take some time to load. + // As a result, only mark images as lazy load that have dimensions. + if (isset($variables['width'], $variables['height']) && !isset($variables['attributes']['loading'])) { + $variables['attributes']['loading'] = 'lazy'; + } + } + +} diff --git a/core/lib/Drupal/Core/Theme/ThemeCommonElements.php b/core/lib/Drupal/Core/Theme/ThemeCommonElements.php index 0d5e7c6638a5..50755c302a70 100644 --- a/core/lib/Drupal/Core/Theme/ThemeCommonElements.php +++ b/core/lib/Drupal/Core/Theme/ThemeCommonElements.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace Drupal\Core\Theme; +use Drupal\Core\Breadcrumb\BreadcrumbPreprocess; use Drupal\Core\Datetime\DatePreprocess; use Drupal\Core\Field\FieldPreprocess; +use Drupal\Core\Menu\MenuPreprocess; use Drupal\Core\Pager\PagerPreprocess; /** @@ -36,6 +38,7 @@ class ThemeCommonElements { ], 'region' => [ 'render element' => 'elements', + 'initial preprocess' => ThemePreprocess::class . ':preprocessRegion', ], 'time' => [ 'variables' => [ @@ -100,11 +103,13 @@ class ThemeCommonElements { 'srcset' => [], 'style_name' => NULL, ], + 'initial preprocess' => ImagePreprocess::class . ':preprocessImage', ], 'breadcrumb' => [ 'variables' => [ 'links' => [], ], + 'initial preprocess' => BreadcrumbPreprocess::class . ':preprocessBreadcrumb', ], 'table' => [ 'variables' => [ @@ -118,11 +123,13 @@ class ThemeCommonElements { 'responsive' => TRUE, 'empty' => '', ], + 'initial preprocess' => ThemePreprocess::class . ':preprocessTable', ], 'tablesort_indicator' => [ 'variables' => [ 'style' => NULL, ], + 'initial preprocess' => ThemePreprocess::class . ':preprocessTablesortIndicator', ], 'mark' => [ 'variables' => [ @@ -139,6 +146,7 @@ class ThemeCommonElements { 'empty' => NULL, 'context' => [], ], + 'initial preprocess' => ThemePreprocess::class . ':preprocessItemList', ], 'feed_icon' => [ 'variables' => [ @@ -157,12 +165,13 @@ class ThemeCommonElements { 'indentation' => [ 'variables' => ['size' => 1], ], - // From theme.maintenance.inc. 'maintenance_page' => [ 'render element' => 'page', + 'initial preprocess' => ThemePreprocess::class . ':preprocessMaintenancePage', ], 'install_page' => [ 'render element' => 'page', + 'initial preprocess' => ThemePreprocess::class . ':preprocessInstallPage', ], 'maintenance_task_list' => [ 'variables' => [ @@ -170,6 +179,7 @@ class ThemeCommonElements { 'active' => NULL, 'variant' => NULL, ], + 'initial preprocess' => ThemePreprocess::class . ':preprocessMaintenanceTaskList', ], 'authorize_report' => [ 'variables' => [ @@ -193,9 +203,11 @@ class ThemeCommonElements { ], 'menu_local_task' => [ 'render element' => 'element', + 'initial preprocess' => MenuPreprocess::class . ':preprocessMenuLocalTask', ], 'menu_local_action' => [ 'render element' => 'element', + 'initial preprocess' => MenuPreprocess::class . ':preprocessMenuLocalAction', ], 'menu_local_tasks' => [ 'variables' => [ diff --git a/core/lib/Drupal/Core/Theme/ThemePreprocess.php b/core/lib/Drupal/Core/Theme/ThemePreprocess.php index 2a93d5b06c91..f712e3d9ff98 100644 --- a/core/lib/Drupal/Core/Theme/ThemePreprocess.php +++ b/core/lib/Drupal/Core/Theme/ThemePreprocess.php @@ -5,15 +5,18 @@ namespace Drupal\Core\Theme; use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\Crypt; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Installer\InstallerKernel; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Path\CurrentPathStack; use Drupal\Core\Path\PathMatcherInterface; +use Drupal\Core\Render\Element; use Drupal\Core\Render\Markup; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Template\Attribute; use Drupal\Core\Url; +use Drupal\Core\Utility\TableSort; /** * Preprocess for common/core theme templates. @@ -356,4 +359,445 @@ class ThemePreprocess { } } + /** + * Prepares variables for maintenance page templates. + * + * Default template: maintenance-page.html.twig. + * + * @param array $variables + * An associative array containing: + * - content - An array of page content. + * + * @see system_page_attachments() + */ + public function preprocessMaintenancePage(array &$variables): void { + // @todo Rename the templates to page--maintenance + page--install. + $this->preprocessPage($variables); + + // @see system_page_attachments() + $variables['#attached']['library'][] = 'system/maintenance'; + + // Maintenance page and install page need branding info in variables because + // there is no blocks. + $site_config = $this->configFactory->get('system.site'); + $variables['logo'] = theme_get_setting('logo.url'); + $variables['site_name'] = $site_config->get('name'); + $variables['site_slogan'] = $site_config->get('slogan'); + + // Maintenance page and install page need page title in variable because + // there are no blocks. + $variables['title'] = $variables['page']['#title']; + } + + /** + * Prepares variables for install page templates. + * + * Default template: install-page.html.twig. + * + * @param array $variables + * An associative array containing: + * - content - An array of page content. + * + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() + */ + public function preprocessInstallPage(array &$variables): void { + $installer_active_task = NULL; + if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE === 'install' && InstallerKernel::installationAttempted()) { + $installer_active_task = $GLOBALS['install_state']['active_task']; + } + + $this->preprocessMaintenancePage($variables); + + // Override the site name that is displayed on the page, since Drupal is + // still in the process of being installed. + $distribution_name = drupal_install_profile_distribution_name(); + $variables['site_name'] = $distribution_name; + $variables['site_version'] = $installer_active_task ? drupal_install_profile_distribution_version() : ''; + } + + /** + * Prepares variables for region templates. + * + * Default template: region.html.twig. + * + * Prepares the values passed to the theme_region function to be passed into a + * pluggable template engine. Uses the region name to generate a template file + * suggestions. + * + * @param array $variables + * An associative array containing: + * - elements: An associative array containing properties of the region. + */ + public function preprocessRegion(array &$variables): void { + // Create the $content variable that templates expect. + $variables['content'] = $variables['elements']['#children']; + $variables['region'] = $variables['elements']['#region']; + } + + /** + * Prepares variables for table templates. + * + * Default template: table.html.twig. + * + * @param array $variables + * An associative array containing: + * - header: An array containing the table headers. Each element of the + * array can be either a localized string or an associative array with the + * following keys: + * - data: The localized title of the table column, as a string or render + * array. + * - field: The database field represented in the table column (required + * if user is to be able to sort on this column). + * - sort: A default sort order for this column ("asc" or "desc"). Only + * one column should be given a default sort order because table sorting + * only applies to one column at a time. + * - initial_click_sort: Set the initial sort of the column when clicked. + * Defaults to "asc". + * - class: An array of values for the 'class' attribute. In particular, + * the least important columns that can be hidden on narrow and medium + * width screens should have a 'priority-low' class, referenced with the + * RESPONSIVE_PRIORITY_LOW constant. Columns that should be shown on + * medium+ wide screens should be marked up with a class of + * 'priority-medium', referenced by with the RESPONSIVE_PRIORITY_MEDIUM + * constant. Themes may hide columns with one of these two classes on + * narrow viewports to save horizontal space. + * - Any HTML attributes, such as "colspan", to apply to the column header + * cell. + * - rows: An array of table rows. Every row is an array of cells, or an + * associative array with the following keys: + * - data: An array of cells. + * - Any HTML attributes, such as "class", to apply to the table row. + * - no_striping: A Boolean indicating that the row should receive no + * 'even / odd' styling. Defaults to FALSE. + * Each cell can be either a string or an associative array with the + * following keys: + * - data: The string or render array to display in the table cell. + * - header: Indicates this cell is a header. + * - Any HTML attributes, such as "colspan", to apply to the table cell. + * Here's an example for $rows: + * @code + * $rows = [ + * // Simple row + * [ + * 'Cell 1', 'Cell 2', 'Cell 3' + * ], + * // Row with attributes on the row and some of its cells. + * [ + * 'data' => ['Cell 1', ['data' => 'Cell 2', 'colspan' => 2]], 'class' => ['funky'] + * ], + * ]; + * @endcode + * - footer: An array of table rows which will be printed within a <tfoot> + * tag, in the same format as the rows element (see above). + * - attributes: An array of HTML attributes to apply to the table tag. + * - caption: A localized string to use for the <caption> tag. + * - colgroups: An array of column groups. Each element of the array can be + * either: + * - An array of columns, each of which is an associative array of HTML + * attributes applied to the <col> element. + * - An array of attributes applied to the <colgroup> element, which must + * include a "data" attribute. To add attributes to <col> elements, + * set the "data" attribute with an array of columns, each of which is + * an associative array of HTML attributes. + * Here's an example for $colgroup: + * @code + * $colgroup = [ + * // <colgroup> with one <col> element. + * [ + * [ + * 'class' => ['funky'], // Attribute for the <col> element. + * ], + * ], + * // <colgroup> with attributes and inner <col> elements. + * [ + * 'data' => [ + * [ + * 'class' => ['funky'], // Attribute for the <col> element. + * ], + * ], + * 'class' => ['jazzy'], // Attribute for the <colgroup> element. + * ], + * ]; + * @endcode + * These optional tags are used to group and set properties on columns + * within a table. For example, one may easily group three columns and + * apply same background style to all. + * - sticky: Use a "sticky" table header. + * - empty: The message to display in an extra row if table does not have + * any rows. + */ + public function preprocessTable(array &$variables): void { + // Format the table columns: + if (!empty($variables['colgroups'])) { + foreach ($variables['colgroups'] as &$colgroup) { + // Check if we're dealing with a simple or complex column + if (isset($colgroup['data'])) { + $cols = $colgroup['data']; + unset($colgroup['data']); + $colgroup_attributes = $colgroup; + } + else { + $cols = $colgroup; + $colgroup_attributes = []; + } + $colgroup = []; + $colgroup['attributes'] = new Attribute($colgroup_attributes); + $colgroup['cols'] = []; + + // Build columns. + if (is_array($cols) && !empty($cols)) { + foreach ($cols as $col_key => $col) { + $colgroup['cols'][$col_key]['attributes'] = new Attribute($col); + } + } + } + } + + // Build an associative array of responsive classes keyed by column. + $responsive_classes = []; + + // Format the table header: + $ts = []; + $header_columns = 0; + if (!empty($variables['header'])) { + $ts = TableSort::getContextFromRequest($variables['header'], \Drupal::request()); + + // Use a separate index with responsive classes as headers + // may be associative. + $responsive_index = -1; + foreach ($variables['header'] as $col_key => $cell) { + // Increase the responsive index. + $responsive_index++; + + if (!is_array($cell)) { + $header_columns++; + $cell_content = $cell; + $cell_attributes = new Attribute(); + $is_header = TRUE; + } + else { + if (isset($cell['colspan'])) { + $header_columns += $cell['colspan']; + } + else { + $header_columns++; + } + $cell_content = ''; + if (isset($cell['data'])) { + $cell_content = $cell['data']; + unset($cell['data']); + } + // Flag the cell as a header or not and remove the flag. + $is_header = $cell['header'] ?? TRUE; + unset($cell['header']); + + // Track responsive classes for each column as needed. Only the header + // cells for a column are marked up with the responsive classes by a + // module developer or themer. The responsive classes on the header + // cells must be transferred to the content cells. + if (!empty($cell['class']) && is_array($cell['class'])) { + if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) { + $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM; + } + elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) { + $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW; + } + } + + TableSort::header($cell_content, $cell, $variables['header'], $ts); + + // TableSort::header() removes the 'sort', 'initial_click_sort' and + // 'field' keys. + $cell_attributes = new Attribute($cell); + } + $variables['header'][$col_key] = []; + $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td'; + $variables['header'][$col_key]['attributes'] = $cell_attributes; + $variables['header'][$col_key]['content'] = $cell_content; + } + } + $variables['header_columns'] = $header_columns; + + // Rows and footer have the same structure. + $sections = ['rows' , 'footer']; + foreach ($sections as $section) { + if (!empty($variables[$section])) { + foreach ($variables[$section] as $row_key => $row) { + $cells = $row; + $row_attributes = []; + + // Check if we're dealing with a simple or complex row + if (isset($row['data'])) { + $cells = $row['data']; + $variables['no_striping'] = $row['no_striping'] ?? FALSE; + + // Set the attributes array and exclude 'data' and 'no_striping'. + $row_attributes = $row; + unset($row_attributes['data']); + unset($row_attributes['no_striping']); + } + + // Build row. + $variables[$section][$row_key] = []; + $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes); + $variables[$section][$row_key]['cells'] = []; + if (!empty($cells)) { + // Reset the responsive index. + $responsive_index = -1; + foreach ($cells as $col_key => $cell) { + // Increase the responsive index. + $responsive_index++; + + if (!is_array($cell)) { + $cell_content = $cell; + $cell_attributes = []; + $is_header = FALSE; + } + else { + $cell_content = ''; + if (isset($cell['data'])) { + $cell_content = $cell['data']; + unset($cell['data']); + } + + // Flag the cell as a header or not and remove the flag. + $is_header = !empty($cell['header']); + unset($cell['header']); + + $cell_attributes = $cell; + } + // Active table sort information. + if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) { + $variables[$section][$row_key]['cells'][$col_key]['active_table_sort'] = TRUE; + } + // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM + // class from header to cell as needed. + if (isset($responsive_classes[$responsive_index])) { + $cell_attributes['class'][] = $responsive_classes[$responsive_index]; + } + $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td'; + $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes); + $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content; + } + } + } + } + } + if (empty($variables['no_striping'])) { + $variables['attributes']['data-striping'] = 1; + } + } + + /** + * Prepares variables for tablesort indicators. + * + * Default template: tablesort-indicator.html.twig. + */ + public function preprocessTablesortIndicator(array &$variables): void { + $variables['#attached']['library'][] = 'core/drupal.tablesort'; + } + + /** + * Prepares variables for item list templates. + * + * Default template: item-list.html.twig. + * + * @param array $variables + * An associative array containing: + * - items: An array of items to be displayed in the list. Each item can be + * either a string or a render array. If #type, #theme, or #markup + * properties are not specified for child render arrays, they will be + * inherited from the parent list, allowing callers to specify larger + * nested lists without having to explicitly specify and repeat the + * render properties for all nested child lists. + * - title: A title to be prepended to the list. + * - list_type: The type of list to return (e.g. "ul", "ol"). + * - wrapper_attributes: HTML attributes to be applied to the list wrapper. + * + * @see https://www.drupal.org/node/1842756 + */ + public function preprocessItemList(array &$variables): void { + $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']); + $variables['#attached']['library'][] = 'core/drupal.item-list'; + foreach ($variables['items'] as &$item) { + $attributes = []; + // If the item value is an array, then it is a render array. + if (is_array($item)) { + // List items support attributes via the '#wrapper_attributes' property. + if (isset($item['#wrapper_attributes'])) { + $attributes = $item['#wrapper_attributes']; + } + // Determine whether there are any child elements in the item that are + // not fully-specified render arrays. If there are any, then the child + // elements present nested lists and we automatically inherit the render + // array properties of the current list to them. + foreach (Element::children($item) as $key) { + $child = &$item[$key]; + // If this child element does not specify how it can be rendered, then + // we need to inherit the render properties of the current list. + if (!isset($child['#type']) && !isset($child['#theme']) && !isset($child['#markup'])) { + // Since item-list.html.twig supports both strings and render arrays + // as items, the items of the nested list may have been specified as + // the child elements of the nested list, instead of #items. For + // convenience, we automatically move them into #items. + if (!isset($child['#items'])) { + // This is the same condition as in + // \Drupal\Core\Render\Element::children(), which cannot be used + // here, since it triggers an error on string values. + foreach ($child as $child_key => $child_value) { + if (is_int($child_key) || $child_key === '' || $child_key[0] !== '#') { + $child['#items'][$child_key] = $child_value; + unset($child[$child_key]); + } + } + } + // Lastly, inherit the original theme variables of the current list. + $child['#theme'] = $variables['theme_hook_original']; + $child['#list_type'] = $variables['list_type']; + } + } + } + + // Set the item's value and attributes for the template. + $item = [ + 'value' => $item, + 'attributes' => new Attribute($attributes), + ]; + } + } + + /** + * Prepares variables for maintenance task list templates. + * + * Default template: maintenance-task-list.html.twig. + * + * @param array $variables + * An associative array containing: + * - items: An associative array of maintenance tasks. + * It's the caller's responsibility to ensure this array's items contain + * no dangerous HTML such as <script> tags. + * - active: The key for the currently active maintenance task. + */ + public function preprocessMaintenanceTaskList(array &$variables): void { + $items = $variables['items']; + $active = $variables['active']; + + $done = isset($items[$active]) || $active == NULL; + foreach ($items as $k => $item) { + $variables['tasks'][$k]['item'] = $item; + $variables['tasks'][$k]['attributes'] = new Attribute(); + if ($active == $k) { + $variables['tasks'][$k]['attributes']->addClass('is-active'); + $variables['tasks'][$k]['status'] = $this->t('active'); + $done = FALSE; + } + else { + if ($done) { + $variables['tasks'][$k]['attributes']->addClass('done'); + $variables['tasks'][$k]['status'] = $this->t('done'); + } + } + } + } + } diff --git a/core/modules/block_content/src/BlockContentTypeInterface.php b/core/modules/block_content/src/BlockContentTypeInterface.php index aaf7957b8952..9c12709b2a55 100644 --- a/core/modules/block_content/src/BlockContentTypeInterface.php +++ b/core/modules/block_content/src/BlockContentTypeInterface.php @@ -3,19 +3,12 @@ namespace Drupal\block_content; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Entity\EntityDescriptionInterface; use Drupal\Core\Entity\RevisionableEntityBundleInterface; /** * Provides an interface defining a block type entity. */ -interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface { - - /** - * Returns the description of the block type. - * - * @return string - * The description of the type of this block. - */ - public function getDescription(); +interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface, EntityDescriptionInterface { } diff --git a/core/modules/block_content/src/Entity/BlockContentType.php b/core/modules/block_content/src/Entity/BlockContentType.php index ecbc6c3866d2..fa6fe3955038 100644 --- a/core/modules/block_content/src/Entity/BlockContentType.php +++ b/core/modules/block_content/src/Entity/BlockContentType.php @@ -100,6 +100,13 @@ class BlockContentType extends ConfigEntityBundleBase implements BlockContentTyp /** * {@inheritdoc} */ + public function setDescription($description): static { + return $this->set('description', $description); + } + + /** + * {@inheritdoc} + */ public function shouldCreateNewRevision() { return $this->revision; } diff --git a/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php b/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php index fba8362cd2cd..2ec82c2195b8 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php @@ -58,14 +58,26 @@ class BlockContentTypeTest extends BlockContentTestBase { } /** - * Tests the order of the block content types on the add page. + * Tests the block types on the block/add page. */ - public function testBlockContentAddPageOrder(): void { - $this->createBlockContentType(['id' => 'bundle_1', 'label' => 'Bundle 1']); - $this->createBlockContentType(['id' => 'bundle_2', 'label' => 'Aaa Bundle 2']); + public function testBlockContentAddPage(): void { + $this->createBlockContentType([ + 'id' => 'bundle_1', + 'label' => 'Bundle 1', + 'description' => 'Bundle 1 description', + ]); + $this->createBlockContentType([ + 'id' => 'bundle_2', + 'label' => 'Aaa Bundle 2', + 'description' => 'Bundle 2 description', + ]); $this->drupalLogin($this->adminUser); $this->drupalGet('block/add'); + // Ensure bundles are ordered by their label, not id. $this->assertSession()->pageTextMatches('/Aaa Bundle 2(.*)Bundle 1/'); + // Block type descriptions should display. + $this->assertSession()->pageTextContains('Bundle 1 description'); + $this->assertSession()->pageTextContains('Bundle 2 description'); } /** diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentTest.php index bb0186431e9a..fe07b3d5652e 100644 --- a/core/modules/block_content/tests/src/Kernel/BlockContentTest.php +++ b/core/modules/block_content/tests/src/Kernel/BlockContentTest.php @@ -38,6 +38,19 @@ class BlockContentTest extends KernelTestBase { } /** + * Tests BlockContentType functionality. + */ + public function testBlockContentType(): void { + $type = BlockContentType::create([ + 'id' => 'foo', + 'label' => 'Foo', + ]); + $this->assertSame('', $type->getDescription()); + $type->setDescription('Test description'); + $this->assertSame('Test description', $type->getDescription()); + } + + /** * Tests the editing links for BlockContentBlock. */ public function testOperationLinks(): void { diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index 8904dd925e57..67c049fa5057 100644 --- a/core/modules/field_ui/field_ui.module +++ b/core/modules/field_ui/field_ui.module @@ -5,6 +5,7 @@ */ use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Theme\ThemePreprocess; use Drupal\field_ui\FieldUI; /** @@ -18,7 +19,7 @@ use Drupal\field_ui\FieldUI; * rendered as a table. */ function template_preprocess_field_ui_table(&$variables): void { - template_preprocess_table($variables); + \Drupal::service(ThemePreprocess::class)->preprocessTable($variables); } /** diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php index 09e7c99f898c..f36dcea82063 100644 --- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php +++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php @@ -293,6 +293,38 @@ class MenuUiHooks { } /** + * Implements hook_ENTITY_TYPE_delete(). + */ + #[Hook('menu_delete')] + public function menuDelete(EntityInterface $entity): void { + if (!$this->entityTypeManager->hasDefinition('node_type')) { + return; + } + + // Remove the menu from content type third party settings. + $menu_id = $entity->id(); + $parent_prefix = $menu_id . ':'; + $storage = $this->entityTypeManager->getStorage('node_type'); + foreach ($storage->loadMultiple() as $content_type) { + $third_party_settings = $original_third_party_settings = $content_type->getThirdPartySettings('menu_ui'); + if (isset($third_party_settings['available_menus']) && in_array($menu_id, $third_party_settings['available_menus'])) { + $key = array_search($menu_id, $third_party_settings['available_menus']); + if ($key !== FALSE) { + unset($third_party_settings['available_menus'][$key]); + } + $content_type->setThirdPartySetting('menu_ui', 'available_menus', $third_party_settings['available_menus']); + } + if (isset($third_party_settings['parent']) && substr($third_party_settings['parent'], 0, strlen($parent_prefix)) == $parent_prefix) { + $third_party_settings['parent'] = ''; + $content_type->setThirdPartySetting('menu_ui', 'parent', $third_party_settings['parent']); + } + if ($third_party_settings != $original_third_party_settings) { + $content_type->save(); + } + } + } + + /** * Implements hook_system_breadcrumb_alter(). */ #[Hook('system_breadcrumb_alter')] diff --git a/core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php b/core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php new file mode 100644 index 000000000000..3bb81874387c --- /dev/null +++ b/core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\menu_ui\Kernel; + +use Drupal\KernelTests\KernelTestBase; +use Drupal\menu_ui\Hook\MenuUiHooks; +use Drupal\node\Entity\NodeType; +use Drupal\system\Entity\Menu; + +/** + * Tests the menu_delete hook. + * + * @group menu_ui + */ +class MenuDeleteTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['node', 'menu_ui', 'system']; + + /** + * @covers \Drupal\menu_ui\Hook\MenuUiHooks::menuDelete + * @dataProvider providerMenuDelete + */ + public function testMenuDelete($settings, $expected): void { + $menu = Menu::create([ + 'id' => 'mock', + 'label' => $this->randomMachineName(16), + 'description' => 'Description text', + ]); + $menu->save(); + $content_type = NodeType::create([ + 'status' => TRUE, + 'dependencies' => [ + 'module' => ['menu_ui'], + ], + 'third_party_settings' => [ + 'menu_ui' => $settings, + ], + 'name' => 'Test type', + 'type' => 'test_type', + ]); + $content_type->save(); + $this->assertEquals($settings['available_menus'], $content_type->getThirdPartySetting('menu_ui', 'available_menus')); + $this->assertEquals($settings['parent'], $content_type->getThirdPartySetting('menu_ui', 'parent')); + + $hooks = new MenuUiHooks(\Drupal::entityTypeManager()); + $hooks->menuDelete($menu); + + $content_type = NodeType::load('test_type'); + $this->assertEquals($expected['available_menus'], $content_type->getThirdPartySetting('menu_ui', 'available_menus')); + $this->assertEquals($expected['parent'], $content_type->getThirdPartySetting('menu_ui', 'parent')); + } + + /** + * Provides data for testMenuDelete(). + */ + public static function providerMenuDelete(): array { + return [ + [ + ['available_menus' => ['mock'], 'parent' => 'mock:'], + ['available_menus' => [], 'parent' => ''], + ], + [ + ['available_menus' => ['mock'], 'parent' => 'mock:menu_link_content:e0cd7689-016e-43e4-af8f-7ce82801ab95'], + ['available_menus' => [], 'parent' => ''], + ], + [ + ['available_menus' => ['main', 'mock'], 'parent' => 'mock:'], + ['available_menus' => ['main'], 'parent' => ''], + ], + [ + ['available_menus' => ['main'], 'parent' => 'main:'], + ['available_menus' => ['main'], 'parent' => 'main:'], + ], + [ + ['available_menus' => ['main'], 'parent' => 'main:menu_link_content:e0cd7689-016e-43e4-af8f-7ce82801ab95'], + ['available_menus' => ['main'], 'parent' => 'main:menu_link_content:e0cd7689-016e-43e4-af8f-7ce82801ab95'], + ], + ]; + } + +} diff --git a/core/modules/migrate/migrate.api.php b/core/modules/migrate/migrate.api.php index 1e58b0090ff2..5d2af7db180e 100644 --- a/core/modules/migrate/migrate.api.php +++ b/core/modules/migrate/migrate.api.php @@ -156,7 +156,7 @@ function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, Migr if ($migration->id() == 'd6_filter_formats') { $value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField(); if ($value) { - $row->setSourceProperty('settings:my_module:foo', unserialize($value)); + $row->setSourceProperty('settings:my_module:foo', unserialize($value, ['allowed_classes' => FALSE])); } } } @@ -179,7 +179,7 @@ function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, Migr function hook_migrate_MIGRATION_ID_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) { $value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField(); if ($value) { - $row->setSourceProperty('settings:my_module:foo', unserialize($value)); + $row->setSourceProperty('settings:my_module:foo', unserialize($value, ['allowed_classes' => FALSE])); } } diff --git a/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php b/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php index 9adf60b46ffe..30cc28562e8d 100644 --- a/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php +++ b/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php @@ -65,7 +65,7 @@ class AnnotatedClassDiscoveryAutomatedProviders extends AnnotatedClassDiscovery if (isset($cached['id'])) { // Explicitly unserialize this to create a new object // instance. - $definitions[$cached['id']] = unserialize($cached['content']); + $definitions[$cached['id']] = unserialize($cached['content'], ['allowed_classes' => FALSE]); } continue; } diff --git a/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php b/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php index dc70496282f3..a5351c748620 100644 --- a/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php +++ b/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php @@ -71,7 +71,8 @@ class ConfigEntity extends SqlBase { * {@inheritdoc} */ public function prepareRow(Row $row) { - $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'))); + // @see \Drupal\Core\Config\DatabaseStorage::decode() + $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'), ['allowed_classes' => FALSE])); return parent::prepareRow($row); } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php index 64ac2031a3fc..06d9bbccc974 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php @@ -173,7 +173,7 @@ abstract class DrupalSqlBase extends SqlBase implements DependentPluginInterface catch (\Exception) { $result = FALSE; } - return $result !== FALSE ? unserialize($result) : $default; + return $result !== FALSE ? unserialize($result, ['allowed_classes' => ['stdClass']]) : $default; } /** diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php index 0b83a099afb2..f99cd69e8983 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php @@ -129,7 +129,10 @@ class Variable extends DrupalSqlBase { // Create an ID field so we can record migration in the map table. // Arbitrarily, use the first variable name. $values['id'] = reset($this->variables); - return $values + array_map('unserialize', $this->prepareQuery()->execute()->fetchAllKeyed()); + return $values + array_map( + fn($data) => unserialize($data, ['allowed_classes' => FALSE]), + $this->prepareQuery()->execute()->fetchAllKeyed(), + ); } /** diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php index ee9b6268ebbf..e2bcbddc8d3c 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php @@ -66,7 +66,7 @@ class VariableMultiRow extends DrupalSqlBase { */ public function prepareRow(Row $row) { if ($value = $row->getSourceProperty('value')) { - $row->setSourceProperty('value', unserialize($value)); + $row->setSourceProperty('value', unserialize($value, ['allowed_classes' => FALSE])); } return parent::prepareRow($row); } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php index 11323f4bf7a0..f9e6ef61d75c 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php @@ -78,7 +78,7 @@ class VariableTranslation extends DrupalSqlBase { foreach ($result as $i18n_variable) { foreach ($values as $key => $value) { if ($values[$key]['language'] === $i18n_variable->language) { - $values[$key][$i18n_variable->name] = unserialize($i18n_variable->value); + $values[$key][$i18n_variable->name] = unserialize($i18n_variable->value, ['allowed_classes' => FALSE]); break; } } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php index 56121db822ac..82d638fa3d8f 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php @@ -78,7 +78,7 @@ class VariableTranslation extends DrupalSqlBase { foreach ($values as $key => $value) { if ($values[$key]['language'] === $variable_store['realm_key']) { if ($variable_store['serialized']) { - $values[$key][$variable_store['name']] = unserialize($variable_store['value']); + $values[$key][$variable_store['name']] = unserialize($variable_store['value'], ['allowed_classes' => FALSE]); break; } else { diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php index b1d409f4017a..1bb5b4f51bf5 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php @@ -88,7 +88,8 @@ class Config extends DrupalSqlBase { * {@inheritdoc} */ public function prepareRow(Row $row) { - $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'))); + // @see \Drupal\Core\Config\DatabaseStorage::decode() + $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'), ['allowed_classes' => FALSE])); return parent::prepareRow($row); } diff --git a/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php index a28dada3e9cc..c6e76787f316 100644 --- a/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php @@ -286,7 +286,7 @@ class ReviewForm extends MigrateUpgradeFormBase { foreach ($migration_state as $source_machine_name => $destination_modules) { $data = NULL; if (isset($this->systemData['module'][$source_machine_name]['info'])) { - $data = unserialize($this->systemData['module'][$source_machine_name]['info']); + $data = unserialize($this->systemData['module'][$source_machine_name]['info'], ['allowed_classes' => FALSE]); } $source_module_name = $data['name'] ?? $source_machine_name; // Get the names of all the destination modules. diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php index 759f59b50a84..a6633fed5e9f 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php @@ -269,7 +269,7 @@ abstract class MigrateUpgradeTestBase extends BrowserTestBase { // Convert $source_id into a keyless array so that // \Drupal\migrate\Plugin\migrate\id_map\Sql::getSourceHash() works as // expected. - $source_id_values = array_values(unserialize($source_id)); + $source_id_values = array_values(unserialize($source_id, ['allowed_classes' => FALSE])); $row = $id_map->getRowBySource($source_id_values); $destination = serialize($id_map->currentDestination()); $message = "Migration of $source_id to $destination as part of the {$migration->id()} migration. The source row status is " . $row['source_row_status']; diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 1fb4071ba47d..d68a04fcbc8a 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -125,8 +125,12 @@ function node_get_type_label(NodeInterface $node) { * * @return string * The node type description. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Use $node_type->getDescription() instead. + * @see https://www.drupal.org/node/3531945 */ function node_type_get_description(NodeTypeInterface $node_type) { + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Use $node_type->getDescription() instead. See https://www.drupal.org/node/3531945', E_USER_DEPRECATED); return $node_type->getDescription(); } diff --git a/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php b/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php index 18c87b11956b..4cc4405d4c81 100644 --- a/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php +++ b/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php @@ -4,13 +4,15 @@ declare(strict_types=1); namespace Drupal\Tests\package_manager\Build; +use PHPUnit\Framework\Attributes\Group; + /** * Tests installing packages in a stage directory. * - * @group package_manager - * @group #slow * @internal */ +#[Group('package_manager')] +#[Group('#slow')] class PackageInstallSubmoduleTest extends TemplateProjectTestBase { /** diff --git a/core/modules/package_manager/tests/src/Build/PackageInstallTest.php b/core/modules/package_manager/tests/src/Build/PackageInstallTest.php index 362343eaa91a..283c6aefa2cf 100644 --- a/core/modules/package_manager/tests/src/Build/PackageInstallTest.php +++ b/core/modules/package_manager/tests/src/Build/PackageInstallTest.php @@ -4,13 +4,15 @@ declare(strict_types=1); namespace Drupal\Tests\package_manager\Build; +use PHPUnit\Framework\Attributes\Group; + /** * Tests installing packages in a stage directory. * - * @group package_manager - * @group #slow * @internal */ +#[Group('package_manager')] +#[Group('#slow')] class PackageInstallTest extends TemplateProjectTestBase { /** diff --git a/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php b/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php index 2b9ef4aa894e..da6fc5eae320 100644 --- a/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php +++ b/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php @@ -5,14 +5,15 @@ declare(strict_types=1); namespace Drupal\Tests\package_manager\Build; use Drupal\package_manager_test_api\ControllerSandboxManager; +use PHPUnit\Framework\Attributes\Group; /** * Tests updating packages in a stage directory. * - * @group package_manager - * @group #slow * @internal */ +#[Group('package_manager')] +#[Group('#slow')] class PackageUpdateTest extends TemplateProjectTestBase { /** diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php index b340555c6289..fc253ac2f7cf 100644 --- a/core/modules/system/src/Controller/SystemController.php +++ b/core/modules/system/src/Controller/SystemController.php @@ -280,8 +280,6 @@ class SystemController extends ControllerBase { continue; } - // @todo Add logic for not displaying hidden modules in - // https://drupal.org/node/3117829. $module_name = $modules[$dependency]->info['name']; $theme->module_dependencies_list[$dependency] = $modules[$dependency]->status ? $this->t('@module_name', ['@module_name' => $module_name]) : $this->t('@module_name (<span class="admin-disabled">disabled</span>)', ['@module_name' => $module_name]); diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index 90dc9ead38b7..13297f3578c3 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -375,8 +375,6 @@ class ModulesListForm extends FormBase { // If this module requires other modules, add them to the array. /** @var \Drupal\Core\Extension\Dependency $dependency_object */ foreach ($module->requires as $dependency => $dependency_object) { - // @todo Add logic for not displaying hidden modules in - // https://drupal.org/node/3117829. if ($incompatible = $this->checkDependencyMessage($modules, $dependency, $dependency_object)) { $row['#requires'][$dependency] = $incompatible; $row['enable']['#disabled'] = TRUE; diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php index c999f37fe23f..97f76abe40e3 100644 --- a/core/modules/system/src/Form/ModulesUninstallForm.php +++ b/core/modules/system/src/Form/ModulesUninstallForm.php @@ -106,7 +106,8 @@ class ModulesUninstallForm extends FormBase { include_once DRUPAL_ROOT . '/core/includes/install.inc'; // Get a list of all available modules that can be uninstalled. - $uninstallable = array_filter($this->moduleExtensionList->getList(), function ($module) { + $modules = $this->moduleExtensionList->getList(); + $uninstallable = array_filter($modules, function ($module) { return empty($module->info['required']) && $module->status; }); @@ -199,7 +200,13 @@ class ModulesUninstallForm extends FormBase { // we can allow this module to be uninstalled. foreach (array_keys($module->required_by) as $dependent) { if ($this->updateRegistry->getInstalledVersion($dependent) !== $this->updateRegistry::SCHEMA_UNINSTALLED) { - $form['modules'][$module->getName()]['#required_by'][] = $dependent; + $module_name = $modules[$dependent]->info['name']; + if ($dependent != strtolower(str_replace(' ', '_', $module_name))) { + $form['modules'][$module->getName()]['#required_by'][] = $module_name . " (" . $dependent . ")"; + } + else { + $form['modules'][$module->getName()]['#required_by'][] = $module_name; + } $form['uninstall'][$module->getName()]['#disabled'] = TRUE; } } diff --git a/core/modules/system/src/Hook/PageAttachmentsHook.php b/core/modules/system/src/Hook/PageAttachmentsHook.php index fb6335f90c31..3f271571ede1 100644 --- a/core/modules/system/src/Hook/PageAttachmentsHook.php +++ b/core/modules/system/src/Hook/PageAttachmentsHook.php @@ -19,7 +19,7 @@ final class PageAttachmentsHook { /** * Implements hook_page_attachments(). * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() * @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter */ #[Hook('page_attachments')] diff --git a/core/modules/system/templates/details.html.twig b/core/modules/system/templates/details.html.twig index 20e4ea7193e3..dcb1cf354ce5 100644 --- a/core/modules/system/templates/details.html.twig +++ b/core/modules/system/templates/details.html.twig @@ -34,7 +34,11 @@ </div> {% endif %} - {{ description }} + {%- if description -%} + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div{{ description_attributes }}>{{ description }}</div> + {%- endif -%} + {{ children }} {{ value }} </details> diff --git a/core/modules/system/templates/image.html.twig b/core/modules/system/templates/image.html.twig index 6411eaa3d07b..1f6d19d6c3e7 100644 --- a/core/modules/system/templates/image.html.twig +++ b/core/modules/system/templates/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() * * @ingroup themeable */ diff --git a/core/modules/system/templates/install-page.html.twig b/core/modules/system/templates/install-page.html.twig index f6091fd3b956..d9144e6a154b 100644 --- a/core/modules/system/templates/install-page.html.twig +++ b/core/modules/system/templates/install-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_install_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessInstallPage() * * @ingroup themeable */ diff --git a/core/modules/system/templates/item-list.html.twig b/core/modules/system/templates/item-list.html.twig index 1462cf41ae0f..c2babdab978e 100644 --- a/core/modules/system/templates/item-list.html.twig +++ b/core/modules/system/templates/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() * * @ingroup themeable */ diff --git a/core/modules/system/templates/maintenance-page.html.twig b/core/modules/system/templates/maintenance-page.html.twig index 748ed5a3aa4a..06fb6065f7a4 100644 --- a/core/modules/system/templates/maintenance-page.html.twig +++ b/core/modules/system/templates/maintenance-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() * * @ingroup themeable */ diff --git a/core/modules/system/templates/menu-local-action.html.twig b/core/modules/system/templates/menu-local-action.html.twig index 0eb03a9534ab..e0280d5fcbc0 100644 --- a/core/modules/system/templates/menu-local-action.html.twig +++ b/core/modules/system/templates/menu-local-action.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the wrapper element. * - link: A rendered link element. * - * @see template_preprocess_menu_local_action() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalAction() * * @ingroup themeable */ diff --git a/core/modules/system/templates/menu-local-task.html.twig b/core/modules/system/templates/menu-local-task.html.twig index ec02a8d530c4..b2a743940a77 100644 --- a/core/modules/system/templates/menu-local-task.html.twig +++ b/core/modules/system/templates/menu-local-task.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() * * @ingroup themeable */ diff --git a/core/modules/system/templates/region.html.twig b/core/modules/system/templates/region.html.twig index 219e14b0a4be..ddcaaa192df4 100644 --- a/core/modules/system/templates/region.html.twig +++ b/core/modules/system/templates/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() * * @ingroup themeable */ diff --git a/core/modules/system/templates/table.html.twig b/core/modules/system/templates/table.html.twig index cfcb0bf976c0..6a73cc1152a8 100644 --- a/core/modules/system/templates/table.html.twig +++ b/core/modules/system/templates/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() * * @ingroup themeable */ diff --git a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install b/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install deleted file mode 100644 index 483a1d01717e..000000000000 --- a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install +++ /dev/null @@ -1,24 +0,0 @@ -<?php - -/** - * @file - * Experimental Test Requirements module to test hook_requirements(). - */ - -declare(strict_types=1); - -use Drupal\Core\Extension\Requirement\RequirementSeverity; - -/** - * Implements hook_requirements(). - */ -function experimental_module_requirements_test_requirements(): array { - $requirements = []; - if (\Drupal::state()->get('experimental_module_requirements_test_requirements', FALSE)) { - $requirements['experimental_module_requirements_test_requirements'] = [ - 'severity' => RequirementSeverity::Error, - 'description' => t('The Experimental Test Requirements module can not be installed.'), - ]; - } - return $requirements; -} diff --git a/core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php b/core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php new file mode 100644 index 000000000000..53834f77c0e1 --- /dev/null +++ b/core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\experimental_module_requirements_test\Install\Requirements; + +use Drupal\Core\Extension\InstallRequirementsInterface; +use Drupal\Core\Extension\Requirement\RequirementSeverity; + +/** + * Install time requirements for the Experimental Requirements Test module. + */ +class ExperimentalModuleRequirementsTestRequirements implements InstallRequirementsInterface { + + /** + * {@inheritdoc} + */ + public static function getRequirements(): array { + $requirements = []; + if (\Drupal::state()->get('experimental_module_requirements_test_requirements', FALSE)) { + $requirements['experimental_module_requirements_test_requirements'] = [ + 'severity' => RequirementSeverity::Error, + 'description' => t('The Experimental Test Requirements module can not be installed.'), + ]; + } + return $requirements; + } + +} diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php index 9babda83ddc0..226eb705802d 100644 --- a/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php @@ -48,6 +48,11 @@ class FormTestGroupDetailsForm extends FormBase { 'data-summary-attribute' => 'test', ], ]; + $form['description_attributes'] = [ + '#type' => 'details', + '#title' => 'Details element with description', + '#description' => 'I am a details description', + ]; return $form; } diff --git a/core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php new file mode 100644 index 000000000000..b57e4c883972 --- /dev/null +++ b/core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\system_test\Controller; + +use Drupal\Core\Controller\ControllerBase; +use Drupal\dblog\Logger\DbLog; +use Symfony\Component\DependencyInjection\Attribute\Autowire; + +/** + * A controller that specifies an optional dependency. + */ +class OptionalServiceSystemTestController extends ControllerBase { + + public function __construct( + #[Autowire('logger.dblog')] + public readonly ?DbLog $dbLog, + ) {} + +} diff --git a/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php b/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php index 6c6f67d77eb8..1d953ff8c337 100644 --- a/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php +++ b/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php @@ -79,27 +79,10 @@ class ThemeTestSubscriber implements EventSubscriberInterface { } /** - * Ensures that the theme registry was not initialized. - */ - public function onView(RequestEvent $event) { - $current_route = $this->currentRouteMatch->getRouteName(); - $entity_autocomplete_route = [ - 'system.entity_autocomplete', - ]; - - if (in_array($current_route, $entity_autocomplete_route)) { - if ($this->container->initialized('theme.registry')) { - throw new \Exception('registry initialized'); - } - } - } - - /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { $events[KernelEvents::REQUEST][] = ['onRequest']; - $events[KernelEvents::VIEW][] = ['onView', -1000]; return $events; } diff --git a/core/modules/system/tests/src/Functional/Form/ElementTest.php b/core/modules/system/tests/src/Functional/Form/ElementTest.php index 4a9755fac7f5..0ebf9e4ce774 100644 --- a/core/modules/system/tests/src/Functional/Form/ElementTest.php +++ b/core/modules/system/tests/src/Functional/Form/ElementTest.php @@ -38,6 +38,7 @@ class ElementTest extends BrowserTestBase { $this->testFormAutocomplete(); $this->testFormElementErrors(); $this->testDetailsSummaryAttributes(); + $this->testDetailsDescriptionAttributes(); } /** @@ -230,4 +231,13 @@ class ElementTest extends BrowserTestBase { $this->assertSession()->elementExists('css', 'summary[data-summary-attribute="test"]'); } + /** + * Tests description attributes of details. + */ + protected function testDetailsDescriptionAttributes(): void { + $this->drupalGet('form-test/group-details'); + $this->assertSession()->elementExists('css', 'details[aria-describedby="edit-description-attributes--description"]'); + $this->assertSession()->elementExists('css', 'div[id="edit-description-attributes--description"]'); + } + } diff --git a/core/modules/system/tests/src/Functional/Module/UninstallTest.php b/core/modules/system/tests/src/Functional/Module/UninstallTest.php index aeace5bb488b..6d1b93cc50db 100644 --- a/core/modules/system/tests/src/Functional/Module/UninstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/UninstallTest.php @@ -22,7 +22,15 @@ class UninstallTest extends BrowserTestBase { /** * {@inheritdoc} */ - protected static $modules = ['module_test', 'user', 'views', 'node']; + protected static $modules = [ + 'ckeditor5', + 'filter', + 'module_test', + 'node', + 'user', + 'views', + 'views_ui', + ]; /** * {@inheritdoc} @@ -118,6 +126,13 @@ class UninstallTest extends BrowserTestBase { // Delete the node to allow node to be uninstalled. $node->delete(); + // Ensure dependent module full names are shown. + $this->assertSession()->pageTextContains('Required by: Views UI'); + // Ensure matching machine names do not display. + $this->assertSession()->pageTextNotContains('Required by: Views UI (views_ui)'); + // Ensure machine names that do not match do display. + $this->assertSession()->pageTextContains('Text Editor (editor)'); + // Uninstall module_test. $edit = []; $edit['uninstall[module_test]'] = TRUE; diff --git a/core/modules/system/tests/src/Functional/Theme/FastTest.php b/core/modules/system/tests/src/Functional/Theme/FastTest.php deleted file mode 100644 index 5cbb7d8f277b..000000000000 --- a/core/modules/system/tests/src/Functional/Theme/FastTest.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\Tests\system\Functional\Theme; - -use Drupal\Tests\BrowserTestBase; -use Drupal\user\Entity\User; - -/** - * Tests autocompletion not loading registry. - * - * @group Theme - */ -class FastTest extends BrowserTestBase { - - /** - * {@inheritdoc} - */ - protected static $modules = ['theme_test']; - - /** - * {@inheritdoc} - */ - protected $defaultTheme = 'stark'; - - /** - * User allowed to access use profiles. - * - * @var \Drupal\user\Entity\User - */ - protected User $account; - - /** - * {@inheritdoc} - */ - protected function setUp(): void { - parent::setUp(); - $this->account = $this->drupalCreateUser(['access user profiles']); - } - - /** - * Tests access to user autocompletion and verify the correct results. - */ - public function testUserAutocomplete(): void { - $this->drupalLogin($this->account); - $this->drupalGet('user/autocomplete', ['query' => ['q' => $this->account->getAccountName()]]); - $this->assertSession()->responseContains($this->account->getAccountName()); - $this->assertSession()->pageTextNotContains('registry initialized'); - } - -} diff --git a/core/modules/user/src/Hook/UserRequirements.php b/core/modules/user/src/Hook/UserRequirements.php index f317ced58bc4..46155e55e3cb 100644 --- a/core/modules/user/src/Hook/UserRequirements.php +++ b/core/modules/user/src/Hook/UserRequirements.php @@ -49,6 +49,7 @@ class UserRequirements { $query->addExpression('LOWER(mail)', 'lower_mail'); $query->isNotNull('mail'); $query->groupBy('lower_mail'); + $query->groupBy('langcode'); $query->having('COUNT(uid) > :matches', [':matches' => 1]); $conflicts = $query->countQuery()->execute()->fetchField(); diff --git a/core/modules/user/tests/src/Kernel/UserRequirementsTest.php b/core/modules/user/tests/src/Kernel/UserRequirementsTest.php index 146ab9c8b904..746370a15d61 100644 --- a/core/modules/user/tests/src/Kernel/UserRequirementsTest.php +++ b/core/modules/user/tests/src/Kernel/UserRequirementsTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\Tests\user\Kernel; use Drupal\KernelTests\KernelTestBase; +use Drupal\language\Entity\ConfigurableLanguage; use Drupal\Tests\user\Traits\UserCreationTrait; /** @@ -70,4 +71,21 @@ class UserRequirementsTest extends KernelTestBase { $this->assertArrayNotHasKey('conflicting emails', $output); } + /** + * Tests that the requirements check does not flag user translations. + */ + public function testTranslatedUserEmail(): void { + \Drupal::service('module_installer')->install(['language']); + ConfigurableLanguage::createFromLangcode('is')->save(); + + $output = $this->moduleHandler->invoke('user', 'runtime_requirements'); + $this->assertArrayNotHasKey('conflicting emails', $output); + + $user = $this->createUser([], 'User A', FALSE, ['mail' => 'unique@example.com']); + $user->addTranslation('is')->save(); + + $output = $this->moduleHandler->invoke('user', 'runtime_requirements'); + $this->assertArrayNotHasKey('conflicting emails', $output); + } + } diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig index e9928fd77660..4940f047477e 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig @@ -17,7 +17,7 @@ * results, the following data is set: * - plugin: The search plugin ID, for example "node_search". * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig index 20541b0b7e66..6e7b8e317b1d 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig index cdfe0bff7e73..d7e6459bd4a4 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() */ #} <table{{ attributes }}> diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig index 31f782bb60a8..90d955c180a6 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() */ #} {% diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig index 3f6e8ddf05c9..e2ae382b56fa 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig @@ -32,7 +32,8 @@ </div> {% endif %} {%- if description -%} - <div class="details-description">{{ description }}</div> + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div {{ description_attributes.addClass(['details-description']) }}>{{ description }}</div> {%- endif -%} {%- if children -%} {{ children }} diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig index 7463b0238ca3..edd2783619b8 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} <div class="layout-container"> diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig index 95e71cec37e4..a4e8cc0af72a 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig b/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig index b1d11d5458c5..99122f1668e0 100644 --- a/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} <li{{ attributes.addClass('tabs__tab', is_active ? 'is-active') }}>{{ link }}</li> diff --git a/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig b/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig index a0b90e94cb6c..d55f70c54ae9 100644 --- a/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/scripts/dev/commit-code-check.sh b/core/scripts/dev/commit-code-check.sh index 541692236263..42d2ac670ad8 100755 --- a/core/scripts/dev/commit-code-check.sh +++ b/core/scripts/dev/commit-code-check.sh @@ -25,6 +25,7 @@ contains_element() { return 1 } +MEMORY_UNLIMITED=0 CACHED=0 DRUPALCI=0 BRANCH="" @@ -58,12 +59,22 @@ while test $# -gt 0; do DRUPALCI=1 shift ;; + --memory-unlimited) + MEMORY_UNLIMITED=1 + shift + ;; *) break ;; esac done +memory_limit="" + +if [[ "$MEMORY_UNLIMITED" == "1" ]]; then + memory_limit="--memory-limit=-1" +fi + # Set up variables to make colored output simple. Color output is disabled on # DrupalCI because it is breaks reporting. # @todo https://www.drupal.org/project/drupalci_testbot/issues/3181869 @@ -238,11 +249,11 @@ printf "\n" # APCu is disabled to ensure that the composer classmap is not corrupted. if [[ $PHPSTAN_DIST_FILE_CHANGED == "1" ]] || [[ "$DRUPALCI" == "1" ]]; then printf "\nRunning PHPStan on *all* files.\n" - php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan.neon.dist" + php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan.neon.dist" $memory_limit else # Only run PHPStan on changed files locally. printf "\nRunning PHPStan on changed files.\n" - php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan-partial.neon" $ABS_FILES + php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan-partial.neon" $ABS_FILES $memory_limit fi if [ "$?" -ne "0" ]; then diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php index d1c07c20124e..1211cfb351b7 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests Ajax callbacks on FAPI elements. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxCallbacksTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php index b2ae9386bd0a..1eff2c19bbee 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\Core\Url; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the usage of form caching for AJAX forms. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxFormCacheTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php index c9a4b3ef2722..347e8ac86f1a 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the Ajax image buttons work with key press events. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxFormImageButtonTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php index a80a627a89cc..c11430fdebff 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Performs tests on AJAX forms in cached pages. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxFormPageCacheTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php index a8282b2b6cfe..6618ec6acede 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests that form elements in groups work correctly with AJAX. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxInGroupTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php index 180381e45c1b..3f4543996f1f 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php @@ -8,12 +8,12 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\Tests\field_ui\Traits\FieldUiTestTrait; use Drupal\Tests\file\Functional\FileFieldCreationTrait; use Drupal\Tests\TestFileCreationTrait; +use PHPUnit\Framework\Attributes\Group; /** * Tests maintenance message during an AJAX call. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxMaintenanceModeTest extends WebDriverTestBase { use FieldUiTestTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php index 03bd477123c1..284a3e35fa9b 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\Component\Utility\UrlHelper; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests AJAX responses. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php index cc81d142d453..877f255a6591 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Performs tests on AJAX framework commands. - * - * @group Ajax */ +#[Group('Ajax')] class CommandsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php index be434633e5ef..beab4c6a242c 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php @@ -7,14 +7,13 @@ namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\ajax_test\Controller\AjaxTestController; use Drupal\Core\Ajax\OpenModalDialogWithUrl; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; // cspell:ignore testdialog - /** * Performs tests on opening and manipulating dialogs via AJAX commands. - * - * @group Ajax */ +#[Group('Ajax')] class DialogTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php index 60fd1f40b95f..5040430abae4 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Various tests of AJAX behavior. - * - * @group Ajax */ +#[Group('Ajax')] class ElementValidationTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php index 7a9b91c52b90..2a27a09a987a 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests setting focus via AJAX command. - * - * @group Ajax */ +#[Group('Ajax')] class FocusFirstCommandTest extends WebDriverTestBase { /** * {@inheritdoc} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php index 2ee6b8ddb2ce..68a9683a4906 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php @@ -5,12 +5,13 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; /** * Tests that form values are properly delivered to AJAX callbacks. - * - * @group Ajax */ +#[Group('Ajax')] class FormValuesTest extends WebDriverTestBase { /** @@ -33,9 +34,8 @@ class FormValuesTest extends WebDriverTestBase { /** * Submits forms with select and checkbox elements via Ajax. - * - * @dataProvider formModeProvider */ + #[DataProvider('formModeProvider')] public function testSimpleAjaxFormValue($form_mode): void { $this->drupalGet('ajax_forms_test_get_form'); diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php index 69b49916163e..f18bf953dd82 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\ExpectationFailedException; /** * Tests adding messages via AJAX command. - * - * @group Ajax */ +#[Group('Ajax')] class MessageCommandTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php index 6a0026976a90..40ed7ac76704 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php @@ -8,12 +8,12 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests AJAX-enabled forms when multiple instances of the form are on a page. - * - * @group Ajax */ +#[Group('Ajax')] class MultiFormTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php index 6b2a17d4c6eb..92905a0c08ba 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\hold_test\HoldTestHelper; +use PHPUnit\Framework\Attributes\Group; /** * Tests the throbber. - * - * @group Ajax */ +#[Group('Ajax')] class ThrobberTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php b/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php index 0a61c5f03cbe..adade6bc4208 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php @@ -4,12 +4,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; + /** * Tests that unnecessary or untracked XHRs will cause a test failure. - * - * @group javascript - * @group legacy */ +#[Group('javascript')] +#[IgnoreDeprecations] class AjaxWaitTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php b/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php index 2f11f7525eed..74c88ac48eed 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Attributes\Group; /** * Tests if we can execute JavaScript in the browser. - * - * @group javascript */ +#[Group('javascript')] class BrowserWithJavascriptTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php index 5b77de188aa2..95847a5d2290 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Components; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the correct rendering of components. - * - * @group sdc */ +#[Group('sdc')] class ComponentRenderTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php index f16b300441de..3684ec3c4976 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Core; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Test race condition for CSRF tokens for simultaneous requests. - * - * @group Session */ +#[Group('Session')] class CsrfTokenRaceTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php index 53b2c5dcb7a5..6e615f4b4f91 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php @@ -10,12 +10,12 @@ use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the 'timestamp' formatter when is used with time difference setting. - * - * @group Field */ +#[Group('Field')] class TimestampFormatterWithTimeDiffTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php index 0b5176182390..f35b5c72171b 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php @@ -7,12 +7,12 @@ namespace Drupal\FunctionalJavascriptTests\Core\Field; use Drupal\entity_test\Entity\EntityTest; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\views\Tests\ViewTestData; +use PHPUnit\Framework\Attributes\Group; /** * Tests the timestamp formatter used with time difference setting in views. - * - * @group Field */ +#[Group('Field')] class TimestampFormatterWithTimeDiffViewsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php index 7c8af15a2e31..f538690daf99 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Core\Form; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests for form grouping elements. - * - * @group form */ +#[Group('form')] class FormGroupingElementsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php index 189af6e49e61..ac41f01653fe 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php @@ -6,6 +6,7 @@ namespace Drupal\FunctionalJavascriptTests\Core\Form; use Drupal\filter\Entity\FilterFormat; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the state of elements based on another elements. @@ -14,9 +15,8 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase; * module under 'system' (core/modules/system/tests/module/form_test). * * @see Drupal\form_test\Form\JavascriptStatesForm - * - * @group javascript */ +#[Group('javascript')] class JavascriptStatesTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php index ce4cf583cd8c..4acec8e11102 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Core; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\js_message_test\Controller\JSMessageTestController; +use PHPUnit\Framework\Attributes\Group; /** * Tests core/drupal.message library. - * - * @group Javascript */ +#[Group('Javascript')] class JsMessageTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php index 3df154e90500..b990be12549c 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Core; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests for the machine name field. - * - * @group field */ +#[Group('field')] class MachineNameTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php index 8adf7a5685a5..94387c04633e 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Core\Session; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\menu_link_content\Entity\MenuLinkContent; +use PHPUnit\Framework\Attributes\Group; /** * Tests that sessions don't expire. - * - * @group session */ +#[Group('session')] class SessionTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php index abc6fd4c5ca4..2085b5518419 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Dialog; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\IgnoreDeprecations; /** * Tests jQuery events deprecations. - * - * @group dialog */ +#[Group('dialog')] class DialogDeprecationsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php index ed73ac1ff706..abda27ed3b70 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Dialog; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the JavaScript functionality of the dialog position. - * - * @group dialog */ +#[Group('dialog')] class DialogPositionTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php index 971af632a993..18fe44d8c4c3 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php @@ -4,18 +4,18 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\EntityReference; -use Drupal\FunctionalJavascriptTests\WebDriverTestBase; -use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\entity_test\Entity\EntityTest; +use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait; use Drupal\Tests\node\Traits\ContentTypeCreationTrait; use Drupal\Tests\node\Traits\NodeCreationTrait; +use PHPUnit\Framework\Attributes\Group; /** * Tests the output of entity reference autocomplete widgets. - * - * @group entity_reference */ +#[Group('entity_reference')] class EntityReferenceAutocompleteWidgetTest extends WebDriverTestBase { use ContentTypeCreationTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php index e45026d8960a..cb2fce7a36c1 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php @@ -4,12 +4,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; + /** * Tests Javascript deprecation notices. - * - * @group javascript - * @group legacy */ +#[Group('javascript')] +#[IgnoreDeprecations] class JavascriptDeprecationTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php index a310d4c9e6e4..c67e27551c07 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php @@ -4,11 +4,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; +use PHPUnit\Framework\Attributes\Group; + /** * Tests that Drupal.throwError can be suppressed to allow a test to pass. - * - * @group javascript */ +#[Group('javascript')] class JavascriptErrorsSuppressionTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php index 3fb355523406..5f96e95dc2bb 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Attributes\Group; /** * Tests that Drupal.throwError will cause a test failure. - * - * @group javascript */ +#[Group('javascript')] class JavascriptErrorsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php index 31ec8c91675b..8054a7395997 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php @@ -4,11 +4,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; +use PHPUnit\Framework\Attributes\Group; + /** * Tests Drupal settings retrieval in WebDriverTestBase tests. - * - * @group javascript */ +#[Group('javascript')] class JavascriptGetDrupalSettingsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php index ad9ba4f89d8e..19cc4bf3a401 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php @@ -5,15 +5,15 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\MachineName; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; - use Drupal\language\Entity\ConfigurableLanguage; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; /** * Tests the machine name transliteration functionality. - * - * @group javascript - * @group #slow */ +#[Group('javascript')] +#[Group('#slow')] class MachineNameTransliterationTest extends WebDriverTestBase { /** @@ -46,9 +46,8 @@ class MachineNameTransliterationTest extends WebDriverTestBase { /** * Test for machine name transliteration functionality. - * - * @dataProvider machineNameInputOutput */ + #[DataProvider('machineNameInputOutput')] public function testMachineNameTransliterations($langcode, $input, $output): void { $page = $this->getSession()->getPage(); if ($langcode !== 'en') { diff --git a/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php b/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php index a9350a1976e6..0a14594369b6 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php @@ -7,12 +7,12 @@ namespace Drupal\FunctionalJavascriptTests\TableDrag; use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\ExpectationException; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests draggable table. - * - * @group javascript */ +#[Group('javascript')] class TableDragTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php index 1ab9d65b3be7..e598f5cc04a4 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php @@ -6,16 +6,18 @@ namespace Drupal\FunctionalJavascriptTests\Tests; use Behat\Mink\Driver\Selenium2Driver; use Drupal\entity_test\Entity\EntityTest; +use Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\Tests\file\Functional\FileFieldCreationTrait; use Drupal\Tests\TestFileCreationTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; /** * Tests the DrupalSelenium2Driver methods. - * - * @coversDefaultClass \Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver - * @group javascript */ +#[CoversClass(DrupalSelenium2Driver::class)] +#[Group('javascript')] class DrupalSelenium2DriverTest extends WebDriverTestBase { use TestFileCreationTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php index 866f04e3a72a..a8018c7b013a 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Tests; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; use WebDriver\Exception; /** * Tests fault tolerant interactions. - * - * @group javascript */ +#[Group('javascript')] class JSInteractionTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php index f200ed28ac98..6388bd0844e1 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php @@ -8,12 +8,12 @@ use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\ElementHtmlException; use Drupal\Component\Utility\Timer; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests for the JSWebAssert class. - * - * @group javascript */ +#[Group('javascript')] class JSWebAssertTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php index dbc7874c735f..62ae39c4db49 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php @@ -5,14 +5,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\Tests\block\FunctionalJavascript\BlockFilterTest; +use PHPUnit\Framework\Attributes\Group; /** * Runs BlockFilterTest in Claro. * - * @group block - * * @see \Drupal\Tests\block\FunctionalJavascript\BlockFilterTest. */ +#[Group('block')] class ClaroBlockFilterTest extends BlockFilterTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php index 85476468d815..c8e018ebfbe5 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php @@ -6,14 +6,14 @@ namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\entity_test\EntityTestHelper; use Drupal\Tests\field_ui\FunctionalJavascript\EntityDisplayTest; +use PHPUnit\Framework\Attributes\Group; /** * Runs EntityDisplayTest in Claro. * - * @group claro - * * @see \Drupal\Tests\field_ui\FunctionalJavascript\EntityDisplayTest. */ +#[Group('claro')] class ClaroEntityDisplayTest extends EntityDisplayTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php index 32f6256396b3..930c7f963a7b 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php @@ -5,14 +5,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\Tests\menu_ui\FunctionalJavascript\MenuUiJavascriptTest; +use PHPUnit\Framework\Attributes\Group; /** * Runs MenuUiJavascriptTest in Claro. * - * @group claro - * * @see \Drupal\Tests\menu_ui\FunctionalJavascript\MenuUiJavascriptTest; */ +#[Group('claro')] class ClaroMenuUiJavascriptTest extends MenuUiJavascriptTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php index c78ff99c80a3..7f110c1a4282 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php @@ -9,12 +9,12 @@ use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\media_library\FunctionalJavascript\MediaLibraryTestBase; use Drupal\Tests\TestFileCreationTrait; +use PHPUnit\Framework\Attributes\Group; /** * Tests that buttons in modals are not in their button pane. - * - * @group claro */ +#[Group('claro')] class ClaroModalDisplayTest extends MediaLibraryTestBase { use TestFileCreationTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php index af01db8d2a8f..c090f658dc56 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\Tests\user\FunctionalJavascript\PasswordConfirmWidgetTest; +use PHPUnit\Framework\Attributes\Group; /** * Tests the password confirm widget with Claro theme. - * - * @group claro */ +#[Group('claro')] class ClaroPasswordConfirmWidgetTest extends PasswordConfirmWidgetTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php index 9938ad22d095..7dda74a5348e 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php @@ -5,14 +5,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\TableDrag\TableDragTest; +use PHPUnit\Framework\Attributes\Group; /** * Tests draggable tables with Claro theme. * - * @group claro - * * @see \Drupal\FunctionalJavascriptTests\TableDrag\TableDragTest */ +#[Group('claro')] class ClaroTableDragTest extends TableDragTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php index e752b79a65af..3f6c21ad8755 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php @@ -7,12 +7,12 @@ namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\Tests\node\Traits\ContentTypeCreationTrait; use Drupal\Tests\node\Traits\NodeCreationTrait; +use PHPUnit\Framework\Attributes\Group; /** * Tests Claro's Views Bulk Operations form. - * - * @group claro */ +#[Group('claro')] class ClaroViewsBulkOperationsTest extends WebDriverTestBase { use ContentTypeCreationTrait; use NodeCreationTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php index 28b3c46a9996..67474ad08a60 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Runs tests on Views UI using Claro. - * - * @group claro */ +#[Group('claro')] class ClaroViewsUiTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php index ee1261a354b0..24d3ca85af26 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests usage of localStorage. - * - * @group olivero */ +#[Group('olivero')] final class OliveroAvoidStorageUsingTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php index 6b0472702c0c..74278efdf4bc 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php @@ -6,14 +6,14 @@ namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\Core\JsMessageTest; use Drupal\js_message_test\Controller\JSMessageTestController; +use PHPUnit\Framework\Attributes\Group; /** * Runs OliveroMessagesTest in Olivero. * - * @group olivero - * * @see \Drupal\FunctionalJavascriptTests\Core\JsMessageTest. */ +#[Group('olivero')] class OliveroMessagesTest extends JsMessageTest { /** diff --git a/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php b/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php index f7509fd72775..f280ad493a57 100644 --- a/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php +++ b/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php @@ -35,6 +35,7 @@ use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait; use Drupal\Tests\media\Traits\MediaTypeCreationTrait; use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait; use Drupal\user\UserInterface; +use Drupal\workspaces\Entity\Workspace; use Psr\Log\LogLevel; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -72,6 +73,7 @@ class ContentImportTest extends BrowserTestBase { 'system', 'taxonomy', 'user', + 'workspaces', ]; /** @@ -180,6 +182,22 @@ class ContentImportTest extends BrowserTestBase { ); }; $this->assertTrue($logger->hasRecordThatPasses($predicate, LogLevel::WARNING)); + + // Visit a page that is published in a non-live workspace; we should not be + // able to see it, because we don't have permission. + $node_in_workspace = $this->container->get(EntityRepositoryInterface::class) + ->loadEntityByUuid('node', '48475954-e878-439c-9d3d-226724a44269'); + $this->assertInstanceOf(NodeInterface::class, $node_in_workspace); + $node_url = $node_in_workspace->toUrl(); + $this->drupalGet($node_url); + $assert_session = $this->assertSession(); + $assert_session->statusCodeEquals(403); + // If we log in with administrative privileges (i.e., we can look at any + // workspace), we should be able to see it. + $this->drupalLogin($this->adminAccount); + $this->drupalGet($node_url); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains($node_in_workspace->label()); } /** @@ -303,6 +321,11 @@ class ContentImportTest extends BrowserTestBase { $this->assertInstanceOf(Section::class, $section); $this->assertCount(2, $section->getComponents()); $this->assertSame('system_powered_by_block', $section->getComponent('03b45f14-cf74-469a-8398-edf3383ce7fa')->getPluginId()); + + // Workspaces should have been imported with their parent references intact. + $workspaces = Workspace::loadMultiple(); + $this->assertArrayHasKey('test_workspace', $workspaces); + $this->assertSame('test_workspace', $workspaces['inner_test']?->parent->entity->id()); } /** diff --git a/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php b/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php index d33d7c4942ab..994ac39d2707 100644 --- a/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php +++ b/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php @@ -90,8 +90,8 @@ class InstallerNonDefaultDatabaseDriverTest extends InstallerTestBase { // uninstalled being dependencies of the "driver_test" module. $this->drupalGet('admin/modules/uninstall'); $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-driver-test"]', "The following reason prevents Contrib database driver test from being uninstalled: The module 'Contrib database driver test' is providing the database driver '{$this->testDriverName}'."); - $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-mysql"]', "The following reason prevents MySQL from being uninstalled: Required by: driver_test"); - $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-pgsql"]', "The following reason prevents PostgreSQL from being uninstalled: Required by: driver_test"); + $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-mysql"]', "The following reason prevents MySQL from being uninstalled: Required by: Contrib database driver test (driver_test)"); + $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-pgsql"]', "The following reason prevents PostgreSQL from being uninstalled: Required by: Contrib database driver test (driver_test)"); } /** diff --git a/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php b/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php index 91cc24234e1a..9882d3d9ef0f 100644 --- a/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php +++ b/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace Drupal\KernelTests\Core\Controller; +use Drupal\dblog\Logger\DbLog; use Drupal\KernelTests\KernelTestBase; use Drupal\system_test\Controller\BrokenSystemTestController; +use Drupal\system_test\Controller\OptionalServiceSystemTestController; use Drupal\system_test\Controller\SystemTestController; use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException; @@ -52,4 +54,17 @@ class ControllerBaseTest extends KernelTestBase { $this->container->get('class_resolver')->getInstanceFromDefinition(BrokenSystemTestController::class); } + /** + * @covers ::create + */ + public function testCreateOptional(): void { + $service = $this->container->get('class_resolver')->getInstanceFromDefinition(OptionalServiceSystemTestController::class); + $this->assertInstanceOf(OptionalServiceSystemTestController::class, $service); + $this->assertNull($service->dbLog); + $this->container->get('module_installer')->install(['dblog']); + $service = $this->container->get('class_resolver')->getInstanceFromDefinition(OptionalServiceSystemTestController::class); + $this->assertInstanceOf(OptionalServiceSystemTestController::class, $service); + $this->assertInstanceOf(DbLog::class, $service->dbLog); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php index fdfa189b880b..8fc0c2013d29 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php @@ -319,4 +319,54 @@ YAML $recipe->input->collectAll($collector); } + /** + * Tests getting default input values from environment variables. + */ + public function testDefaultInputFromEnvironmentVariables(): void { + $this->config('system.site') + ->set('name', 'Hello Thar') + ->set('slogan', 'Very important') + ->save(); + + $recipe = $this->createRecipe(<<<YAML +name: 'Input from environment variables' +input: + name: + data_type: string + description: The name of the site. + default: + source: env + env: SITE_NAME + slogan: + data_type: string + description: The site slogan. + default: + source: env + env: SITE_SLOGAN +config: + actions: + system.site: + simpleConfigUpdate: + name: \${name} + slogan: \${slogan} +YAML + ); + putenv('SITE_NAME=Input Test'); + + // Mock a collector that only returns the default value. + $collector = $this->createMock(InputCollectorInterface::class); + $collector->expects($this->any()) + ->method('collectValue') + ->withAnyParameters() + ->willReturnArgument(2); + $recipe->input->collectAll($collector); + + RecipeRunner::processRecipe($recipe); + $config = $this->config('system.site'); + $this->assertSame('Input Test', $config->get('name')); + // There was no SITE_SLOGAN environment variable, so it should have been + // set to an empty string. + $this->assertSame('', $config->get('slogan')); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php index 62f14df4d202..64b4c17869f5 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php @@ -761,6 +761,36 @@ extra: YAML, NULL, ]; + yield 'input env variable name is not a string' => [ + <<<YAML +name: Bad input +input: + bad_news: + data_type: string + description: 'Bad default definition' + default: + source: env + env: -40 +YAML, + [ + '[input][bad_news][default][env]' => ['This value should be of type string.'], + ], + ]; + yield 'input env variable name is empty' => [ + <<<YAML +name: Bad input +input: + bad_news: + data_type: string + description: 'Bad default definition' + default: + source: env + env: '' +YAML, + [ + '[input][bad_news][default][env]' => ['This value should not be blank.'], + ], + ]; } /** diff --git a/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php b/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php index ad6f98c3cf78..bd64798d2406 100644 --- a/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php +++ b/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php @@ -19,18 +19,55 @@ class FinderTest extends UnitTestCase { */ public function testFoundDataIsInDependencyOrder(): void { $finder = new Finder(__DIR__ . '/../../../../fixtures/default_content'); + $actual_order = array_keys($finder->data); - $expected_order = [ - // First is the author of the node. - '94503467-be7f-406c-9795-fc25baa22203', - // Next, the taxonomy term referenced by the node. - '550f86ad-aa11-4047-953f-636d42889f85', - // Then we have the node itself, since it has no other dependencies. - 'e1714f23-70c0-4493-8e92-af1901771921', - // Finally, the menu link to the node. - '3434bd5a-d2cd-4f26-bf79-a7f6b951a21b', - ]; - $this->assertSame($expected_order, array_slice(array_keys($finder->data), 0, 4)); + $node_uuid = 'e1714f23-70c0-4493-8e92-af1901771921'; + // The author of the node should come before the node itself. We're using + // named arguments here purely for clarity. + $this->assertRelativeOrder( + $actual_order, + earlier: '94503467-be7f-406c-9795-fc25baa22203', + later: $node_uuid, + ); + // Same with the taxonomy term referenced by the node. + $this->assertRelativeOrder( + $actual_order, + earlier: '550f86ad-aa11-4047-953f-636d42889f85', + later: $node_uuid, + ); + // The menu link to the node should come after the node. + $this->assertRelativeOrder( + $actual_order, + earlier: $node_uuid, + later: '3434bd5a-d2cd-4f26-bf79-a7f6b951a21b', + ); + + // A node that is in a workspace should come after the workspace itself. + $this->assertRelativeOrder( + $actual_order, + earlier: '384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d', + later: '48475954-e878-439c-9d3d-226724a44269', + ); + } + + /** + * Asserts that an item in an array comes before another item in that array. + * + * @param array $haystack + * The array to examine. + * @param mixed $earlier + * The item which should come first. + * @param mixed $later + * The item which should come after. + */ + private function assertRelativeOrder(array $haystack, mixed $earlier, mixed $later): void { + $haystack = array_values($haystack); + $earlier_index = array_search($earlier, $haystack, TRUE); + $later_index = array_search($later, $haystack, TRUE); + $this->assertIsInt($earlier_index); + $this->assertIsInt($later_index); + // "Later" should be greater than "earlier". + $this->assertGreaterThan($earlier_index, $later_index); } /** diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php index df504f4b7269..cdfb897046c6 100644 --- a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php @@ -6,6 +6,7 @@ namespace Drupal\Tests\Core\DependencyInjection\Compiler; use Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass; use Drupal\Tests\UnitTestCase; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; @@ -307,6 +308,31 @@ class TaggedHandlersPassTest extends UnitTestCase { } /** + * Tests child handler with parent service. + * + * @covers ::process + */ + public function testProcessChildDefinition(): void { + $container = $this->buildContainer(); + + $container + ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer') + ->addTag('service_collector'); + $container + ->register('root_handler', __NAMESPACE__ . '\ValidHandler'); + $container->addDefinitions([ + 'parent_handler' => new ChildDefinition('root_handler'), + 'child_handler' => (new ChildDefinition('parent_handler'))->addTag('consumer_id'), + ]); + + $handler_pass = new TaggedHandlersPass(); + $handler_pass->process($container); + + $method_calls = $container->getDefinition('consumer_id')->getMethodCalls(); + $this->assertCount(1, $method_calls); + } + + /** * Tests consumer method with extra parameters. * * @covers ::process diff --git a/core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml b/core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml new file mode 100644 index 000000000000..bae16bcd3f9e --- /dev/null +++ b/core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml @@ -0,0 +1,47 @@ +_meta: + version: '1.0' + entity_type: node + uuid: 48475954-e878-439c-9d3d-226724a44269 + bundle: page + default_langcode: en + depends: + 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d: workspace +default: + revision_uid: + - + target_id: 1 + status: + - + value: false + uid: + - + target_id: 1 + title: + - + value: 'A happy little workspace' + created: + - + value: 1751155670 + promote: + - + value: false + sticky: + - + value: false + revision_translation_affected: + - + value: true + path: + - + alias: '' + langcode: en + # TRICKY! Default Content does not export the `workspace` field because it skips internal + # properties, but core's exporter should be sure to include it. + workspace: + - + target_id: test_workspace + body: + - + value: 'This page lives in a workspace! How neat!' + format: plain_text + summary: '' diff --git a/core/tests/fixtures/default_content/workspace/inner_test.yml b/core/tests/fixtures/default_content/workspace/inner_test.yml new file mode 100644 index 000000000000..45a1bcc22dcb --- /dev/null +++ b/core/tests/fixtures/default_content/workspace/inner_test.yml @@ -0,0 +1,24 @@ +_meta: + version: '1.0' + entity_type: workspace + uuid: 93f5b0b4-ada9-4bcd-a11d-f7329e9afe21 + depends: + 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d: workspace +default: + # TRICKY! Default Content does not export the `id` field, but core's exporter should. + # Without it, the import will fail. + id: + - + value: inner_test + uid: + - + target_id: 1 + label: + - + value: 'Inner Test' + parent: + - + entity: 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d + created: + - + value: 1751154834 diff --git a/core/tests/fixtures/default_content/workspace/test_workspace.yml b/core/tests/fixtures/default_content/workspace/test_workspace.yml new file mode 100644 index 000000000000..c22379406797 --- /dev/null +++ b/core/tests/fixtures/default_content/workspace/test_workspace.yml @@ -0,0 +1,19 @@ +_meta: + version: '1.0' + entity_type: workspace + uuid: 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d +default: + # TRICKY! Default Content does not export the `id` field, but core's exporter should. + # Without it, the import will fail. + id: + - + value: test_workspace + uid: + - + target_id: 1 + label: + - + value: 'Test Workspace' + created: + - + value: 1751154825 diff --git a/core/themes/claro/claro.theme b/core/themes/claro/claro.theme index 81255d798a3e..e6b5cc443163 100644 --- a/core/themes/claro/claro.theme +++ b/core/themes/claro/claro.theme @@ -1181,7 +1181,7 @@ function claro_preprocess_table(&$variables): void { $first_cell_key = array_key_first($row['cells']); // The 'attributes' key is always here and it is an // \Drupal\Core\Template\Attribute. - // @see template_preprocess_table(); + // @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable(); $row['cells'][$first_cell_key]['attributes']->addClass('tabledrag-cell'); // Check that the first cell is empty or not. diff --git a/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig b/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig index e9928fd77660..4940f047477e 100644 --- a/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig +++ b/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig @@ -17,7 +17,7 @@ * results, the following data is set: * - plugin: The search plugin ID, for example "node_search". * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% diff --git a/core/themes/claro/templates/classy/dataset/item-list.html.twig b/core/themes/claro/templates/classy/dataset/item-list.html.twig index 20541b0b7e66..6e7b8e317b1d 100644 --- a/core/themes/claro/templates/classy/dataset/item-list.html.twig +++ b/core/themes/claro/templates/classy/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/themes/claro/templates/classy/dataset/table.html.twig b/core/themes/claro/templates/classy/dataset/table.html.twig index cdfe0bff7e73..d7e6459bd4a4 100644 --- a/core/themes/claro/templates/classy/dataset/table.html.twig +++ b/core/themes/claro/templates/classy/dataset/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() */ #} <table{{ attributes }}> diff --git a/core/themes/claro/templates/classy/field/image.html.twig b/core/themes/claro/templates/classy/field/image.html.twig index 31f782bb60a8..90d955c180a6 100644 --- a/core/themes/claro/templates/classy/field/image.html.twig +++ b/core/themes/claro/templates/classy/field/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() */ #} {% diff --git a/core/themes/claro/templates/classy/layout/region.html.twig b/core/themes/claro/templates/classy/layout/region.html.twig index 95e71cec37e4..a4e8cc0af72a 100644 --- a/core/themes/claro/templates/classy/layout/region.html.twig +++ b/core/themes/claro/templates/classy/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/claro/templates/details.html.twig b/core/themes/claro/templates/details.html.twig index 196f6f21e03f..bf5037ead199 100644 --- a/core/themes/claro/templates/details.html.twig +++ b/core/themes/claro/templates/details.html.twig @@ -75,7 +75,12 @@ </div> {% endif %} {%- if description -%} - <div class="claro-details__description{{ disabled ? ' is-disabled' }}">{{ description }}</div> + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + {% set description_classes = [ + 'claro-details__description', + disabled ? 'is-disabled', + ] %} + <div{{ description_attributes.addClass(description_classes) }}>{{ description }}</div> {%- endif -%} {%- if children -%} {{ children }} diff --git a/core/themes/claro/templates/install-page.html.twig b/core/themes/claro/templates/install-page.html.twig index bd22c5a94598..f7456333c8d1 100644 --- a/core/themes/claro/templates/install-page.html.twig +++ b/core/themes/claro/templates/install-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_install_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessInstallPage() */ #} <div class="layout-container"> diff --git a/core/themes/claro/templates/maintenance-page--front.html.twig b/core/themes/claro/templates/maintenance-page--front.html.twig index 12a364ee6a00..daeaf2d7ca23 100644 --- a/core/themes/claro/templates/maintenance-page--front.html.twig +++ b/core/themes/claro/templates/maintenance-page--front.html.twig @@ -9,7 +9,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} <div class="layout-container"> diff --git a/core/themes/claro/templates/maintenance-page.html.twig b/core/themes/claro/templates/maintenance-page.html.twig index 9bbb9a0e0ce4..ef40bbcbec0e 100644 --- a/core/themes/claro/templates/maintenance-page.html.twig +++ b/core/themes/claro/templates/maintenance-page.html.twig @@ -9,7 +9,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() * @see maintenance-page--front.html.twig */ #} diff --git a/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig b/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig index d7931e16b1d5..302e437c8a1f 100644 --- a/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig +++ b/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig @@ -20,7 +20,7 @@ * - list_style: The custom list style. * * @see claro_preprocess_item_list__media_library_add_form_media_list() - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if items -%} diff --git a/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig b/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig index be73cf7bb89c..7c8a59ebd35a 100644 --- a/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig +++ b/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() * * @todo remove this after https://www.drupal.org/node/3051605 has been solved. */ diff --git a/core/themes/claro/templates/navigation/menu-local-task.html.twig b/core/themes/claro/templates/navigation/menu-local-task.html.twig index b135a5ddcf3b..b0b6ce08db94 100644 --- a/core/themes/claro/templates/navigation/menu-local-task.html.twig +++ b/core/themes/claro/templates/navigation/menu-local-task.html.twig @@ -12,7 +12,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} {% diff --git a/core/themes/claro/templates/region--breadcrumb.html.twig b/core/themes/claro/templates/region--breadcrumb.html.twig index a66f43131d60..e58c6a01a2d5 100644 --- a/core/themes/claro/templates/region--breadcrumb.html.twig +++ b/core/themes/claro/templates/region--breadcrumb.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/olivero/olivero.theme b/core/themes/olivero/olivero.theme index 88aa7531379c..8ddfb49b87ff 100644 --- a/core/themes/olivero/olivero.theme +++ b/core/themes/olivero/olivero.theme @@ -83,7 +83,7 @@ function olivero_preprocess_maintenance_page(&$variables): void { // By default, site_name is set to Drupal if no db connection is available // or during site installation. Setting site_name to an empty string makes // the site and update pages look cleaner. - // @see template_preprocess_maintenance_page + // @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() if (!$variables['db_is_active']) { $variables['site_name'] = ''; } diff --git a/core/themes/olivero/templates/dataset/item-list--search-results.html.twig b/core/themes/olivero/templates/dataset/item-list--search-results.html.twig index 6bd6441738d6..665634e86885 100644 --- a/core/themes/olivero/templates/dataset/item-list--search-results.html.twig +++ b/core/themes/olivero/templates/dataset/item-list--search-results.html.twig @@ -17,7 +17,7 @@ * results, the following data is set: * - plugin: The search plugin ID, for example "node_search". * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% diff --git a/core/themes/olivero/templates/dataset/item-list.html.twig b/core/themes/olivero/templates/dataset/item-list.html.twig index 7c7a6e365bfe..06b1e2ebe4fe 100644 --- a/core/themes/olivero/templates/dataset/item-list.html.twig +++ b/core/themes/olivero/templates/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/themes/olivero/templates/form/details.html.twig b/core/themes/olivero/templates/form/details.html.twig index c7a7f9c416a2..e4de3d168152 100644 --- a/core/themes/olivero/templates/form/details.html.twig +++ b/core/themes/olivero/templates/form/details.html.twig @@ -50,7 +50,8 @@ </div> {% endif %} {%- if description -%} - <div class="olivero-details__description">{{ description }}</div> + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div{{ description_attributes.addClass(['olivero-details__description']) }}>{{ description }}</div> {%- endif -%} {%- if children -%} {{ children }} diff --git a/core/themes/olivero/templates/layout/region--breadcrumb.html.twig b/core/themes/olivero/templates/layout/region--breadcrumb.html.twig index 5dbd5d45ffd7..a76183c62c4d 100644 --- a/core/themes/olivero/templates/layout/region--breadcrumb.html.twig +++ b/core/themes/olivero/templates/layout/region--breadcrumb.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--content-above.html.twig b/core/themes/olivero/templates/layout/region--content-above.html.twig index e50082ea3952..1a86195d928e 100644 --- a/core/themes/olivero/templates/layout/region--content-above.html.twig +++ b/core/themes/olivero/templates/layout/region--content-above.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--content-below.html.twig b/core/themes/olivero/templates/layout/region--content-below.html.twig index c0f9ed29e5e2..5cae5f8b825d 100644 --- a/core/themes/olivero/templates/layout/region--content-below.html.twig +++ b/core/themes/olivero/templates/layout/region--content-below.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/olivero/templates/layout/region--content.html.twig b/core/themes/olivero/templates/layout/region--content.html.twig index 7f86b5810362..60e53660b1f8 100644 --- a/core/themes/olivero/templates/layout/region--content.html.twig +++ b/core/themes/olivero/templates/layout/region--content.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--footer-bottom.html.twig b/core/themes/olivero/templates/layout/region--footer-bottom.html.twig index 36ffd885f95a..9677f84970ec 100644 --- a/core/themes/olivero/templates/layout/region--footer-bottom.html.twig +++ b/core/themes/olivero/templates/layout/region--footer-bottom.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--footer-top.html.twig b/core/themes/olivero/templates/layout/region--footer-top.html.twig index 8baaf314056c..c657df25ba3b 100644 --- a/core/themes/olivero/templates/layout/region--footer-top.html.twig +++ b/core/themes/olivero/templates/layout/region--footer-top.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--header.html.twig b/core/themes/olivero/templates/layout/region--header.html.twig index 8ecee6633649..47912006894d 100644 --- a/core/themes/olivero/templates/layout/region--header.html.twig +++ b/core/themes/olivero/templates/layout/region--header.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--highlighted.html.twig b/core/themes/olivero/templates/layout/region--highlighted.html.twig index 6c4293680741..b3dd5f2570c1 100644 --- a/core/themes/olivero/templates/layout/region--highlighted.html.twig +++ b/core/themes/olivero/templates/layout/region--highlighted.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--primary-menu.html.twig b/core/themes/olivero/templates/layout/region--primary-menu.html.twig index 32ade151fe6e..f1fa999007e3 100644 --- a/core/themes/olivero/templates/layout/region--primary-menu.html.twig +++ b/core/themes/olivero/templates/layout/region--primary-menu.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--secondary-menu.html.twig b/core/themes/olivero/templates/layout/region--secondary-menu.html.twig index ce7312194fe3..e64c0743b98b 100644 --- a/core/themes/olivero/templates/layout/region--secondary-menu.html.twig +++ b/core/themes/olivero/templates/layout/region--secondary-menu.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--sidebar.html.twig b/core/themes/olivero/templates/layout/region--sidebar.html.twig index a3f88864f958..8b44262fee3a 100644 --- a/core/themes/olivero/templates/layout/region--sidebar.html.twig +++ b/core/themes/olivero/templates/layout/region--sidebar.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/olivero/templates/layout/region--social.html.twig b/core/themes/olivero/templates/layout/region--social.html.twig index 6f3cf697408d..dff6e3bbda7f 100644 --- a/core/themes/olivero/templates/layout/region--social.html.twig +++ b/core/themes/olivero/templates/layout/region--social.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region.html.twig b/core/themes/olivero/templates/layout/region.html.twig index 651a0112a56c..df39e7d8b451 100644 --- a/core/themes/olivero/templates/layout/region.html.twig +++ b/core/themes/olivero/templates/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/olivero/templates/maintenance-page.html.twig b/core/themes/olivero/templates/maintenance-page.html.twig index f1c97e5c6e7f..cce49aea822a 100644 --- a/core/themes/olivero/templates/maintenance-page.html.twig +++ b/core/themes/olivero/templates/maintenance-page.html.twig @@ -5,7 +5,7 @@ * * All available variables are mirrored in page.html.twig. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} diff --git a/core/themes/olivero/templates/menu-local-action.html.twig b/core/themes/olivero/templates/menu-local-action.html.twig index f78e6f538f9c..12bf7d27ec53 100644 --- a/core/themes/olivero/templates/menu-local-action.html.twig +++ b/core/themes/olivero/templates/menu-local-action.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the wrapper element. * - link: A rendered link element. * - * @see template_preprocess_menu_local_action() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalAction() * * @ingroup themeable */ diff --git a/core/themes/olivero/templates/navigation/menu-local-task.html.twig b/core/themes/olivero/templates/navigation/menu-local-task.html.twig index 34bbb1c44d4c..50b71dabee56 100644 --- a/core/themes/olivero/templates/navigation/menu-local-task.html.twig +++ b/core/themes/olivero/templates/navigation/menu-local-task.html.twig @@ -12,7 +12,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} <li{{ attributes.addClass('tabs__tab', is_active ? 'is-active') }}> diff --git a/core/themes/stable9/templates/dataset/item-list.html.twig b/core/themes/stable9/templates/dataset/item-list.html.twig index 86cc63670c92..4fceba2d0702 100644 --- a/core/themes/stable9/templates/dataset/item-list.html.twig +++ b/core/themes/stable9/templates/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/themes/stable9/templates/dataset/table.html.twig b/core/themes/stable9/templates/dataset/table.html.twig index d9e12ff284f8..89ab32c486e7 100644 --- a/core/themes/stable9/templates/dataset/table.html.twig +++ b/core/themes/stable9/templates/dataset/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() */ #} <table{{ attributes }}> diff --git a/core/themes/stable9/templates/field/image.html.twig b/core/themes/stable9/templates/field/image.html.twig index b342eee6dada..bb3d34e23210 100644 --- a/core/themes/stable9/templates/field/image.html.twig +++ b/core/themes/stable9/templates/field/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() */ #} <img{{ attributes }} /> diff --git a/core/themes/stable9/templates/form/details.html.twig b/core/themes/stable9/templates/form/details.html.twig index 19879959273d..5a6538a21cce 100644 --- a/core/themes/stable9/templates/form/details.html.twig +++ b/core/themes/stable9/templates/form/details.html.twig @@ -32,7 +32,11 @@ </div> {% endif %} - {{ description }} + {%- if description -%} + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div{{ description_attributes }}>{{ description }}</div> + {%- endif -%} + {{ children }} {{ value }} </details> diff --git a/core/themes/stable9/templates/layout/install-page.html.twig b/core/themes/stable9/templates/layout/install-page.html.twig index e2d3381e4858..3a7a346edfe6 100644 --- a/core/themes/stable9/templates/layout/install-page.html.twig +++ b/core/themes/stable9/templates/layout/install-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_install_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessInstallPage() */ #} <div class="layout-container"> diff --git a/core/themes/stable9/templates/layout/maintenance-page.html.twig b/core/themes/stable9/templates/layout/maintenance-page.html.twig index de0acaabbbb4..cdbb3c0e11fe 100644 --- a/core/themes/stable9/templates/layout/maintenance-page.html.twig +++ b/core/themes/stable9/templates/layout/maintenance-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} <header role="banner"> diff --git a/core/themes/stable9/templates/layout/region.html.twig b/core/themes/stable9/templates/layout/region.html.twig index e5e36d07410b..400b985d07f6 100644 --- a/core/themes/stable9/templates/layout/region.html.twig +++ b/core/themes/stable9/templates/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% if content %} diff --git a/core/themes/stable9/templates/navigation/menu-local-action.html.twig b/core/themes/stable9/templates/navigation/menu-local-action.html.twig index 27872837abdc..138d29a00997 100644 --- a/core/themes/stable9/templates/navigation/menu-local-action.html.twig +++ b/core/themes/stable9/templates/navigation/menu-local-action.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the wrapper element. * - link: A rendered link element. * - * @see template_preprocess_menu_local_action() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalAction() */ #} <li{{ attributes }}>{{ link }}</li> diff --git a/core/themes/stable9/templates/navigation/menu-local-task.html.twig b/core/themes/stable9/templates/navigation/menu-local-task.html.twig index b6c3ca241913..2bd91d6bf64f 100644 --- a/core/themes/stable9/templates/navigation/menu-local-task.html.twig +++ b/core/themes/stable9/templates/navigation/menu-local-task.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} <li{{ attributes }}>{{ link }}</li> diff --git a/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig b/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig index e9928fd77660..4940f047477e 100644 --- a/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig +++ b/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig @@ -17,7 +17,7 @@ * results, the following data is set: * - plugin: The search plugin ID, for example "node_search". * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% diff --git a/core/themes/starterkit_theme/templates/dataset/item-list.html.twig b/core/themes/starterkit_theme/templates/dataset/item-list.html.twig index 20541b0b7e66..6e7b8e317b1d 100644 --- a/core/themes/starterkit_theme/templates/dataset/item-list.html.twig +++ b/core/themes/starterkit_theme/templates/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/themes/starterkit_theme/templates/dataset/table.html.twig b/core/themes/starterkit_theme/templates/dataset/table.html.twig index cdfe0bff7e73..d7e6459bd4a4 100644 --- a/core/themes/starterkit_theme/templates/dataset/table.html.twig +++ b/core/themes/starterkit_theme/templates/dataset/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() */ #} <table{{ attributes }}> diff --git a/core/themes/starterkit_theme/templates/field/image.html.twig b/core/themes/starterkit_theme/templates/field/image.html.twig index 31f782bb60a8..90d955c180a6 100644 --- a/core/themes/starterkit_theme/templates/field/image.html.twig +++ b/core/themes/starterkit_theme/templates/field/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() */ #} {% diff --git a/core/themes/starterkit_theme/templates/form/details.html.twig b/core/themes/starterkit_theme/templates/form/details.html.twig index c554096da9d7..5dea0b485f1a 100644 --- a/core/themes/starterkit_theme/templates/form/details.html.twig +++ b/core/themes/starterkit_theme/templates/form/details.html.twig @@ -32,7 +32,8 @@ </div> {% endif %} {%- if description -%} - <div class="details-description">{{ description }}</div> + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div{{ description_attributes.addClass(['details-description']) }}>{{ description }}</div> {%- endif -%} {%- if children -%} {{ children }} diff --git a/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig b/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig index 7463b0238ca3..edd2783619b8 100644 --- a/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig +++ b/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} <div class="layout-container"> diff --git a/core/themes/starterkit_theme/templates/layout/region.html.twig b/core/themes/starterkit_theme/templates/layout/region.html.twig index 95e71cec37e4..a4e8cc0af72a 100644 --- a/core/themes/starterkit_theme/templates/layout/region.html.twig +++ b/core/themes/starterkit_theme/templates/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig b/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig index 27872837abdc..138d29a00997 100644 --- a/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig +++ b/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the wrapper element. * - link: A rendered link element. * - * @see template_preprocess_menu_local_action() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalAction() */ #} <li{{ attributes }}>{{ link }}</li> diff --git a/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig b/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig index b8559815b9e9..ce62d46c9f84 100644 --- a/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig +++ b/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} <li{{ attributes.addClass(is_active ? 'is-active') }}>{{ link }}</li> |