Commit a8127491 authored by Sergey Shadrin's avatar Sergey Shadrin

[#124455] Update module patch, git removed composer packages to install drupal

parent d5d3a9a4
......@@ -5,8 +5,16 @@ sites/*/files/*
sites/*/private
sites/*/settings.php
# Exclude update module
!core/modules/update/update.info.yml
core/modules/update
# Exclude IDE specific directories.
.idea
.vscode
# Exclude composer plugin vendor packages.
vendor/drupal/core-composer-scaffold
vendor/drupal/core-project-message
vendor/oomphinc/composer-installers-extender
vendor/cweagans/composer-patches
vendor/wikimedia/composer-merge-plugin
name: 'Update Manager'
type: module
description: 'Не поддерживается в ДАР CMS'
version: VERSION
package: Core
......@@ -41,3 +41,15 @@ index 2de1283198..208fc8125d 100644
return $output;
case 'system.modules_uninstall':
diff --git a/core/modules/update/update.info.yml b/core/modules/update/update.info.yml
index 684c544b39..8321d08e93 100644
--- a/core/modules/update/update.info.yml
+++ b/core/modules/update/update.info.yml
@@ -1,6 +1,5 @@
name: 'Update Manager'
type: module
-description: 'Checks for updates and allows users to manage them through a user interface.'
+description: 'Не поддерживается в ДАР CMS'
version: VERSION
package: Core
-configure: update.settings
# Russian translation of CKEditor 5 Plugin Pack (1.0.1)
# Copyright (c) 2024 by the Russian translation team
#
msgid ""
msgstr ""
"Project-Id-Version: CKEditor 5 Plugin Pack (1.0.1)\n"
"POT-Creation-Date: 2024-04-30 08:46+0000\n"
"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=((((n%10)==1)&&((n%100)!=11))?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));\n"
msgid "Status"
msgstr "Статус"
msgid "Type"
msgstr "Тип"
msgid "Remove"
msgstr "Удалить"
msgid "Description"
msgstr "Описание"
msgid "Disabled"
msgstr "Отключено"
msgid "Enabled"
msgstr "Включено"
msgid "Label"
msgstr "Метка"
msgid "Font Size"
msgstr "Размер шрифта"
msgid "Background Color"
msgstr "Цвет фона"
msgid "Options"
msgstr "Настройки"
msgid "Colors"
msgstr "Цвета"
msgid "Color"
msgstr "Цвет"
msgid "Marker"
msgstr "Маркер"
msgid "Font Family"
msgstr "Семейство шрифтов"
msgid "Formats"
msgstr "Форматы"
msgid "Machine name"
msgstr "Машинное имя"
msgid "Icon preview"
msgstr "Пред просмотр иконки"
msgid "Font Color"
msgstr "Цвет шрифта"
msgid "HTML Code"
msgstr "HTML Code"
msgid "Custom classes"
msgstr "Пользовательские классы"
......@@ -3,7 +3,7 @@
'name' => 'drupal/recommended-project',
'pretty_version' => '10.3.x-dev',
'version' => '10.3.9999999.9999999-dev',
'reference' => '69c1608e100aa2f0d6f1e5e9fa488312ccfedbfc',
'reference' => 'd5d3a9a4c04c7d1e169cf2acf18008a3e4c73fc6',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
......@@ -1102,7 +1102,7 @@
'drupal/recommended-project' => array(
'pretty_version' => '10.3.x-dev',
'version' => '10.3.9999999.9999999-dev',
'reference' => '69c1608e100aa2f0d6f1e5e9fa488312ccfedbfc',
'reference' => 'd5d3a9a4c04c7d1e169cf2acf18008a3e4c73fc6',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
......
# This is the top-most .editorconfig file; do not search in parent directories.
root = true
# All files.
[*]
end_of_line = LF
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
Copyright 2013 Cameron Eagans
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# composer-patches
Simple patches plugin for Composer. Applies a patch from a local or remote file to any package required with composer.
Note that the 1.x versions of Composer Patches are supported on a best-effort
basis due to the imminent release of 2.0.0. You may still be interested in
using 1.x if you need Composer to cooperate with earlier PHP versions. No new
features will be added to 1.x releases, but any security or bug fixes will
still be accepted.
## Usage
Example composer.json:
```json
{
"require": {
"cweagans/composer-patches": "~1.0",
"drupal/drupal": "~8.2"
},
"config": {
"preferred-install": "source"
},
"extra": {
"patches": {
"drupal/drupal": {
"Add startup configuration for PHP server": "https://www.drupal.org/files/issues/add_a_startup-1543858-30.patch"
}
}
}
}
```
## Using an external patch file
Instead of a patches key in your root composer.json, use a patches-file key.
```json
{
"require": {
"cweagans/composer-patches": "~1.0",
"drupal/drupal": "~8.2"
},
"config": {
"preferred-install": "source"
},
"extra": {
"patches-file": "local/path/to/your/composer.patches.json"
}
}
```
Then your `composer.patches.json` should look like this:
```
{
"patches": {
"vendor/project": {
"Patch title": "http://example.com/url/to/patch.patch"
}
}
}
```
## Allowing patches to be applied from dependencies
If your project doesn't supply any patches of its own, but you still want to accept patches from dependencies, you must have the following in your composer file:
```json
{
"require": {
"cweagans/composer-patches": "^1.5.0"
},
"extra": {
"enable-patching": true
}
}
```
If you do have a `patches` section in your composer file that defines your own set of patches then the `enable-patching` setting will be ignored and patches from dependencies will always be applied.
## Ignoring patches
There may be situations in which you want to ignore a patch supplied by a dependency. For example:
- You use a different more recent version of a dependency, and now a patch isn't applying.
- You have a more up to date patch than the dependency, and want to use yours instead of theirs.
- A dependency's patch adds a feature to a project that you don't need.
- Your patches conflict with a dependency's patches.
```json
{
"require": {
"cweagans/composer-patches": "~1.0",
"drupal/drupal": "~8.2",
"drupal/lightning": "~8.1"
},
"config": {
"preferred-install": "source"
},
"extra": {
"patches": {
"drupal/drupal": {
"Add startup configuration for PHP server": "https://www.drupal.org/files/issues/add_a_startup-1543858-30.patch"
}
},
"patches-ignore": {
"drupal/lightning": {
"drupal/panelizer": {
"This patch has known conflicts with our Quick Edit integration": "https://www.drupal.org/files/issues/2664682-49.patch"
}
}
}
}
}
```
## Allowing to force the patch level (-pX)
Some situations require to force the patchLevel used to apply patches on a particular package.
Its useful for packages like drupal/core which packages only a subdir of the original upstream project on which patches are based.
```json
{
"extra": {
"patchLevel": {
"drupal/core": "-p2"
}
}
}
```
## Using patches from HTTP URLs
Composer [blocks](https://getcomposer.org/doc/06-config.md#secure-http) you from downloading anything from HTTP URLs, you can disable this for your project by adding a `secure-http` setting in the config section of your `composer.json`. Note that the `config` section should be under the root of your `composer.json`.
```json
{
"config": {
"secure-http": false
}
}
```
However, it's always advised to setup HTTPS to prevent MITM code injection.
## Patches containing modifications to composer.json files
Because patching occurs _after_ Composer calculates dependencies and installs packages, changes to an underlying dependency's `composer.json` file introduced in a patch will have _no effect_ on installed packages.
If you need to modify a dependency's `composer.json` or its underlying dependencies, you cannot use this plugin. Instead, you must do one of the following:
- Work to get the underlying issue resolved in the upstream package.
- Fork the package and [specify your fork as the package repository](https://getcomposer.org/doc/05-repositories.md#vcs) in your root `composer.json`
- Specify compatible package version requirements in your root `composer.json`
## Error handling
If a patch cannot be applied (hunk failed, different line endings, etc.) a message will be shown and the patch will be skipped.
To enforce throwing an error and stopping package installation/update immediately, you have two available options:
1. Add `"composer-exit-on-patch-failure": true` option to the `extra` section of your composer.json file.
1. Export `COMPOSER_EXIT_ON_PATCH_FAILURE=1`
By default, failed patches are skipped.
## Patches reporting
When a patch is applied, the plugin writes a report-file `PATCHES.txt` to a patching directory (e.g. `./patch-me/PATCHES.txt`),
which contains a list of applied patches.
If you want to avoid this behavior, add a specific key to the `extra` section:
```json
"extra": {
"composer-patches-skip-reporting": true
}
```
Or provide an environment variable `COMPOSER_PATCHES_SKIP_REPORTING` with a config.
## Patching composer.json in dependencies
This doesn't work like you'd want. By the time you're running `composer install`,
the metadata from your dependencies' composer.json has already been aggregated by
packagist (or whatever metadata repo you're using). Unfortunately, this means that
you cannot e.g. patch a dependency to be compatible with an earlier version of PHP
or change the framework version that a plugin depends on.
@anotherjames over at @computerminds wrote an article about how to work around
that particular problem for a Drupal 8 -> Drupal 9 upgrade:
[Apply Drupal 9 compatibility patches with Composer](https://www.computerminds.co.uk/articles/apply-drupal-9-compatibility-patches-composer) ([archive](https://web.archive.org/web/20210124171010/https://www.computerminds.co.uk/articles/apply-drupal-9-compatibility-patches-composer))
## Difference between this and netresearch/composer-patches-plugin
- This plugin is much more simple to use and maintain
- This plugin doesn't require you to specify which package version you're patching
- This plugin is easy to use with Drupal modules (which don't use semantic versioning).
- This plugin will gather patches from all dependencies and apply them as if they were in the root composer.json
## Credits
A ton of this code is adapted or taken straight from https://github.com/jpstacey/composer-patcher, which is abandoned in favor of https://github.com/netresearch/composer-patches-plugin, which is (IMHO) overly complex and difficult to use.
{
"name": "cweagans/composer-patches",
"description": "Provides a way to patch Composer packages.",
"minimum-stability": "dev",
"license": "BSD-3-Clause",
"type": "composer-plugin",
"extra": {
"class": "cweagans\\Composer\\Patches"
},
"authors": [
{
"name": "Cameron Eagans",
"email": "me@cweagans.net"
}
],
"require": {
"php": ">=5.3.0",
"composer-plugin-api": "^1.0 || ^2.0"
},
"require-dev": {
"composer/composer": "~1.0 || ~2.0",
"phpunit/phpunit": "~4.6"
},
"autoload": {
"psr-4": {"cweagans\\Composer\\": "src"}
},
"autoload-dev": {
"psr-4": {"cweagans\\Composer\\Tests\\": "tests"}
}
}
This diff is collapsed.
<!--?xml version="1.0" encoding="UTF-8"?-->
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="composer-patches">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<!-- Filter for coverage reports. -->
<filter>
<whitelist>
<directory>src/</directory>
</whitelist>
<blacklist>
<directory>vendor/</directory>
</blacklist>
</filter>
</phpunit>
<?php
/**
* @file
* Dispatch events when patches are applied.
*/
namespace cweagans\Composer;
use Composer\EventDispatcher\Event;
use Composer\Package\PackageInterface;
class PatchEvent extends Event {
/**
* @var PackageInterface $package
*/
protected $package;
/**
* @var string $url
*/
protected $url;
/**
* @var string $description
*/
protected $description;
/**
* Constructs a PatchEvent object.
*
* @param string $eventName
* @param PackageInterface $package
* @param string $url
* @param string $description
*/
public function __construct($eventName, PackageInterface $package, $url, $description) {
parent::__construct($eventName);
$this->package = $package;
$this->url = $url;
$this->description = $description;
}
/**
* Returns the package that is patched.
*
* @return PackageInterface
*/
public function getPackage() {
return $this->package;
}
/**
* Returns the url of the patch.
*
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* Returns the description of the patch.
*
* @return string
*/
public function getDescription() {
return $this->description;
}
}
<?php
/**
* @file
* Dispatch events when patches are applied.
*/
namespace cweagans\Composer;
class PatchEvents {
/**
* The PRE_PATCH_APPLY event occurs before a patch is applied.
*
* The event listener method receives a cweagans\Composer\PatchEvent instance.
*
* @var string
*/
const PRE_PATCH_APPLY = 'pre-patch-apply';
/**
* The POST_PATCH_APPLY event occurs after a patch is applied.
*
* The event listener method receives a cweagans\Composer\PatchEvent instance.
*
* @var string
*/
const POST_PATCH_APPLY = 'post-patch-apply';
}
This diff is collapsed.
<?php
/**
* @file
* Tests event dispatching.
*/
namespace cweagans\Composer\Tests;
use cweagans\Composer\PatchEvent;
use cweagans\Composer\PatchEvents;
use Composer\Package\PackageInterface;
class PatchEventTest extends \PHPUnit_Framework_TestCase {
/**
* Tests all the getters.
*
* @dataProvider patchEventDataProvider
*/
public function testGetters($event_name, PackageInterface $package, $url, $description) {
$patch_event = new PatchEvent($event_name, $package, $url, $description);
$this->assertEquals($event_name, $patch_event->getName());
$this->assertEquals($package, $patch_event->getPackage());
$this->assertEquals($url, $patch_event->getUrl());
$this->assertEquals($description, $patch_event->getDescription());
}
public function patchEventDataProvider() {
$prophecy = $this->prophesize('Composer\Package\PackageInterface');
$package = $prophecy->reveal();
return array(
array(PatchEvents::PRE_PATCH_APPLY, $package, 'https://www.drupal.org', 'A test patch'),
array(PatchEvents::POST_PATCH_APPLY, $package, 'https://www.drupal.org', 'A test patch'),
);
}
}
<?php
namespace Drupal\Composer\Plugin\Scaffold;
use Composer\Composer;
use Composer\Installer\PackageEvent;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
/**
* Determine recursively which packages have been allowed to scaffold files.
*
* If the root-level composer.json allows drupal/core, and drupal/core allows
* drupal/assets, then the later package will also implicitly be allowed.
*
* @internal
*/
class AllowedPackages implements PostPackageEventListenerInterface {
/**
* The Composer service.
*
* @var \Composer\Composer
*/
protected $composer;
/**
* Composer's I/O service.
*
* @var \Composer\IO\IOInterface
*/
protected $io;
/**
* Manager of the options in the top-level composer.json's 'extra' section.
*
* @var \Drupal\Composer\Plugin\Scaffold\ManageOptions
*/
protected $manageOptions;
/**
* The list of new packages added by this Composer command.
*
* @var array
*/
protected $newPackages = [];
/**
* AllowedPackages constructor.
*
* @param \Composer\Composer $composer
* The composer object.
* @param \Composer\IO\IOInterface $io
* IOInterface to write to.
* @param \Drupal\Composer\Plugin\Scaffold\ManageOptions $manage_options
* Manager of the options in the top-level composer.json's 'extra' section.
*/
public function __construct(Composer $composer, IOInterface $io, ManageOptions $manage_options) {
$this->composer = $composer;
$this->io = $io;
$this->manageOptions = $manage_options;
}
/**
* Gets a list of all packages that are allowed to copy scaffold files.
*
* We will implicitly allow the projects 'drupal/legacy-scaffold-assets'
* and 'drupal/core' to scaffold files, if they are present. Any other
* project must be explicitly whitelisted in the top-level composer.json
* file in order to be allowed to override scaffold files.
* Configuration for packages specified later will override configuration
* specified by packages listed earlier. In other words, the last listed
* package has the highest priority. The root package will always be returned
* at the end of the list.
*
* @return \Composer\Package\PackageInterface[]
* An array of allowed Composer packages.
*/
public function getAllowedPackages() {
$top_level_packages = $this->getTopLevelAllowedPackages();
$allowed_packages = $this->recursiveGetAllowedPackages($top_level_packages);
// If the root package defines any file mappings, then implicitly add it
// to the list of allowed packages. Add it at the end so that it overrides
// all the preceding packages.
if ($this->manageOptions->getOptions()->hasFileMapping()) {
$root_package = $this->composer->getPackage();
unset($allowed_packages[$root_package->getName()]);
$allowed_packages[$root_package->getName()] = $root_package;
}
// Handle any newly-added packages that are not already allowed.
return $this->evaluateNewPackages($allowed_packages);
}
/**
* {@inheritdoc}
*/
public function event(PackageEvent $event) {
$operation = $event->getOperation();
// Determine the package. Later, in evaluateNewPackages(), we will report
// which of the newly-installed packages have scaffold operations, and
// whether or not they are allowed to scaffold by the allowed-packages
// option in the root-level composer.json file.
$package = $operation->getOperationType() === 'update' ? $operation->getTargetPackage() : $operation->getPackage();
if (ScaffoldOptions::hasOptions($package->getExtra())) {
$this->newPackages[$package->getName()] = $package;
}
}
/**
* Gets all packages that are allowed in the top-level composer.json.
*
* We will implicitly allow the projects 'drupal/legacy-scaffold-assets'
* and 'drupal/core' to scaffold files, if they are present. Any other
* project must be explicitly whitelisted in the top-level composer.json
* file in order to be allowed to override scaffold files.
*
* @return array
* An array of allowed Composer package names.
*/
protected function getTopLevelAllowedPackages() {
$implicit_packages = [
'drupal/legacy-scaffold-assets',
'drupal/core',
];
$top_level_packages = $this->manageOptions->getOptions()->allowedPackages();
return array_merge($implicit_packages, $top_level_packages);
}
/**
* Builds a name-to-package mapping from a list of package names.
*
* @param string[] $packages_to_allow
* List of package names to allow.
* @param array $allowed_packages
* Mapping of package names to PackageInterface of packages already
* accumulated.
*
* @return \Composer\Package\PackageInterface[]
* Mapping of package names to PackageInterface in priority order.
*/
protected function recursiveGetAllowedPackages(array $packages_to_allow, array $allowed_packages = []) {
foreach ($packages_to_allow as $name) {
$package = $this->getPackage($name);
if ($package instanceof PackageInterface && !isset($allowed_packages[$name])) {
$allowed_packages[$name] = $package;
$package_options = $this->manageOptions->packageOptions($package);
$allowed_packages = $this->recursiveGetAllowedPackages($package_options->allowedPackages(), $allowed_packages);
}
}
return $allowed_packages;
}
/**
* Evaluates newly-added packages and see if they are already allowed.
*
* For now we will only emit warnings if they are not.
*
* @param array $allowed_packages
* Mapping of package names to PackageInterface of packages already
* accumulated.
*
* @return \Composer\Package\PackageInterface[]
* Mapping of package names to PackageInterface in priority order.
*/
protected function evaluateNewPackages(array $allowed_packages) {
foreach ($this->newPackages as $name => $newPackage) {
if (!array_key_exists($name, $allowed_packages)) {
$this->io->write("Not scaffolding files for <comment>{$name}</comment>, because it is not listed in the element 'extra.drupal-scaffold.allowed-packages' in the root-level composer.json file.");
}
else {
$this->io->write("Package <comment>{$name}</comment> has scaffold operations, and is already allowed in the root-level composer.json file.");
}
}
// @todo We could prompt the user and ask if they wish to allow a
// newly-added package. This might be useful if, for example, the user
// might wish to require an installation profile that contains scaffolded
// assets. For more information, see:
// https://www.drupal.org/project/drupal/issues/3064990
return $allowed_packages;
}
/**
* Retrieves a package from the current composer process.
*
* @param string $name
* Name of the package to get from the current composer installation.
*
* @return \Composer\Package\PackageInterface|null
* The Composer package.
*/
protected function getPackage($name) {
return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*');
}
}
<?php
namespace Drupal\Composer\Plugin\Scaffold;
use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
/**
* List of all commands provided by this package.
*
* @internal
*/
class CommandProvider implements CommandProviderCapability {
/**
* {@inheritdoc}
*/
public function getCommands() {
return [new ComposerScaffoldCommand()];
}
}
<?php
namespace Drupal\Composer\Plugin\Scaffold;
use Composer\Command\BaseCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* The "drupal:scaffold" command class.
*
* Manually run the scaffold operation that normally happens after
* 'composer install'.
*
* @internal
*/
class ComposerScaffoldCommand extends BaseCommand {
/**
* {@inheritdoc}
*/
protected function configure() {
$this
->setName('drupal:scaffold')
->setAliases(['scaffold'])
->setDescription('Update the Drupal scaffold files.')
->setHelp(
<<<EOT
The <info>drupal:scaffold</info> command places the scaffold files in their
respective locations according to the layout stipulated in the composer.json
file.
<info>php composer.phar drupal:scaffold</info>
It is usually not necessary to call <info>drupal:scaffold</info> manually,
because it is called automatically as needed, e.g. after an <info>install</info>
or <info>update</info> command. Note, though, that only packages explicitly
allowed to scaffold in the top-level composer.json will be processed by this
command.
For more information, see https://www.drupal.org/docs/develop/using-composer/using-drupals-composer-scaffold.
EOT
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$handler = new Handler($this->requireComposer(), $this->getIO());
$handler->scaffold();
return 0;
}
}
<?php
namespace Drupal\Composer\Plugin\Scaffold;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult;
/**
* Generates an 'autoload.php' that includes the autoloader created by Composer.
*
* @internal
*/
final class GenerateAutoloadReferenceFile {
/**
* This class provides only static methods.
*/
private function __construct() {
}
/**
* Generates the autoload file at the specified location.
*
* This only writes a bit of PHP that includes the autoload file that
* Composer generated. Drupal does this so that it can guarantee that there
* will always be an `autoload.php` file in a well-known location.
*
* @param \Composer\IO\IOInterface $io
* IOInterface to write to.
* @param string $package_name
* The name of the package defining the autoload file (the root package).
* @param string $web_root
* The path to the web root.
* @param string $vendor
* The path to the vendor directory.
*
* @return \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult
* The result of the autoload file generation.
*/
public static function generateAutoload(IOInterface $io, $package_name, $web_root, $vendor) {
$autoload_path = static::autoloadPath($package_name, $web_root);
// Calculate the relative path from the webroot (location of the project
// autoload.php) to the vendor directory.
$fs = new Filesystem();
$relative_autoload_path = $fs->findShortestPath($autoload_path->fullPath(), "$vendor/autoload.php");
file_put_contents($autoload_path->fullPath(), static::autoLoadContents($relative_autoload_path));
return new ScaffoldResult($autoload_path, TRUE);
}
/**
* Determines whether or not the autoload file has been committed.
*
* @param \Composer\IO\IOInterface $io
* IOInterface to write to.
* @param string $package_name
* The name of the package defining the autoload file (the root package).
* @param string $web_root
* The path to the web root.
*
* @return bool
* True if autoload.php file exists and has been committed to the repository
*/
public static function autoloadFileCommitted(IOInterface $io, $package_name, $web_root) {
$autoload_path = static::autoloadPath($package_name, $web_root);
$autoload_file = $autoload_path->fullPath();
$location = dirname($autoload_file);
if (!file_exists($autoload_file)) {
return FALSE;
}
return Git::checkTracked($io, $autoload_file, $location);
}
/**
* Generates a scaffold file path object for the autoload file.
*
* @param string $package_name
* The name of the package defining the autoload file (the root package).
* @param string $web_root
* The path to the web root.
*
* @return \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
* Object wrapping the relative and absolute path to the destination file.
*/
protected static function autoloadPath($package_name, $web_root) {
$rel_path = 'autoload.php';
$dest_rel_path = '[web-root]/' . $rel_path;
$dest_full_path = $web_root . '/' . $rel_path;
return new ScaffoldFilePath('autoload', $package_name, $dest_rel_path, $dest_full_path);
}
/**
* Builds the contents of the autoload file.
*
* @param string $relative_autoload_path
* The relative path to the autoloader in vendor.
*
* @return string
* Return the contents for the autoload.php.
*/
protected static function autoLoadContents($relative_autoload_path) {
$relative_autoload_path = preg_replace('#^\./#', '', $relative_autoload_path);
return <<<EOF
<?php
/**
* @file
* Includes the autoloader created by Composer.
*
* This file was generated by drupal-scaffold.
*
* @see composer.json
* @see index.php
* @see core/install.php
* @see core/rebuild.php
*/
return require __DIR__ . '/{$relative_autoload_path}';
EOF;
}
}
<?php
namespace Drupal\Composer\Plugin\Scaffold;
// cspell:ignore unmatch
use Composer\IO\IOInterface;
use Composer\Util\ProcessExecutor;
/**
* Provide some Git utility operations.
*
* @internal
*/
class Git {
/**
* This class provides only static methods.
*/
private function __construct() {
}
/**
* Determines whether the specified scaffold file is already ignored.
*
* @param \Composer\IO\IOInterface $io
* The Composer IO interface.
* @param string $path
* Path to scaffold file to check.
* @param string $dir
* Base directory for git process.
*
* @return bool
* Whether the specified file is already ignored or not (TRUE if ignored).
*/
public static function checkIgnore(IOInterface $io, $path, $dir = NULL) {
$process = new ProcessExecutor($io);
$output = '';
$exitCode = $process->execute('git check-ignore ' . $process->escape($path), $output, $dir);
return $exitCode == 0;
}
/**
* Determines whether the specified scaffold file is tracked by git.
*
* @param \Composer\IO\IOInterface $io
* The Composer IO interface.
* @param string $path
* Path to scaffold file to check.
* @param string $dir
* Base directory for git process.
*
* @return bool
* Whether the specified file is already tracked or not (TRUE if tracked).
*/
public static function checkTracked(IOInterface $io, $path, $dir = NULL) {
$process = new ProcessExecutor($io);
$output = '';
$exitCode = $process->execute('git ls-files --error-unmatch ' . $process->escape($path), $output, $dir);
return $exitCode == 0;
}
/**
* Checks to see if the project root dir is in a git repository.
*
* @param \Composer\IO\IOInterface $io
* The Composer IO interface.
* @param string $dir
* Base directory for git process.
*
* @return bool
* True if this is a repository.
*/
public static function isRepository(IOInterface $io, $dir = NULL) {
$process = new ProcessExecutor($io);
$output = '';
$exitCode = $process->execute('git rev-parse --show-toplevel', $output, $dir);
return $exitCode == 0;
}
}
<?php
namespace Drupal\Composer\Plugin\Scaffold;
use Composer\Composer;
use Composer\Installer\PackageEvent;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Drupal\Composer\Plugin\Scaffold\Operations\OperationData;
use Drupal\Composer\Plugin\Scaffold\Operations\OperationFactory;
use Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldFileCollection;
/**
* Core class of the plugin.
*
* Contains the primary logic which determines the files to be fetched and
* processed.
*
* @internal
*/
class Handler {
/**
* Composer hook called before scaffolding begins.
*/
const PRE_DRUPAL_SCAFFOLD_CMD = 'pre-drupal-scaffold-cmd';
/**
* Composer hook called after scaffolding completes.
*/
const POST_DRUPAL_SCAFFOLD_CMD = 'post-drupal-scaffold-cmd';
/**
* The Composer service.
*
* @var \Composer\Composer
*/
protected $composer;
/**
* Composer's I/O service.
*
* @var \Composer\IO\IOInterface
*/
protected $io;
/**
* The scaffold options in the top-level composer.json's 'extra' section.
*
* @var \Drupal\Composer\Plugin\Scaffold\ManageOptions
*/
protected $manageOptions;
/**
* The manager that keeps track of which packages are allowed to scaffold.
*
* @var \Drupal\Composer\Plugin\Scaffold\AllowedPackages
*/
protected $manageAllowedPackages;
/**
* The list of listeners that are notified after a package event.
*
* @var \Drupal\Composer\Plugin\Scaffold\PostPackageEventListenerInterface[]
*/
protected $postPackageListeners = [];
/**
* Handler constructor.
*
* @param \Composer\Composer $composer
* The Composer service.
* @param \Composer\IO\IOInterface $io
* The Composer I/O service.
*/
public function __construct(Composer $composer, IOInterface $io) {
$this->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 = $this->composer->getEventDispatcher();
$dispatcher->dispatchScript(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->dispatchScript(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();
}
}
<?php
namespace Drupal\Composer\Plugin\Scaffold;
/**
* Injects config values from an associative array into a string.
*
* @internal
*/
class Interpolator {
/**
* The character sequence that identifies the start of a token.
*
* @var string
*/
protected $startToken;
/**
* The character sequence that identifies the end of a token.
*
* @var string
*/
protected $endToken;
/**
* The associative array of replacements.
*
* @var array
*/
protected $data = [];
/**
* Interpolator constructor.
*
* @param string $start_token
* The start marker for a token, e.g. '['.
* @param string $end_token
* The end marker for a token, e.g. ']'.
*/
public function __construct($start_token = '\\[', $end_token = '\\]') {
$this->startToken = $start_token;
$this->endToken = $end_token;
}
/**
* Sets the data set to use when interpolating.
*
* @param array $data
* The key:value pairs to use when interpolating.
*
* @return $this
*/
public function setData(array $data) {
$this->data = $data;
return $this;
}
/**
* Adds to the data set to use when interpolating.
*
* @param array $data
* The key:value pairs to use when interpolating.
*
* @return $this
*/
public function addData(array $data) {
$this->data = array_merge($this->data, $data);
return $this;
}
/**
* Replaces tokens in a string with values from an associative array.
*
* Tokens are surrounded by delimiters, e.g. square brackets "[key]". The
* characters that surround the key may be defined when the Interpolator is
* constructed.
*
* Example:
* If the message is 'Hello, [user.name]', then the value of the user.name
* item is fetched from the array, and the token [user.name] is replaced with
* the result.
*
* @param string $message
* Message containing tokens to be replaced.
* @param array $extra
* Data to use for interpolation in addition to whatever was provided to
* self::setData().
* @param string|bool $default
* (optional) The value to substitute for tokens that are not found in the
* data. If FALSE, then missing tokens are not replaced. Defaults to an
* empty string.
*
* @return string
* The message after replacements have been made.
*/
public function interpolate($message, array $extra = [], $default = '') {
$data = $extra + $this->data;
$replacements = $this->replacements($message, $data, $default);
return strtr($message, $replacements);
}
/**
* Finds the tokens that exist in a message and builds a replacement array.
*
* All of the replacements in the data array are looked up given the token
* keys from the provided message. Keys that do not exist in the configuration
* are replaced with the default value.
*
* @param string $message
* String with tokens.
* @param array $data
* Data to use for interpolation.
* @param string $default
* (optional) The value to substitute for tokens that are not found in the
* data. If FALSE, then missing tokens are not replaced. Defaults to an
* empty string.
*
* @return string[]
* An array of replacements to make. Keyed by tokens and the replacements
* are the values.
*/
protected function replacements($message, array $data, $default = '') {
$tokens = $this->findTokens($message);
$replacements = [];
foreach ($tokens as $sourceText => $key) {
$replacement_text = array_key_exists($key, $data) ? $data[$key] : $default;
if ($replacement_text !== FALSE) {
$replacements[$sourceText] = $replacement_text;
}
}
return $replacements;
}
/**
* Finds all of the tokens in the provided message.
*
* @param string $message
* String with tokens.
*
* @return string[]
* map of token to key, e.g. {{key}} => key
*/
protected function findTokens($message) {
$reg_ex = '#' . $this->startToken . '([a-zA-Z0-9._-]+)' . $this->endToken . '#';
if (!preg_match_all($reg_ex, $message, $matches, PREG_SET_ORDER)) {
return [];
}
$tokens = [];
foreach ($matches as $matchSet) {
[$sourceText, $key] = $matchSet;
$tokens[$sourceText] = $key;
}
return $tokens;
}
}
This diff is collapsed.
<?php
namespace Drupal\Composer\Plugin\Scaffold;
use Composer\Composer;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
/**
* Per-project options from the 'extras' section of the composer.json file.
*
* Projects that describe scaffold files do so via their scaffold options.
* This data is pulled from the 'drupal-scaffold' portion of the extras
* section of the project data.
*
* @internal
*/
class ManageOptions {
/**
* The Composer service.
*
* @var \Composer\Composer
*/
protected $composer;
/**
* ManageOptions constructor.
*
* @param \Composer\Composer $composer
* The Composer service.
*/
public function __construct(Composer $composer) {
$this->composer = $composer;
}
/**
* Gets the root-level scaffold options for this project.
*
* @return \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions
* The scaffold options object.
*/
public function getOptions() {
return $this->packageOptions($this->composer->getPackage());
}
/**
* Gets the scaffold options for the stipulated project.
*
* @param \Composer\Package\PackageInterface $package
* The package to fetch the scaffold options from.
*
* @return \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions
* The scaffold options object.
*/
public function packageOptions(PackageInterface $package) {
return ScaffoldOptions::create($package->getExtra());
}
/**
* Creates an interpolator for the 'locations' element.
*
* The interpolator returned will replace a path string with the tokens
* defined in the 'locations' element.
*
* Note that only the root package may define locations.
*
* @return \Drupal\Composer\Plugin\Scaffold\Interpolator
* Interpolator that will do replacements in a string using tokens in
* 'locations' element.
*/
public function getLocationReplacements() {
return (new Interpolator())->setData($this->ensureLocations());
}
/**
* Ensures that all of the locations defined in the scaffold files exist.
*
* Create them on the filesystem if they do not.
*/
protected function ensureLocations() {
$fs = new Filesystem();
$locations = $this->getOptions()->locations() + ['web_root' => './'];
$locations = array_map(function ($location) use ($fs) {
$fs->ensureDirectoryExists($location);
$location = realpath($location);
return $location;
}, $locations);
return $locations;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment