countdown-8.x-1.8/src/Service/CountdownLibraryManager.php

src/Service/CountdownLibraryManager.php
<?php

declare(strict_types=1);

namespace Drupal\countdown\Service;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\countdown\CountdownConstants;
use Drupal\countdown\CountdownLibraryPluginManager;
use Drupal\countdown\Utility\CdnUrlBuilder;
use Drupal\countdown\Utility\ConfigAccessor;

/**
 * Service for managing countdown libraries using Plugin System.
 *
 * This service handles all countdown library operations including:
 * - Library activation and switching.
 * - Local and CDN loading methods.
 * - Configuration management.
 * - Validation and requirements checking.
 * - Library definitions for Drupal's library system.
 */
class CountdownLibraryManager implements CountdownLibraryManagerInterface {

  use StringTranslationTrait;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected ConfigFactoryInterface $configFactory;

  /**
   * The plugin manager.
   *
   * @var \Drupal\countdown\CountdownLibraryPluginManager
   */
  protected CountdownLibraryPluginManager $pluginManager;

  /**
   * The library discovery service.
   *
   * @var \Drupal\countdown\Service\CountdownLibraryDiscoveryInterface
   */
  protected CountdownLibraryDiscoveryInterface $libraryDiscovery;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected ModuleHandlerInterface $moduleHandler;

  /**
   * The messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected MessengerInterface $messenger;

  /**
   * The configuration accessor.
   *
   * @var \Drupal\countdown\Utility\ConfigAccessor
   */
  protected ConfigAccessor $configAccessor;

  /**
   * The CDN URL builder.
   *
   * @var \Drupal\countdown\Utility\CdnUrlBuilder
   */
  protected CdnUrlBuilder $cdnBuilder;

  /**
   * Static cache for library definitions.
   *
   * @var array
   */
  protected array $libraryDefinitionsCache = [];

  /**
   * Static cache for CDN providers.
   *
   * @var array
   */
  protected array $cdnProvidersCache = [];

  /**
   * Constructs a CountdownLibraryManager object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\countdown\CountdownLibraryPluginManager $plugin_manager
   *   The plugin manager.
   * @param \Drupal\countdown\Service\CountdownLibraryDiscoveryInterface $library_discovery
   *   The library discovery service.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger service.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
   *   The string translation service.
   */
  public function __construct(
    ConfigFactoryInterface $config_factory,
    CountdownLibraryPluginManager $plugin_manager,
    CountdownLibraryDiscoveryInterface $library_discovery,
    ModuleHandlerInterface $module_handler,
    MessengerInterface $messenger,
    TranslationInterface $string_translation,
  ) {
    $this->configFactory = $config_factory;
    $this->pluginManager = $plugin_manager;
    $this->libraryDiscovery = $library_discovery;
    $this->moduleHandler = $module_handler;
    $this->messenger = $messenger;
    $this->stringTranslation = $string_translation;

    // Create utilities.
    $this->configAccessor = new ConfigAccessor($config_factory);
    $this->cdnBuilder = new CdnUrlBuilder();
  }

  /**
   * {@inheritdoc}
   */
  public function getActiveLibrary(): string {
    $active = $this->configAccessor->getActiveLibrary();
    $method = $this->getLoadingMethod();

    // Validate the active library exists and is appropriate for the method.
    if ($active && $this->pluginManager->hasPlugin($active)) {
      $plugin = $this->pluginManager->getPlugin($active);

      if ($plugin) {
        // For local method, library must be installed.
        if ($method === 'local' && $plugin->isInstalled()) {
          return $active;
        }

        // For CDN method, library must support CDN.
        if ($method === 'cdn' && $plugin->getCdnConfig() !== NULL) {
          return $active;
        }
      }
    }

    // Fall back to first available library for the current method.
    if ($method === 'local') {
      $installed = $this->pluginManager->getInstalledPlugins();
      if (!empty($installed)) {
        return key($installed);
      }
    }
    else {
      $cdn_plugins = $this->pluginManager->getCdnCapablePlugins();
      if (!empty($cdn_plugins)) {
        return key($cdn_plugins);
      }
    }

    // Final fallback to core library.
    return CountdownConstants::DEFAULT_LIBRARY;
  }

