preprocessors-8.x-1.0-beta8/preprocessors.module
preprocessors.module
<?php
/**
* @file
* Module file for the Preprocessors module.
*
* Allows Drupal to load preprocessor plugins. These act like an OOP alternative
* to preprocess hooks that you would place in your .theme or .module files.
*/
use Drupal\preprocessors\Preprocessors;
/**
* Implements hook_help().
*
* @noinspection PhpUnused
*/
function preprocessors_help(string $route_name) : ?string {
switch ($route_name) {
case 'help.page.preprocessors':
$output = '<p><strong>' . t('Refer to the official <a href="https://git.drupalcode.org/project/preprocessors">README</a> for more detailed documentation!') . '</strong></p>';
$output .= '<p>' . t('The Preprocessors module allows you to create dedicated plugins to preprocess template variables instead of hooks.') . '</p>';
$output .= '<h2>' . t('Main Functionality') . '</h2>';
$output .= '<p>' . t('This behaviour is similar to <a href="https://www.drupal.org/docs/8/theming-drupal-8/modifying-attributes-in-a-theme-file">Preprocess Functions</a>. Understanding how they work is crucial to the use of this module.') . '</p>';
$output .= '<p>' . t('Additionally, understanding the <a href="https://www.drupal.org/docs/drupal-apis/plugin-api">Plugin API</a> is essentially to the usage of this module.') . '</p>';
return $output;
}
return NULL;
}
/**
* Implements hook_theme_registry_alter().
*/
function preprocessors_theme_registry_alter(&$theme_registry) : void {
// Load our Preprocessor Plugins first. If there are none, we can stop here.
$plugins = Preprocessors::service()->getPreprocessorPlugins();
if (empty($plugins)) {
return;
}
// Sort our plugins.
// This is a way to make sure base hooks execute before subhooks.
// Our plugins are keyed by the hook they are to act on.
uksort($plugins, function ($a, $b) {
return strcmp($a, $b);
});
// Loop in our plugins and set some parameters in the registry.
foreach ($plugins as $hook => $collections) {
// If the hook isn't in the registry, we try to find a base hook, so we
// can instantiate it.
if (!isset($theme_registry[$hook])) {
// This aims to emulate the same functionality as preprocess functions.
// e.g. If node--article.html.twig doesn't exist, but a
// HOOK_preprocess_node__article() function exists, a registry entry is
// created for the 'node__article' hook. We need to cover the case where
// a preprocessor plugin exists, but no template or hook exists.
$baseHookCandidate = explode("__", $hook)[0];
// If no base hook exists, we simply stop here.
if (!isset($theme_registry[$baseHookCandidate])) {
continue;
}
// Now we know a base hook exists.
// We simply want to copy the implementation of the base hook into a new
// entry for our subhook.
$theme_registry[$hook] = $theme_registry[$baseHookCandidate];
// Adjust the data for our subhook.
$theme_registry[$hook]['base hook'] = $baseHookCandidate;
}
_preprocessors_insert_hook_preprocess_function($theme_registry, $hook);
// Entries with base hooks should inherit any preprocessor plugins.
if (
isset($theme_registry[$hook]['base hook'])
&& isset($theme_registry[$theme_registry[$hook]['base hook']]['preprocessors']['preprocess plugins'])
) {
$theme_registry[$hook]['preprocessors']['base preprocess plugins'] = $theme_registry[$theme_registry[$hook]['base hook']]['preprocessors']['preprocess plugins'];
}
// Add our preprocessor plugins to the list of plugins.
foreach ($collections['module'] ?? [] as $preprocessor) {
$theme_registry[$hook]['preprocessors']['preprocess plugins']['module'][] = $preprocessor->getId();
}
foreach ($collections['theme'] ?? [] as $preprocessor) {
$theme_registry[$hook]['preprocessors']['preprocess plugins']['theme'][] = $preprocessor->getId();
}
}
}
/**
* Add custom functions to the 'preprocess functions' entry in the registry.
*
* @param array $theme_registry
* The Drupal theme registry.
* @param string $hook
* The hook to add our custom function to.
* @param bool $base
* Set to TRUE if processing the base hook of the hook.
*/
function _preprocessors_insert_hook_preprocess_function(array &$theme_registry, string $hook, bool $base = FALSE) : void {
// Add our custom preprocess function to the registry entry.
if (!$base && isset($theme_registry[$hook]['base hook']) && !in_array('_preprocessors_load_base_hook_plugins', $theme_registry[$hook]['preprocess functions'])) {
_preprocessors_insert_hook_preprocess_function($theme_registry, $hook, TRUE);
}
$implementation = '_preprocessors_load_hook_plugins';
$check = $hook;
if ($base) {
$implementation = '_preprocessors_load_base_hook_plugins';
$check = $theme_registry[$hook]['base hook'];
}
// Add our custom preprocess function to the registry entry.
if (!in_array($implementation, $theme_registry[$hook]['preprocess functions'])) {
// We want our custom function to run after all other module hooks,
// but before theme hooks.
// We'll do some shenanigans here to accomplish this.
$moduleList = Preprocessors::service()->getModuleList();
// Here we want to find the last module preprocess function.
$lastModulePreprocess = NULL;
foreach ($moduleList as $moduleName => $extension) {
$foundModulePreprocess = array_filter($theme_registry[$hook]['preprocess functions'], function ($entry) use ($moduleName, $check) {
return str_starts_with($entry, $moduleName . '_preprocess') && str_ends_with($entry, 'preprocess_' . $check);
});
if (!empty($foundModulePreprocess)) {
$lastModulePreprocess = end($foundModulePreprocess);
}
}
// Place plugin preprocessing right before theme preprocessing.
// Otherwise, we try something different.
if ($lastModulePreprocess !== NULL) {
array_splice($theme_registry[$hook]['preprocess functions'], array_search($lastModulePreprocess, $theme_registry[$hook]['preprocess functions']) + 1, 0, $implementation);
}
else {
// If a module preprocess could not be located, we try to place it
// after the traditional 'template_preprocess'.
// We go further than that if 'template_preprocess_HOOK' exists.
$templatePreprocessKey = array_search('template_preprocess', $theme_registry[$hook]['preprocess functions']);
$templatePreprocessHookKey = array_search('template_preprocess_' . $hook, $theme_registry[$hook]['preprocess functions']);
$templatePreprocessBaseHookKey = FALSE;
if (isset($theme_registry[$hook]['base hook'])) {
$templatePreprocessBaseHookKey = array_search('template_preprocess_' . $hook, $theme_registry[$hook]['preprocess functions']);
}
if ($templatePreprocessHookKey !== FALSE) {
array_splice($theme_registry[$hook]['preprocess functions'], $templatePreprocessHookKey + 1, 0, $implementation);
}
elseif (isset($theme_registry[$hook]['base hook']) && $templatePreprocessBaseHookKey !== FALSE) {
array_splice($theme_registry[$hook]['preprocess functions'], $templatePreprocessBaseHookKey + 1, 0, $implementation);
}
else {
array_splice($theme_registry[$hook]['preprocess functions'], $templatePreprocessKey + 1, 0, $implementation);
}
}
}
}
/**
* Handle loading of relevant preprocessor file for a given template.
*
* @param array $variables
* The array of variables passed to the Twig template.
* @param string $hook
* The theme hook.
* @param array $info
* The theme info.
*/
function _preprocessors_load_hook_plugins(array &$variables, string $hook, array $info) : void {
// Preprocess with plugins.
Preprocessors::preprocess($info['preprocessors']['preprocess plugins'], $variables, $hook, $info);
}
/**
* Handle loading of relevant preprocessor file for a given template.
*
* @param array $variables
* The array of variables passed to the Twig template.
* @param string $hook
* The theme hook.
* @param array $info
* The theme info.
*/
function _preprocessors_load_base_hook_plugins(array &$variables, string $hook, array $info) : void {
// Preprocess with base hook plugins first.
if (isset($info['preprocessors']['base preprocess plugins'])) {
Preprocessors::preprocess($info['preprocessors']['base preprocess plugins'], $variables, $hook, $info);
}
}
