class ModifiedFiles in Automatic Updates 8
Same name in this branch
- 8 src/ReadinessChecker/ModifiedFiles.php \Drupal\automatic_updates\ReadinessChecker\ModifiedFiles
 - 8 src/Services/ModifiedFiles.php \Drupal\automatic_updates\Services\ModifiedFiles
 
Modified files service.
Hierarchy
- class \Drupal\automatic_updates\Services\ModifiedFiles implements ModifiedFilesInterface uses IgnoredPathsTrait, ProjectInfoTrait
 
Expanded class hierarchy of ModifiedFiles
1 string reference to 'ModifiedFiles'
1 service uses ModifiedFiles
File
- src/
Services/ ModifiedFiles.php, line 22  
Namespace
Drupal\automatic_updates\ServicesView source
class ModifiedFiles implements ModifiedFilesInterface {
  use IgnoredPathsTrait;
  use ProjectInfoTrait;
  /**
   * The logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;
  /**
   * The HTTP client.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected $httpClient;
  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;
  /**
   * ModifiedFiles constructor.
   *
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.
   * @param \GuzzleHttp\ClientInterface $http_client
   *   The HTTP client.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   */
  public function __construct(LoggerInterface $logger, ClientInterface $http_client, ConfigFactoryInterface $config_factory) {
    $this->logger = $logger;
    $this->httpClient = $http_client;
    $this->configFactory = $config_factory;
    $project_root = drupal_get_path('module', 'automatic_updates');
    require_once $project_root . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
  }
  /**
   * {@inheritdoc}
   */
  public function getModifiedFiles(array $extensions = []) {
    $modified_files = new \ArrayIterator();
    /** @var \GuzzleHttp\Promise\PromiseInterface[] $promises */
    $promises = $this
      ->getHashRequests($extensions);
    // Wait until all the requests are finished.
    (new EachPromise($promises, [
      'concurrency' => 4,
      'fulfilled' => function (array $resource) use ($modified_files) {
        $this
          ->processHashes($resource, $modified_files);
      },
      'rejected' => function (RequestException $exception) {
        $this
          ->processFailures($exception);
      },
    ]))
      ->promise()
      ->wait();
    return $modified_files;
  }
  /**
   * Process checking hashes of files from external URL.
   *
   * @param array $hash
   *   An array of http response and project info.
   * @param \ArrayIterator $modified_files
   *   The list of modified files.
   *
   * @throws \SodiumException
   */
  protected function processHashes(array $hash, \ArrayIterator $modified_files) {
    $contents = $hash['contents'];
    $info = $hash['info'];
    $directory_root = $info['install path'];
    if ($info['project'] === 'drupal') {
      $directory_root = '';
    }
    $module_path = drupal_get_path('module', 'automatic_updates');
    $key = file_get_contents($module_path . '/artifacts/keys/root.pub');
    $verifier = new Verifier($key);
    $files = $verifier
      ->verifyCsigMessage($contents);
    $checksums = new ChecksumList($files, TRUE);
    foreach (new FailedCheckumFilter($checksums, $directory_root) as $failed_checksum) {
      $file_path = implode(DIRECTORY_SEPARATOR, array_filter([
        $directory_root,
        $failed_checksum->filename,
      ]));
      if (!file_exists($file_path)) {
        $modified_files
          ->append($file_path);
        continue;
      }
      $actual_hash = @hash_file(strtolower($failed_checksum->algorithm), $file_path);
      if ($actual_hash === FALSE || empty($actual_hash) || strlen($actual_hash) < 64 || strcmp($actual_hash, $failed_checksum->hex_hash) !== 0) {
        $modified_files
          ->append($file_path);
      }
    }
  }
  /**
   * Handle HTTP failures.
   *
   * @param \GuzzleHttp\Exception\RequestException $exception
   *   The request exception.
   */
  protected function processFailures(RequestException $exception) {
    // Log all the exceptions, even modules that aren't the main project.
    watchdog_exception('automatic_updates', $exception, NULL, [], RfcLogLevel::INFO);
    // HTTP 404 is expected for modules that aren't the main project. But
    // other error codes should complain loudly.
    if ($exception
      ->getCode() !== 404) {
      throw $exception;
    }
  }
  /**
   * Get an iterator of promises that return a resource stream.
   *
   * @param array $extensions
   *   The list of extensions, keyed by extension name and value the info array.
   *
   * @codingStandardsIgnoreStart
   *
   * @return \Generator
   *
   * @@codingStandardsIgnoreEnd
   */
  protected function getHashRequests(array $extensions) {
    foreach ($extensions as $info) {
      // We can't check for modifications if we don't know the version.
      if (!$info['version']) {
        continue;
      }
      $url = $this
        ->buildUrl($info);
      (yield $this
        ->getPromise($url, $info));
    }
  }
  /**
   * Get a promise.
   *
   * @param string $url
   *   The URL.
   * @param array $info
   *   The extension's info.
   *
   * @return \GuzzleHttp\Promise\PromiseInterface
   *   The promise.
   */
  protected function getPromise($url, array $info) {
    return $this->httpClient
      ->requestAsync('GET', $url, [
      'stream' => TRUE,
      'read_timeout' => 30,
    ])
      ->then(static function (ResponseInterface $response) use ($info) {
      return [
        'contents' => $response
          ->getBody()
          ->getContents(),
        'info' => $info,
      ];
    });
  }
  /**
   * Build an extension's hash file URL.
   *
   * @param array $info
   *   The extension's info.
   *
   * @return string
   *   The URL endpoint with for an extension.
   */
  protected function buildUrl(array $info) {
    $version = $info['version'];
    $project_name = $info['project'];
    $hash_name = $this
      ->getHashName($info);
    $uri = ltrim($this->configFactory
      ->get('automatic_updates.settings')
      ->get('hashes_uri'), '/');
    return Url::fromUri("{$uri}/{$project_name}/{$version}/{$hash_name}")
      ->toString();
  }
  /**
   * Get the hash file name.
   *
   * @param array $info
   *   The extension's info.
   *
   * @return string|null
   *   The hash name.
   */
  protected function getHashName(array $info) {
    $hash_name = 'contents-sha256sums';
    if ($info['packaged']) {
      $hash_name .= '-packaged';
    }
    return $hash_name . '.csig';
  }
}Members
| 
            Name | 
                  Modifiers | Type | Description | Overrides | 
