composer = $composer; $this->io = $io; $this->manageOptions = new ManageOptions($composer); $this->manageAllowedPackages = new AllowedPackages($composer, $io, $this->manageOptions); } /** * Registers post-package events if the 'require' command was called. */ public function requireWasCalled() { // In order to differentiate between post-package events called after // 'composer require' vs. the same events called at other times, we will // only install our handler when a 'require' event is detected. $this->postPackageListeners[] = $this->manageAllowedPackages; } /** * Posts package command event. * * We want to detect packages 'require'd that have scaffold files, but are not * yet allowed in the top-level composer.json file. * * @param \Composer\Installer\PackageEvent $event * Composer package event sent on install/update/remove. */ public function onPostPackageEvent(PackageEvent $event) { foreach ($this->postPackageListeners as $listener) { $listener->event($event); } } /** * Creates scaffold operation objects for all items in the file mappings. * * @param \Composer\Package\PackageInterface $package * The package that relative paths will be relative from. * @param array $package_file_mappings * The package file mappings array keyed by destination path and the values * are operation metadata arrays. * * @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[] * A list of scaffolding operation objects */ protected function createScaffoldOperations(PackageInterface $package, array $package_file_mappings) { $scaffold_op_factory = new OperationFactory($this->composer); $scaffold_ops = []; foreach ($package_file_mappings as $dest_rel_path => $data) { $operation_data = new OperationData($dest_rel_path, $data); $scaffold_ops[$dest_rel_path] = $scaffold_op_factory->create($package, $operation_data); } return $scaffold_ops; } /** * Copies all scaffold files from source to destination. */ public function scaffold() { // Recursively get the list of allowed packages. Only allowed packages // may declare scaffold files. Note that the top-level composer.json file // is implicitly allowed. $allowed_packages = $this->manageAllowedPackages->getAllowedPackages(); if (empty($allowed_packages)) { $this->io->write("Nothing scaffolded because no packages are allowed in the top-level composer.json file."); return; } // Call any pre-scaffold scripts that may be defined. $dispatcher = new EventDispatcher($this->composer, $this->io); $dispatcher->dispatch(self::PRE_DRUPAL_SCAFFOLD_CMD); // Fetch the list of file mappings from each allowed package and normalize // them. $file_mappings = $this->getFileMappingsFromPackages($allowed_packages); $location_replacements = $this->manageOptions->getLocationReplacements(); $scaffold_options = $this->manageOptions->getOptions(); // Create a collection of scaffolded files to process. This determines which // take priority and which are combined. $scaffold_files = new ScaffoldFileCollection($file_mappings, $location_replacements); // Get the scaffold files whose contents on disk match what we are about to // write. We can remove these from consideration, as rewriting would be a // no-op. $unchanged = $scaffold_files->checkUnchanged(); $scaffold_files->filterFiles($unchanged); // Process the list of scaffolded files. $scaffold_results = $scaffold_files->processScaffoldFiles($this->io, $scaffold_options); // Generate an autoload file in the document root that includes the // autoload.php file in the vendor directory, wherever that is. Drupal // requires this in order to easily locate relocated vendor dirs. $web_root = $this->manageOptions->getOptions()->getLocation('web-root'); if (!GenerateAutoloadReferenceFile::autoloadFileCommitted($this->io, $this->rootPackageName(), $web_root)) { $scaffold_results[] = GenerateAutoloadReferenceFile::generateAutoload($this->io, $this->rootPackageName(), $web_root, $this->getVendorPath()); } // Add the managed scaffold files to .gitignore if applicable. $gitIgnoreManager = new ManageGitIgnore($this->io, getcwd()); $gitIgnoreManager->manageIgnored($scaffold_results, $scaffold_options); // Call post-scaffold scripts. $dispatcher->dispatch(self::POST_DRUPAL_SCAFFOLD_CMD); } /** * Gets the path to the 'vendor' directory. * * @return string * The file path of the vendor directory. */ protected function getVendorPath() { $vendor_dir = $this->composer->getConfig()->get('vendor-dir'); $filesystem = new Filesystem(); return $filesystem->normalizePath(realpath($vendor_dir)); } /** * Gets a consolidated list of file mappings from all allowed packages. * * @param \Composer\Package\Package[] $allowed_packages * A multidimensional array of file mappings, as returned by * self::getAllowedPackages(). * * @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[][] * An array of destination paths => scaffold operation objects. */ protected function getFileMappingsFromPackages(array $allowed_packages) { $file_mappings = []; foreach ($allowed_packages as $package_name => $package) { $file_mappings[$package_name] = $this->getPackageFileMappings($package); } return $file_mappings; } /** * Gets the array of file mappings provided by a given package. * * @param \Composer\Package\PackageInterface $package * The Composer package from which to get the file mappings. * * @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[] * An array of destination paths => scaffold operation objects. */ protected function getPackageFileMappings(PackageInterface $package) { $options = $this->manageOptions->packageOptions($package); if ($options->hasFileMapping()) { return $this->createScaffoldOperations($package, $options->fileMapping()); } // Warn the user if they allow a package that does not have any scaffold // files. We will ignore drupal/core, though, as it is implicitly allowed, // but might not have scaffold files (version 8.7.x and earlier). if (!$options->hasAllowedPackages() && ($package->getName() != 'drupal/core')) { $this->io->writeError("The allowed package {$package->getName()} does not provide a file mapping for Composer Scaffold."); } return []; } /** * Gets the root package name. * * @return string * The package name of the root project */ protected function rootPackageName() { $root_package = $this->composer->getPackage(); return $root_package->getName(); } }