FeaturesGenerationWrite.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <?php
  2. namespace Drupal\features\Plugin\FeaturesGeneration;
  3. use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
  4. use Drupal\features\FeaturesGenerationMethodBase;
  5. use Drupal\features\FeaturesBundleInterface;
  6. use Drupal\features\Package;
  7. use Symfony\Component\DependencyInjection\ContainerInterface;
  8. /**
  9. * Class for writing packages to the local file system.
  10. *
  11. * @Plugin(
  12. * id = \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite::METHOD_ID,
  13. * weight = 2,
  14. * name = @Translation("Write"),
  15. * description = @Translation("Write packages and optional profile to the file system."),
  16. * )
  17. */
  18. class FeaturesGenerationWrite extends FeaturesGenerationMethodBase implements ContainerFactoryPluginInterface {
  19. /**
  20. * The package generation method id.
  21. */
  22. const METHOD_ID = 'write';
  23. /**
  24. * The app root.
  25. *
  26. * @var string
  27. */
  28. protected $root;
  29. /**
  30. * Creates a new FeaturesGenerationWrite instance.
  31. *
  32. * @param string $root
  33. * The app root.
  34. */
  35. public function __construct($root) {
  36. $this->root = $root;
  37. }
  38. /**
  39. * {@inheritdoc}
  40. */
  41. public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
  42. return new static(
  43. $container->get('app.root')
  44. );
  45. }
  46. /**
  47. * Reads and merges in existing files for a given package or profile.
  48. *
  49. * @param \Drupal\features\Package &$package
  50. * The package.
  51. * @param array $existing_packages
  52. * An array of existing packages.
  53. * @param \Drupal\features\FeaturesBundleInterface $bundle
  54. * The bundle the package belongs to.
  55. */
  56. protected function preparePackage(Package $package, array $existing_packages, FeaturesBundleInterface $bundle = NULL) {
  57. // If this package is already present, prepare files.
  58. if (isset($existing_packages[$package->getMachineName()])) {
  59. $existing_directory = $existing_packages[$package->getMachineName()];
  60. $package->setDirectory($existing_directory);
  61. // Merge in the info file.
  62. $info_file_uri = $this->root . '/' . $existing_directory . '/' . $package->getMachineName() . '.info.yml';
  63. if (file_exists($info_file_uri)) {
  64. $files = $package->getFiles();
  65. $files['info']['string'] = $this->mergeInfoFile($package->getFiles()['info']['string'], $info_file_uri);
  66. $package->setFiles($files);
  67. }
  68. // Remove the config directories, as they will be replaced.
  69. foreach (array_keys($this->featuresManager->getExtensionStorages()->getExtensionStorages()) as $directory) {
  70. $config_directory = $this->root . '/' . $existing_directory . '/' . $directory;
  71. if (is_dir($config_directory)) {
  72. file_unmanaged_delete_recursive($config_directory);
  73. }
  74. }
  75. }
  76. }
  77. /**
  78. * {@inheritdoc}
  79. */
  80. public function generate(array $packages = array(), FeaturesBundleInterface $bundle = NULL) {
  81. // If no packages were specified, get all packages.
  82. if (empty($packages)) {
  83. $packages = $this->featuresManager->getPackages();
  84. }
  85. $return = [];
  86. // Add package files.
  87. // We need to update the system.module.files state because it's cached.
  88. // Cannot just call system_rebuild_module_data() because $listing->scan() has
  89. // it's own internal static cache that we cannot clear at this point.
  90. $files = \Drupal::state()->get('system.module.files');
  91. foreach ($packages as $package) {
  92. $this->generatePackage($return, $package);
  93. if (!isset($files[$package->getMachineName()]) && isset($package->getFiles()['info'])) {
  94. $files[$package->getMachineName()] = $package->getDirectory() . '/' . $package->getFiles()['info']['filename'];
  95. }
  96. }
  97. // Rebuild system module cache
  98. \Drupal::state()->set('system.module.files', $files);
  99. return $return;
  100. }
  101. /**
  102. * Writes a package or profile's files to the file system.
  103. *
  104. * @param array &$return
  105. * The return value, passed by reference.
  106. * @param \Drupal\features\Package $package
  107. * The package or profile.
  108. */
  109. protected function generatePackage(array &$return, Package $package) {
  110. if (!$package->getFiles()) {
  111. $this->failure($return, $package, NULL, $this->t('No configuration was selected to be exported.'));
  112. return;
  113. }
  114. $success = TRUE;
  115. foreach ($package->getFiles() as $file) {
  116. try {
  117. $this->generateFile($package->getDirectory(), $file);
  118. }
  119. catch (\Exception $exception) {
  120. $this->failure($return, $package, $exception);
  121. $success = FALSE;
  122. break;
  123. }
  124. }
  125. if ($success) {
  126. $this->success($return, $package);
  127. }
  128. }
  129. /**
  130. * Registers a successful package or profile write operation.
  131. *
  132. * @param array &$return
  133. * The return value, passed by reference.
  134. * @param \Drupal\features\Package $package
  135. * The package or profile.
  136. */
  137. protected function success(array &$return, Package $package) {
  138. $type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
  139. $return[] = [
  140. 'success' => TRUE,
  141. 'display' => TRUE,
  142. 'message' => '@type @package written to @directory.',
  143. 'variables' => [
  144. '@type' => $type,
  145. '@package' => $package->getName(),
  146. '@directory' => $package->getDirectory(),
  147. ],
  148. ];
  149. }
  150. /**
  151. * Registers a failed package or profile write operation.
  152. *
  153. * @param array &$return
  154. * The return value, passed by reference.
  155. * @param \Drupal\features\Package $package
  156. * The package or profile.
  157. * @param \Exception $exception
  158. * The exception object.
  159. * @param string $message
  160. * Error message when there isn't an Exception object.
  161. */
  162. protected function failure(array &$return, Package $package, \Exception $exception = NULL, $message = '') {
  163. $type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
  164. $return[] = [
  165. 'success' => FALSE,
  166. 'display' => TRUE,
  167. 'message' => '@type @package not written to @directory. Error: @error.',
  168. 'variables' => [
  169. '@type' => $type,
  170. '@package' => $package->getName(),
  171. '@directory' => $package->getDirectory(),
  172. '@error' => isset($exception) ? $exception->getMessage() : $message,
  173. ],
  174. ];
  175. }
  176. /**
  177. * Writes a file to the file system, creating its directory as needed.
  178. *
  179. * @param string $directory
  180. * The extension's directory.
  181. * @param array $file
  182. * Array with the following keys:
  183. * - 'filename': the name of the file.
  184. * - 'subdirectory': any subdirectory of the file within the extension
  185. * directory.
  186. * - 'string': the contents of the file.
  187. *
  188. * @throws Exception
  189. */
  190. protected function generateFile($directory, array $file) {
  191. if (!empty($file['subdirectory'])) {
  192. $directory .= '/' . $file['subdirectory'];
  193. }
  194. $directory = $this->root . '/' . $directory;
  195. if (!is_dir($directory)) {
  196. if (drupal_mkdir($directory, NULL, TRUE) === FALSE) {
  197. throw new \Exception($this->t('Failed to create directory @directory.', ['@directory' => $directory]));
  198. }
  199. }
  200. if (file_put_contents($directory . '/' . $file['filename'], $file['string']) === FALSE) {
  201. throw new \Exception($this->t('Failed to write file @filename.', ['@filename' => $file['filename']]));
  202. }
  203. }
  204. }