countdown-8.x-1.8/src/Service/CountdownLibraryDiscovery.php
src/Service/CountdownLibraryDiscovery.php
<?php
declare(strict_types=1);
namespace Drupal\countdown\Service;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\countdown\CountdownConstants;
use Drupal\countdown\CountdownLibraryPluginManager;
use Drupal\countdown\Utility\ConfigAccessor;
use Psr\Log\LoggerInterface;
/**
* Service for discovering countdown libraries using Plugin System.
*
* This service provides comprehensive library discovery functionality,
* integrating with the plugin system to find, validate, and manage
* countdown libraries across the Drupal installation.
*
* @package Drupal\countdown\Service
*/
class CountdownLibraryDiscovery implements CountdownLibraryDiscoveryInterface {
use StringTranslationTrait;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The plugin manager.
*
* @var \Drupal\countdown\CountdownLibraryPluginManager
*/
protected CountdownLibraryPluginManager $pluginManager;
/**
* The cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected CacheBackendInterface $cache;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected FileSystemInterface $fileSystem;
/**
* The logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected LoggerInterface $logger;
/**
* The configuration accessor.
*
* @var \Drupal\countdown\Utility\ConfigAccessor
*/
protected ConfigAccessor $configAccessor;
/**
* The site path.
*
* @var string
*/
protected string $sitePath;
/**
* Static cache for discovered libraries.
*
* @var array|null
*/
protected ?array $discoveredLibraries = NULL;
/**
* Static cache for library paths.
*
* @var array
*/
protected array $libraryPaths = [];
/**
* Static cache for detected versions.
*
* @var array
*/
protected array $detectedVersions = [];
/**
* Constructs a CountdownLibraryDiscovery object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\countdown\CountdownLibraryPluginManager $plugin_manager
* The plugin manager.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system service.
* @param \Psr\Log\LoggerInterface $logger
* The logger.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param string $site_path
* The site path.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
CountdownLibraryPluginManager $plugin_manager,
CacheBackendInterface $cache,
FileSystemInterface $file_system,
LoggerInterface $logger,
TranslationInterface $string_translation,
string $site_path,
) {
$this->pluginManager = $plugin_manager;
$this->cache = $cache;
$this->fileSystem = $file_system;
$this->logger = $logger;
$this->stringTranslation = $string_translation;
$this->sitePath = $site_path;
// Create config accessor.
$this->configAccessor = new ConfigAccessor($config_factory);
}
/**
* {@inheritdoc}
*/
public function discover(bool $reset = FALSE): array {
// Use static cache if available and not resetting.
if (!$reset && $this->discoveredLibraries !== NULL) {
return $this->discoveredLibraries;
}
// Try to get from cache.
$cache_id = 'countdown:available_libraries:' . $this->sitePath;
if (!$reset && $cached = $this->cache->get($cache_id)) {
$this->discoveredLibraries = $cached->data;
return $this->discoveredLibraries;
}
// Perform discovery using plugin system.
$this->discoveredLibraries = $this->performDiscovery();
// Cache the results.
$this->cache->set(
$cache_id,
$this->discoveredLibraries,
time() + $this->configAccessor->getCacheLifetime(),
[CountdownConstants::CACHE_TAG_DISCOVERY]
);
return $this->discoveredLibraries;
}
/**
* {@inheritdoc}
*/
public function getLibrary(string $library_id): ?array {
$libraries = $this->discover();
return $libraries[$library_id] ?? NULL;
}
/**
* {@inheritdoc}
*/
public function getAvailableLibraries(): array {
$libraries = $this->discover();
$available = [];
foreach ($libraries as $id => $info) {
if ($info['installed']) {
$available[$id] = $info;
}
}
return $available;
}
/**
* {@inheritdoc}
*/
public function isInstalled(string $library_id): bool {
$library = $this->getLibrary($library_id);
return $library && $library['installed'];
}
/**
* {@inheritdoc}
*/
public function getLibraryPath(string $library_id): ?string {
// Check cache first.
if (isset($this->libraryPaths[$library_id])) {
return $this->libraryPaths[$library_id];
}
$library = $this->getLibrary($library_id);
$path = $library ? $library['path'] : NULL;
// Cache the result.
$this->libraryPaths[$library_id] = $path;
return $path;
}
/**
* {@inheritdoc}
*/
public function getLibraryAssets(string $library_id): array {
$plugin = $this->pluginManager->getPlugin($library_id);
if (!$plugin || !$plugin->isInstalled()) {
return [];
}
// Core library assets are handled by static library definition.
if ($library_id === 'countdown') {
return [];
}
$variant = $this->configAccessor->getBuildVariant();
// Get all assets - JS and CSS are always loaded.
$assets = $plugin->getAssets($variant);
return $assets;
}
/**
* {@inheritdoc}
*/
public function getInstalledVersion(string $library_id): ?string {
// Check cache first.
if (isset($this->detectedVersions[$library_id])) {
return $this->detectedVersions[$library_id] !== FALSE ? $this->detectedVersions[$library_id] : NULL;
}
$plugin = $this->pluginManager->getPlugin($library_id);
if (!$plugin || !$plugin->isInstalled()) {
$this->detectedVersions[$library_id] = FALSE;
return NULL;
}
$version = $plugin->getInstalledVersion();
$this->detectedVersions[$library_id] = $version ?: FALSE;
return $version;
}
/**
* {@inheritdoc}
*/
public function versionMeetsRequirements(string $library_id): bool {
$plugin = $this->pluginManager->getPlugin($library_id);
if (!$plugin || !$plugin->isInstalled()) {
return FALSE;
}
return $plugin->versionMeetsRequirements();
}
/**
* {@inheritdoc}
*/
public function getLibraryStatus(string $library_id): array {
$plugin = $this->pluginManager->getPlugin($library_id);
if (!$plugin) {
return [
'installed' => FALSE,
'version_status' => 'unknown',
'messages' => [$this->t('Library not found in registry.')],
'severity' => 'error',
];
}
return $plugin->getStatus();
}
/**
* {@inheritdoc}
*/
public function clearCache(): void {
$cache_id = 'countdown:available_libraries:' . $this->sitePath;
$this->cache->delete($cache_id);
// Clear static caches.
$this->discoveredLibraries = NULL;
$this->libraryPaths = [];
$this->detectedVersions = [];
// Also reset plugin manager cache.
$this->pluginManager->resetAllCaches();
if ($this->configAccessor->isDebugMode()) {
$this->logger->debug('Countdown library discovery cache cleared.');
}
}
/**
* Perform the actual library discovery using plugin system.
*
* @return array
* Array of discovered libraries with their status.
*/
protected function performDiscovery(): array {
$libraries = [];
// Get all plugins from the plugin manager.
$all_plugins = $this->pluginManager->getAllPlugins();
foreach ($all_plugins as $plugin_id => $plugin) {
$library_info = [
'id' => $plugin_id,
'label' => $plugin->getLabel(),
'description' => $plugin->getDescription(),
'type' => $plugin->getType(),
'installed' => $plugin->isInstalled(),
'path' => $plugin->getLibraryPath(),
'version' => $plugin->getRequiredVersion(),
'installed_version' => NULL,
'version_match' => FALSE,
'errors' => [],
'warnings' => [],
'homepage' => $plugin->getHomepage(),
'repository' => $plugin->getRepository(),
'author' => $plugin->getAuthor(),
'license' => $plugin->getLicense(),
'experimental' => $plugin->isExperimental(),
'weight' => $plugin->getWeight(),
'dependencies' => $plugin->getDependencies(),
'cdn_capable' => $plugin->getCdnConfig() !== NULL,
'npm_package' => $plugin->getNpmPackage(),
];
// Get installed version if library is installed.
if ($plugin->isInstalled()) {
$installed_version = $plugin->getInstalledVersion();
if ($installed_version) {
$library_info['installed_version'] = $installed_version;
$library_info['version_match'] = $plugin->versionMeetsRequirements();
if (!$library_info['version_match'] && $library_info['version']) {
$library_info['warnings'][] = $this->t('Version @installed does not meet requirement @required', [
'@installed' => $installed_version,
'@required' => $library_info['version'],
]);
}
}
else {
$library_info['warnings'][] = $this->t('Could not detect installed version.');
}
// Log successful discovery if debug enabled.
if ($this->configAccessor->isDebugMode()) {
$this->logger->debug('Discovered library @library at @path (version: @version)', [
'@library' => $plugin_id,
'@path' => $plugin->getLibraryPath(),
'@version' => $installed_version ?: 'unknown',
]);
}
}
else {
// Library not installed.
if ($plugin->getType() === 'external') {
$library_info['errors'][] = $this->t('Library is not installed.');
// Add installation hints.
if ($npm_package = $plugin->getNpmPackage()) {
$library_info['install_hint'] = $this->t('Install with: npm install @package', [
'@package' => $npm_package,
]);
}
elseif ($homepage = $plugin->getHomepage()) {
$library_info['install_hint'] = $this->t('Download from: @url', [
'@url' => $homepage,
]);
}
}
if ($this->configAccessor->isDebugMode()) {
$this->logger->debug('Library @library is not installed', [
'@library' => $plugin_id,
]);
}
}
// Add validation messages if any.
$validation_messages = $this->pluginManager->validatePlugin($plugin_id);
if (!empty($validation_messages)) {
$library_info['errors'] = array_merge($library_info['errors'], $validation_messages);
}
// Determine overall status.
if (!empty($library_info['errors'])) {
$library_info['status'] = 'error';
}
elseif (!empty($library_info['warnings'])) {
$library_info['status'] = 'warning';
}
else {
$library_info['status'] = 'ok';
}
$libraries[$plugin_id] = $library_info;
}
// Sort libraries by weight and then by label.
uasort($libraries, function ($a, $b) {
$weight_compare = $a['weight'] <=> $b['weight'];
if ($weight_compare !== 0) {
return $weight_compare;
}
return strcasecmp($a['label'], $b['label']);
});
return $libraries;
}
}
