diff options
90 files changed, 624 insertions, 267 deletions
diff --git a/core/core.api.php b/core/core.api.php index e93fa0bb5b5c..23c0ef741c43 100644 --- a/core/core.api.php +++ b/core/core.api.php @@ -1677,8 +1677,7 @@ * - hook_update_last_removed() * - hook_update_N() * - * Theme hooks: - * - hook_preprocess_HOOK() + * Hooks implemented by themes must remain procedural. * * @subsection procedural-hooks Procedural hook implementation * diff --git a/core/core.services.yml b/core/core.services.yml index 4372e6512c3e..a337173741f7 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1564,7 +1564,7 @@ services: Drupal\Core\Theme\ThemeInitializationInterface: '@theme.initialization' theme.registry: class: Drupal\Core\Theme\Registry - arguments: ['%app.root%', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization', '@cache.bootstrap', '@extension.list.module', '@kernel'] + arguments: ['%app.root%', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization', '@cache.bootstrap', '@extension.list.module', '@kernel', ~, '%preprocess_for_suggestions%'] tags: - { name: needs_destruction } calls: diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php index 33da9558b512..1651e1578f7b 100644 --- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php +++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php @@ -83,8 +83,7 @@ use Drupal\Core\Hook\Order\OrderInterface; * - hook_update_last_removed() * - hook_update_N() * - * Theme hooks: - * - hook_preprocess_HOOK() + * Hooks implemented by themes must remain procedural. * * @section sec_backwards_compatibility Backwards-compatibility * @@ -97,12 +96,28 @@ use Drupal\Core\Hook\Order\OrderInterface; */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Hook implements HookAttributeInterface { + /** + * The hook prefix such as `form`. + * + * @var string + */ + public const string PREFIX = ''; + + /** + * The hook suffix such as `alter`. + * + * @var string + */ + public const string SUFFIX = ''; /** * Constructs a Hook attribute object. * * @param string $hook * The short hook name, without the 'hook_' prefix. + * $hook is only optional when Hook is extended and a PREFIX or SUFFIX is + * defined. When using the [#Hook] attribute directly $hook is required. + * See Drupal\Core\Hook\Attribute\Preprocess. * @param string $method * (optional) The method name. If this attribute is on a method, this * parameter is not required. If this attribute is on a class and this @@ -116,10 +131,15 @@ class Hook implements HookAttributeInterface { * (optional) Set the order of the implementation. */ public function __construct( - public string $hook, + public string $hook = '', public string $method = '', public ?string $module = NULL, public OrderInterface|null $order = NULL, - ) {} + ) { + $this->hook = implode('_', array_filter([static::PREFIX, $hook, static::SUFFIX])); + if ($this->hook === '') { + throw new \LogicException('The Hook attribute or an attribute extending the Hook attribute must provide the $hook parameter, a PREFIX or a SUFFIX.'); + } + } } diff --git a/core/lib/Drupal/Core/Hook/Attribute/Preprocess.php b/core/lib/Drupal/Core/Hook/Attribute/Preprocess.php new file mode 100644 index 000000000000..47642859a20b --- /dev/null +++ b/core/lib/Drupal/Core/Hook/Attribute/Preprocess.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Hook\Attribute; + +/** + * Attribute for defining a class method as a preprocess function. + * + * Pass no arguments for hook_preprocess `#[Preprocess]`. + * For `hook_preprocess_HOOK` pass the `HOOK` without the `hook_preprocess` + * portion `#[Preprocess('HOOK')]`. + * + * See \Drupal\Core\Hook\Attribute\Hook for additional information. + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Preprocess extends Hook { + /** + * {@inheritdoc} + */ + public const string PREFIX = 'preprocess'; + +} diff --git a/core/lib/Drupal/Core/Hook/Attribute/ProceduralHookScanStop.php b/core/lib/Drupal/Core/Hook/Attribute/ProceduralHookScanStop.php new file mode 100644 index 000000000000..a030b750262b --- /dev/null +++ b/core/lib/Drupal/Core/Hook/Attribute/ProceduralHookScanStop.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Hook\Attribute; + +/** + * Defines a ProceduralHookScanStop attribute object. + * + * This allows contrib and core to mark when a file has no more + * procedural hooks to be gathered. Any procedural hooks in the file should + * be placed before the function with this attribute. This includes all hooks + * that can be converted to object oriented hooks and also includes: + * - hook_hook_info() + * - hook_module_implements_alter() + * - hook_requirements() + * - hook_preprocess() + * - hook_preprocess_HOOK() + */ +#[\Attribute(\Attribute::TARGET_FUNCTION)] +class ProceduralHookScanStop { + +} diff --git a/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php b/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php deleted file mode 100644 index 73f0ce6915bd..000000000000 --- a/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\Core\Hook\Attribute; - -/** - * Defines a StopProceduralHookScan attribute object. - * - * This allows contrib and core to mark when a file has no more - * procedural hooks. - */ -#[\Attribute(\Attribute::TARGET_FUNCTION)] -class StopProceduralHookScan { - -} diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php index 3fe2a6d28307..7c0f913ca219 100644 --- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php +++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php @@ -14,7 +14,7 @@ use Drupal\Core\Hook\Attribute\LegacyHook; use Drupal\Core\Hook\Attribute\LegacyModuleImplementsAlter; use Drupal\Core\Hook\Attribute\RemoveHook; use Drupal\Core\Hook\Attribute\ReorderHook; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Hook\OrderOperation\OrderOperation; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -97,6 +97,16 @@ class HookCollectorPass implements CompilerPassInterface { private array $hookInfo = []; /** + * Preprocess suggestions discovered in modules. + * + * These are stored to prevent adding preprocess suggestions to the invoke map + * that are not discovered in modules. + * + * @var array<string, true> + */ + protected array $preprocessForSuggestions; + + /** * Include files, keyed by the $group part of "/$module.$group.inc". * * @var array<string, list<string>> @@ -122,7 +132,7 @@ class HookCollectorPass implements CompilerPassInterface { $parameters = $container->getParameterBag()->all(); $skip_procedural_modules = array_filter( array_keys($module_list), - static fn (string $module) => !empty($parameters["$module.hooks_converted"]), + static fn (string $module) => !empty($parameters["$module.skip_procedural_hook_scan"]), ); $collector = static::collectAllHookImplementations($module_list, $skip_procedural_modules); @@ -154,6 +164,7 @@ class HookCollectorPass implements CompilerPassInterface { $implementationsByHook = $this->calculateImplementations(); static::writeImplementationsToContainer($container, $implementationsByHook); + $container->setParameter('preprocess_for_suggestions', $this->preprocessForSuggestions ?? []); // Update the module handler definition. $definition = $container->getDefinition('module_handler'); @@ -228,6 +239,9 @@ class HookCollectorPass implements CompilerPassInterface { $alter($moduleImplements, $hook); } foreach ($moduleImplements as $module => $v) { + if (is_string($hook) && str_starts_with($hook, 'preprocess_') && str_contains($hook, '__')) { + $this->preprocessForSuggestions[$module . '_' . $hook] = TRUE; + } foreach (array_keys($implementationsByHookOrig[$hook], $module, TRUE) as $identifier) { $implementationsByHook[$hook][$identifier] = $module; } @@ -354,7 +368,7 @@ class HookCollectorPass implements CompilerPassInterface { static fn ($x) => preg_quote($x, '/'), $modules_by_length, )); - $module_preg = '/^(?<function>(?<module>' . $known_modules_pattern . ')_(?!preprocess_)(?!update_\d)(?<hook>[a-zA-Z0-9_\x80-\xff]+$))/'; + $module_preg = '/^(?<function>(?<module>' . $known_modules_pattern . ')_(?!update_\d)(?<hook>[a-zA-Z0-9_\x80-\xff]+$))/'; $collector = new static($modules); foreach ($module_list as $module => $info) { $skip_procedural = in_array($module, $skipProceduralModules); @@ -441,7 +455,7 @@ class HookCollectorPass implements CompilerPassInterface { $parser = new StaticReflectionParser('', $finder); $implementations = []; foreach ($parser->getMethodAttributes() as $function => $attributes) { - if (StaticReflectionParser::hasAttribute($attributes, StopProceduralHookScan::class)) { + if (StaticReflectionParser::hasAttribute($attributes, ProceduralHookScanStop::class)) { break; } if (!StaticReflectionParser::hasAttribute($attributes, LegacyHook::class) && preg_match($module_preg, $function, $matches) && !StaticReflectionParser::hasAttribute($attributes, LegacyModuleImplementsAlter::class)) { @@ -572,7 +586,7 @@ class HookCollectorPass implements CompilerPassInterface { 'install_tasks_alter', ]; - if (in_array($hookAttribute->hook, $staticDenyHooks) || preg_match('/^(post_update_|preprocess_|update_\d+$)/', $hookAttribute->hook)) { + if (in_array($hookAttribute->hook, $staticDenyHooks) || preg_match('/^(post_update_|update_\d+$)/', $hookAttribute->hook)) { throw new \LogicException("The hook $hookAttribute->hook on class $class does not support attributes and must remain procedural."); } } diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php index 2317cedb91c3..383776b65be8 100644 --- a/core/lib/Drupal/Core/Theme/Registry.php +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -29,6 +29,17 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; class Registry implements DestructableInterface { /** + * A common key for storing preprocess invoke locations for modules. + * + * This is used to create a lookup for module preprocess implementations for + * being invoked in ThemeManager. The keys are a string representing the + * module and preprocess hook. The value is an array with the keys module + * and preprocess. This supports invoking preprocess hooks + * implemented using #[Preprocess] attributes or procedural functions. + */ + private const string PREPROCESS_INVOKES = 'preprocess invokes'; + + /** * The theme object representing the active theme for this registry. * * @var \Drupal\Core\Theme\ActiveTheme @@ -162,6 +173,16 @@ class Registry implements DestructableInterface { protected $moduleList; /** + * Preprocess suggestions discovered in modules. + * + * These are stored to prevent adding preprocess suggestions to the invoke map + * that are not discovered in modules. + * + * @var array<string, true> + */ + protected array $preprocessForSuggestions; + + /** * Constructs a \Drupal\Core\Theme\Registry object. * * @param string $root @@ -184,8 +205,10 @@ class Registry implements DestructableInterface { * The kernel. * @param string $theme_name * (optional) The name of the theme for which to construct the registry. + * @param array<string, true> $preprocess_for_suggestions + * (optional) Grouped preprocess functions from modules. */ - public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization, CacheBackendInterface $runtime_cache, ModuleExtensionList $module_list, protected HttpKernelInterface $kernel, $theme_name = NULL) { + public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization, CacheBackendInterface $runtime_cache, ModuleExtensionList $module_list, protected HttpKernelInterface $kernel, $theme_name = NULL, array $preprocess_for_suggestions = []) { $this->root = $root; $this->cache = $cache; $this->lock = $lock; @@ -195,6 +218,7 @@ class Registry implements DestructableInterface { $this->runtimeCache = $runtime_cache; $this->moduleList = $module_list; $this->themeName = $theme_name; + $this->preprocessForSuggestions = $preprocess_for_suggestions; } /** @@ -386,7 +410,10 @@ class Registry implements DestructableInterface { * @see hook_theme_registry_alter() */ protected function build() { - $cache = []; + $cache = [ + static::PREPROCESS_INVOKES => [], + ]; + $fixed_preprocess_functions = $this->collectModulePreprocess($cache, 'preprocess'); // First, preprocess the theme hooks advertised by modules. This will // serve as the basic registry. Since the list of enabled modules is the // same regardless of the theme used, this is cached in its own entry to @@ -404,6 +431,7 @@ class Registry implements DestructableInterface { $this->processExtension($cache, $module, 'module', $module, $this->moduleList->getPath($module)); }); } + $this->addFixedPreprocessFunctions($cache, $fixed_preprocess_functions); // Only cache this registry if all modules are loaded. if ($this->moduleHandler->isLoaded()) { @@ -411,6 +439,8 @@ class Registry implements DestructableInterface { } } + $old_cache = $cache; + // Process each base theme. // Ensure that we start with the root of the parents, so that both CSS files // and preprocess functions comes first. @@ -431,6 +461,10 @@ class Registry implements DestructableInterface { // Hooks provided by the theme itself. $this->processExtension($cache, $this->theme->getName(), 'theme', $this->theme->getName(), $this->theme->getPath()); + // Add the fixed preprocess functions to hooks defined by themes. They + // were already added to hooks defined by modules and potentially cached. + $this->addFixedPreprocessFunctions($cache, $fixed_preprocess_functions, $old_cache); + // Discover and add all preprocess functions for theme hook suggestions. $this->postProcessExtension($cache, $this->theme); @@ -504,12 +538,9 @@ class Registry implements DestructableInterface { 'base hook' => TRUE, ]; - $module_list = array_keys($this->moduleHandler->getModuleList()); - // Invoke the hook_theme() implementation, preprocess what is returned, and // merge it into $cache. $args = [$cache, $type, $theme, $path]; - $result = []; if ($type === 'module') { $result = $this->moduleHandler->invoke($name, 'theme', $args); } @@ -597,10 +628,7 @@ class Registry implements DestructableInterface { // @todo trigger deprecation in https://www.drupal.org/project/drupal/issues/3513595. $info['preprocess functions'][] = 'template_preprocess_' . $hook; } - // Add all modules so they can intervene with their own variable - // preprocessors. This allows them to provide variable preprocessors - // even if they are not the owner of the current hook. - $prefixes = array_merge($prefixes, $module_list); + $info['preprocess functions'] = array_merge($info['preprocess functions'], $this->collectModulePreprocess($cache, 'preprocess_' . $hook)); } elseif ($type == 'theme_engine' || $type == 'base_theme_engine') { // Theme engines get an extra set that come before the normally @@ -616,10 +644,7 @@ class Registry implements DestructableInterface { $prefixes[] = $name; } foreach ($prefixes as $prefix) { - // Only use non-hook-specific variable preprocessors for theming - // hooks implemented as templates. See the @defgroup themeable - // topic. - if (isset($info['template']) && function_exists($prefix . '_preprocess')) { + if (function_exists($prefix . '_preprocess')) { $info['preprocess functions'][] = $prefix . '_preprocess'; } if (function_exists($prefix . '_preprocess_' . $hook)) { @@ -649,14 +674,15 @@ class Registry implements DestructableInterface { // template. if ($type == 'theme' || $type == 'base_theme') { foreach ($cache as $hook => $info) { + if ($hook == static::PREPROCESS_INVOKES) { + continue; + } // Check only if not registered by the theme or engine. if (empty($result[$hook])) { if (!isset($info['preprocess functions'])) { $cache[$hook]['preprocess functions'] = []; } - // Only use non-hook-specific variable preprocessors for theme hooks - // implemented as templates. See the @defgroup themeable topic. - if (isset($info['template']) && function_exists($name . '_preprocess')) { + if (function_exists($name . '_preprocess')) { $cache[$hook]['preprocess functions'][] = $name . '_preprocess'; } if (function_exists($name . '_preprocess_' . $hook)) { @@ -761,7 +787,7 @@ class Registry implements DestructableInterface { // Collect all variable preprocess functions in the correct order. $suggestion_level = []; - $matches = []; + $invokes = []; // Look for functions named according to the pattern and add them if they // have matching hooks in the registry. foreach ($prefixes as $prefix) { @@ -778,6 +804,10 @@ class Registry implements DestructableInterface { if (isset($cache[$matches[2]])) { $level = substr_count($matches[1], '__'); $suggestion_level[$level][$candidate] = $matches[1]; + $module_preprocess_function = $prefix . '_preprocess_' . $matches[1]; + if (isset($this->preprocessForSuggestions[$module_preprocess_function])) { + $invokes[$candidate] = ['module' => $prefix, 'hook' => 'preprocess_' . $matches[1]]; + } } } } @@ -795,6 +825,9 @@ class Registry implements DestructableInterface { if (isset($cache[$hook]['preprocess functions']) && !in_array($preprocessor, $cache[$hook]['preprocess functions'])) { // Add missing preprocessor to existing hook. $cache[$hook]['preprocess functions'][] = $preprocessor; + if (isset($invokes[$preprocessor])) { + $cache[static::PREPROCESS_INVOKES][$preprocessor] = $invokes[$preprocessor]; + } } elseif (!isset($cache[$hook]) && strpos($hook, '__')) { // Process non-existing hook and register it. @@ -802,6 +835,9 @@ class Registry implements DestructableInterface { // suggestion hook or the base hook. $this->completeSuggestion($hook, $cache); $cache[$hook]['preprocess functions'][] = $preprocessor; + if (isset($invokes[$preprocessor])) { + $cache[static::PREPROCESS_INVOKES][$preprocessor] = $invokes[$preprocessor]; + } } } } @@ -809,6 +845,9 @@ class Registry implements DestructableInterface { // hooks. This ensures that derivative hooks have a complete set of variable // preprocess functions. foreach ($cache as $hook => $info) { + if ($hook == static::PREPROCESS_INVOKES) { + continue; + } // The 'base hook' is only applied to derivative hooks already registered // from a pattern. This is typically set from // drupal_find_theme_templates(). @@ -890,6 +929,7 @@ class Registry implements DestructableInterface { $theme_functions = $functions['user']; } + $theme_functions = array_merge($theme_functions, array_keys($this->preprocessForSuggestions)); $grouped_functions = []; // Splitting user defined functions into groups by the first prefix. foreach ($theme_functions as $function) { @@ -900,4 +940,60 @@ class Registry implements DestructableInterface { return $grouped_functions; } + /** + * Adds $prefix_preprocess functions to every hook. + * + * @param array $cache + * The theme registry, as documented in + * \Drupal\Core\Theme\Registry::processExtension(). + * @param array $fixed_preprocess_functions + * A list of preprocess functions. + * @param array $old_cache + * An already processed theme registry. + */ + protected function addFixedPreprocessFunctions(array &$cache, array $fixed_preprocess_functions, array $old_cache = []): void { + foreach (array_keys(array_diff_key($cache, $old_cache)) as $hook) { + if ($hook == static::PREPROCESS_INVOKES) { + continue; + } + if (!isset($cache[$hook]['preprocess functions'])) { + $cache[$hook]['preprocess functions'] = $fixed_preprocess_functions; + } + else { + $offset = 0; + while (isset($cache[$hook]['preprocess functions'][$offset]) && is_string($cache[$hook]['preprocess functions'][$offset]) && str_starts_with($cache[$hook]['preprocess functions'][$offset], 'template_')) { + $offset++; + } + array_splice($cache[$hook]['preprocess functions'], $offset, 0, $fixed_preprocess_functions); + } + } + } + + /** + * Collect module implementations of a single hook. + * + * @param array $cache + * The preprocess hook. + * @param string $hook + * The theme registry, as documented in + * \Drupal\Core\Theme\Registry::processExtension(). + * + * @return array + * A list of preprocess functions. + */ + protected function collectModulePreprocess(array &$cache, string $hook): array { + $preprocess_functions = []; + // This is used so we can collect all preprocess functions in modules but + // prevent them from being executed. Registry needs to cache preprocess + // functions so we only want to gather the ones that exist, but we do not + // want to execute them. Callable is not used so that preprocess + // implementations are not executed. + $this->moduleHandler->invokeAllWith($hook, function (callable $callable, string $module) use ($hook, &$cache, &$preprocess_functions) { + $function = $module . '_' . $hook; + $cache[static::PREPROCESS_INVOKES][$function] = ['module' => $module, 'hook' => $hook]; + $preprocess_functions[] = $function; + }); + return $preprocess_functions; + } + } diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php index 8962896ee3ef..3724ea6e1563 100644 --- a/core/lib/Drupal/Core/Theme/ThemeManager.php +++ b/core/lib/Drupal/Core/Theme/ThemeManager.php @@ -187,6 +187,7 @@ class ThemeManager implements ThemeManagerInterface { } $info = $theme_registry->get($hook); + $invoke_map = $theme_registry->getPreprocessInvokes(); if (isset($info['deprecated'])) { @trigger_error($info['deprecated'], E_USER_DEPRECATED); } @@ -293,7 +294,17 @@ class ThemeManager implements ThemeManagerInterface { // overridden. See \Drupal\Core\Theme\Registry. if (isset($info['preprocess functions'])) { foreach ($info['preprocess functions'] as $preprocessor_function) { - if (is_callable($preprocessor_function)) { + // Preprocess hooks are stored as strings resembling functions. + // This is for backwards compatibility and may represent OOP + // implementations as well. + if (is_string($preprocessor_function) && isset($invoke_map[$preprocessor_function])) { + // While themes are not modules, ModuleHandlerInterface::invoke calls + // a legacy invoke which can can call any extension, not just + // modules. + $this->moduleHandler->invoke(... $invoke_map[$preprocessor_function], args: [&$variables, $hook, $info]); + } + // Check if hook_theme_registry_alter added a manual callback. + elseif (is_callable($preprocessor_function)) { call_user_func_array($preprocessor_function, [&$variables, $hook, $info]); } } diff --git a/core/lib/Drupal/Core/Utility/ThemeRegistry.php b/core/lib/Drupal/Core/Utility/ThemeRegistry.php index d0ca9918dd23..a6846ba263c3 100644 --- a/core/lib/Drupal/Core/Utility/ThemeRegistry.php +++ b/core/lib/Drupal/Core/Utility/ThemeRegistry.php @@ -168,4 +168,16 @@ class ThemeRegistry extends CacheCollector implements DestructableInterface { } } + /** + * Gets preprocess invoke map. + * + * @return array + * An array of preprocess invokes for preprocess functions in modules. + * + * @internal + */ + public function getPreprocessInvokes() { + return $this->get('preprocess invokes'); + } + } diff --git a/core/modules/announcements_feed/announcements_feed.services.yml b/core/modules/announcements_feed/announcements_feed.services.yml index b9e88231be16..ea927c887d1f 100644 --- a/core/modules/announcements_feed/announcements_feed.services.yml +++ b/core/modules/announcements_feed/announcements_feed.services.yml @@ -1,7 +1,7 @@ parameters: announcements_feed.feed_json_url: https://www.drupal.org/announcements.json announcements_feed.feed_link: https://www.drupal.org/about/announcements - announcements_feed.hooks_converted: true + announcements_feed.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/automated_cron/automated_cron.services.yml b/core/modules/automated_cron/automated_cron.services.yml index bbb55f5f6f39..0cc66d22384e 100644 --- a/core/modules/automated_cron/automated_cron.services.yml +++ b/core/modules/automated_cron/automated_cron.services.yml @@ -1,5 +1,5 @@ parameters: - automated_cron.hooks_converted: true + automated_cron.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/ban/ban.services.yml b/core/modules/ban/ban.services.yml index 7bd8a245e5f4..585f8fa0d60a 100644 --- a/core/modules/ban/ban.services.yml +++ b/core/modules/ban/ban.services.yml @@ -1,5 +1,5 @@ parameters: - ban.hooks_converted: true + ban.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/basic_auth/basic_auth.services.yml b/core/modules/basic_auth/basic_auth.services.yml index 09b21c68f33d..af0de75629e9 100644 --- a/core/modules/basic_auth/basic_auth.services.yml +++ b/core/modules/basic_auth/basic_auth.services.yml @@ -1,5 +1,5 @@ parameters: - basic_auth.hooks_converted: true + basic_auth.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/block_content/block_content.services.yml b/core/modules/block_content/block_content.services.yml index eb8b9924ce9f..2661eda513b5 100644 --- a/core/modules/block_content/block_content.services.yml +++ b/core/modules/block_content/block_content.services.yml @@ -1,5 +1,5 @@ parameters: - block_content.hooks_converted: true + block_content.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/breakpoint/breakpoint.services.yml b/core/modules/breakpoint/breakpoint.services.yml index 7558eb6d7650..46d8f0d27e16 100644 --- a/core/modules/breakpoint/breakpoint.services.yml +++ b/core/modules/breakpoint/breakpoint.services.yml @@ -1,5 +1,5 @@ parameters: - breakpoint.hooks_converted: true + breakpoint.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index b357f3069aeb..139513c1c37c 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -111,15 +111,6 @@ function comment_preview(CommentInterface $comment, FormStateInterface $form_sta } /** - * Implements hook_preprocess_HOOK() for block templates. - */ -function comment_preprocess_block(&$variables): void { - if ($variables['configuration']['provider'] == 'comment') { - $variables['attributes']['role'] = 'navigation'; - } -} - -/** * Prepares variables for comment templates. * * By default this function performs special preprocessing of some base fields diff --git a/core/modules/comment/comment.services.yml b/core/modules/comment/comment.services.yml index 22cc8f9a428b..7f9e82245518 100644 --- a/core/modules/comment/comment.services.yml +++ b/core/modules/comment/comment.services.yml @@ -1,5 +1,5 @@ parameters: - comment.hooks_converted: true + comment.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/comment/src/Hook/CommentThemeHooks.php b/core/modules/comment/src/Hook/CommentThemeHooks.php new file mode 100644 index 000000000000..e789af6dab10 --- /dev/null +++ b/core/modules/comment/src/Hook/CommentThemeHooks.php @@ -0,0 +1,22 @@ +<?php + +namespace Drupal\comment\Hook; + +use Drupal\Core\Hook\Attribute\Preprocess; + +/** + * Hook implementations for comment. + */ +class CommentThemeHooks { + + /** + * Implements hook_preprocess_HOOK() for block templates. + */ + #[Preprocess('block')] + public function preprocessBlock(&$variables): void { + if ($variables['configuration']['provider'] == 'comment') { + $variables['attributes']['role'] = 'navigation'; + } + } + +} diff --git a/core/modules/comment/tests/modules/comment_empty_title_test/comment_empty_title_test.module b/core/modules/comment/tests/modules/comment_empty_title_test/comment_empty_title_test.module deleted file mode 100644 index c107fd11466f..000000000000 --- a/core/modules/comment/tests/modules/comment_empty_title_test/comment_empty_title_test.module +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -/** - * @file - * Empties comment titles to test markup in edge case scenarios. - */ - -declare(strict_types=1); - -/** - * Implements hook_preprocess_comment(). - */ -function comment_empty_title_test_preprocess_comment(&$variables): void { - $variables['title'] = ''; -} diff --git a/core/modules/comment/tests/modules/comment_empty_title_test/src/Hook/CommentEmptyTitleTestThemeHooks.php b/core/modules/comment/tests/modules/comment_empty_title_test/src/Hook/CommentEmptyTitleTestThemeHooks.php new file mode 100644 index 000000000000..db1ffae5a6d0 --- /dev/null +++ b/core/modules/comment/tests/modules/comment_empty_title_test/src/Hook/CommentEmptyTitleTestThemeHooks.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\comment_empty_title_test\Hook; + +use Drupal\Core\Hook\Attribute\Preprocess; + +/** + * Hook implementations for comment_empty_title_test. + */ +class CommentEmptyTitleTestThemeHooks { + + /** + * Implements hook_preprocess_comment(). + */ + #[Preprocess('comment')] + public function preprocessComment(&$variables): void { + $variables['title'] = ''; + } + +} diff --git a/core/modules/config/config.services.yml b/core/modules/config/config.services.yml index 8667240a82f2..49a17dd40081 100644 --- a/core/modules/config/config.services.yml +++ b/core/modules/config/config.services.yml @@ -1,5 +1,5 @@ parameters: - config.hooks_converted: true + config.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/config_translation/config_translation.services.yml b/core/modules/config_translation/config_translation.services.yml index 5c35e5ee588b..458905d06292 100644 --- a/core/modules/config_translation/config_translation.services.yml +++ b/core/modules/config_translation/config_translation.services.yml @@ -1,5 +1,5 @@ parameters: - config_translation.hooks_converted: true + config_translation.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/contact/contact.services.yml b/core/modules/contact/contact.services.yml index b078fd2cfdc4..61ec8dd7fdac 100644 --- a/core/modules/contact/contact.services.yml +++ b/core/modules/contact/contact.services.yml @@ -1,5 +1,5 @@ parameters: - contact.hooks_converted: true + contact.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/content_moderation/content_moderation.services.yml b/core/modules/content_moderation/content_moderation.services.yml index 85abe7b2f7cd..8977847173c4 100644 --- a/core/modules/content_moderation/content_moderation.services.yml +++ b/core/modules/content_moderation/content_moderation.services.yml @@ -1,5 +1,5 @@ parameters: - content_moderation.hooks_converted: true + content_moderation.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module index f6c3335b619b..8daec06c8ee5 100644 --- a/core/modules/contextual/contextual.module +++ b/core/modules/contextual/contextual.module @@ -8,46 +8,6 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Language\LanguageInterface; /** - * Implements hook_preprocess(). - * - * @see \Drupal\contextual\Element\ContextualLinksPlaceholder - * @see contextual_page_attachments() - * @see \Drupal\contextual\ContextualController::render() - */ -function contextual_preprocess(&$variables, $hook, $info): void { - // Determine the primary theme function argument. - if (!empty($info['variables'])) { - $keys = array_keys($info['variables']); - $key = $keys[0]; - } - elseif (!empty($info['render element'])) { - $key = $info['render element']; - } - if (!empty($key) && isset($variables[$key])) { - $element = $variables[$key]; - } - - if (isset($element) && is_array($element) && !empty($element['#contextual_links'])) { - $variables['#cache']['contexts'][] = 'user.permissions'; - if (\Drupal::currentUser()->hasPermission('access contextual links')) { - // Mark this element as potentially having contextual links attached to - // it. - $variables['attributes']['class'][] = 'contextual-region'; - - // Renders a contextual links placeholder unconditionally, thus not - // breaking the render cache. Although the empty placeholder is rendered - // for all users, contextual_page_attachments() only adds the asset - // library for users with the 'access contextual links' permission, thus - // preventing unnecessary HTTP requests for users without that permission. - $variables['title_suffix']['contextual_links'] = [ - '#type' => 'contextual_links_placeholder', - '#id' => _contextual_links_to_id($element['#contextual_links']), - ]; - } - } -} - -/** * Serializes #contextual_links property value array to a string. * * Examples: diff --git a/core/modules/contextual/contextual.services.yml b/core/modules/contextual/contextual.services.yml index dff437ec3393..558e7d029ae5 100644 --- a/core/modules/contextual/contextual.services.yml +++ b/core/modules/contextual/contextual.services.yml @@ -1,2 +1,2 @@ parameters: - contextual.hooks_converted: true + contextual.skip_procedural_hook_scan: true diff --git a/core/modules/contextual/src/Hook/ContextualThemeHooks.php b/core/modules/contextual/src/Hook/ContextualThemeHooks.php new file mode 100644 index 000000000000..47db1f9bde6a --- /dev/null +++ b/core/modules/contextual/src/Hook/ContextualThemeHooks.php @@ -0,0 +1,57 @@ +<?php + +namespace Drupal\contextual\Hook; + +use Drupal\Core\Hook\Attribute\Preprocess; +use Drupal\Core\Session\AccountInterface; + +/** + * Hook implementations for contextual. + */ +class ContextualThemeHooks { + + public function __construct( + protected readonly AccountInterface $currentUser, + ) {} + + /** + * Implements hook_preprocess(). + * + * @see \Drupal\contextual\Element\ContextualLinksPlaceholder + * @see contextual_page_attachments() + * @see \Drupal\contextual\ContextualController::render() + */ + #[Preprocess] + public function preprocess(&$variables, $hook, $info): void { + // Determine the primary theme function argument. + if (!empty($info['variables'])) { + $keys = array_keys($info['variables']); + $key = $keys[0]; + } + elseif (!empty($info['render element'])) { + $key = $info['render element']; + } + if (!empty($key) && isset($variables[$key])) { + $element = $variables[$key]; + } + + if (isset($element) && is_array($element) && !empty($element['#contextual_links'])) { + $variables['#cache']['contexts'][] = 'user.permissions'; + if ($this->currentUser->hasPermission('access contextual links')) { + // Mark this element as potentially having contextual links attached to it. + $variables['attributes']['class'][] = 'contextual-region'; + + // Renders a contextual links placeholder unconditionally, thus not breaking + // the render cache. Although the empty placeholder is rendered for all + // users, contextual_page_attachments() only adds the asset library for + // users with the 'access contextual links' permission, thus preventing + // unnecessary HTTP requests for users without that permission. + $variables['title_suffix']['contextual_links'] = [ + '#type' => 'contextual_links_placeholder', + '#id' => _contextual_links_to_id($element['#contextual_links']), + ]; + } + } + } + +} diff --git a/core/modules/datetime/datetime.services.yml b/core/modules/datetime/datetime.services.yml index af7d5c9a4d11..3f9c511d1e74 100644 --- a/core/modules/datetime/datetime.services.yml +++ b/core/modules/datetime/datetime.services.yml @@ -1,5 +1,5 @@ parameters: - datetime.hooks_converted: true + datetime.skip_procedural_hook_scan: true services: datetime.views_helper: diff --git a/core/modules/datetime_range/datetime_range.services.yml b/core/modules/datetime_range/datetime_range.services.yml index 6234cb0e6872..6701b44dd713 100644 --- a/core/modules/datetime_range/datetime_range.services.yml +++ b/core/modules/datetime_range/datetime_range.services.yml @@ -1,2 +1,2 @@ parameters: - datetime_range.hooks_converted: true + datetime_range.skip_procedural_hook_scan: true diff --git a/core/modules/dblog/dblog.services.yml b/core/modules/dblog/dblog.services.yml index 50398593fe98..477f913e6bfd 100644 --- a/core/modules/dblog/dblog.services.yml +++ b/core/modules/dblog/dblog.services.yml @@ -1,5 +1,5 @@ parameters: - dblog.hooks_converted: true + dblog.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/dynamic_page_cache/dynamic_page_cache.services.yml b/core/modules/dynamic_page_cache/dynamic_page_cache.services.yml index 0ec8b7c2f8da..8cee8fd0d8b9 100644 --- a/core/modules/dynamic_page_cache/dynamic_page_cache.services.yml +++ b/core/modules/dynamic_page_cache/dynamic_page_cache.services.yml @@ -1,5 +1,5 @@ parameters: - dynamic_page_cache.hooks_converted: true + dynamic_page_cache.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/editor/editor.services.yml b/core/modules/editor/editor.services.yml index 0db1dc61968c..46f674b27ab9 100644 --- a/core/modules/editor/editor.services.yml +++ b/core/modules/editor/editor.services.yml @@ -1,5 +1,5 @@ parameters: - editor.hooks_converted: true + editor.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/field/field.services.yml b/core/modules/field/field.services.yml index 8b0f5a33093f..3e986982cdbb 100644 --- a/core/modules/field/field.services.yml +++ b/core/modules/field/field.services.yml @@ -1,5 +1,5 @@ parameters: - field.hooks_converted: true + field.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/field_layout/field_layout.services.yml b/core/modules/field_layout/field_layout.services.yml index e099e35a9533..48e3a6abe6f9 100644 --- a/core/modules/field_layout/field_layout.services.yml +++ b/core/modules/field_layout/field_layout.services.yml @@ -1,2 +1,2 @@ parameters: - field_layout.hooks_converted: true + field_layout.skip_procedural_hook_scan: true diff --git a/core/modules/field_ui/field_ui.services.yml b/core/modules/field_ui/field_ui.services.yml index dd094ee72aae..194b7287cef2 100644 --- a/core/modules/field_ui/field_ui.services.yml +++ b/core/modules/field_ui/field_ui.services.yml @@ -1,5 +1,5 @@ parameters: - field_ui.hooks_converted: true + field_ui.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/filter/filter.services.yml b/core/modules/filter/filter.services.yml index 6b9c243c7ab9..58caf58f76c0 100644 --- a/core/modules/filter/filter.services.yml +++ b/core/modules/filter/filter.services.yml @@ -1,5 +1,5 @@ parameters: - filter.hooks_converted: true + filter.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/help/help.services.yml b/core/modules/help/help.services.yml index 21a8e2f3adf1..2cb3ecbd8ec5 100644 --- a/core/modules/help/help.services.yml +++ b/core/modules/help/help.services.yml @@ -1,5 +1,5 @@ parameters: - help.hooks_converted: true + help.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/history/history.services.yml b/core/modules/history/history.services.yml index 7b2b654310f2..fc83944febaf 100644 --- a/core/modules/history/history.services.yml +++ b/core/modules/history/history.services.yml @@ -1,2 +1,2 @@ parameters: - history.hooks_converted: true + history.skip_procedural_hook_scan: true diff --git a/core/modules/inline_form_errors/inline_form_errors.services.yml b/core/modules/inline_form_errors/inline_form_errors.services.yml index 0358ee160271..f3ae13843ace 100644 --- a/core/modules/inline_form_errors/inline_form_errors.services.yml +++ b/core/modules/inline_form_errors/inline_form_errors.services.yml @@ -1,2 +1,2 @@ parameters: - inline_form_errors.hooks_converted: true + inline_form_errors.skip_procedural_hook_scan: false diff --git a/core/modules/language/language.services.yml b/core/modules/language/language.services.yml index 2fc8f9ada778..93f994c4f67e 100644 --- a/core/modules/language/language.services.yml +++ b/core/modules/language/language.services.yml @@ -1,5 +1,5 @@ parameters: - language.hooks_converted: true + language.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/link/link.services.yml b/core/modules/link/link.services.yml index becc0b945dd7..de8f823e8cb9 100644 --- a/core/modules/link/link.services.yml +++ b/core/modules/link/link.services.yml @@ -1,2 +1,2 @@ parameters: - link.hooks_converted: true + link.skip_procedural_hook_scan: false diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc index 780333876415..0f204b6af2df 100644 --- a/core/modules/locale/locale.batch.inc +++ b/core/modules/locale/locale.batch.inc @@ -7,7 +7,7 @@ use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\Exception\InvalidStreamWrapperException; use Drupal\Core\File\FileExists; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Url; use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; @@ -37,7 +37,7 @@ require_once __DIR__ . '/locale.translation.inc'; * @param array|\ArrayAccess $context * The batch context. */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function locale_translation_batch_version_check(string $project, string $langcode, array|\ArrayAccess &$context): void { $locale_project = \Drupal::service('locale.project')->get($project); diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index e8396622d616..0cb5e9058566 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -6,7 +6,7 @@ use Drupal\Core\Batch\BatchBuilder; use Drupal\Core\File\Exception\FileException; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Url; use Drupal\file\FileInterface; @@ -37,7 +37,7 @@ use Drupal\locale\Gettext; * l10n_update functionality to feed in translation files alike. * https://www.drupal.org/node/1191488 */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function locale_translate_batch_import_files(array $options, $force = FALSE) { $options += [ 'overwrite_options' => [], diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc index a1ddbf61a4f8..069f200942f5 100644 --- a/core/modules/locale/locale.compare.inc +++ b/core/modules/locale/locale.compare.inc @@ -5,7 +5,7 @@ */ use Drupal\Core\Batch\BatchBuilder; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Utility\ProjectInfo; /** @@ -18,7 +18,7 @@ require_once __DIR__ . '/locale.translation.inc'; /** * Clear the project data table. */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function locale_translation_flush_projects(): void { \Drupal::service('locale.project')->deleteAll(); } diff --git a/core/modules/locale/locale.fetch.inc b/core/modules/locale/locale.fetch.inc index 34d0fda2e94a..678e1e25e58a 100644 --- a/core/modules/locale/locale.fetch.inc +++ b/core/modules/locale/locale.fetch.inc @@ -5,7 +5,7 @@ */ use Drupal\Core\Batch\BatchBuilder; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; /** * Load the common translation API. @@ -28,7 +28,7 @@ require_once __DIR__ . '/locale.translation.inc'; * @return array * Batch definition array. */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function locale_translation_batch_update_build($projects = [], $langcodes = [], $options = []) { \Drupal::moduleHandler()->loadInclude('locale', 'inc', 'locale.compare'); $projects = $projects ?: array_keys(locale_translation_get_projects()); diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index 30aa89000b8a..f583df4426eb 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -7,7 +7,7 @@ use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Link; use Drupal\Core\Url; @@ -78,7 +78,7 @@ function locale_requirements($phase): array { /** * Implements hook_install(). */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function locale_install(): void { // Create the interface translations directory and ensure it's writable. if (!$directory = \Drupal::config('locale.settings')->get('translation.path')) { diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index ba7cbc15122e..3a6856d4c07f 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -11,11 +11,10 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Component\Utility\Xss; use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Installer\InstallerKernel; use Drupal\Core\Site\Settings; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Language\LanguageInterface; use Drupal\Component\Utility\Crypt; use Drupal\locale\LocaleEvent; use Drupal\locale\LocaleEvents; @@ -146,7 +145,7 @@ const LOCALE_TRANSLATION_CURRENT = 'current'; * Array of installed languages keyed by language name. English is omitted * unless it is marked as translatable. */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function locale_translatable_language_list() { $languages = \Drupal::languageManager()->getLanguages(); if (!locale_is_translatable('en')) { @@ -443,30 +442,6 @@ function locale_system_file_system_settings_submit(&$form, FormStateInterface $f } /** - * Implements hook_preprocess_HOOK() for node templates. - */ -function locale_preprocess_node(&$variables): void { - /** @var \Drupal\node\NodeInterface $node */ - $node = $variables['node']; - if ($node->language()->getId() != LanguageInterface::LANGCODE_NOT_SPECIFIED) { - $interface_language = \Drupal::languageManager()->getCurrentLanguage(); - - $node_language = $node->language(); - if ($node_language->getId() != $interface_language->getId()) { - // If the node language was different from the page language, we should - // add markup to identify the language. Otherwise the page language is - // inherited. - $variables['attributes']['lang'] = $node_language->getId(); - if ($node_language->getDirection() != $interface_language->getDirection()) { - // If text direction is different form the page's text direction, add - // direction information as well. - $variables['attributes']['dir'] = $node_language->getDirection(); - } - } - } -} - -/** * Gets current translation status from the {locale_file} table. * * @return array diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index e016a1a99a3f..aef8c5f6fe5f 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -4,7 +4,7 @@ * @file */ -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Link; use Drupal\Core\Url; @@ -22,7 +22,7 @@ use Drupal\Core\Url; * * @see \Drupal\locale\Form\TranslationStatusForm */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function template_preprocess_locale_translation_update_info(array &$variables): void { foreach ($variables['updates'] as $update) { $variables['modules'][] = $update['name']; diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc index 7f48a5d3fa0f..c82ba2e86663 100644 --- a/core/modules/locale/locale.translation.inc +++ b/core/modules/locale/locale.translation.inc @@ -4,7 +4,7 @@ * @file */ -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\StreamWrapper\StreamWrapperManager; /** @@ -55,7 +55,7 @@ const LOCALE_TRANSLATION_SOURCE_COMPARE_GT = 1; * * @see locale_translation_build_projects() */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function locale_translation_get_projects(array $project_names = []) { $projects = &drupal_static(__FUNCTION__, []); diff --git a/core/modules/locale/src/Hook/LocaleThemeHooks.php b/core/modules/locale/src/Hook/LocaleThemeHooks.php new file mode 100644 index 000000000000..d1e438f50ace --- /dev/null +++ b/core/modules/locale/src/Hook/LocaleThemeHooks.php @@ -0,0 +1,43 @@ +<?php + +namespace Drupal\locale\Hook; + +use Drupal\Core\Hook\Attribute\Preprocess; +use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Language\LanguageManagerInterface; + +/** + * Hook implementations for locale. + */ +class LocaleThemeHooks { + + public function __construct( + protected readonly LanguageManagerInterface $languageManager, + ) {} + + /** + * Implements hook_preprocess_HOOK() for node templates. + */ + #[Preprocess('node')] + public function preprocessNode(&$variables): void { + /** @var \Drupal\node\NodeInterface $node */ + $node = $variables['node']; + if ($node->language()->getId() != LanguageInterface::LANGCODE_NOT_SPECIFIED) { + $interface_language = $this->languageManager->getCurrentLanguage(); + + $node_language = $node->language(); + if ($node_language->getId() != $interface_language->getId()) { + // If the node language was different from the page language, we should + // add markup to identify the language. Otherwise the page language is + // inherited. + $variables['attributes']['lang'] = $node_language->getId(); + if ($node_language->getDirection() != $interface_language->getDirection()) { + // If text direction is different form the page's text direction, add + // direction information as well. + $variables['attributes']['dir'] = $node_language->getDirection(); + } + } + } + } + +} diff --git a/core/modules/media/media.install b/core/modules/media/media.install index f0acbf482da4..48ed664fdef7 100644 --- a/core/modules/media/media.install +++ b/core/modules/media/media.install @@ -8,7 +8,7 @@ use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileExists; use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; use Drupal\image\Plugin\Field\FieldType\ImageItem; @@ -112,7 +112,7 @@ function media_requirements($phase): array { /** * Implements hook_install(). */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function media_install(): void { $source = \Drupal::service('extension.list.module')->getPath('media') . '/images/icons'; $destination = \Drupal::config('media.settings')->get('icon_base_uri'); diff --git a/core/modules/media/media.module b/core/modules/media/media.module index 537cbee18620..dbf031d16a9a 100644 --- a/core/modules/media/media.module +++ b/core/modules/media/media.module @@ -5,7 +5,7 @@ */ use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Render\Element; use Drupal\Core\Render\Element\RenderElementBase; use Drupal\Core\Template\Attribute; @@ -23,7 +23,6 @@ use Drupal\Core\Url; * - name: The label for the media item. * - view_mode: View mode; e.g., 'full', 'teaser', etc. */ -#[StopProceduralHookScan] function template_preprocess_media(array &$variables): void { $variables['media'] = $variables['elements']['#media']; $variables['view_mode'] = $variables['elements']['#view_mode']; @@ -74,6 +73,7 @@ function media_preprocess_media_reference_help(&$variables): void { * @internal * This function is internal and may be removed in a minor release. */ +#[ProceduralHookScanStop] function _media_get_add_url($allowed_bundles) { $access_handler = \Drupal::entityTypeManager()->getAccessControlHandler('media'); $create_bundles = array_filter($allowed_bundles, [$access_handler, 'createAccess']); diff --git a/core/modules/media_library/media_library.services.yml b/core/modules/media_library/media_library.services.yml index 510769de9d6d..7c0d2131601a 100644 --- a/core/modules/media_library/media_library.services.yml +++ b/core/modules/media_library/media_library.services.yml @@ -1,5 +1,5 @@ parameters: - media_library.hooks_converted: true + media_library.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/menu_link_content/menu_link_content.services.yml b/core/modules/menu_link_content/menu_link_content.services.yml index 63d3867b1adc..e1a01a283f95 100644 --- a/core/modules/menu_link_content/menu_link_content.services.yml +++ b/core/modules/menu_link_content/menu_link_content.services.yml @@ -1,2 +1,2 @@ parameters: - menu_link_content.hooks_converted: true + menu_link_content.skip_procedural_hook_scan: true diff --git a/core/modules/menu_ui/menu_ui.services.yml b/core/modules/menu_ui/menu_ui.services.yml index 962a86b9c3df..45682dc6e4d1 100644 --- a/core/modules/menu_ui/menu_ui.services.yml +++ b/core/modules/menu_ui/menu_ui.services.yml @@ -1,5 +1,5 @@ parameters: - menu_ui.hooks_converted: true + menu_ui.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/migrate/migrate.services.yml b/core/modules/migrate/migrate.services.yml index 2af458d08270..8b2e40285719 100644 --- a/core/modules/migrate/migrate.services.yml +++ b/core/modules/migrate/migrate.services.yml @@ -1,5 +1,5 @@ parameters: - migrate.hooks_converted: true + migrate.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/migrate_drupal/migrate_drupal.services.yml b/core/modules/migrate_drupal/migrate_drupal.services.yml index e0d16a99459d..358992f62dfb 100644 --- a/core/modules/migrate_drupal/migrate_drupal.services.yml +++ b/core/modules/migrate_drupal/migrate_drupal.services.yml @@ -1,5 +1,5 @@ parameters: - migrate_drupal.hooks_converted: true + migrate_drupal.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/migrate_drupal_ui/migrate_drupal_ui.services.yml b/core/modules/migrate_drupal_ui/migrate_drupal_ui.services.yml index 59ef9f3a1de6..d4cd82212c5d 100644 --- a/core/modules/migrate_drupal_ui/migrate_drupal_ui.services.yml +++ b/core/modules/migrate_drupal_ui/migrate_drupal_ui.services.yml @@ -1,5 +1,5 @@ parameters: - migrate_drupal_ui.hooks_converted: true + migrate_drupal_ui.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/node/node.module b/core/modules/node/node.module index e2b0fcccb2e1..4ee3c48a00b2 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -257,30 +257,6 @@ function node_preprocess_block(&$variables): void { } /** - * Implements hook_preprocess_HOOK() for node field templates. - */ -function node_preprocess_field__node(&$variables): void { - // Set a variable 'is_inline' in cases where inline markup is required, - // without any block elements such as <div>. - - if ($variables['element']['#is_page_title'] ?? FALSE) { - // Page title is always inline because it will be displayed inside <h1>. - $variables['is_inline'] = TRUE; - } - elseif (in_array($variables['field_name'], ['created', 'uid', 'title'], TRUE)) { - // Display created, uid and title fields inline because they will be - // displayed inline by node.html.twig. Skip this if the field - // display is configurable and skipping has been enabled. - // @todo Delete as part of https://www.drupal.org/node/3015623 - - /** @var \Drupal\node\NodeInterface $node */ - $node = $variables['element']['#object']; - $skip_custom_preprocessing = $node->getEntityType()->get('enable_base_field_custom_preprocess_skipping'); - $variables['is_inline'] = !$skip_custom_preprocessing || !$node->getFieldDefinition($variables['field_name'])->isDisplayConfigurable('view'); - } -} - -/** * Implements hook_theme_suggestions_HOOK(). */ function node_theme_suggestions_node(array $variables): array { diff --git a/core/modules/node/src/Hook/NodeThemeHooks.php b/core/modules/node/src/Hook/NodeThemeHooks.php new file mode 100644 index 000000000000..7ee443c458f8 --- /dev/null +++ b/core/modules/node/src/Hook/NodeThemeHooks.php @@ -0,0 +1,38 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\node\Hook; + +use Drupal\Core\Hook\Attribute\Preprocess; + +/** + * Hook implementations for the node module. + */ +class NodeThemeHooks { + + /** + * Implements hook_preprocess_HOOK() for node field templates. + */ + #[Preprocess('field__node')] + public function preprocessFieldNode(&$variables): void { + // Set a variable 'is_inline' in cases where inline markup is required, + // without any block elements such as <div>. + if ($variables['element']['#is_page_title'] ?? FALSE) { + // Page title is always inline because it will be displayed inside <h1>. + $variables['is_inline'] = TRUE; + } + elseif (in_array($variables['field_name'], ['created', 'uid', 'title'], TRUE)) { + // Display created, uid and title fields inline because they will be + // displayed inline by node.html.twig. Skip this if the field + // display is configurable and skipping has been enabled. + // @todo Delete as part of https://www.drupal.org/node/3015623 + + /** @var \Drupal\node\NodeInterface $node */ + $node = $variables['element']['#object']; + $skip_custom_preprocessing = $node->getEntityType()->get('enable_base_field_custom_preprocess_skipping'); + $variables['is_inline'] = !$skip_custom_preprocessing || !$node->getFieldDefinition($variables['field_name'])->isDisplayConfigurable('view'); + } + } + +} diff --git a/core/modules/options/options.services.yml b/core/modules/options/options.services.yml index b3734c763b1d..dbce14350f80 100644 --- a/core/modules/options/options.services.yml +++ b/core/modules/options/options.services.yml @@ -1,2 +1,2 @@ parameters: - options.hooks_converted: true + options.skip_procedural_hook_scan: true diff --git a/core/modules/path/path.services.yml b/core/modules/path/path.services.yml index c500d12fbeea..ce638d5e3ede 100644 --- a/core/modules/path/path.services.yml +++ b/core/modules/path/path.services.yml @@ -1,2 +1,2 @@ parameters: - path.hooks_converted: true + path.skip_procedural_hook_scan: true diff --git a/core/modules/responsive_image/responsive_image.services.yml b/core/modules/responsive_image/responsive_image.services.yml index c382d2567225..a145f1dad9e1 100644 --- a/core/modules/responsive_image/responsive_image.services.yml +++ b/core/modules/responsive_image/responsive_image.services.yml @@ -1,2 +1,2 @@ parameters: - responsive_image.hooks_converted: true + responsive_image.skip_procedural_hook_scan: false diff --git a/core/modules/rest/rest.services.yml b/core/modules/rest/rest.services.yml index aa249ed37825..cb19a18e1ae7 100644 --- a/core/modules/rest/rest.services.yml +++ b/core/modules/rest/rest.services.yml @@ -1,5 +1,5 @@ parameters: - rest.hooks_converted: true + rest.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/serialization/serialization.services.yml b/core/modules/serialization/serialization.services.yml index 9dc3a849ce9a..9629e55d6520 100644 --- a/core/modules/serialization/serialization.services.yml +++ b/core/modules/serialization/serialization.services.yml @@ -1,5 +1,5 @@ parameters: - serialization.hooks_converted: true + serialization.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/settings_tray/settings_tray.services.yml b/core/modules/settings_tray/settings_tray.services.yml index b09ec37f1aff..8ff9184774e9 100644 --- a/core/modules/settings_tray/settings_tray.services.yml +++ b/core/modules/settings_tray/settings_tray.services.yml @@ -1,5 +1,5 @@ parameters: - settings_tray.hooks_converted: true + settings_tray.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/shortcut/shortcut.services.yml b/core/modules/shortcut/shortcut.services.yml index 00671a667b53..fdd4bc85c217 100644 --- a/core/modules/shortcut/shortcut.services.yml +++ b/core/modules/shortcut/shortcut.services.yml @@ -1,5 +1,5 @@ parameters: - shortcut.hooks_converted: true + shortcut.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/syslog/syslog.services.yml b/core/modules/syslog/syslog.services.yml index 3aaa962709cc..8892ab797eeb 100644 --- a/core/modules/syslog/syslog.services.yml +++ b/core/modules/syslog/syslog.services.yml @@ -1,5 +1,5 @@ parameters: - syslog.hooks_converted: true + syslog.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/modules/system/tests/modules/hook_collector_skip_procedural/hook_collector_skip_procedural.services.yml b/core/modules/system/tests/modules/hook_collector_skip_procedural/hook_collector_skip_procedural.services.yml index 205fd7c2b670..2309835abaac 100644 --- a/core/modules/system/tests/modules/hook_collector_skip_procedural/hook_collector_skip_procedural.services.yml +++ b/core/modules/system/tests/modules/hook_collector_skip_procedural/hook_collector_skip_procedural.services.yml @@ -1,2 +1,2 @@ parameters: - hook_collector_skip_procedural.hooks_converted: true + hook_collector_skip_procedural.skip_procedural_hook_scan: true diff --git a/core/modules/system/tests/modules/hook_collector_skip_procedural_attribute/hook_collector_skip_procedural_attribute.module b/core/modules/system/tests/modules/hook_collector_skip_procedural_attribute/hook_collector_skip_procedural_attribute.module index e7b4bf3f6829..31bc606d37e0 100644 --- a/core/modules/system/tests/modules/hook_collector_skip_procedural_attribute/hook_collector_skip_procedural_attribute.module +++ b/core/modules/system/tests/modules/hook_collector_skip_procedural_attribute/hook_collector_skip_procedural_attribute.module @@ -7,7 +7,7 @@ declare(strict_types=1); -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; /** * This implements a hook and should be picked up. @@ -25,7 +25,7 @@ function hook_collector_skip_procedural_attribute_cache_flush(): void { * This attribute should stop all procedural hooks after. * We implement on behalf of other modules so we can pick them up. */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function hook_collector_on_behalf_procedural_cache_flush(): void { // Set a global value we can check in test code. $GLOBALS['procedural_attribute_skip_has_attribute'] = 'procedural_attribute_skip_has_attribute'; diff --git a/core/modules/system/tests/modules/module_test_oop_preprocess/module_test_oop_preprocess.info.yml b/core/modules/system/tests/modules/module_test_oop_preprocess/module_test_oop_preprocess.info.yml new file mode 100644 index 000000000000..c3d2e98f6f46 --- /dev/null +++ b/core/modules/system/tests/modules/module_test_oop_preprocess/module_test_oop_preprocess.info.yml @@ -0,0 +1,5 @@ +name: test oop preprocess functions +type: module +description: 'Test module used to test oop preprocess hooks are executed.' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/module_test_oop_preprocess/src/Hook/ModuleTestOopPreprocessThemeHooks.php b/core/modules/system/tests/modules/module_test_oop_preprocess/src/Hook/ModuleTestOopPreprocessThemeHooks.php new file mode 100644 index 000000000000..1cbb9e6b422b --- /dev/null +++ b/core/modules/system/tests/modules/module_test_oop_preprocess/src/Hook/ModuleTestOopPreprocessThemeHooks.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\module_test_oop_preprocess\Hook; + +use Drupal\Core\Hook\Attribute\Preprocess; + +/** + * Hook implementations for module_test_oop_preprocess. + */ +class ModuleTestOopPreprocessThemeHooks { + + #[Preprocess] + public function rootPreprocess($arg): mixed { + return $arg; + } + + #[Preprocess('test')] + public function preprocessTest($arg): mixed { + return $arg; + } + +} diff --git a/core/modules/system/tests/modules/module_test_procedural_preprocess/module_test_procedural_preprocess.info.yml b/core/modules/system/tests/modules/module_test_procedural_preprocess/module_test_procedural_preprocess.info.yml new file mode 100644 index 000000000000..2cc1e7840d5d --- /dev/null +++ b/core/modules/system/tests/modules/module_test_procedural_preprocess/module_test_procedural_preprocess.info.yml @@ -0,0 +1,5 @@ +name: test procedural preprocess functions +type: module +description: 'Test module used to test procedural preprocess hooks are executed.' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/module_test_procedural_preprocess/module_test_procedural_preprocess.module b/core/modules/system/tests/modules/module_test_procedural_preprocess/module_test_procedural_preprocess.module new file mode 100644 index 000000000000..1e7665b412d0 --- /dev/null +++ b/core/modules/system/tests/modules/module_test_procedural_preprocess/module_test_procedural_preprocess.module @@ -0,0 +1,20 @@ +<?php + +/** + * @file + * Test module. + */ + +declare(strict_types=1); + +function template_preprocess_test($arg): mixed { + return $arg; +} + +function module_test_procedural_preprocess_preprocess($arg): mixed { + return $arg; +} + +function module_test_procedural_preprocess_preprocess_test($arg): mixed { + return $arg; +} diff --git a/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestThemeHooks.php b/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestThemeHooks.php new file mode 100644 index 000000000000..fc48756de51a --- /dev/null +++ b/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestThemeHooks.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\theme_test\Hook; + +use Drupal\Core\Hook\Attribute\Preprocess; + +/** + * Hook implementations for theme_test. + */ +class ThemeTestThemeHooks { + + /** + * Implements hook_preprocess_HOOK(). + */ + #[Preprocess('theme_test_preprocess_suggestions__monkey')] + public function preprocessTestSuggestions(&$variables): void { + $variables['foo'] = 'Monkey'; + } + +} diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module index aabef2b6c4aa..72c92352b617 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.module +++ b/core/modules/system/tests/modules/theme_test/theme_test.module @@ -32,13 +32,6 @@ function theme_test_preprocess_theme_test_preprocess_suggestions(&$variables): v } /** - * Tests a module overriding a default hook with a suggestion. - */ -function theme_test_preprocess_theme_test_preprocess_suggestions__monkey(&$variables): void { - $variables['foo'] = 'Monkey'; -} - -/** * Prepares variables for test render element templates. * * Default template: theme-test-render-element.html.twig. diff --git a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php index eccaceaeca70..5520b49f762e 100644 --- a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php +++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php @@ -362,6 +362,56 @@ class ModuleHandlerTest extends KernelTestBase { } /** + * Tests procedural preprocess functions. + */ + public function testProceduralPreprocess(): void { + $this->moduleInstaller()->install(['module_test_procedural_preprocess']); + $preprocess_function = []; + $preprocess_invoke = []; + $prefix = 'module_test_procedural_preprocess'; + $hook = 'test'; + if ($this->moduleHandler()->hasImplementations('preprocess', [$prefix], TRUE)) { + $function = "{$prefix}_preprocess"; + $preprocess_function[] = $function; + $preprocess_invoke[$function] = ['module' => $prefix, 'hook' => 'preprocess']; + } + if ($this->moduleHandler()->hasImplementations('preprocess_' . $hook, [$prefix], TRUE)) { + $function = "{$prefix}_preprocess_{$hook}"; + $preprocess_function[] = $function; + $preprocess_invoke[$function] = ['module' => $prefix, 'hook' => 'preprocess_' . $hook]; + } + + foreach ($preprocess_function as $function) { + $this->assertTrue($this->moduleHandler()->invoke(... $preprocess_invoke[$function], args: [TRUE]), 'Procedural hook_preprocess runs.'); + } + } + + /** + * Tests Oop preprocess functions. + */ + public function testOopPreprocess(): void { + $this->moduleInstaller()->install(['module_test_oop_preprocess']); + $preprocess_function = []; + $preprocess_invoke = []; + $prefix = 'module_test_oop_preprocess'; + $hook = 'test'; + if ($this->moduleHandler()->hasImplementations('preprocess', [$prefix], TRUE)) { + $function = "{$prefix}_preprocess"; + $preprocess_function[] = $function; + $preprocess_invoke[$function] = ['module' => $prefix, 'hook' => 'preprocess']; + } + if ($this->moduleHandler()->hasImplementations('preprocess_' . $hook, [$prefix], TRUE)) { + $function = "{$prefix}_preprocess_{$hook}"; + $preprocess_function[] = $function; + $preprocess_invoke[$function] = ['module' => $prefix, 'hook' => 'preprocess_' . $hook]; + } + + foreach ($preprocess_function as $function) { + $this->assertTrue($this->moduleHandler()->invoke(... $preprocess_invoke[$function], args: [TRUE]), 'Procedural hook_preprocess runs.'); + } + } + + /** * Returns the ModuleHandler. * * @return \Drupal\Core\Extension\ModuleHandlerInterface diff --git a/core/modules/text/text.services.yml b/core/modules/text/text.services.yml index 207641245768..720e0fe820b7 100644 --- a/core/modules/text/text.services.yml +++ b/core/modules/text/text.services.yml @@ -1,2 +1,2 @@ parameters: - text.hooks_converted: true + text.skip_procedural_hook_scan: true diff --git a/core/modules/toolbar/toolbar.services.yml b/core/modules/toolbar/toolbar.services.yml index 238520267207..67a1b0719165 100644 --- a/core/modules/toolbar/toolbar.services.yml +++ b/core/modules/toolbar/toolbar.services.yml @@ -1,5 +1,5 @@ parameters: - toolbar.hooks_converted: true + toolbar.skip_procedural_hook_scan: false services: _defaults: diff --git a/core/modules/update/update.authorize.inc b/core/modules/update/update.authorize.inc index 8a2ba55d667f..64f8fe7b014a 100644 --- a/core/modules/update/update.authorize.inc +++ b/core/modules/update/update.authorize.inc @@ -5,7 +5,7 @@ */ use Drupal\Core\Batch\BatchBuilder; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Updater\UpdaterException; use Drupal\Core\Url; @@ -31,7 +31,7 @@ use Drupal\Core\Url; * an instance of \Symfony\Component\HttpFoundation\Response the calling code * should use that response for the current page request. */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function update_authorize_run_update($filetransfer, $projects) { @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED); diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc index 1ea781a974b6..05c6565ce197 100644 --- a/core/modules/update/update.compare.inc +++ b/core/modules/update/update.compare.inc @@ -5,7 +5,7 @@ */ use Drupal\Core\Extension\ExtensionVersion; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Utility\Error; use Drupal\update\ProjectRelease; use Drupal\update\UpdateFetcherInterface; @@ -23,7 +23,7 @@ use Drupal\update\ProjectCoreCompatibility; * Array of project information from * \Drupal\update\UpdateManager::getProjects(). */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function update_process_project_info(&$projects): void { foreach ($projects as $key => $project) { // Assume an official release until we see otherwise. diff --git a/core/modules/update/update.fetch.inc b/core/modules/update/update.fetch.inc index 2c828cc3e7fb..296dfbd3d06d 100644 --- a/core/modules/update/update.fetch.inc +++ b/core/modules/update/update.fetch.inc @@ -4,7 +4,7 @@ * @file */ -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\update\UpdateManagerInterface; /** @@ -16,7 +16,7 @@ use Drupal\update\UpdateManagerInterface; * * @see update_requirements() */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function _update_cron_notify(): void { $update_config = \Drupal::config('update.settings'); \Drupal::moduleHandler()->loadInclude('update', 'install'); diff --git a/core/modules/update/update.install b/core/modules/update/update.install index 2981ea91373a..3ad55265aab1 100644 --- a/core/modules/update/update.install +++ b/core/modules/update/update.install @@ -5,7 +5,7 @@ * Install, update, and uninstall functions for the Update Status module. */ -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\update\ProjectSecurityData; @@ -74,7 +74,7 @@ function update_requirements($phase): array { /** * Implements hook_install(). */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function update_install(): void { $queue = \Drupal::queue('update_fetch_tasks', TRUE); $queue->createQueue(); diff --git a/core/modules/update/update.manager.inc b/core/modules/update/update.manager.inc index 46611f85cd87..f4b18321002b 100644 --- a/core/modules/update/update.manager.inc +++ b/core/modules/update/update.manager.inc @@ -7,7 +7,7 @@ use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\Exception\InvalidStreamWrapperException; use Drupal\Core\File\FileExists; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Psr\Http\Client\ClientExceptionInterface; /** @@ -24,7 +24,7 @@ use Psr\Http\Client\ClientExceptionInterface; * workflow, or FALSE if we've hit a fatal configuration and must halt the * workflow. */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function _update_manager_check_backends(&$form, $operation) { @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED); diff --git a/core/modules/update/update.module b/core/modules/update/update.module index c4366293a059..e06ac21ea317 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -5,7 +5,7 @@ */ use Drupal\Core\File\Exception\FileException; -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; +use Drupal\Core\Hook\Attribute\ProceduralHookScanStop; use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\Core\Site\Settings; @@ -15,7 +15,7 @@ use Drupal\update\UpdateManagerInterface; /** * Returns a warning message when there is no data about available updates. */ -#[StopProceduralHookScan] +#[ProceduralHookScanStop] function _update_no_data() { $destination = \Drupal::destination()->getAsArray(); return t('No update information available. <a href=":run_cron">Run cron</a> or <a href=":check_manually">check manually</a>.', [ diff --git a/core/modules/update/update.report.inc b/core/modules/update/update.report.inc index 615e54b35b20..75957754bd87 100644 --- a/core/modules/update/update.report.inc +++ b/core/modules/update/update.report.inc @@ -4,7 +4,6 @@ * @file */ -use Drupal\Core\Hook\Attribute\StopProceduralHookScan; use Drupal\Core\Template\Attribute; use Drupal\Core\Url; use Drupal\update\ProjectRelease; @@ -20,7 +19,6 @@ use Drupal\update\UpdateManagerInterface; * An associative array containing: * - data: An array of data about each project's status. */ -#[StopProceduralHookScan] function template_preprocess_update_report(&$variables): void { $data = isset($variables['data']) && is_array($variables['data']) ? $variables['data'] : []; diff --git a/core/modules/workflows/workflows.services.yml b/core/modules/workflows/workflows.services.yml index 868807dfc639..ad50650e6dbc 100644 --- a/core/modules/workflows/workflows.services.yml +++ b/core/modules/workflows/workflows.services.yml @@ -1,5 +1,5 @@ parameters: - workflows.hooks_converted: true + workflows.skip_procedural_hook_scan: true services: _defaults: diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php index 774f1289ccdb..58b608248c9f 100644 --- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php +++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php @@ -143,19 +143,6 @@ class HookCollectorPassTest extends KernelTestBase { } /** - * Test Hook attribute with named arguments, and class with invoke method. - */ - public function testHookAttribute(): void { - $module_installer = $this->container->get('module_installer'); - $this->assertTrue($module_installer->install(['hook_collector_hook_attribute'])); - $this->assertFalse(isset($GLOBALS['hook_named_arguments'])); - $this->assertFalse(isset($GLOBALS['hook_invoke_method'])); - drupal_flush_all_caches(); - $this->assertTrue(isset($GLOBALS['hook_named_arguments'])); - $this->assertTrue(isset($GLOBALS['hook_invoke_method'])); - } - - /** * Tests hook ordering with attributes. */ public function testHookFirst(): void { diff --git a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php index f237c09d1caa..862d7e2f65d8 100644 --- a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php +++ b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php @@ -157,19 +157,21 @@ class RegistryTest extends UnitTestCase { include_once $this->root . '/core/modules/system/tests/modules/theme_test/theme_test.module'; include_once $this->root . '/core/tests/fixtures/test_stable/test_stable.theme'; $themeTestTheme = new ThemeTestHooks(); - $this->moduleHandler->expects($this->atLeastOnce()) + $this->moduleHandler->expects($this->exactly(2)) ->method('invoke') ->with('theme_test', 'theme') ->willReturn($themeTestTheme->theme(NULL, NULL, NULL, NULL)); - $this->moduleHandler->expects($this->atLeastOnce()) + $this->moduleHandler->expects($this->atMost(50)) ->method('invokeAllWith') - ->with('theme') - ->willReturnCallback(function (string $hook, callable $callback) { - $callback(function () {}, 'theme_test'); - }); - $this->moduleHandler->expects($this->atLeastOnce()) + // $callback is documented on ModuleHandlerInterface::invokeAllWith(). + // The first argument expects a callable, but it doesn't matter what it + // is, use pi() as a canary in case code changes, and it begins to use it. + // The second argument is the module name and for that theme_test is + // always correct here. + ->willReturnCallback(fn (string $hook, callable $callback) => $callback('pi', 'theme_test')); + $this->moduleHandler->expects($this->exactly(2)) ->method('getModuleList') - ->willReturn([]); + ->willReturn(['theme_test' => NULL]); $this->moduleList->expects($this->exactly(2)) ->method('getPath') ->with('theme_test') |