utilikit-1.0.0/src/Service/UtilikitFileManager.php

src/Service/UtilikitFileManager.php
<?php

declare(strict_types=1);

namespace Drupal\utilikit\Service;

use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Asset\AssetOptimizerInterface;
use Psr\Log\LoggerInterface;
use Drupal\Core\File\FileExists;

/**
 * Manages CSS file operations for UtiliKit static mode.
 *
 * This service handles the creation, optimization, cleanup, and URL generation
 * for UtiliKit CSS files when operating in static mode. It provides file
 * system operations with proper error handling, CSS optimization capabilities,
 * and cache-busting URL generation.
 */
class UtilikitFileManager {

  /**
   * The state manager service.
   *
   * @var \Drupal\utilikit\Service\UtilikitStateManager
   */
  protected UtilikitStateManager $stateManager;

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected FileSystemInterface $fileSystem;

  /**
   * The file URL generator service.
   *
   * @var \Drupal\Core\File\FileUrlGeneratorInterface
   */
  protected FileUrlGeneratorInterface $fileUrlGenerator;

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

  /**
   * The CSS optimizer service.
   *
   * @var \Drupal\Core\Asset\AssetOptimizerInterface
   */
  protected AssetOptimizerInterface $cssOptimizer;

  /**
   * The logger service.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $logger;

  /**
   * Constructs a new UtilikitFileManager object.
   *
   * @param \Drupal\utilikit\Service\UtilikitStateManager $stateManager
   *   The state manager service for accessing CSS data.
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   *   The file system service for file operations.
   * @param \Drupal\Core\File\FileUrlGeneratorInterface $fileUrlGenerator
   *   The file URL generator service for creating file URLs.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The configuration factory service.
   * @param \Drupal\Core\Asset\AssetOptimizerInterface $cssOptimizer
   *   The CSS optimizer service.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger service for recording file operations.
   */
  public function __construct(
    UtilikitStateManager $stateManager,
    FileSystemInterface $fileSystem,
    FileUrlGeneratorInterface $fileUrlGenerator,
    ConfigFactoryInterface $configFactory,
    AssetOptimizerInterface $cssOptimizer,
    LoggerInterface $logger,
  ) {
    $this->stateManager = $stateManager;
    $this->fileSystem = $fileSystem;
    $this->fileUrlGenerator = $fileUrlGenerator;
    $this->configFactory = $configFactory;
    $this->cssOptimizer = $cssOptimizer;
    $this->logger = $logger;
  }

  /**
   * Ensures the static CSS file exists and is up to date.
   *
   * Creates or updates the static CSS file with the current generated CSS
   * content from the state manager. Handles directory preparation, file
   * writing, and optional CSS optimization based on configuration.
   *
   * @return bool
   *   TRUE if the CSS file was successfully created/updated, FALSE otherwise.
   */
  public function ensureStaticCssFile(): bool {
    $config = $this->configFactory->get('utilikit.settings');
    if (!$config->get('rendering_mode')) {
      return FALSE;
    }

    try {
      $static_css = $this->stateManager->getGeneratedCss();

      if (empty($static_css)) {
        $this->logger->warning('No CSS to write to static file.');
        return FALSE;
      }

      $css_file_uri = UtilikitConstants::CSS_DIRECTORY . '/' . UtilikitConstants::CSS_FILENAME;
      $css_directory = $this->fileSystem->dirname($css_file_uri);

      if (!$this->fileSystem->prepareDirectory($css_directory, FileSystemInterface::CREATE_DIRECTORY)) {
        $this->logger->error('Failed to prepare directory for static CSS: @dir', [
          '@dir' => $css_directory,
        ]);
        return FALSE;
      }

      $result = $this->fileSystem->saveData($static_css, $css_file_uri, FileExists::Replace);
      if ($result === FALSE) {
        $this->logger->error('Failed to save static CSS file to: @uri', [
          '@uri' => $css_file_uri,
        ]);
        return FALSE;
      }

      if ($config->get('optimize_css')) {
        $this->optimizeCssFile($css_file_uri);
      }

      $this->logger->info('Successfully saved static CSS file to: @uri', [
        '@uri' => $css_file_uri,
      ]);

      return TRUE;

    }
    catch (\Exception $e) {
      $this->logger->error('Exception while saving static CSS: @message', [
        '@message' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

  /**
   * Optimizes a CSS file using Drupal's CSS optimizer or fallback minifier.
   *
   * Attempts to optimize the CSS file using Drupal's built-in CSS optimizer
   * service. If that fails, falls back to a simple minification process.
   * Logs the optimization results including file size reduction.
   *
   * @param string $css_file_uri
   *   The URI of the CSS file to optimize.
   */
  private function optimizeCssFile(string $css_file_uri): void {
    try {
      $css_content = file_get_contents($css_file_uri);
      if ($css_content === FALSE) {
        $this->logger->error('Failed to read CSS file for optimization: @uri', [
          '@uri' => $css_file_uri,
        ]);
        return;
      }

      try {
        $css_asset = [
          'type' => 'file',
          'data' => $css_file_uri,
          'weight' => 0,
          'group' => 0,
          'every_page' => FALSE,
          'media' => 'all',
          'preprocess' => TRUE,
          'browsers' => ['IE' => TRUE, '!IE' => TRUE],
        ];

        $optimized_assets = $this->cssOptimizer->optimize($css_asset);

        if (!empty($optimized_assets) && isset($optimized_assets['data'])) {
          $optimized_css = file_get_contents($optimized_assets['data']);
        }
        else {
          $optimized_css = $this->minifyCss($css_content);
        }
      }
      catch (\Exception $e) {
        $optimized_css = $this->minifyCss($css_content);
      }

      if (!empty($optimized_css) && $optimized_css !== $css_content) {
        $this->fileSystem->saveData($optimized_css, $css_file_uri, FileExists::Replace);

        $original_size = strlen($css_content);
        $optimized_size = strlen($optimized_css);
        $reduction = round((1 - $optimized_size / $original_size) * 100, 1);

        $this->logger->info('CSS optimized: @original KB → @optimized KB (@reduction% reduction)', [
          '@original' => round($original_size / 1024, 1),
          '@optimized' => round($optimized_size / 1024, 1),
          '@reduction' => $reduction,
        ]);
      }
    }
    catch (\Exception $e) {
      $this->logger->warning('CSS optimization failed: @message. Using unoptimized CSS.', [
        '@message' => $e->getMessage(),
      ]);
    }
  }

  /**
   * Minifies CSS content by removing comments and whitespace.
   *
   * Provides a fallback CSS minification when Drupal's CSS optimizer
   * is not available or fails. Removes comments, normalizes whitespace,
   * and optimizes selector and property formatting.
   *
   * @param string $css
   *   The CSS content to minify.
   *
   * @return string
   *   The minified CSS content.
   */
  public function minifyCss(string $css): string {
    $css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
    $css = strtr($css, [
      "\r\n" => '',
      "\r" => '',
      "\n" => '',
      "\t" => '',
    ]);
    $css = preg_replace('/\s*([{}:;,])\s*/', '$1', $css);
    $css = str_replace(';}', '}', $css);
    $css = preg_replace('/\s*>\s*/', '>', $css);
    $css = preg_replace('/\s*\+\s*/', '+', $css);
    $css = preg_replace('/\s*~\s*/', '~', $css);
    $css = preg_replace('/;(?=\s*})/', '', $css);
    $css = preg_replace('/\s+/', ' ', $css);
    $css = preg_replace('/@media\s+/', '@media ', $css);
    return trim($css);
  }

  /**
   * Cleans up UtiliKit static CSS files and directories.
   *
   * Removes the static CSS file and attempts to clean up empty directories
   * in the UtiliKit file structure. Safely handles missing files and
   * directories without throwing exceptions.
   */
  public function cleanupStaticFiles(): void {
    $css_file_uri = UtilikitConstants::CSS_DIRECTORY . '/' . UtilikitConstants::CSS_FILENAME;
    $css_directory = $this->fileSystem->dirname($css_file_uri);

    $css_real_path = $this->fileSystem->realpath($css_file_uri);
    if ($css_real_path && file_exists($css_real_path)) {
      $this->fileSystem->delete($css_file_uri);
    }

    $dir_real_path = $this->fileSystem->realpath($css_directory);
    if ($dir_real_path && is_dir($dir_real_path)) {
      $files = scandir($dir_real_path);
      if ($files !== FALSE && count($files) <= 2) {
        @rmdir($dir_real_path);

        $parent_dir = $this->fileSystem->dirname($css_directory);
        $parent_real_path = $this->fileSystem->realpath($parent_dir);
        if ($parent_real_path && is_dir($parent_real_path)) {
          $parent_files = scandir($parent_real_path);
          if ($parent_files !== FALSE && count($parent_files) <= 2) {
            @rmdir($parent_real_path);
          }
        }
      }
    }
  }

  /**
   * Gets the URL for the static CSS file with cache busting parameters.
   *
   * Generates a public URL for the static CSS file with cache-busting
   * parameters based on CSS content hash and timestamp. Returns NULL
   * if the file doesn't exist.
   *
   * @return string|null
   *   The cache-busted URL to the static CSS file, or NULL if file doesn't
   *   exist.
   */
  public function getStaticCssUrl(): ?string {
    $css_file_uri = UtilikitConstants::CSS_DIRECTORY . '/' . UtilikitConstants::CSS_FILENAME;
    $css_real_path = $this->fileSystem->realpath($css_file_uri);

    if ($css_real_path && file_exists($css_real_path)) {
      $static_css = $this->stateManager->getGeneratedCss();
      $css_hash = substr(md5($static_css), 0, 8);

      $timestamp = $this->stateManager->getCssTimestamp();

      $base_url = $this->fileUrlGenerator->generateAbsoluteString($css_file_uri);
      return $base_url . '?v=' . $css_hash . '&t=' . $timestamp;
    }

    return NULL;
  }

}

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

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