component_connector-1.1.0/src/ComponentConnectorManager.php
src/ComponentConnectorManager.php
<?php
namespace Drupal\component_connector;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Utility\NestedArray;
use Drupal\component_connector\Plugin\Layout\ComponentConnectorLayout;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Layout\LayoutDefinition;
use Drupal\Core\Render\Element;
use Drupal\Core\Theme\Registry;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\Core\Serialization\Yaml;
/**
* ComponentConnectorManager service.
*/
class ComponentConnectorManager {
/**
* The theme manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* The search theme.registry service.
*
* @var \Drupal\Core\Theme\Registry
*/
protected $themeRegistry;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The search manager.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The name of the theme from config.
*
* @var string|null
*/
protected $themeName;
/**
* The theme manager.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Constructs a ComponentManager object.
*
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
* The theme manager.
* @param \Drupal\Core\Theme\Registry $theme_registry
* The theme.registry service.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file_system service.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* Config factory service.
*/
public function __construct(ThemeManagerInterface $theme_manager, Registry $theme_registry, FileSystemInterface $file_system, ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory) {
$this->themeRegistry = $theme_registry;
$this->fileSystem = $file_system;
$this->themeHandler = $theme_handler;
$this->configFactory = $config_factory;
$this->themeName = $config_factory->get('component_connector.settings')
->get('theme');
$this->themeManager = $theme_manager;
}
/**
* Get internal definitions.
*
* @return array
* Files array.
*/
private function getDefinitions($type = 'theme') {
if (!$this->themeName || !$this->themeHandler->themeExists($this->themeName)) {
return [];
}
$mask = '/^.*\.' . $type . '\.yml$/';
$theme = $this->themeHandler->getTheme($this->themeName);
$file_cache = FileCacheFactory::get('component_connector:' . $type);
$files = $this->fileSystem->scanDirectory($theme->getPath(), $mask, ['key' => 'uri']);
$all = [];
// Try to load from the file cache first.
foreach ($file_cache->getMultiple(array_keys($files)) as $file => $data) {
$all[$file] = $data;
unset($files[$file]);
}
if ($files) {
foreach ($files as $id => $file) {
$data = Yaml::decode(file_get_contents($file->uri)) ?: [];
$all[$id] = $data;
$file_cache->set($id, $data);
}
}
return $all;
}
/**
* Altering registry.
*/
public function alterRegistry(array &$theme_registry) {
$active_theme_name = $this->themeManager->getActiveTheme()->getName();
if (!$this->themeName || $this->themeName != $active_theme_name) {
return;
}
foreach ($this->getDefinitions() as $id => $definition) {
$definition = reset($definition);
// Replace hooks.
$new_hook_theme = [];
if (isset($definition['hook theme'])) {
$path_parts = pathinfo($id);
$template = str_replace('.theme', '', $path_parts['filename']);
$new_hook_theme['template'] = $template;
$new_hook_theme['path'] = $path_parts['dirname'];
$this->attachLibraries($definition, $new_hook_theme, $path_parts);
// Check for additional preprocess functions.
// Legacy to remove. See https://www.drupal.org/project/drupal/issues/2060773 for details.
if (function_exists($this->themeName . '_preprocess_' . $definition['hook theme'])) {
$new_hook_theme['preprocess functions'][] = $this->themeName . '_preprocess_' . $definition['hook theme'];
}
if (isset($definition['base hook']) && isset($theme_registry[$definition['base hook']])) {
if ($definition['base hook'] == 'layout') {
$new_hook_theme['preprocess functions'][] = [
'\Drupal\component_connector\ComponentConnectorManager',
'preprocessLayout',
];
}
$theme_registry[$definition['hook theme']] = NestedArray::mergeDeep($theme_registry[$definition['base hook']], $new_hook_theme);
$theme_registry[$definition['hook theme']]['base hook'] = $definition['base hook'];
}
elseif (isset($theme_registry[$definition['hook theme']])) {
// Replace hook theme.
$theme_registry[$definition['hook theme']] = NestedArray::mergeDeep($theme_registry[$definition['hook theme']], $new_hook_theme);
}
else {
// Check and add variables if any.
if (isset($definition['fields'])) {
$new_hook_theme['variables'] = array_fill_keys(array_keys($definition['fields']), NULL);
if (isset($definition['settings'])) {
$new_hook_theme['variables'] += array_fill_keys(array_keys($definition['settings']), NULL);
}
}
// Register new hook theme.
$new_hook_theme['type'] = 'theme_engine';
$theme_registry[$definition['hook theme']] = $new_hook_theme;
}
if (isset($theme_registry[$definition['hook theme']]['render element'])) {
$this->attachVariables($definition, $theme_registry[$definition['hook theme']]);
}
}
}
foreach ($this->getDefinitions('suggestion') as $id => $definition) {
// Process template suggestion and map to component html.twig.
$definition = reset($definition);
if (isset($definition['base hook']) && !isset($definition['hook theme'])
&& isset($theme_registry[$definition['base hook']])) {
$path_parts = pathinfo($id);
$template = str_replace('.suggestion', '', $path_parts['filename']);
$theme_registry[$definition['base hook']]['template'] = $template;
$theme_registry[$definition['base hook']]['path'] = $path_parts['dirname'];
$theme_registry[$definition['base hook']]['type'] = 'theme_engine';
$this->attachLibraries($definition, $theme_registry[$definition['base hook']], $path_parts);
$this->attachVariables($definition, $theme_registry[$definition['base hook']]);
if (isset($theme_registry[$definition['base hook']]['variables'])) {
// Merge variables if any.
if (isset($definition['fields'])) {
$theme_registry[$definition['base hook']]['variables'] += array_fill_keys(array_keys($definition['fields']), NULL);
if (isset($definition['settings'])) {
$theme_registry[$definition['base hook']]['variables'] += array_fill_keys(array_keys($definition['settings']), NULL);
}
}
}
}
}
}
/**
* Preprocess callback for attaching libraries.
*
* @param array $definition
* An definitions array.
* @param array $new_hook_theme
* An new hook theme array.
*/
private function attachLibraries(array $definition, array &$new_hook_theme, $path_parts) {
$active_theme_name = $this->themeManager->getActiveTheme()->getName();
$libraries = [];
if (isset($definition['libraries'])) {
// Attach libraries.
foreach ($definition['libraries'] as $library) {
if (is_array($library)) {
$libraries[] = $active_theme_name . '/' . key($library);
}
else {
$libraries[] = $library;
}
}
}
// Check and attach libraries detected automatically.
$name = str_replace('.theme', '', $path_parts['filename']);
$asset = $path_parts["dirname"] . '/' . $name;
if (file_exists($asset . '.css') || file_exists($asset . '.js')) {
$libraries[] = $active_theme_name . '/' . $name;
}
if (!empty($libraries)) {
$libraries = array_unique($libraries);
$new_hook_theme['attached']['library'] = $libraries;
$new_hook_theme['preprocess functions'][] = [
'\Drupal\component_connector\ComponentConnectorManager',
'preprocessLibraries',
];
}
}
/**
* Preprocess callback for processing additional variables.
*
* @param array $definition
* An definitions array.
* @param array $hook_theme
* Hook theme array to modify.
*/
private function attachVariables(array $definition, array &$hook_theme) {
if (isset($definition['fields']) || isset($definition['settings'])) {
// Attach libraries.
$hook_theme['preprocess functions'][] = [
'\Drupal\component_connector\ComponentConnectorManager',
'preprocessVariables',
];
}
}
/**
* Preprocess callback for attaching libraries.
*
* @param array $variables
* An associative array.
* @param string $hook
* Hook name.
* @param array $info
* Hook info.
*/
public static function preprocessLibraries(array &$variables, $hook, array $info) {
if (isset($info['attached'])) {
if (!empty($variables['#attached'])) {
$variables['#attached'] = NestedArray::mergeDeep($variables['#attached'], $info['attached']);
}
else {
$variables['#attached'] = $info['attached'];
}
}
}
/**
* Preprocess callback for processing additional variables.
*
* @param array $variables
* An associative array.
* @param string $hook
* Hook name.
* @param array $info
* Hook info.
*/
public static function preprocessVariables(array &$variables, $hook, array $info) {
if (isset($info['render element']) && !empty($variables[$info['render element']])) {
$element = $variables[$info['render element']];
if (!empty($element['#variables'])) {
foreach ($element['#variables'] as $name => $variable) {
$variables[$name] = $variable;
}
}
}
}
/**
* Preprocess callback for attaching libraries.
*
* @param array $variables
* An associative array.
* @param string $hook
* Hook name.
* @param array $info
* Hook info.
*/
public static function preprocessLayout(array &$variables, $hook, array $info) {
if (isset($info["base hook"]) && $info["base hook"] == 'layout' && !empty($variables['content'])) {
// Move variables level up.
$content = [];
foreach (Element::children($variables['content']) as $child) {
if (!empty(Element::children($variables['content'][$child]))) {
$content[$child] = $variables['content'][$child];
}
}
if (!empty($variables["content"]["#settings"])) {
foreach ($variables["content"]["#settings"] as $key => $setting) {
$content[$key] = $setting;
}
}
unset($variables['content']);
$variables += $content;
}
}
/**
* Altering libraries.
*
* @return array
* Components libraries.
*/
public function buildLibraries(): array {
$libraries = [];
// Process components libraries.
foreach (array_merge($this->getDefinitions(), $this->getDefinitions('suggestion')) as $id => $definition) {
$definition = reset($definition);
$path_parts = pathinfo($id);
// Find and declare libraries automatically.
$name = str_replace(['.theme','.suggestion'], '', $path_parts['filename']);
$asset = $path_parts["dirname"] . '/' . $name;
$data = [];
if (file_exists($asset . '.css')) {
$data['css'] = [
'component' => [
'/' . $asset . '.css' => [],
],
];
}
if (file_exists($asset . '.js')) {
$data['js'] = ['/' . $asset . '.js' => [],];
}
if (!empty($data)) {
$libraries[$name] = $data;
}
}
return $libraries;
}
/**
* Altering layouts.
*
* @param \Drupal\Core\Layout\LayoutDefinition[] $definitions
* The array of layout definitions, keyed by plugin ID.
*/
public function alterLayouts(array &$definitions) {
$this->themeRegistry->initDefault();
foreach ($this->getDefinitions() as $id => $definition) {
$pattern_name = key($definition);
$pattern_definition = $definition[$pattern_name];
if (isset($pattern_definition['base hook']) && $pattern_definition['base hook'] == 'layout') {
// Register a layout.
$definition = [
'label' => $pattern_definition['label'],
'category' => 'Layouts',
'class' => ComponentConnectorLayout::class,
'path' => pathinfo($id)['dirname'],
'theme_hook' => $pattern_definition['hook theme'],
'provider' => 'component_connector',
];
if (isset($pattern_definition['fields'])) {
foreach ($pattern_definition['fields'] as $key => $field) {
$definition['regions'][$key]['label'] = $field['label'];
}
if (isset($pattern_definition['settings'])) {
$definition['settings'] = $pattern_definition['settings'];
}
if (isset($pattern_definition['icon_map'])) {
$definition['icon_map'] = $pattern_definition['icon_map'];
}
}
$definitions[$pattern_name] = new LayoutDefinition($definition);;
}
}
}
}
