ppf-1.2.x-dev/src/PreprocessorFiles.php
src/PreprocessorFiles.php
<?php
namespace Drupal\ppf;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeExtensionList;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Theme\ThemeManagerInterface;
use Symfony\Component\Finder\Finder;
/**
* Provides a service for Preprocessor Files.
*/
final class PreprocessorFiles {
use StringTranslationTrait;
/**
* Stores the name of the directory where preprocessor files are stored.
*
* @var string
*/
public const PREPROCESSOR_FILES_DIRECTORY = 'preprocessors';
/**
* The ID of the main configuration for the module.
*
* @var string
*/
public const CONFIG_ID = 'ppf.settings';
/**
* ID of the 'Preprocessor Files Extension' configuration.
*
* Allows specification of the directory to load preprocessor files from.
*
* e.g. By default, it searches for '*.preprocess.php' files.
*
* @var string
*/
public const CONFIG__PREPROCESSOR_FILES_EXTENSION = 'preprocessor_files_extension';
/**
* Stores default value of the 'Preprocessor Files Extension' configuration.
*
* @var string
*/
public const CONFIG__PREPROCESSOR_FILES_EXTENSION__DEFAULT = '.preprocess.php';
/**
* Use DI to inject Drupal's configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
public ConfigFactoryInterface $configFactory;
/**
* Drupal's Module Handler injected through DI.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
public ModuleHandlerInterface $moduleHandler;
/**
* Drupal's Theme Manager injected through DI.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
public ThemeManagerInterface $themeManager;
/**
* Drupal's Theme Extension List injected through DI.
*
* @var \Drupal\Core\Extension\ThemeExtensionList
*/
public ThemeExtensionList $themeExtensionList;
/**
* Constructs the PreprocessorFiles service.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* Configuration Factory service injected through DI.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* Module Handler service injected through DI.
* @param \Drupal\Core\Theme\ThemeManagerInterface $themeManager
* Theme Manager service injected through DI.
* @param \Drupal\Core\Extension\ThemeExtensionList $themeExtensionList
* Theme Extension List service injected through DI.
*/
public function __construct(
ConfigFactoryInterface $configFactory,
ModuleHandlerInterface $moduleHandler,
ThemeManagerInterface $themeManager,
ThemeExtensionList $themeExtensionList,
) {
$this->configFactory = $configFactory;
$this->moduleHandler = $moduleHandler;
$this->themeManager = $themeManager;
$this->themeExtensionList = $themeExtensionList;
}
/**
* Shortcut to obtain the PreprocessorFiles service from the global container.
*
* @return \Drupal\ppf\PreprocessorFiles
* The PreprocessorFiles service.
*/
public static function service() : PreprocessorFiles {
static $service;
if (!empty($service)) {
return $service;
}
$service = \Drupal::service('ppf');
return $service;
}
/**
* Get main configuration for the Preprocessor Files module.
*
* @return \Drupal\Core\Config\ImmutableConfig
* The immutable configuration object.
*/
public function config() : ImmutableConfig {
static $config = NULL;
if (!empty($config)) {
return $config;
}
$config = $this->configFactory->get(self::CONFIG_ID);
return $config;
}
/**
* Get the directory to search for preprocessor files in.
*
* @param string|null $theme
* Theme to obtain the directory path for.
*
* @return string
* Returns the path to the configured directory in the current active theme.
*/
public function getPreprocessorFilesDirectoryForTheme(string|null $theme) : string {
static $directory = [];
if ($theme === NULL) {
$theme = $this->themeManager->getActiveTheme()->getName();
}
if (!empty($directory[$theme])) {
return $directory[$theme];
}
$themePath = $this->themeExtensionList->getPath($theme);
$directory[$theme] = $themePath . '/' . self::PREPROCESSOR_FILES_DIRECTORY;
return $directory[$theme];
}
/**
* Get the configured preprocessor files extension.
*
* @return string
* Returns the file extension that is configured.
*/
public function getPreprocessorFilesExtension() : string {
static $extension;
if ($extension !== NULL) {
return $extension;
}
$extension = $this->config()->get(self::CONFIG__PREPROCESSOR_FILES_EXTENSION) ?? self::CONFIG__PREPROCESSOR_FILES_EXTENSION__DEFAULT;
return $extension;
}
/**
* Check if preprocessor files exist in the currently active theme.
*
* @param string|null $theme
* Theme to check for.
*
* @return bool
* Returns TRUE if files exists. Returns FALSE otherwise.
*/
public function preprocessorFilesDirectoryExistsInTheme(string|null $theme = NULL) : bool {
static $exists = [];
if ($theme === NULL) {
$theme = $this->themeManager->getActiveTheme()->getName();
}
if (!empty($exists[$theme])) {
return $exists[$theme];
}
$directory = self::getPreprocessorFilesDirectoryForTheme($theme);
$exists[$theme] = file_exists($directory);
return $exists[$theme];
}
/**
* Get preprocessor files for a given theme.
*
* Will get files for the active theme if no theme is provided.
*
* @param string|null $theme
* Theme to obtain preprocessor files for.
*
* @return array
* Array of file paths.
*/
public static function getPreprocessorFilesForTheme(string|null $theme = NULL) : array {
// Store files in a static variable. Only fetch them once.
static $files = [];
// Get active theme if theme is null.
if ($theme === NULL) {
$theme = self::service()->themeManager->getActiveTheme()->getName();
}
// If files are set, get them.
if (isset($files[$theme])) {
return $files[$theme];
}
// If the directory doesn't exist for the theme, we stop here.
if (!self::service()->preprocessorFilesDirectoryExistsInTheme($theme)) {
$files[$theme] = [];
return $files[$theme];
}
// Get base theme files if this is the active theme.
if ($theme === self::service()->themeManager->getActiveTheme()->getName()) {
/** @var ThemeExtensionList $themeExtension */
$baseThemes = array_reverse(self::service()->themeManager->getActiveTheme()->getBaseThemeExtensions());
foreach ($baseThemes as $baseThemeName => $extension) {
$files[$theme] = array_merge_recursive($files[$theme] ?? [], self::getPreprocessorFilesForTheme($baseThemeName));
}
}
// Get the directory and load files for it.
$directory = self::service()->getPreprocessorFilesDirectoryForTheme($theme);
$files[$theme] = array_merge_recursive($files[$theme] ?? [], self::getPreprocessorFiles($directory));
return $files[$theme];
}
/**
* Get preprocessor files for active modules.
*
* @return array
* Array of file paths.
*/
public static function getPreprocessorFilesForActiveModules() : array {
static $files = [];
if (!empty($files)) {
return $files;
}
$moduleList = self::service()->moduleHandler->getModuleList();
foreach ($moduleList as $module) {
$moduleDirectory = $module->getPath();
$files = array_merge_recursive($files, self::getPreprocessorFiles($moduleDirectory));
}
return $files;
}
/**
* Get array of preprocessor files from a given directory.
*
* @param string $directory
* The directory to search in.
*
* @return array
* Array of files that were found.
*/
private static function getPreprocessorFiles(string $directory) : array {
static $files;
if (isset($files[$directory])) {
return $files[$directory];
}
// Get file extension.
$extension = self::service()->getPreprocessorFilesExtension();
// Initialize a Symfony Finder component to aid us here. Very handy!
// We try to load all preprocessor files here.
$finder = new Finder();
$finder->in($directory);
$finder->name("*$extension");
$finder->files();
// If we have no results, we can stop here.
if (!$finder->hasResults()) {
return [];
}
// We do a quick first loop to simply get all found files and set hooks.
foreach ($finder as $file) {
$files[$directory][str_replace('-', '_', explode(".", $file->getFilename())[0])][] = $file->getPathname();
}
// Sort our hooks.
// This is a way to make sure base hooks execute before subhooks.
uksort($files[$directory], function ($a, $b) {
return strcmp($a, $b);
});
return $files[$directory];
}
}
