class CriticalCssProvider in Critical CSS 8
Critical CSS Provider.
Calculate which CSS file must be used for Critical CSS based on current request (entity id, path, bundle name, etc).
Hierarchy
- class \Drupal\critical_css\Asset\CriticalCssProvider implements CriticalCssProviderInterface
Expanded class hierarchy of CriticalCssProvider
1 file declares its use of CriticalCssProvider
- CriticalCssProviderTest.php in tests/
src/ Unit/ CriticalCssProviderTest.php
1 string reference to 'CriticalCssProvider'
1 service uses CriticalCssProvider
File
- src/
Asset/ CriticalCssProvider.php, line 20
Namespace
Drupal\critical_css\AssetView source
class CriticalCssProvider implements CriticalCssProviderInterface {
/**
* Critical CSS config.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $config;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* Current Route Match.
*
* @var \Drupal\Core\Routing\ResettableStackedRouteMatchInterface
*/
protected $currentRouteMatch;
/**
* Current path stack.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPathStack;
/**
* Current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* Theme Manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* The route admin context to determine whether a route is an admin one.
*
* @var \Drupal\Core\Routing\AdminContext
*/
protected $adminContext;
/**
* Flag set when this request has already been processed.
*
* @var bool
*/
protected $isAlreadyProcessed;
/**
* Critical CSS data to be inlined.
*
* @var string
*/
protected $criticalCss;
/**
* Possible file paths to find CSS contents.
*
* @var array
*/
protected $filePaths = [];
/**
* File used for critical CSS.
*
* @var string
*/
protected $matchedFilePath;
/**
* CriticalCssProvider constructor.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* Request.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* Config Factory.
* @param \Drupal\Core\Routing\ResettableStackedRouteMatchInterface $current_route_match
* Current route.
* @param \Drupal\Core\Path\CurrentPathStack $current_path_stack
* Current path.
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* Current user.
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
* The Theme Manager.
* @param \Drupal\Core\Routing\AdminContext $admin_context
* The route admin context service.
*/
public function __construct(ModuleHandlerInterface $module_handler, RequestStack $request_stack, ConfigFactoryInterface $config_factory, ResettableStackedRouteMatchInterface $current_route_match, CurrentPathStack $current_path_stack, AccountProxyInterface $current_user, ThemeManagerInterface $theme_manager, AdminContext $admin_context) {
$this->moduleHandler = $module_handler;
$this->request = $request_stack
->getCurrentRequest();
$this->config = $config_factory
->get('critical_css.settings');
$this->currentRouteMatch = $current_route_match;
$this->currentPathStack = $current_path_stack;
$this->currentUser = $current_user;
$this->themeManager = $theme_manager;
$this->adminContext = $admin_context;
}
/**
* {@inheritdoc}
*/
public function getCriticalCss() {
// Return previous result, if any.
if ($this->isAlreadyProcessed) {
return $this->criticalCss;
}
$this->isAlreadyProcessed = TRUE;
// Get possible file paths and return first match.
$filePaths = $this
->getFilePaths();
foreach ($filePaths as $filePath) {
if (is_file($filePath)) {
$this->matchedFilePath = $filePath;
$this->criticalCss = trim(file_get_contents($filePath));
break;
}
}
return $this->criticalCss;
}
/**
* {@inheritdoc}
*
* When accessing an admin route, this module is always disabled to avoid
* multiple problems with Drupal's admin theme. On the other hand, this is a
* module aimed to be used for a site's frontend, and not for the backend.
*/
public function isEnabled() {
$route = $this->currentRouteMatch
->getRouteObject();
if ($route && $this->adminContext
->isAdminRoute($route)) {
return FALSE;
}
return (bool) $this->config
->get('enabled');
}
/**
* Check if module is enabled for logged-in users.
*
* @return bool
* True if this module is enabled for logged-in users.
*/
protected function isEnabledForLoggedInUsers() {
return (bool) $this->config
->get('enabled_for_logged_in_users');
}
/**
* Check if entity id is excluded by configuration.
*
* @param int $entityId
* Entity ID (integer).
*
* @return bool
* True if entity is excluded.
*/
protected function isEntityIdExcluded($entityId) {
$excludedIds = explode("\n", $this->config
->get('excluded_ids'));
$excludedIds = array_map(function ($item) {
return trim($item);
}, $excludedIds);
return is_array($excludedIds) && in_array($entityId, $excludedIds);
}
/**
* Get critical CSS file path by a key (id, string, etc).
*
* @param string $key
* Key to search.
*
* @return string
* Critical CSS string.
*/
protected function getFilePathByKey($key) {
if (empty($key)) {
return NULL;
}
$criticalCssDirPath = str_replace('..', '', $this->config
->get('dir_path'));
$themePath = $this->themeManager
->getActiveTheme()
->getPath();
$criticalCssDir = $themePath . $criticalCssDirPath;
return $criticalCssDir . '/' . $key . '.css';
}
/**
* {@inheritdoc}
*/
public function getFilePaths() {
if (!$this->filePaths) {
$this->filePaths = $this
->calculateFilePaths();
}
return $this->filePaths;
}
/**
* Get all possible paths to search, relatives to theme.
*
* @return array
* Array with all possible paths.
*/
protected function calculateFilePaths() {
// Opt out if module is disabled.
if (!$this
->isEnabled()) {
return [];
}
// Check if module is enabled for logged-in users.
if (!$this->currentUser
->isAnonymous() && !$this
->isEnabledForLoggedInUsers()) {
return [];
}
// Get current entity's data.
$entity = $this
->getCurrentEntity();
$entityId = NULL;
$bundleName = NULL;
if (is_object($entity) && method_exists($entity, 'id') && method_exists($entity, 'bundle')) {
$entityId = $entity
->id();
$bundleName = $entity
->bundle();
}
// Check if this entity id is excluded.
if ($entityId && $this
->isEntityIdExcluded($entityId)) {
return [];
}
// Get sanitized path, which is something like /node/{X}.
$sanitizedPath = $this
->sanitizePath($this->currentPathStack
->getPath());
// Get sanitized path info, which is something like /article/{title}.
$sanitizedPathInfo = $this
->sanitizePath($this->request
->getPathInfo());
// Get all possible paths in order, starting with the most specific ones
// (entity id) and finishing with a fallback. Between them, use a "path-"
// prefix to avoid collisions when there is a node and a bundle with the
// same name.
$filePaths[] = $this
->getFilePathByKey($entityId);
$filePaths[] = $this
->getFilePathByKey('path-' . $sanitizedPath);
$filePaths[] = $this
->getFilePathByKey($sanitizedPath);
$filePaths[] = $this
->getFilePathByKey('path-' . $sanitizedPathInfo);
$filePaths[] = $this
->getFilePathByKey($sanitizedPathInfo);
$filePaths[] = $this
->getFilePathByKey($bundleName);
$filePaths[] = $this
->getFilePathByKey('default-critical');
// Remove all null paths (if no callback is supplied to array_filter, all
// entries of array equal to FALSE are removed)
$filePaths = array_filter($filePaths);
// Remove repeated paths.
$filePaths = array_unique($filePaths);
// Allow other modules to alter file paths array.
$this->moduleHandler
->alter('critical_css_file_paths_suggestion', $filePaths, $entity);
return $filePaths;
}
/**
* Get current entity.
*
* At this moment, it only works for nodes and taxonomy terms.
*
* @return \Drupal\Core\Entity\ContentEntityInterface|null
* Matched file path, or null if nothing found.
*/
protected function getCurrentEntity() {
$entityBundles = [
'node',
'taxonomy_term',
];
foreach ($entityBundles as $entityBundle) {
$entity = $this->currentRouteMatch
->getParameter($entityBundle);
if ($entity) {
return $entity;
}
}
return NULL;
}
/**
* Sanitizes a path so its usable as a filename.
*
* @return string
* The sanitized path.
*/
protected function sanitizePath($path) {
$path = preg_replace("/^\\//", "", $path);
$path = preg_replace("/[^a-zA-Z0-9\\/-]/", "", $path);
$path = str_replace("/", "-", $path);
if (empty($path)) {
$path = 'front';
}
return $path;
}
/**
* Get matched file path.
*
* @return string|null
* Matched file path, or null if nothing found.
*/
public function getMatchedFilePath() {
// Ensure $this->getCriticalCss() is called before returning anything.
if (!$this->isAlreadyProcessed) {
$this
->getCriticalCss();
}
return $this->matchedFilePath;
}
/**
* {@inheritdoc}
*/
public function isAlreadyProcessed() {
return $this->isAlreadyProcessed;
}
/**
* {@inheritdoc}
*/
public function reset() {
$this->isAlreadyProcessed = FALSE;
$this->filePaths = [];
$this->matchedFilePath = NULL;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
CriticalCssProvider:: |
protected | property | The route admin context to determine whether a route is an admin one. | |
CriticalCssProvider:: |
protected | property | Critical CSS config. | |
CriticalCssProvider:: |
protected | property | Critical CSS data to be inlined. | |
CriticalCssProvider:: |
protected | property | Current path stack. | |
CriticalCssProvider:: |
protected | property | Current Route Match. | |
CriticalCssProvider:: |
protected | property | Current user. | |
CriticalCssProvider:: |
protected | property | Possible file paths to find CSS contents. | |
CriticalCssProvider:: |
protected | property | Flag set when this request has already been processed. | |
CriticalCssProvider:: |
protected | property | File used for critical CSS. | |
CriticalCssProvider:: |
protected | property | The module handler. | |
CriticalCssProvider:: |
protected | property | Request. | |
CriticalCssProvider:: |
protected | property | Theme Manager. | |
CriticalCssProvider:: |
protected | function | Get all possible paths to search, relatives to theme. | |
CriticalCssProvider:: |
public | function |
Get critical CSS contents. Overrides CriticalCssProviderInterface:: |
|
CriticalCssProvider:: |
protected | function | Get current entity. | |
CriticalCssProvider:: |
protected | function | Get critical CSS file path by a key (id, string, etc). | |
CriticalCssProvider:: |
public | function |
Get all possible paths to search, relatives to theme. Overrides CriticalCssProviderInterface:: |
|
CriticalCssProvider:: |
public | function |
Get matched file path. Overrides CriticalCssProviderInterface:: |
|
CriticalCssProvider:: |
public | function |
Tells whether this request has been already processed. Overrides CriticalCssProviderInterface:: |
|
CriticalCssProvider:: |
public | function |
When accessing an admin route, this module is always disabled to avoid
multiple problems with Drupal's admin theme. On the other hand, this is a
module aimed to be used for a site's frontend, and not for the backend. Overrides CriticalCssProviderInterface:: |
|
CriticalCssProvider:: |
protected | function | Check if module is enabled for logged-in users. | |
CriticalCssProvider:: |
protected | function | Check if entity id is excluded by configuration. | |
CriticalCssProvider:: |
public | function |
Reset provider so calculations are made again. Overrides CriticalCssProviderInterface:: |
|
CriticalCssProvider:: |
protected | function | Sanitizes a path so its usable as a filename. | |
CriticalCssProvider:: |
public | function | CriticalCssProvider constructor. |