View source
<?php
namespace Drupal\cookie_content_blocker\Render;
use function array_diff_key;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_merge;
use function array_unshift;
use function defined;
use function in_array;
use function is_callable;
use function uasort;
use Drupal;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\cookie_content_blocker\BlockedLibraryManagerInterface;
use Drupal\Core\Asset\AssetCollectionRendererInterface;
use Drupal\Core\Asset\AssetResolver;
use Drupal\Core\Asset\AssetResolverInterface;
use Drupal\Core\Asset\AttachedAssets;
use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Render\HtmlResponseAttachmentsProcessor as CoreHtmlResponseAttachmentsProcessor;
use Drupal\Core\Render\RendererInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\RequestStack;
class HtmlResponseAttachmentsProcessor extends CoreHtmlResponseAttachmentsProcessor {
protected static $drupalSettings = [
'type' => 'setting',
'group' => JS_SETTING,
'weight' => 0,
'browsers' => [],
'data' => [],
'position' => 'scripts_bottom',
];
protected $allowedAssets;
protected $blockedAssets;
protected $libraryManager;
public function __construct(BlockedLibraryManagerInterface $library_manager, AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
parent::__construct($asset_resolver, $config_factory, $css_collection_renderer, $js_collection_renderer, $request_stack, $renderer, $module_handler);
$this->libraryManager = $library_manager;
$this->blockedAssets = $this->allowedAssets = new AttachedAssets();
}
protected function mergeSettings(array $settings) : void {
self::$drupalSettings = NestedArray::mergeDeepArray([
self::$drupalSettings,
$settings,
], TRUE);
}
protected function processAssetLibraries(AttachedAssetsInterface $assets, array $placeholders) : array {
if (!$this->libraryManager
->hasBlockedLibraries()) {
return parent::processAssetLibraries($assets, $placeholders);
}
$variables = [
'styles' => [],
'scripts' => [],
'scripts_bottom' => [],
];
$blocked_libraries = $this->libraryManager
->getBlockedLibraries();
$allowed_libraries = array_filter($assets
->getLibraries(), function ($library) use ($blocked_libraries) {
return !in_array($library, $blocked_libraries, TRUE);
});
$this->allowedAssets = $assets
->setLibraries($allowed_libraries);
$this->blockedAssets = AttachedAssets::createFromRenderArray([
'#attached' => [
'library' => $blocked_libraries,
],
]);
$this->blockedAssets
->setAlreadyLoadedLibraries($allowed_libraries);
foreach (array_keys($variables) as $type) {
$process_method = 'process' . Container::camelize($type);
if (!isset($placeholders[$type]) || !is_callable([
$this,
$process_method,
])) {
continue;
}
$variables[$type] = $this
->{$process_method}();
}
$settings = self::$drupalSettings;
array_unshift($variables[$settings['position']], ...$this
->renderAssetCollection([
$settings,
], $this->jsCollectionRenderer));
return $variables;
}
private function generateAssetPlaceholder(array $asset) : array {
$id = Html::getUniqueId(Crypt::randomBytesBase64());
$placeholder = [
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => '',
'#attributes' => [
'data-cookie-content-blocker-asset-id' => $id,
],
];
$attached = [
'#attached' => [
'drupalSettings' => [
'cookieContentBlocker' => [
'blockedAssets' => [
$id => (string) $this->renderer
->renderPlain($asset),
],
],
],
],
];
$placeholder_asset = AttachedAssets::createFromRenderArray($attached);
$this->allowedAssets
->setSettings(NestedArray::mergeDeepArray([
$placeholder_asset
->getSettings(),
$this->allowedAssets
->getSettings(),
], TRUE));
$this
->mergeSettings([
'data' => $this->allowedAssets
->getSettings(),
]);
return $placeholder;
}
private function getCssAssetCollection() : array {
$optimize_css = !defined('MAINTENANCE_MODE') && $this->config
->get('css.preprocess');
return $this
->getMergedAndSortedAssets(...$this
->resolveAssets([
$this->assetResolver,
'getCssAssets',
], $optimize_css));
}
private function getJsAssetCollection(string $region) : array {
static $header = NULL, $footer = NULL, $processed = FALSE;
if ($processed) {
return ${$region} ?? [];
}
$optimize_js = !defined('MAINTENANCE_MODE') && !Drupal::state()
->get('system.maintenance_mode') && $this->config
->get('js.preprocess');
[
[
$allowed_js_assets_header,
$allowed_js_assets_footer,
],
[
$allowed_js_assets_header_raw,
$allowed_js_assets_footer_raw,
],
[
$blocked_js_assets_header,
$blocked_js_assets_footer,
],
] = $this
->resolveAssets([
$this->assetResolver,
'getJsAssets',
], $optimize_js);
$header = $this
->getMergedAndSortedAssets($allowed_js_assets_header, $allowed_js_assets_header_raw, $blocked_js_assets_header);
$footer = $this
->getMergedAndSortedAssets($allowed_js_assets_footer, $allowed_js_assets_footer_raw, $blocked_js_assets_footer);
$settings = $header['drupalSettings'] ?? $footer['drupalSettings'] ?? [];
$settings['position'] = array_key_exists('drupalSettings', $header) ? 'scripts' : 'scripts_bottom';
$this
->mergeSettings($settings);
unset($header['drupalSettings'], $footer['drupalSettings']);
$processed = TRUE;
return ${$region} ?? [];
}
private function getMergedAndSortedAssets(array $allowed_assets, array $allowed_assets_raw, array $blocked_assets) : array {
$assets = array_merge($allowed_assets, $this
->markAssetsAsBlocked(array_diff_key($blocked_assets, $allowed_assets_raw)));
uasort($assets, [
AssetResolver::class,
'sort',
]);
return $assets;
}
private function markAssetsAsBlocked(array $assets) : array {
return array_map(function ($asset) {
$asset['preprocess'] = $asset['cache'] = FALSE;
$asset['is_blocked'] = TRUE;
return $asset;
}, $assets);
}
private function processStyles() : array {
return $this
->renderAssetCollection($this
->getCssAssetCollection(), $this->cssCollectionRenderer);
}
private function processScripts() : array {
return $this
->renderAssetCollection($this
->getJsAssetCollection('header'), $this->jsCollectionRenderer);
}
private function processScriptsBottom() : array {
return $this
->renderAssetCollection($this
->getJsAssetCollection('footer'), $this->jsCollectionRenderer);
}
private function renderAssetCollection(array $collection, AssetCollectionRendererInterface $renderer) : array {
$rendered = [
[],
];
foreach ($collection as $asset) {
$rendered_asset = $renderer
->render([
$asset,
]);
$rendered[] = !empty($asset['is_blocked']) ? [
$this
->generateAssetPlaceholder(...$rendered_asset),
] : $rendered_asset;
}
return array_merge(...$rendered);
}
private function resolveAssets(callable $resolver, bool $optimize) : array {
if (!is_callable($resolver)) {
return [];
}
return [
$resolver($this->allowedAssets, $optimize),
$resolver($this->allowedAssets, FALSE),
$resolver($this->blockedAssets, FALSE),
];
}
}