Pull merge.
[yaffs-website] / vendor / drupal-composer / drupal-scaffold / src / Handler.php
1 <?php
2
3 namespace DrupalComposer\DrupalScaffold;
4
5 use Composer\Script\Event;
6 use Composer\Installer\PackageEvent;
7 use Composer\Plugin\CommandEvent;
8 use Composer\Composer;
9 use Composer\DependencyResolver\Operation\InstallOperation;
10 use Composer\DependencyResolver\Operation\UpdateOperation;
11 use Composer\EventDispatcher\EventDispatcher;
12 use Composer\IO\IOInterface;
13 use Composer\Package\PackageInterface;
14 use Composer\Semver\Semver;
15 use Composer\Util\Filesystem;
16 use Composer\Util\RemoteFilesystem;
17 use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
18
19 /**
20  * Core class of the plugin, contains all logic which files should be fetched.
21  */
22 class Handler {
23
24   const PRE_DRUPAL_SCAFFOLD_CMD = 'pre-drupal-scaffold-cmd';
25   const POST_DRUPAL_SCAFFOLD_CMD = 'post-drupal-scaffold-cmd';
26
27   /**
28    * @var \Composer\Composer
29    */
30   protected $composer;
31
32   /**
33    * @var \Composer\IO\IOInterface
34    */
35   protected $io;
36
37   /**
38    * @var bool
39    *
40    * A boolean indicating if progress should be displayed.
41    */
42   protected $progress;
43
44   /**
45    * @var \Composer\Package\PackageInterface
46    */
47   protected $drupalCorePackage;
48
49   /**
50    * Handler constructor.
51    *
52    * @param \Composer\Composer $composer
53    * @param \Composer\IO\IOInterface $io
54    */
55   public function __construct(Composer $composer, IOInterface $io) {
56     $this->composer = $composer;
57     $this->io = $io;
58     $this->progress = TRUE;
59
60     // Pre-load all of our sources so that we do not run up
61     // against problems in `composer update` operations.
62     $this->manualLoad();
63   }
64
65   protected function manualLoad() {
66     $src_dir = __DIR__;
67
68     $classes = [
69       'CommandProvider',
70       'DrupalScaffoldCommand',
71       'FileFetcher',
72       'PrestissimoFileFetcher',
73     ];
74
75     foreach ($classes as $src) {
76       if (!class_exists('\\DrupalComposer\\DrupalScaffold\\' . $src)) {
77         include "{$src_dir}/{$src}.php";
78       }
79     }
80   }
81
82   /**
83    * @param $operation
84    * @return mixed
85    */
86   protected function getCorePackage($operation) {
87     if ($operation instanceof InstallOperation) {
88       $package = $operation->getPackage();
89     }
90     elseif ($operation instanceof UpdateOperation) {
91       $package = $operation->getTargetPackage();
92     }
93     if (isset($package) && $package instanceof PackageInterface && $package->getName() == 'drupal/core') {
94       return $package;
95     }
96     return NULL;
97   }
98
99   /**
100    * Get the command options.
101    *
102    * @param \Composer\Plugin\CommandEvent $event
103    */
104   public function onCmdBeginsEvent(CommandEvent $event) {
105     if ($event->getInput()->hasOption('no-progress')) {
106       $this->progress = !($event->getInput()->getOption('no-progress'));
107     }
108     else {
109       $this->progress = TRUE;
110     }
111   }
112
113   /**
114    * Marks scaffolding to be processed after an install or update command.
115    *
116    * @param \Composer\Installer\PackageEvent $event
117    */
118   public function onPostPackageEvent(PackageEvent $event) {
119     $package = $this->getCorePackage($event->getOperation());
120     if ($package) {
121       // By explicitly setting the core package, the onPostCmdEvent() will
122       // process the scaffolding automatically.
123       $this->drupalCorePackage = $package;
124     }
125   }
126
127   /**
128    * Post install command event to execute the scaffolding.
129    *
130    * @param \Composer\Script\Event $event
131    */
132   public function onPostCmdEvent(Event $event) {
133     // Only install the scaffolding if drupal/core was installed,
134     // AND there are no scaffolding files present.
135     if (isset($this->drupalCorePackage)) {
136       $this->downloadScaffold();
137       // Generate the autoload.php file after generating the scaffold files.
138       $this->generateAutoload();
139     }
140   }
141
142   /**
143    * Downloads drupal scaffold files for the current process.
144    */
145   public function downloadScaffold() {
146     $drupalCorePackage = $this->getDrupalCorePackage();
147     $webroot = realpath($this->getWebRoot());
148
149     // Collect options, excludes and settings files.
150     $options = $this->getOptions();
151     $files = array_diff($this->getIncludes(), $this->getExcludes());
152
153     // Call any pre-scaffold scripts that may be defined.
154     $dispatcher = new EventDispatcher($this->composer, $this->io);
155     $dispatcher->dispatch(self::PRE_DRUPAL_SCAFFOLD_CMD);
156
157     $version = $this->getDrupalCoreVersion($drupalCorePackage);
158
159     $remoteFs = new RemoteFilesystem($this->io);
160
161     $fetcher = new PrestissimoFileFetcher($remoteFs, $options['source'], $this->io, $this->progress, $this->composer->getConfig());
162     $fetcher->setFilenames(array_combine($files, $files));
163     $fetcher->fetch($version, $webroot, TRUE);
164
165     $fetcher->setFilenames($this->getInitial());
166     $fetcher->fetch($version, $webroot, FALSE);
167
168     // Call post-scaffold scripts.
169     $dispatcher->dispatch(self::POST_DRUPAL_SCAFFOLD_CMD);
170   }
171
172   /**
173    * Generate the autoload file at the project root.  Include the
174    * autoload file that Composer generated.
175    */
176   public function generateAutoload() {
177     $vendorPath = $this->getVendorPath();
178     $webroot = $this->getWebRoot();
179
180     // Calculate the relative path from the webroot (location of the
181     // project autoload.php) to the vendor directory.
182     $fs = new SymfonyFilesystem();
183     $relativeVendorPath = $fs->makePathRelative($vendorPath, realpath($webroot));
184
185     $fs->dumpFile($webroot . "/autoload.php", $this->autoLoadContents($relativeVendorPath));
186   }
187
188   /**
189    * Build the contents of the autoload file.
190    *
191    * @return string
192    */
193   protected function autoLoadContents($relativeVendorPath) {
194     $relativeVendorPath = rtrim($relativeVendorPath, '/');
195
196     $autoloadContents = <<<EOF
197 <?php
198
199 /**
200  * @file
201  * Includes the autoloader created by Composer.
202  *
203  * This file was generated by drupal-composer/drupal-scaffold.
204  * https://github.com/drupal-composer/drupal-scaffold
205  *
206  * @see composer.json
207  * @see index.php
208  * @see core/install.php
209  * @see core/rebuild.php
210  * @see core/modules/statistics/statistics.php
211  */
212
213 return require __DIR__ . '/$relativeVendorPath/autoload.php';
214
215 EOF;
216     return $autoloadContents;
217   }
218
219   /**
220    * Get the path to the 'vendor' directory.
221    *
222    * @return string
223    */
224   public function getVendorPath() {
225     $config = $this->composer->getConfig();
226     $filesystem = new Filesystem();
227     $filesystem->ensureDirectoryExists($config->get('vendor-dir'));
228     $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
229
230     return $vendorPath;
231   }
232
233   /**
234    * Look up the Drupal core package object, or return it from where we cached
235    * it in the $drupalCorePackage field.
236    *
237    * @return \Composer\Package\PackageInterface
238    */
239   public function getDrupalCorePackage() {
240     if (!isset($this->drupalCorePackage)) {
241       $this->drupalCorePackage = $this->getPackage('drupal/core');
242     }
243     return $this->drupalCorePackage;
244   }
245
246   /**
247    * Returns the Drupal core version for the given package.
248    *
249    * @param \Composer\Package\PackageInterface $drupalCorePackage
250    *
251    * @return string
252    */
253   protected function getDrupalCoreVersion(PackageInterface $drupalCorePackage) {
254     $version = $drupalCorePackage->getPrettyVersion();
255     if ($drupalCorePackage->getStability() == 'dev' && substr($version, -4) == '-dev') {
256       $version = substr($version, 0, -4);
257       return $version;
258     }
259     return $version;
260   }
261
262   /**
263    * Retrieve the path to the web root.
264    *
265    * @return string
266    */
267   public function getWebRoot() {
268     $drupalCorePackage = $this->getDrupalCorePackage();
269     $installationManager = $this->composer->getInstallationManager();
270     $corePath = $installationManager->getInstallPath($drupalCorePackage);
271     // Webroot is the parent path of the drupal core installation path.
272     $webroot = dirname($corePath);
273
274     return $webroot;
275   }
276
277   /**
278    * Retrieve a package from the current composer process.
279    *
280    * @param string $name
281    *   Name of the package to get from the current composer installation.
282    *
283    * @return \Composer\Package\PackageInterface
284    */
285   protected function getPackage($name) {
286     return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*');
287   }
288
289   /**
290    * Retrieve excludes from optional "extra" configuration.
291    *
292    * @return array
293    */
294   protected function getExcludes() {
295     return $this->getNamedOptionList('excludes', 'getExcludesDefault');
296   }
297
298   /**
299    * Retrieve list of additional settings files from optional "extra" configuration.
300    *
301    * @return array
302    */
303   protected function getIncludes() {
304     return $this->getNamedOptionList('includes', 'getIncludesDefault');
305   }
306
307   /**
308    * Retrieve list of initial files from optional "extra" configuration.
309    *
310    * @return array
311    */
312   protected function getInitial() {
313     return $this->getNamedOptionList('initial', 'getInitialDefault');
314   }
315
316   /**
317    * Retrieve a named list of options from optional "extra" configuration.
318    * Respects 'omit-defaults', and either includes or does not include the
319    * default values, as requested.
320    *
321    * @return array
322    */
323   protected function getNamedOptionList($optionName, $defaultFn) {
324     $options = $this->getOptions($this->composer);
325     $result = array();
326     if (empty($options['omit-defaults'])) {
327       $result = $this->$defaultFn();
328     }
329     $result = array_merge($result, (array) $options[$optionName]);
330
331     return $result;
332   }
333
334   /**
335    * Retrieve excludes from optional "extra" configuration.
336    *
337    * @return array
338    */
339   protected function getOptions() {
340     $extra = $this->composer->getPackage()->getExtra() + ['drupal-scaffold' => []];
341     $options = $extra['drupal-scaffold'] + [
342       'omit-defaults' => FALSE,
343       'excludes' => [],
344       'includes' => [],
345       'initial' => [],
346       'source' => 'https://cgit.drupalcode.org/drupal/plain/{path}?h={version}',
347       // Github: https://raw.githubusercontent.com/drupal/drupal/{version}/{path}
348     ];
349     return $options;
350   }
351
352   /**
353    * Holds default excludes.
354    */
355   protected function getExcludesDefault() {
356     return [];
357   }
358
359   /**
360    * Holds default settings files list.
361    */
362   protected function getIncludesDefault() {
363     $version = $this->getDrupalCoreVersion($this->getDrupalCorePackage());
364     list($major, $minor) = explode('.', $version, 3);
365     $version = "$major.$minor";
366
367     /**
368      * Files from 8.3.x
369      *
370      * @see https://cgit.drupalcode.org/drupal/tree/?h=8.3.x
371      */
372     $common = [
373       '.csslintrc',
374       '.editorconfig',
375       '.eslintignore',
376       '.gitattributes',
377       '.htaccess',
378       'index.php',
379       'robots.txt',
380       'sites/default/default.settings.php',
381       'sites/default/default.services.yml',
382       'sites/development.services.yml',
383       'sites/example.settings.local.php',
384       'sites/example.sites.php',
385       'update.php',
386       'web.config',
387     ];
388
389     // Version specific variations.
390     if (Semver::satisfies($version, '<8.3')) {
391       $common[] = '.eslintrc';
392     }
393     if (Semver::satisfies($version, '>=8.3')) {
394       $common[] = '.eslintrc.json';
395     }
396     if (Semver::satisfies($version, '>=8.5')) {
397       $common[] = '.ht.router.php';
398     }
399
400     sort($common);
401     return $common;
402   }
403
404   /**
405    * Holds default initial files.
406    */
407   protected function getInitialDefault() {
408     return [];
409   }
410
411 }