|---|---|---|---|---|
| 
            IgnoredPathsTrait:: | 
                  protected | function | Gets the config factory. | |
| 
            IgnoredPathsTrait:: | 
                  protected | function | Get the path matcher service. | |
| 
            IgnoredPathsTrait:: | 
                  protected | function | Check if the file path is ignored. | |
| 
            ModifiedFiles:: | 
                  protected | property | The config factory. | |
| 
            ModifiedFiles:: | 
                  protected | property | The HTTP client. | |
| 
            ModifiedFiles:: | 
                  protected | property | The logger. | |
| 
            ModifiedFiles:: | 
                  protected | function | Build an extension's hash file URL. | |
| 
            ModifiedFiles:: | 
                  protected | function | Get the hash file name. | |
| 
            ModifiedFiles:: | 
                  protected | function | Get an iterator of promises that return a resource stream. | |
| 
            ModifiedFiles:: | 
                  public | function | 
            Get list of modified files. Overrides ModifiedFilesInterface:: | 
                  |
| 
            ModifiedFiles:: | 
                  protected | function | Get a promise. | |
| 
            ModifiedFiles:: | 
                  protected | function | Handle HTTP failures. | |
| 
            ModifiedFiles:: | 
                  protected | function | Process checking hashes of files from external URL. | |
| 
            ModifiedFiles:: | 
                  public | function | ModifiedFiles constructor. | |
| 
            ProjectInfoTrait:: | 
                  protected | function | Get the composer.json as a JSON array. | |
| 
            ProjectInfoTrait:: | 
                  protected | function | Get extension list. | |
| 
            ProjectInfoTrait:: | 
                  protected | function | Get the extension version. | |
| 
            ProjectInfoTrait:: | 
                  protected | function | Returns an array of info files information of available extensions. | 1 | 
| 
            ProjectInfoTrait:: | 
                  protected | function | Get the extension's project name. | |
| 
            ProjectInfoTrait:: | 
                  protected | function | Get string suffix. |