cache_review-1.0.x-dev/src/Render/RenderCacheReview.php
src/Render/RenderCacheReview.php
<?php
namespace Drupal\cache_review\Render;
use Drupal\cache_review\Form\CacheReviewConfigForm;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\Cache\VariationCacheFactoryInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Render\PlaceholderGeneratorInterface;
use Drupal\Core\Render\PlaceholderingRenderCache;
use Drupal\Core\Routing\CurrentRouteMatch;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Decorate RenderCache service.
*
* Adding mark for cached and non-cached elements.
*/
class RenderCacheReview extends PlaceholderingRenderCache {
/**
* Original PlaceholderingRenderCache.
*
* @var \Drupal\Core\Render\PlaceholderingRenderCache
*/
protected $placeholderingRenderCache;
/**
* The current route match service.
*
* @var \Drupal\Core\Routing\CurrentRouteMatch
*/
protected $currentRouteMatch;
/**
* The config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The renderer configuration array.
*
* @var array
*/
protected array $rendererConfig;
/**
* Construct RenderCacheReview object.
*
* @param \Drupal\Core\Render\PlaceholderingRenderCache $original_placeholdering_render_cache
* The original placeholdering_render_cache.
* @param array $renderer_config
* The renderer configuration array.
* @param \Drupal\Core\Routing\CurrentRouteMatch $current_route_match
* The current route match service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Cache\Context\CacheContextsManager $cache_contexts_manager
* The cache contexts manager.
* @param \Drupal\Core\Render\PlaceholderGeneratorInterface $placeholder_generator
* The placeholder generator.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory for retrieving required config objects.
* @param \Drupal\Core\Cache\VariationCacheFactoryInterface $cache_factory
* The variation cache factory.
*/
public function __construct(PlaceholderingRenderCache $original_placeholdering_render_cache, array $renderer_config, CurrentRouteMatch $current_route_match, RequestStack $request_stack, CacheContextsManager $cache_contexts_manager, PlaceholderGeneratorInterface $placeholder_generator, ConfigFactoryInterface $config_factory, VariationCacheFactoryInterface $cache_factory) {
$this->placeholderingRenderCache = $original_placeholdering_render_cache;
$this->rendererConfig = $renderer_config;
$this->currentRouteMatch = $current_route_match;
$this->configFactory = $config_factory;
parent::__construct($request_stack, $cache_factory, $cache_contexts_manager, $placeholder_generator);
}
/**
* {@inheritdoc}
*/
public function get(array $elements) {
// Part from original PlaceholderingRenderCache->get().
// @todo remove this check when https://www.drupal.org/node/2367555 lands.
if (!$this->requestStack->getCurrentRequest()->isMethodCacheable()) {
return FALSE;
}
if (isset($elements['#create_placeholder']) && $elements['#create_placeholder'] === FALSE) {
$cached_placeholder_result = $this->getFromPlaceholderResultsCache($elements);
if ($cached_placeholder_result !== FALSE) {
return $cached_placeholder_result;
}
}
// Call overridden RenderCache->get().
$cached_element = $this->parentGet($elements);
if ($cached_element === FALSE) {
return FALSE;
}
else {
if ($this->placeholderGenerator->canCreatePlaceholder($elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($cached_element)) {
return $this->createPlaceholderAndRemember($cached_element, $elements);
}
return $cached_element;
}
}
/**
* Overridden Part from RenderCache->get().
*/
private function parentGet(array $elements) {
$check_cache_item = !$this->isElementCacheable($elements);
if (!$this->requestStack->getCurrentRequest()->isMethodCacheable() || $check_cache_item) {
return FALSE;
}
$bin = $elements['#cache']['bin'] ?? 'render';
if ($this->isElementCacheable($elements) &&
($cache_bin = $this->cacheFactory->get($bin)) &&
($cache = $cache_bin->get($elements['#cache']['keys'], CacheableMetadata::createFromRenderArray($elements)))) {
$cached_element = $cache->data;
if (isset($cached_element['#cache_redirect'])) {
return $this->parentGet($cached_element);
}
// Ignore html_tags from cache_review configuration.
$cache_review_config = $this->configFactory->get(CacheReviewConfigForm::CACHE_REVIEW_CONF);
$framing = $cache_review_config->get('framing');
// Check html id tags if framing is set to disable.
$analyze_by_id = FALSE;
if ($framing === 'no_frame') {
$html_tags_values = $cache_review_config->get('html_tags');
$html_tags = array_filter(explode("\r\n", trim($html_tags_values)));
foreach ($html_tags as $html_tag) {
if (str_contains($cached_element['#markup'], 'id="' . $html_tag . '"')) {
$analyze_by_id = TRUE;
}
}
}
// Check if it needs to show cache info on admin routes.
$check_admin_routes = $cache_review_config->get('check_admin_routes');
$is_admin = (bool) $this->currentRouteMatch->getRouteObject()->getOption('_admin_route');
// Add cache info for non-admin routes only.
// Check framing settings.
if (!$is_admin || $check_admin_routes) {
if (($framing !== 'frame_lazy' && $framing !== 'no_frame') ||
($framing === 'no_frame' && $analyze_by_id)) {
// Add mark to cached element.
$markup_string = (string) $cached_element['#markup'];
$caught_condition = '';
if (!empty($markup_string)) {
$apc = $this->rendererConfig['auto_placeholder_conditions'];
if ($cached_element['#cache']['max-age'] == $apc['max-age'] ||
array_intersect($cached_element['#cache']['contexts'], $apc['contexts']) ||
array_intersect($cached_element['#cache']['tags'], $apc['tags'])) {
$caught_condition = ' cache_caught';
}
$prefix = '<div class="cached-wrapper">';
$suffix = '</div>';
$options_data = 'contexts: ' . json_encode(array_values($cached_element['#cache']['contexts'])) . '<br>' .
'max-age: ' . json_encode($cached_element['#cache']['max-age']) . '<br>' .
'tags: ' . json_encode(array_values($cached_element['#cache']['tags']));
$options = '<span class="cache_tooltip_text">' . $options_data . '</span>';
$cache_mark = '<div class="cached-item cache_tooltip' . $caught_condition . '">C' . $options . '</div>';
$cached_element['#markup'] = Markup::create($prefix . $cache_mark . $markup_string . $suffix);
}
}
}
return $cached_element;
}
return FALSE;
}
/**
* Checks whether a renderable array can be cached.
*
* This allows us to not even have to instantiate the cache backend if a
* renderable array does not have any cache keys or specifies a zero cache
* max age.
* Added for compatibility with previous versions of RenderCache.
*
* @param array $element
* A renderable array.
*
* @return bool
* Whether the renderable array is cacheable.
*/
protected function isElementCacheable(array $element) {
// If the maximum age is zero, then caching is effectively prohibited.
if (isset($element['#cache']['max-age']) && $element['#cache']['max-age'] === 0) {
return FALSE;
}
return isset($element['#cache']['keys']);
}
}