  /**
   * {@inheritdoc}
   */
  public function setActiveLibrary(string $library_id): bool {
    $plugin = $this->pluginManager->getPlugin($library_id);

    if (!$plugin) {
      throw new \InvalidArgumentException(
        sprintf('Library "%s" does not exist.', $library_id)
      );
    }

    $method = $this->getLoadingMethod();

    // Validate based on loading method.
    if ($method === 'local' && !$plugin->isInstalled()) {
      throw new \InvalidArgumentException(
        sprintf('Library "%s" is not installed. Install it or switch to CDN loading.', $library_id)
      );
    }

    if ($method === 'cdn' && $plugin->getCdnConfig() === NULL) {
      throw new \InvalidArgumentException(
        sprintf('Library "%s" does not support CDN loading.', $library_id)
      );
    }

    // Check version compatibility if local.
    if ($method === 'local' && !$plugin->versionMeetsRequirements()) {
      $status = $plugin->getStatus();
      $this->messenger->addWarning($this->t('Library @library may not work correctly: @messages', [
        '@library' => $plugin->getLabel(),
        '@messages' => implode(' ', $status['messages']),
      ]));
    }

    // Save configuration.
    $this->configFactory->getEditable('countdown.settings')
      ->set('library', $library_id)
      ->save();

    // Clear caches to ensure new library is loaded.
    $this->clearCache();

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getLoadingMethod(): string {
    return $this->configAccessor->getLoadingMethod();
  }

  /**
   * {@inheritdoc}
   */
  public function setLoadingMethod(string $method): bool {
    if (!in_array($method, ['local', 'cdn'])) {
      throw new \InvalidArgumentException(
        sprintf('Invalid loading method "%s". Must be "local" or "cdn".', $method)
      );
    }

    $this->configFactory->getEditable('countdown.settings')
      ->set('method', $method)
      ->save();

    // Clear caches.
    $this->clearCache();

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getCdnProvider(): string {
    return $this->configAccessor->getCdnProvider();
  }

  /**
   * {@inheritdoc}
   */
  public function setCdnProvider(string $provider): bool {
    $valid_providers = $this->getAvailableCdnProviders();

    if (!in_array($provider, $valid_providers)) {
      throw new \InvalidArgumentException(
        sprintf('Invalid CDN provider "%s". Valid providers are: %s',
          $provider,
          implode(', ', $valid_providers)
        )
      );
    }

    $this->configFactory->getEditable('countdown.settings')
      ->set('cdn.provider', $provider)
      ->save();

    // Clear caches.
    $this->clearCache();

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getAvailableCdnProviders(): array {
    return ['jsdelivr', 'cdnjs', 'unpkg', 'custom'];
  }

  /**
   * {@inheritdoc}
   */
  public function getCdnProvidersForLibrary(string $library_id): array {
    // Use cache if available.
    if (isset($this->cdnProvidersCache[$library_id])) {
      return $this->cdnProvidersCache[$library_id];
    }

    $plugin = $this->pluginManager->getPlugin($library_id);
    if (!$plugin) {
      return [];
    }

    $cdn_config = $plugin->getCdnConfig();
    if (!$cdn_config) {
      return [];
    }

    // Build options array with provider labels.
    $providers = [];
    foreach (array_keys($cdn_config) as $provider_key) {
      // Create human-readable labels for providers.
      $label = match($provider_key) {
        'jsdelivr' => 'jsDelivr',
        'cdnjs' => 'cdnjs',
        'unpkg' => 'unpkg',
        'custom' => $this->t('Custom CDN'),
        default => ucfirst($provider_key),
      };
      $providers[$provider_key] = $label;
    }

    $this->cdnProvidersCache[$library_id] = $providers;

    return $providers;
  }

  /**
   * {@inheritdoc}
   */
  public function getLibraryDefinition(): array {
    $active = $this->getActiveLibrary();
    $method = $this->getLoadingMethod();

    // Check cache.
    $cache_key = $active . '_' . $method;
    if (isset($this->libraryDefinitionsCache[$cache_key])) {
      return $this->libraryDefinitionsCache[$cache_key];
    }

    $plugin = $this->pluginManager->getPlugin($active);

    if (!$plugin) {
      return [];
    }

    // Validate library based on method.
    if ($method === 'local' && !$plugin->isInstalled()) {
      return [];
    }

    if ($method === 'cdn' && !$plugin->getCdnConfig()) {
      return [];
    }

    $variant = $this->configAccessor->getBuildVariant();

    // Build definition based on method.
    if ($method === 'cdn') {
      $definition = $this->buildCdnDefinition($plugin, $variant);
    }
    else {
      $definition = $plugin->buildLibraryDefinition($variant);
    }

    // Add general settings.
    $definition['drupalSettings']['countdown'] = [
      'activeLibrary' => $active,
      'libraryType' => $plugin->getType(),
      'loadingMethod' => $method,
      'initFunction' => $plugin->getInitFunction(),
    ];

    // Allow other modules to alter the definition.
    $this->moduleHandler->alter('countdown_library_definition', $definition, $active);

    // Cache the definition.
    $this->libraryDefinitionsCache[$cache_key] = $definition;

    return $definition;
  }

  /**
   * {@inheritdoc}
   */
  public function buildCdnDefinition($plugin, bool $minified = TRUE): array {
    $cdn_config = $plugin->getCdnConfig();
    if (!$cdn_config) {
      return [];
    }

    $provider = $this->getCdnProvider();

    // Check if provider is available for this library.
    if (!isset($cdn_config[$provider])) {
      // Fall back to first available provider.
      $provider = key($cdn_config);
    }

    $definition = [
      'version' => $plugin->getRequiredVersion() ?: '1.0',
      'dependencies' => $plugin->getDependencies(),
    ];

    // Add CDN assets.
    if (isset($cdn_config[$provider])) {
      $cdn_assets = $cdn_config[$provider];

      if (isset($cdn_assets['js'])) {
        $js_url = $cdn_assets['js'];
        // Use minified version if available and requested.
        if ($minified && isset($cdn_assets['js_min'])) {
          $js_url = $cdn_assets['js_min'];
        }

        $definition['js'][$js_url] = [
          'type' => 'external',
          'minified' => $minified,
          'attributes' => [
            'defer' => TRUE,
            'crossorigin' => 'anonymous',
          ],
        ];
      }

      if (isset($cdn_assets['css'])) {
        $css_url = $cdn_assets['css'];
        // Use minified version if available and requested.
        if ($minified && isset($cdn_assets['css_min'])) {
          $css_url = $cdn_assets['css_min'];
        }

        $definition['css']['theme'][$css_url] = [
          'type' => 'external',
          'minified' => $minified,
        ];
      }
    }

    return $definition;
  }

  /**
   * {@inheritdoc}
   */
  public function getAvailableLibraryOptions(string $for_method = NULL): array {
    if ($for_method === NULL) {
      $for_method = $this->getLoadingMethod();
    }

    if ($for_method === 'cdn') {
      // Get CDN-capable libraries.
      $options = [];
      $cdn_plugins = $this->pluginManager->getCdnCapablePlugins();

      foreach ($cdn_plugins as $plugin_id => $plugin) {
        $label = $plugin->getLabel();
        $version = $plugin->getRequiredVersion();
        if ($version) {
          $label .= ' (v' . $version . ')';
        }
        if ($plugin->isExperimental()) {
          $label .= ' [' . $this->t('Experimental') . ']';
        }
        $options[$plugin_id] = $label;
      }

      return $options;
    }

    // Local method: get installed libraries.
    return $this->pluginManager->getPluginOptions(TRUE, TRUE, FALSE);
  }

  /**
   * {@inheritdoc}
   */
  public function validateLibraryConfiguration(): array {
    $messages = [];
    $active = $this->getActiveLibrary();
    $method = $this->getLoadingMethod();

    // Validate active library.
    $plugin = $this->pluginManager->getPlugin($active);

    if (!$plugin) {
      $messages[] = $this->t('Active library "@library" not found.', ['@library' => $active]);
      return $messages;
    }

    // Validate based on method.
    if ($method === 'local') {
      if (!$plugin->isInstalled()) {
        $messages[] = $this->t('Active library "@library" is not installed for local loading.', [
          '@library' => $plugin->getLabel(),
        ]);
      }
      elseif (!$plugin->versionMeetsRequirements()) {
        $messages[] = $this->t('Active library "@library" version does not meet requirements.', [
          '@library' => $plugin->getLabel(),
        ]);
      }

      // Check if any libraries are available for local loading.
      $installed = $this->pluginManager->getInstalledPlugins();
      if (empty($installed)) {
        $messages[] = $this->t('No countdown libraries are installed. Please install at least one library for local loading.');
      }
    }
    elseif ($method === 'cdn') {
      if (!$plugin->getCdnConfig()) {
        $messages[] = $this->t('Active library "@library" does not support CDN loading.', [
          '@library' => $plugin->getLabel(),
        ]);
      }

      // Check CDN provider.
      $provider = $this->getCdnProvider();
      $providers = $this->getCdnProvidersForLibrary($active);

      if (!empty($providers) && !isset($providers[$provider])) {
        $messages[] = $this->t('CDN provider "@provider" is not available for library "@library". Available providers: @providers', [
          '@provider' => $provider,
          '@library' => $plugin->getLabel(),
          '@providers' => implode(', ', array_keys($providers)),
        ]);
      }

      // Check if any libraries support CDN.
      $cdn_plugins = $this->pluginManager->getCdnCapablePlugins();
      if (empty($cdn_plugins)) {
        $messages[] = $this->t('No countdown libraries support CDN loading.');
      }
    }

    return $messages;
  }

  /**
   * {@inheritdoc}
   */
  public function getLibraryRequirements(): array {
    $requirements = [];
    $method = $this->getLoadingMethod();
    $active = $this->getActiveLibrary();

    if ($method === 'cdn') {
      // CDN mode requirements.
      $cdn_plugins = $this->pluginManager->getCdnCapablePlugins();

      if (empty($cdn_plugins)) {
        $requirements['countdown_library'] = [
          'title' => $this->t('Countdown Library (CDN)'),
          'value' => $this->t('No CDN support'),
          'severity' => REQUIREMENT_ERROR,
          'description' => $this->t('No countdown libraries support CDN loading. Please switch to local loading or add CDN-capable libraries.'),
        ];
      }
      else {
        $plugin = $this->pluginManager->getPlugin($active);

        if ($plugin && $plugin->getCdnConfig()) {
          $requirements['countdown_library'] = [
            'title' => $this->t('Countdown Library (CDN)'),
            'value' => $plugin->getLabel() . ' (CDN)',
            'severity' => REQUIREMENT_OK,
            'description' => $this->t('Library "@library" is configured for CDN loading. @count CDN-capable libraries available.', [
              '@library' => $plugin->getLabel(),
              '@count' => count($cdn_plugins),
            ]),
          ];
        }
        else {
          $requirements['countdown_library'] = [
            'title' => $this->t('Countdown Library (CDN)'),
            'value' => $this->t('Configuration error'),
            'severity' => REQUIREMENT_WARNING,
            'description' => $this->t('Active library does not support CDN loading. Please select a CDN-capable library.'),
          ];
        }
      }
    }
    else {
      // Local mode requirements.
      $installed = $this->pluginManager->getInstalledPlugins();

      if (empty($installed)) {
        $requirements['countdown_library'] = [
          'title' => $this->t('Countdown Library (Local)'),
          'value' => $this->t('Not installed'),
          'severity' => REQUIREMENT_ERROR,
          'description' => $this->t('No countdown libraries found. Please install a countdown library in the libraries directory.'),
        ];
      }
      else {
        $plugin = $this->pluginManager->getPlugin($active);

        if ($plugin && $plugin->isInstalled()) {
          $status = $plugin->getStatus();
          $value = $plugin->getLabel();

          // Add version info.
          $version = $plugin->getInstalledVersion();
          if ($version) {
            $value .= ' (v' . $version . ')';
          }

          // Determine severity.
          $severity = REQUIREMENT_OK;
          if ($status['severity'] === 'error') {
            $severity = REQUIREMENT_ERROR;
          }
          elseif ($status['severity'] === 'warning') {
            $severity = REQUIREMENT_WARNING;
          }

          $description_parts = $status['messages'];
          $description_parts[] = $this->t('@count libraries installed locally.', [
            '@count' => count($installed),
          ]);

          $requirements['countdown_library'] = [
            'title' => $this->t('Countdown Library (Local)'),
            'value' => $value,
            'severity' => $severity,
            'description' => implode(' ', $description_parts),
          ];
        }
        else {
          $requirements['countdown_library'] = [
            'title' => $this->t('Countdown Library (Local)'),
            'value' => $this->t('Configuration error'),
            'severity' => REQUIREMENT_WARNING,
            'description' => $this->t('Active library is not installed. Please install it or select an installed library.'),
          ];
        }
      }
    }

    return $requirements;
  }

  /**
   * {@inheritdoc}
   */
  public function isAutoLoadEnabled(): bool {
    return $this->configAccessor->isAutoLoadEnabled();
  }

  /**
   * {@inheritdoc}
   */
  public function setAutoLoad(bool $enabled): bool {
    $this->configFactory->getEditable('countdown.settings')
      ->set('load', $enabled)
      ->save();

    $this->clearCache();
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getBuildVariant(): bool {
    return $this->configAccessor->getBuildVariant();
  }

  /**
   * {@inheritdoc}
   */
  public function setBuildVariant(bool $minified): bool {
    $this->configFactory->getEditable('countdown.settings')
      ->set('build.variant', $minified)
      ->save();

    $this->clearCache();
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getAssetTypes(): array {
    // JS and CSS files are always loaded for main library.
    // This method is kept for backward compatibility.
    return [
      'js' => TRUE,
      'css' => TRUE,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function setAssetTypes(array $types): bool {
    // This method is deprecated since JS/CSS are always loaded.
    // Only extensions can be configured now.
    // Kept for backward compatibility.
    $this->clearCache();
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function switchLibrary(string $library_id, string $method = NULL): bool {
    // Set method if provided.
    if ($method !== NULL) {
      $this->setLoadingMethod($method);
    }

    // Set the library.
    return $this->setActiveLibrary($library_id);
  }

  /**
   * {@inheritdoc}
   */
  public function getLibraryInfo(string $library_id): ?array {
    $plugin = $this->pluginManager->getPlugin($library_id);

    if (!$plugin) {
      return NULL;
    }

    return [
      'id' => $library_id,
      'label' => $plugin->getLabel(),
      'description' => $plugin->getDescription(),
      'type' => $plugin->getType(),
      'installed' => $plugin->isInstalled(),
      'version' => $plugin->getInstalledVersion(),
      'required_version' => $plugin->getRequiredVersion(),
      'version_ok' => $plugin->versionMeetsRequirements(),
      'path' => $plugin->getLibraryPath(),
      'cdn_support' => $plugin->getCdnConfig() !== NULL,
      'cdn_providers' => $this->getCdnProvidersForLibrary($library_id),
      'homepage' => $plugin->getHomepage(),
      'author' => $plugin->getAuthor(),
      'license' => $plugin->getLicense(),
      'experimental' => $plugin->isExperimental(),
      'status' => $plugin->getStatus(),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getAllLibrariesInfo(): array {
    $info = [];
    $all_plugins = $this->pluginManager->getAllPlugins();

    foreach ($all_plugins as $plugin_id => $plugin) {
      $info[$plugin_id] = $this->getLibraryInfo($plugin_id);
    }

    return $info;
  }

  /**
   * {@inheritdoc}
   */
  public function clearCache(): void {
    // Clear internal caches.
    $this->libraryDefinitionsCache = [];
    $this->cdnProvidersCache = [];

    // Clear Drupal caches.
    Cache::invalidateTags([
      CountdownConstants::CACHE_TAG_SETTINGS,
      CountdownConstants::CACHE_TAG_DISCOVERY,
      'library_info',
    ]);

    // Clear plugin manager cache.
    $this->pluginManager->resetAllCaches();

    // Clear discovery cache.
    $this->libraryDiscovery->clearCache();
  }

  /**
   * {@inheritdoc}
   */
  public function getCdnProviders(): array {
    $providers = [];
    $cdn_plugins = $this->pluginManager->getCdnCapablePlugins();

    foreach ($cdn_plugins as $plugin) {
      $cdn_config = $plugin->getCdnConfig();
      if ($cdn_config) {
        foreach (array_keys($cdn_config) as $provider) {
          $providers[$provider] = $provider;
        }
      }
    }

    // Add default providers.
    $default_providers = $this->getAvailableCdnProviders();
    foreach ($default_providers as $provider) {
      $providers[$provider] = $provider;
    }

    return array_unique($providers);
  }

  /**
   * {@inheritdoc}
   */
  public function isLibraryCompatible(string $library_id, string $method): bool {
    $plugin = $this->pluginManager->getPlugin($library_id);

    if (!$plugin) {
      return FALSE;
    }

    if ($method === 'local') {
      return $plugin->isInstalled();
    }

    if ($method === 'cdn') {
      return $plugin->getCdnConfig() !== NULL;
    }

    return FALSE;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc