ai_upgrade_assistant-0.2.0-alpha2/src/Service/UpdateMonitorService.php

src/Service/UpdateMonitorService.php
<?php

namespace Drupal\ai_upgrade_assistant\Service;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;

/**
 * Service for monitoring Drupal module updates and security advisories.
 *
 * This service:
 * - Monitors drupal.org for new releases
 * - Tracks security advisories
 * - Prioritizes updates based on security risk and complexity
 * - Maintains update schedules
 */
class UpdateMonitorService {

  /**
   * The HTTP client.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected $httpClient;

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

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

  /**
   * The state service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * The logger factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $loggerFactory;

  /**
   * The project analyzer service.
   *
   * @var \Drupal\ai_upgrade_assistant\Service\ProjectAnalyzer
   */
  protected $projectAnalyzer;

  /**
   * Drupal.org API endpoints.
   */
  const DRUPAL_UPDATES_ENDPOINT = 'https://updates.drupal.org/release-history';
  const DRUPAL_SECURITY_FEED = 'https://www.drupal.org/security/rss.xml';

  /**
   * Update check interval in seconds (default: 6 hours).
   */
  const UPDATE_CHECK_INTERVAL = 21600;

  /**
   * Constructs a new UpdateMonitorService.
   *
   * @param \GuzzleHttp\ClientInterface $http_client
   *   The HTTP client.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory service.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger factory service.
   * @param \Drupal\ai_upgrade_assistant\Service\ProjectAnalyzer $project_analyzer
   *   The project analyzer service.
   */
  public function __construct(
    ClientInterface $http_client,
    ModuleHandlerInterface $module_handler,
    ConfigFactoryInterface $config_factory,
    StateInterface $state,
    LoggerChannelFactoryInterface $logger_factory,
    ProjectAnalyzer $project_analyzer
  ) {
    $this->httpClient = $http_client;
    $this->moduleHandler = $module_handler;
    $this->configFactory = $config_factory;
    $this->state = $state;
    $this->loggerFactory = $logger_factory;
    $this->projectAnalyzer = $project_analyzer;
  }

  /**
   * Checks for available updates for all installed modules.
   *
   * @return array
   *   Array of modules with available updates, keyed by module name.
   *   Each module entry contains:
   *   - current_version: Current installed version
   *   - available_version: Latest available version
   *   - security_update: Boolean indicating if it's a security update
   *   - complexity: Estimated update complexity (low/medium/high)
   *   - last_checked: Timestamp of last check
   */
  public function checkForUpdates() {
    $updates = [];
    $modules = $this->moduleHandler->getModuleList();

    foreach ($modules as $name => $module) {
      // Skip if checked recently
      $last_check = $this->state->get("ai_upgrade_assistant.update_check.{$name}", 0);
      if ((time() - $last_check) < self::UPDATE_CHECK_INTERVAL) {
        continue;
      }

      try {
        // Query drupal.org updates API
        $response = $this->httpClient->get(self::DRUPAL_UPDATES_ENDPOINT . '/' . $name . '/current');
        $data = new \SimpleXMLElement($response->getBody()->getContents());

        if (isset($data->releases->release[0])) {
          $latest = $data->releases->release[0];
          $current_version = $this->getModuleVersion($name);

          if (version_compare($latest->version, $current_version, '>')) {
            // Analyze update complexity
            $complexity = $this->analyzeUpdateComplexity($name, $current_version, $latest->version);

            $updates[$name] = [
              'current_version' => $current_version,
              'available_version' => (string) $latest->version,
              'security_update' => (bool) $latest->security,
              'complexity' => $complexity,
              'last_checked' => time(),
            ];
          }
        }

        // Update last check timestamp
        $this->state->set("ai_upgrade_assistant.update_check.{$name}", time());
      }
      catch (RequestException $e) {
        $this->loggerFactory->get('ai_upgrade_assistant')->error(
          'Failed to check updates for @module: @error',
          ['@module' => $name, '@error' => $e->getMessage()]
        );
      }
    }

    // Store update data
    $this->state->set('ai_upgrade_assistant.available_updates', $updates);

    return $updates;
  }

  /**
   * Checks for security advisories.
   *
   * @return array
   *   Array of security advisories.
   */
  public function checkSecurityAdvisories() {
    try {
      $response = $this->httpClient->get(self::DRUPAL_SECURITY_FEED);
      $feed = new \SimpleXMLElement($response->getBody()->getContents());

      $advisories = [];
      foreach ($feed->channel->item as $item) {
        // Parse advisory details from title and description
        $advisory = $this->parseSecurityAdvisory($item);
        if ($advisory) {
          $advisories[] = $advisory;
        }
      }

      // Store advisories
      $this->state->set('ai_upgrade_assistant.security_advisories', $advisories);

      return $advisories;
    }
    catch (RequestException $e) {
      $this->loggerFactory->get('ai_upgrade_assistant')->error(
        'Failed to fetch security advisories: @error',
        ['@error' => $e->getMessage()]
      );
      return [];
    }
  }

  /**
   * Analyzes the complexity of an update.
   *
   * @param string $module_name
   *   The name of the module.
   * @param string $current_version
   *   Current version of the module.
   * @param string $target_version
   *   Target version for update.
   *
   * @return string
   *   Complexity level: 'low', 'medium', or 'high'
   */
  protected function analyzeUpdateComplexity($module_name, $current_version, $target_version) {
    // Get module's usage of APIs and hooks
    $analysis = $this->projectAnalyzer->analyzeModule($module_name);
    
    // Major version change indicates high complexity
    if ($this->isMajorVersionChange($current_version, $target_version)) {
      return 'high';
    }

    // Count the number of deprecated API uses
    $deprecated_count = isset($analysis['deprecated_apis']) ? count($analysis['deprecated_apis']) : 0;
    
    // Basic complexity heuristic
    if ($deprecated_count > 10) {
      return 'high';
    }
    elseif ($deprecated_count > 5) {
      return 'medium';
    }
    
    return 'low';
  }

  /**
   * Parses a security advisory from RSS feed item.
   *
   * @param \SimpleXMLElement $item
   *   RSS feed item.
   *
   * @return array|null
   *   Parsed advisory or NULL if not relevant.
   */
  protected function parseSecurityAdvisory($item) {
    $title = (string) $item->title;
    $description = (string) $item->description;
    $link = (string) $item->link;
    
    // Only process security advisories
    if (strpos($title, 'SA-') === false) {
      return NULL;
    }

    return [
      'title' => $title,
      'description' => $description,
      'link' => $link,
      'date' => strtotime($item->pubDate),
      'severity' => $this->parseSeverity($description),
      'affected_modules' => $this->parseAffectedModules($description),
    ];
  }

  /**
   * Parses severity from advisory description.
   *
   * @param string $description
   *   Advisory description.
   *
   * @return string
   *   Severity level: 'critical', 'high', 'moderate', or 'low'
   */
  protected function parseSeverity($description) {
    $description = strtolower($description);
    if (strpos($description, 'critical') !== false) {
      return 'critical';
    }
    elseif (strpos($description, 'highly critical') !== false) {
      return 'high';
    }
    elseif (strpos($description, 'moderately critical') !== false) {
      return 'moderate';
    }
    return 'low';
  }

  /**
   * Extracts affected modules from advisory description.
   *
   * @param string $description
   *   Advisory description.
   *
   * @return array
   *   Array of affected module names.
   */
  protected function parseAffectedModules($description) {
    $modules = [];
    // Common patterns for module names in security advisories
    $patterns = [
      '/\b(?:module|project)\s+([a-z0-9_]+)\b/i',
      '/\b([a-z0-9_]+)\.module\b/i',
    ];

    foreach ($patterns as $pattern) {
      if (preg_match_all($pattern, $description, $matches)) {
        $modules = array_merge($modules, $matches[1]);
      }
    }

    return array_unique($modules);
  }

  /**
   * Gets the installed version of a module.
   *
   * @param string $module_name
   *   The name of the module.
   *
   * @return string
   *   The installed version.
   */
  protected function getModuleVersion($module_name) {
    $module_data = system_get_info('module', $module_name);
    return $module_data['version'] ?? '0.0';
  }

  /**
   * Checks if update is a major version change.
   *
   * @param string $current_version
   *   Current version string.
   * @param string $target_version
   *   Target version string.
   *
   * @return bool
   *   TRUE if major version change.
   */
  protected function isMajorVersionChange($current_version, $target_version) {
    $current_parts = explode('.', $current_version);
    $target_parts = explode('.', $target_version);
    
    return isset($current_parts[0]) && isset($target_parts[0]) &&
      $current_parts[0] !== $target_parts[0];
  }

}

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

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