5 * Contains \DrupalComposer\DrupalScaffold\Handler.
8 namespace DrupalComposer\DrupalScaffold;
10 use Composer\Composer;
11 use Composer\DependencyResolver\Operation\InstallOperation;
12 use Composer\DependencyResolver\Operation\UpdateOperation;
13 use Composer\EventDispatcher\EventDispatcher;
14 use Composer\IO\IOInterface;
15 use Composer\Package\PackageInterface;
16 use Composer\Util\Filesystem;
17 use Composer\Util\RemoteFilesystem;
18 use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
22 const PRE_DRUPAL_SCAFFOLD_CMD = 'pre-drupal-scaffold-cmd';
23 const POST_DRUPAL_SCAFFOLD_CMD = 'post-drupal-scaffold-cmd';
26 * @var \Composer\Composer
31 * @var \Composer\IO\IOInterface
36 * @var \Composer\Package\PackageInterface
38 protected $drupalCorePackage;
41 * Handler constructor.
43 * @param Composer $composer
44 * @param IOInterface $io
46 public function __construct(Composer $composer, IOInterface $io) {
47 $this->composer = $composer;
55 protected function getCorePackage($operation) {
56 if ($operation instanceof InstallOperation) {
57 $package = $operation->getPackage();
59 elseif ($operation instanceof UpdateOperation) {
60 $package = $operation->getTargetPackage();
62 if (isset($package) && $package instanceof PackageInterface && $package->getName() == 'drupal/core') {
69 * Marks scaffolding to be processed after an install or update command.
71 * @param \Composer\Installer\PackageEvent $event
73 public function onPostPackageEvent(\Composer\Installer\PackageEvent $event){
74 $package = $this->getCorePackage($event->getOperation());
76 // By explicitly setting the core package, the onPostCmdEvent() will
77 // process the scaffolding automatically.
78 $this->drupalCorePackage = $package;
83 * Post install command event to execute the scaffolding.
85 * @param \Composer\Script\Event $event
87 public function onPostCmdEvent(\Composer\Script\Event $event) {
88 // Only install the scaffolding if drupal/core was installed,
89 // AND there are no scaffolding files present.
90 if (isset($this->drupalCorePackage)) {
91 $this->downloadScaffold();
92 // Generate the autoload.php file after generating the scaffold files.
93 $this->generateAutoload();
98 * Downloads drupal scaffold files for the current process.
100 public function downloadScaffold() {
101 $drupalCorePackage = $this->getDrupalCorePackage();
102 $webroot = realpath($this->getWebRoot());
104 // Collect options, excludes and settings files.
105 $options = $this->getOptions();
106 $files = array_diff($this->getIncludes(), $this->getExcludes());
108 // Call any pre-scaffold scripts that may be defined.
109 $dispatcher = new EventDispatcher($this->composer, $this->io);
110 $dispatcher->dispatch(self::PRE_DRUPAL_SCAFFOLD_CMD);
112 $version = $this->getDrupalCoreVersion($drupalCorePackage);
114 $remoteFs = new RemoteFilesystem($this->io);
116 $fetcher = new PrestissimoFileFetcher($remoteFs, $options['source'], $files, $this->io, $this->composer->getConfig());
117 $fetcher->fetch($version, $webroot);
119 $initialFileFetcher = new InitialFileFetcher($remoteFs, $options['source'], $this->getInitial());
120 $initialFileFetcher->fetch($version, $webroot);
122 // Call post-scaffold scripts.
123 $dispatcher->dispatch(self::POST_DRUPAL_SCAFFOLD_CMD);
127 * Generate the autoload file at the project root. Include the
128 * autoload file that Composer generated.
130 public function generateAutoload() {
131 $vendorPath = $this->getVendorPath();
132 $webroot = $this->getWebRoot();
134 // Calculate the relative path from the webroot (location of the
135 // project autoload.php) to the vendor directory.
136 $fs = new SymfonyFilesystem();
137 $relativeVendorPath = $fs->makePathRelative($vendorPath, realpath($webroot));
139 $fs->dumpFile($webroot . "/autoload.php", $this->autoLoadContents($relativeVendorPath));
143 * Build the contents of the autoload file.
147 protected function autoLoadContents($relativeVendorPath) {
148 $relativeVendorPath = rtrim($relativeVendorPath, '/');
150 $autoloadContents = <<<EOF
155 * Includes the autoloader created by Composer.
157 * This file was generated by drupal-composer/drupal-scaffold.
158 * https://github.com/drupal-composer/drupal-scaffold
162 * @see core/install.php
163 * @see core/rebuild.php
164 * @see core/modules/statistics/statistics.php
167 return require __DIR__ . '/$relativeVendorPath/autoload.php';
170 return $autoloadContents;
174 * Get the path to the 'vendor' directory.
178 public function getVendorPath() {
179 $config = $this->composer->getConfig();
180 $filesystem = new Filesystem();
181 $filesystem->ensureDirectoryExists($config->get('vendor-dir'));
182 $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
188 * Look up the Drupal core package object, or return it from where we cached
189 * it in the $drupalCorePackage field.
191 * @return PackageInterface
193 public function getDrupalCorePackage() {
194 if (!isset($this->drupalCorePackage)) {
195 $this->drupalCorePackage = $this->getPackage('drupal/core');
197 return $this->drupalCorePackage;
201 * Returns the Drupal core version for the given package.
203 * @param \Composer\Package\PackageInterface $drupalCorePackage
207 protected function getDrupalCoreVersion(PackageInterface $drupalCorePackage) {
208 $version = $drupalCorePackage->getPrettyVersion();
209 if ($drupalCorePackage->getStability() == 'dev' && substr($version, -4) == '-dev') {
210 $version = substr($version, 0, -4);
217 * Retrieve the path to the web root.
221 public function getWebRoot() {
222 $drupalCorePackage = $this->getDrupalCorePackage();
223 $installationManager = $this->composer->getInstallationManager();
224 $corePath = $installationManager->getInstallPath($drupalCorePackage);
225 // Webroot is the parent path of the drupal core installation path.
226 $webroot = dirname($corePath);
232 * Retrieve a package from the current composer process.
234 * @param string $name
235 * Name of the package to get from the current composer installation.
237 * @return PackageInterface
239 protected function getPackage($name) {
240 return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*');
244 * Retrieve excludes from optional "extra" configuration.
248 protected function getExcludes() {
249 return $this->getNamedOptionList('excludes', 'getExcludesDefault');
253 * Retrieve list of additional settings files from optional "extra" configuration.
257 protected function getIncludes() {
258 return $this->getNamedOptionList('includes', 'getIncludesDefault');
262 * Retrieve list of initial files from optional "extra" configuration.
266 protected function getInitial() {
267 return $this->getNamedOptionList('initial', 'getInitialDefault');
271 * Retrieve a named list of options from optional "extra" configuration.
272 * Respects 'omit-defaults', and either includes or does not include the
273 * default values, as requested.
277 protected function getNamedOptionList($optionName, $defaultFn) {
278 $options = $this->getOptions($this->composer);
280 if (empty($options['omit-defaults'])) {
281 $result = $this->$defaultFn();
283 $result = array_merge($result, (array) $options[$optionName]);
289 * Retrieve excludes from optional "extra" configuration.
293 protected function getOptions() {
294 $extra = $this->composer->getPackage()->getExtra() + ['drupal-scaffold' => []];
295 $options = $extra['drupal-scaffold'] + [
296 'omit-defaults' => FALSE,
300 'source' => 'http://cgit.drupalcode.org/drupal/plain/{path}?h={version}',
301 // Github: https://raw.githubusercontent.com/drupal/drupal/{version}/{path}
307 * Holds default excludes.
309 protected function getExcludesDefault() {
314 * Holds default settings files list.
316 protected function getIncludesDefault() {
317 $version = $this->getDrupalCoreVersion($this->getDrupalCorePackage());
318 list($major, $minor) = explode('.', $version, 3);
319 $version = "$major.$minor";
324 * @see http://cgit.drupalcode.org/drupal/tree/?h=8.3.x
335 'sites/default/default.settings.php',
336 'sites/default/default.services.yml',
337 'sites/development.services.yml',
338 'sites/example.settings.local.php',
339 'sites/example.sites.php',
344 // Version specific variations.
349 $common[] = '.eslintrc';
350 $common = array_diff($common, ['.eslintrc.json']);
359 * Holds default initial files.
361 protected function getInitialDefault() {