features-8.x-3.11/src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php
src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php
<?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->getParameter('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|null $bundle
* (optional) The bundle the package belongs to. Defaults to NULL.
*/
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|null $exception
* (optional) The exception object. Defaults to NULL.
* @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']]));
}
}
}
