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\Semver\Semver;
17 use Composer\Util\Filesystem;
18 use Composer\Util\RemoteFilesystem;
19 use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
23 const PRE_DRUPAL_SCAFFOLD_CMD = 'pre-drupal-scaffold-cmd';
24 const POST_DRUPAL_SCAFFOLD_CMD = 'post-drupal-scaffold-cmd';
27 * @var \Composer\Composer
32 * @var \Composer\IO\IOInterface
37 * @var \Composer\Package\PackageInterface
39 protected $drupalCorePackage;
42 * Handler constructor.
44 * @param Composer $composer
45 * @param IOInterface $io
47 public function __construct(Composer $composer, IOInterface $io) {
48 $this->composer = $composer;
56 protected function getCorePackage($operation) {
57 if ($operation instanceof InstallOperation) {
58 $package = $operation->getPackage();
60 elseif ($operation instanceof UpdateOperation) {
61 $package = $operation->getTargetPackage();
63 if (isset($package) && $package instanceof PackageInterface && $package->getName() == 'drupal/core') {
70 * Marks scaffolding to be processed after an install or update command.
72 * @param \Composer\Installer\PackageEvent $event
74 public function onPostPackageEvent(\Composer\Installer\PackageEvent $event){
75 $package = $this->getCorePackage($event->getOperation());
77 // By explicitly setting the core package, the onPostCmdEvent() will
78 // process the scaffolding automatically.
79 $this->drupalCorePackage = $package;
84 * Post install command event to execute the scaffolding.
86 * @param \Composer\Script\Event $event
88 public function onPostCmdEvent(\Composer\Script\Event $event) {
89 // Only install the scaffolding if drupal/core was installed,
90 // AND there are no scaffolding files present.
91 if (isset($this->drupalCorePackage)) {
92 $this->downloadScaffold();
93 // Generate the autoload.php file after generating the scaffold files.
94 $this->generateAutoload();
99 * Downloads drupal scaffold files for the current process.
101 public function downloadScaffold() {
102 $drupalCorePackage = $this->getDrupalCorePackage();
103 $webroot = realpath($this->getWebRoot());
105 // Collect options, excludes and settings files.
106 $options = $this->getOptions();
107 $files = array_diff($this->getIncludes(), $this->getExcludes());
109 // Call any pre-scaffold scripts that may be defined.
110 $dispatcher = new EventDispatcher($this->composer, $this->io);
111 $dispatcher->dispatch(self::PRE_DRUPAL_SCAFFOLD_CMD);
113 $version = $this->getDrupalCoreVersion($drupalCorePackage);
115 $remoteFs = new RemoteFilesystem($this->io);
117 $fetcher = new PrestissimoFileFetcher($remoteFs, $options['source'], $files, $this->io, $this->composer->getConfig());
118 $fetcher->fetch($version, $webroot);
120 $initialFileFetcher = new InitialFileFetcher($remoteFs, $options['source'], $this->getInitial());
121 $initialFileFetcher->fetch($version, $webroot);
123 // Call post-scaffold scripts.
124 $dispatcher->dispatch(self::POST_DRUPAL_SCAFFOLD_CMD);
128 * Generate the autoload file at the project root. Include the
129 * autoload file that Composer generated.
131 public function generateAutoload() {
132 $vendorPath = $this->getVendorPath();
133 $webroot = $this->getWebRoot();
135 // Calculate the relative path from the webroot (location of the
136 // project autoload.php) to the vendor directory.
137 $fs = new SymfonyFilesystem();
138 $relativeVendorPath = $fs->makePathRelative($vendorPath, realpath($webroot));
140 $fs->dumpFile($webroot . "/autoload.php", $this->autoLoadContents($relativeVendorPath));
144 * Build the contents of the autoload file.
148 protected function autoLoadContents($relativeVendorPath) {
149 $relativeVendorPath = rtrim($relativeVendorPath, '/');
151 $autoloadContents = <<<EOF
156 * Includes the autoloader created by Composer.
158 * This file was generated by drupal-composer/drupal-scaffold.
159 * https://github.com/drupal-composer/drupal-scaffold
163 * @see core/install.php
164 * @see core/rebuild.php
165 * @see core/modules/statistics/statistics.php
168 return require __DIR__ . '/$relativeVendorPath/autoload.php';
171 return $autoloadContents;
175 * Get the path to the 'vendor' directory.
179 public function getVendorPath() {
180 $config = $this->composer->getConfig();
181 $filesystem = new Filesystem();
182 $filesystem->ensureDirectoryExists($config->get('vendor-dir'));
183 $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
189 * Look up the Drupal core package object, or return it from where we cached
190 * it in the $drupalCorePackage field.
192 * @return PackageInterface
194 public function getDrupalCorePackage() {
195 if (!isset($this->drupalCorePackage)) {
196 $this->drupalCorePackage = $this->getPackage('drupal/core');
198 return $this->drupalCorePackage;
202 * Returns the Drupal core version for the given package.
204 * @param \Composer\Package\PackageInterface $drupalCorePackage
208 protected function getDrupalCoreVersion(PackageInterface $drupalCorePackage) {
209 $version = $drupalCorePackage->getPrettyVersion();
210 if ($drupalCorePackage->getStability() == 'dev' && substr($version, -4) == '-dev') {
211 $version = substr($version, 0, -4);
218 * Retrieve the path to the web root.
222 public function getWebRoot() {
223 $drupalCorePackage = $this->getDrupalCorePackage();
224 $installationManager = $this->composer->getInstallationManager();
225 $corePath = $installationManager->getInstallPath($drupalCorePackage);
226 // Webroot is the parent path of the drupal core installation path.
227 $webroot = dirname($corePath);
233 * Retrieve a package from the current composer process.
235 * @param string $name
236 * Name of the package to get from the current composer installation.
238 * @return PackageInterface
240 protected function getPackage($name) {
241 return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*');
245 * Retrieve excludes from optional "extra" configuration.
249 protected function getExcludes() {
250 return $this->getNamedOptionList('excludes', 'getExcludesDefault');
254 * Retrieve list of additional settings files from optional "extra" configuration.
258 protected function getIncludes() {
259 return $this->getNamedOptionList('includes', 'getIncludesDefault');
263 * Retrieve list of initial files from optional "extra" configuration.
267 protected function getInitial() {
268 return $this->getNamedOptionList('initial', 'getInitialDefault');
272 * Retrieve a named list of options from optional "extra" configuration.
273 * Respects 'omit-defaults', and either includes or does not include the
274 * default values, as requested.
278 protected function getNamedOptionList($optionName, $defaultFn) {
279 $options = $this->getOptions($this->composer);
281 if (empty($options['omit-defaults'])) {
282 $result = $this->$defaultFn();
284 $result = array_merge($result, (array) $options[$optionName]);
290 * Retrieve excludes from optional "extra" configuration.
294 protected function getOptions() {
295 $extra = $this->composer->getPackage()->getExtra() + ['drupal-scaffold' => []];
296 $options = $extra['drupal-scaffold'] + [
297 'omit-defaults' => FALSE,
301 'source' => 'http://cgit.drupalcode.org/drupal/plain/{path}?h={version}',
302 // Github: https://raw.githubusercontent.com/drupal/drupal/{version}/{path}
308 * Holds default excludes.
310 protected function getExcludesDefault() {
315 * Holds default settings files list.
317 protected function getIncludesDefault() {
318 $version = $this->getDrupalCoreVersion($this->getDrupalCorePackage());
319 list($major, $minor) = explode('.', $version, 3);
320 $version = "$major.$minor";
325 * @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.
345 if (Semver::satisfies($version, '<8.3')) {
346 $common[] = '.eslintrc';
348 if (Semver::satisfies($version, '>=8.3')) {
349 $common[] = '.eslintrc.json';
351 if (Semver::satisfies($version, '>=8.5')) {
352 $common[] = '.ht.router.php';
360 * Holds default initial files.
362 protected function getInitialDefault() {