You are here

FeaturesGenerationWrite.php in Features 8.4

Same filename and directory in other branches
  1. 8.3 src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php

File

src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php
View source
<?php

namespace Drupal\features\Plugin\FeaturesGeneration;

use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\features\FeaturesGenerationMethodBase;
use Drupal\features\FeaturesBundleInterface;
use Drupal\features\Package;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Class for writing packages to the local file system.
 *
 * @Plugin(
 *   id = \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite::METHOD_ID,
 *   weight = 2,
 *   name = @Translation("Write"),
 *   description = @Translation("Write packages and optional profile to the file system."),
 * )
 */
class FeaturesGenerationWrite extends FeaturesGenerationMethodBase implements ContainerFactoryPluginInterface {

  /**
   * The package generation method id.
   */
  const METHOD_ID = 'write';

  /**
   * The app root.
   *
   * @var string
   */
  protected $root;

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

  /**
   * Creates a new FeaturesGenerationWrite instance.
   *
   * @param string $root
   *   The app root.
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   *   The filesystem service.
   */
  public function __construct($root, FileSystemInterface $fileSystem) {
    $this->root = $root;
    $this->fileSystem = $fileSystem;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($container
      ->get('app.root'), $container
      ->get('file_system'));
  }

  /**
   * Reads and merges in existing files for a given package or profile.
   *
   * @param \Drupal\features\Package $package
   *   The package.
   * @param array $existing_packages
   *   An array of existing packages.
   * @param \Drupal\features\FeaturesBundleInterface $bundle
   *   The bundle the package belongs to.
   */
  protected function preparePackage(Package $package, array $existing_packages, FeaturesBundleInterface $bundle = NULL) {

    // If this package is already present, prepare files.
    if (isset($existing_packages[$package
      ->getMachineName()])) {
      $existing_directory = $existing_packages[$package
        ->getMachineName()];
      $package
        ->setDirectory($existing_directory);
    }
    else {
      $existing_directory = $package
        ->getDirectory();
    }

    // Merge in the info file.
    $info_file_uri = $this->root . '/' . $existing_directory . '/' . $package
      ->getMachineName() . '.info.yml';
    if (file_exists($info_file_uri)) {
      $files = $package
        ->getFiles();
      $files['info']['string'] = $this
        ->mergeInfoFile($package
        ->getFiles()['info']['string'], $info_file_uri);
      $package
        ->setFiles($files);

      // Remove the config directories, as they will be replaced.
      foreach (array_keys($this->featuresManager
        ->getExtensionStorages()
        ->getExtensionStorages()) as $directory) {
        $config_directory = $this->root . '/' . $existing_directory . '/' . $directory;
        if (is_dir($config_directory)) {
          $this->fileSystem
            ->deleteRecursive($config_directory);
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function generate(array $packages = [], FeaturesBundleInterface $bundle = NULL) {

    // If no packages were specified, get all packages.
    if (empty($packages)) {
      $packages = $this->featuresManager
        ->getPackages();
    }
    $return = [];

    // Add package files.
    // We need to update the system.module.files state because it's cached.
    // Cannot just call system_rebuild_module_data() because $listing->scan()
    // has it's own internal static cache that we cannot clear at this point.
    $files = \Drupal::state()
      ->get('system.module.files');
    foreach ($packages as $package) {
      $this
        ->generatePackage($return, $package);
      if (!isset($files[$package
        ->getMachineName()]) && isset($package
        ->getFiles()['info'])) {
        $files[$package
          ->getMachineName()] = $package
          ->getDirectory() . '/' . $package
          ->getFiles()['info']['filename'];
      }
    }

    // Rebuild system module cache.
    \Drupal::state()
      ->set('system.module.files', $files);
    return $return;
  }

  /**
   * Writes a package or profile's files to the file system.
   *
   * @param array &$return
   *   The return value, passed by reference.
   * @param \Drupal\features\Package $package
   *   The package or profile.
   */
  protected function generatePackage(array &$return, Package $package) {
    if (!$package
      ->getFiles()) {
      $this
        ->failure($return, $package, NULL, $this
        ->t('No configuration was selected to be exported.'));
      return;
    }
    $success = TRUE;
    foreach ($package
      ->getFiles() as $file) {
      try {
        $this
          ->generateFile($package
          ->getDirectory(), $file);
      } catch (\Exception $exception) {
        $this
          ->failure($return, $package, $exception);
        $success = FALSE;
        break;
      }
    }
    if ($success) {
      $this
        ->success($return, $package);
    }
  }

  /**
   * Registers a successful package or profile write operation.
   *
   * @param array &$return
   *   The return value, passed by reference.
   * @param \Drupal\features\Package $package
   *   The package or profile.
   */
  protected function success(array &$return, Package $package) {
    $type = $package
      ->getType() == 'module' ? $this
      ->t('Package') : $this
      ->t('Profile');
    $return[] = [
      'success' => TRUE,
      'display' => TRUE,
      'message' => '@type @package written to @directory.',
      'variables' => [
        '@type' => $type,
        '@package' => $package
          ->getName(),
        '@directory' => $package
          ->getDirectory(),
      ],
    ];
  }

  /**
   * Registers a failed package or profile write operation.
   *
   * @param array &$return
   *   The return value, passed by reference.
   * @param \Drupal\features\Package $package
   *   The package or profile.
   * @param \Exception $exception
   *   The exception object.
   * @param string $message
   *   Error message when there isn't an Exception object.
   */
  protected function failure(array &$return, Package $package, \Exception $exception = NULL, $message = '') {
    $type = $package
      ->getType() == 'module' ? $this
      ->t('Package') : $this
      ->t('Profile');
    $return[] = [
      'success' => FALSE,
      'display' => TRUE,
      'message' => '@type @package not written to @directory. Error: @error.',
      'variables' => [
        '@type' => $type,
        '@package' => $package
          ->getName(),
        '@directory' => $package
          ->getDirectory(),
        '@error' => isset($exception) ? $exception
          ->getMessage() : $message,
      ],
    ];
  }

  /**
   * Writes a file to the file system, creating its directory as needed.
   *
   * @param string $directory
   *   The extension's directory.
   * @param array $file
   *   Array with the following keys:
   *   - 'filename': the name of the file.
   *   - 'subdirectory': any subdirectory of the file within the extension
   *      directory.
   *   - 'string': the contents of the file.
   *
   * @throws Exception
   */
  protected function generateFile($directory, array $file) {
    if (!empty($file['subdirectory'])) {
      $directory .= '/' . $file['subdirectory'];
    }
    $directory = $this->root . '/' . $directory;
    if (!is_dir($directory)) {
      if ($this->fileSystem
        ->mkdir($directory, NULL, TRUE) === FALSE) {
        throw new \Exception($this
          ->t('Failed to create directory @directory.', [
          '@directory' => $directory,
        ]));
      }
    }
    if (file_put_contents($directory . '/' . $file['filename'], $file['string']) === FALSE) {
      throw new \Exception($this
        ->t('Failed to write file @filename.', [
        '@filename' => $file['filename'],
      ]));
    }
  }

}

Classes

Namesort descending Description
FeaturesGenerationWrite Class for writing packages to the local file system.