aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'phpBB/phpbb/composer')
-rw-r--r--phpBB/phpbb/composer/exception/managed_with_clean_error_exception_exception.php35
-rw-r--r--phpBB/phpbb/composer/exception/managed_with_enable_error_exception.php35
-rw-r--r--phpBB/phpbb/composer/exception/managed_with_error_exception.php35
-rw-r--r--phpBB/phpbb/composer/exception/runtime_exception.php37
-rw-r--r--phpBB/phpbb/composer/extension_manager.php316
-rw-r--r--phpBB/phpbb/composer/installer.php716
-rw-r--r--phpBB/phpbb/composer/io/console_io.php40
-rw-r--r--phpBB/phpbb/composer/io/html_output_formatter.php86
-rw-r--r--phpBB/phpbb/composer/io/io_interface.php26
-rw-r--r--phpBB/phpbb/composer/io/null_io.php27
-rw-r--r--phpBB/phpbb/composer/io/translate_composer_trait.php245
-rw-r--r--phpBB/phpbb/composer/io/web_io.php37
-rw-r--r--phpBB/phpbb/composer/manager.php332
-rw-r--r--phpBB/phpbb/composer/manager_interface.php110
14 files changed, 2077 insertions, 0 deletions
diff --git a/phpBB/phpbb/composer/exception/managed_with_clean_error_exception_exception.php b/phpBB/phpbb/composer/exception/managed_with_clean_error_exception_exception.php
new file mode 100644
index 0000000000..2339fa4096
--- /dev/null
+++ b/phpBB/phpbb/composer/exception/managed_with_clean_error_exception_exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\exception;
+
+/**
+ * Packaged managed with success but error occurred when cleaning the filesystem
+ */
+class managed_with_clean_error_exception extends managed_with_error_exception
+{
+ /**
+ * Constructor
+ *
+ * @param string $prefix The language string prefix
+ * @param string $message The Exception message to throw (must be a language variable).
+ * @param array $parameters The parameters to use with the language var.
+ * @param \Exception $previous The previous runtime_exception used for the runtime_exception chaining.
+ * @param integer $code The Exception code.
+ */
+ public function __construct($prefix, $message = '', array $parameters = [], \Exception $previous = null, $code = 0)
+ {
+ parent::__construct($prefix . $message, $parameters, $previous, $code);
+ }
+
+}
diff --git a/phpBB/phpbb/composer/exception/managed_with_enable_error_exception.php b/phpBB/phpbb/composer/exception/managed_with_enable_error_exception.php
new file mode 100644
index 0000000000..7ef7a42df3
--- /dev/null
+++ b/phpBB/phpbb/composer/exception/managed_with_enable_error_exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\exception;
+
+/**
+ * Packaged managed with success but error occurred when re-enabling the extension
+ */
+class managed_with_enable_error_exception extends managed_with_error_exception
+{
+ /**
+ * Constructor
+ *
+ * @param string $prefix The language string prefix
+ * @param string $message The Exception message to throw (must be a language variable).
+ * @param array $parameters The parameters to use with the language var.
+ * @param \Exception $previous The previous runtime_exception used for the runtime_exception chaining.
+ * @param integer $code The Exception code.
+ */
+ public function __construct($prefix, $message = '', array $parameters = [], \Exception $previous = null, $code = 0)
+ {
+ parent::__construct($prefix . $message, $parameters, $previous, $code);
+ }
+
+}
diff --git a/phpBB/phpbb/composer/exception/managed_with_error_exception.php b/phpBB/phpbb/composer/exception/managed_with_error_exception.php
new file mode 100644
index 0000000000..9e7c67580e
--- /dev/null
+++ b/phpBB/phpbb/composer/exception/managed_with_error_exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\exception;
+
+/**
+ * Packaged managed with success but errored at some point
+ */
+class managed_with_error_exception extends runtime_exception
+{
+ /**
+ * Constructor
+ *
+ * @param string $prefix The language string prefix
+ * @param string $message The Exception message to throw (must be a language variable).
+ * @param array $parameters The parameters to use with the language var.
+ * @param \Exception $previous The previous runtime_exception used for the runtime_exception chaining.
+ * @param integer $code The Exception code.
+ */
+ public function __construct($prefix, $message = '', array $parameters = [], \Exception $previous = null, $code = 0)
+ {
+ parent::__construct($prefix . $message, $parameters, $previous, $code);
+ }
+
+}
diff --git a/phpBB/phpbb/composer/exception/runtime_exception.php b/phpBB/phpbb/composer/exception/runtime_exception.php
new file mode 100644
index 0000000000..eb92759318
--- /dev/null
+++ b/phpBB/phpbb/composer/exception/runtime_exception.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\exception;
+
+use phpbb\exception\runtime_exception as base;
+
+/**
+ * Base class for exceptions thrown when managing packages through composer
+ */
+class runtime_exception extends base
+{
+ /**
+ * Constructor
+ *
+ * @param string $prefix The language string prefix
+ * @param string $message The Exception message to throw (must be a language variable).
+ * @param array $parameters The parameters to use with the language var.
+ * @param \Exception $previous The previous runtime_exception used for the runtime_exception chaining.
+ * @param integer $code The Exception code.
+ */
+ public function __construct($prefix, $message = '', array $parameters = [], \Exception $previous = null, $code = 0)
+ {
+ parent::__construct($prefix . $message, $parameters, $previous, $code);
+ }
+
+}
diff --git a/phpBB/phpbb/composer/extension_manager.php b/phpBB/phpbb/composer/extension_manager.php
new file mode 100644
index 0000000000..96250e9dd3
--- /dev/null
+++ b/phpBB/phpbb/composer/extension_manager.php
@@ -0,0 +1,316 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer;
+
+use Composer\IO\IOInterface;
+use phpbb\cache\driver\driver_interface;
+use phpbb\composer\exception\managed_with_clean_error_exception;
+use phpbb\composer\exception\managed_with_enable_error_exception;
+use phpbb\composer\exception\runtime_exception;
+use phpbb\config\config;
+use phpbb\extension\manager as ext_manager;
+use phpbb\filesystem\exception\filesystem_exception;
+use phpbb\filesystem\filesystem;
+
+/**
+ * Class to safely manage extensions through composer.
+ */
+class extension_manager extends manager
+{
+ /**
+ * @var \phpbb\extension\manager
+ */
+ protected $extension_manager;
+
+ /**
+ * @var \phpbb\filesystem\filesystem
+ */
+ protected $filesystem;
+
+ /**
+ * @var array
+ */
+ private $enabled_extensions;
+
+ /**
+ * @var bool Enables extensions when installing them?
+ */
+ private $enable_on_install = false;
+
+ /**
+ * @var bool Purges extensions data when removing them?
+ */
+ private $purge_on_remove = true;
+
+ /**
+ * @param installer $installer Installer object
+ * @param driver_interface $cache Cache object
+ * @param ext_manager $extension_manager phpBB extension manager
+ * @param filesystem $filesystem Filesystem object
+ * @param string $package_type Composer type of managed packages
+ * @param string $exception_prefix Exception prefix to use
+ * @param string $root_path phpBB root path
+ * @param config $config Config object
+ */
+ public function __construct(installer $installer, driver_interface $cache, ext_manager $extension_manager, filesystem $filesystem, $package_type, $exception_prefix, $root_path, config $config = null)
+ {
+ $this->extension_manager = $extension_manager;
+ $this->filesystem = $filesystem;
+ $this->root_path = $root_path;
+
+ if ($config)
+ {
+ $this->enable_on_install = (bool) $config['exts_composer_enable_on_install'];
+ $this->purge_on_remove = (bool) $config['exts_composer_purge_on_remove'];
+ }
+
+ parent::__construct($installer, $cache, $package_type, $exception_prefix);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function pre_install(array $packages, IOInterface $io = null)
+ {
+ $installed_manually = array_intersect(array_keys($this->extension_manager->all_available()), array_keys($packages));
+ if (count($installed_manually) !== 0)
+ {
+ throw new runtime_exception($this->exception_prefix, 'ALREADY_INSTALLED_MANUALLY', [implode('|', array_keys($installed_manually))]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function post_install(array $packages, IOInterface $io = null)
+ {
+ if ($this->enable_on_install)
+ {
+ $io->writeError([['ENABLING_EXTENSIONS', [], 1]], true);
+ foreach ($packages as $package => $version)
+ {
+ try
+ {
+ $this->extension_manager->enable($package);
+ }
+ catch (\phpbb\exception\runtime_exception $e)
+ {
+ $io->writeError([[$e->getMessage(), $e->get_parameters(), 4]], true);
+ }
+ catch (\Exception $e)
+ {
+ $io->writeError([[$e->getMessage(), [], 4]], true);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function pre_update(array $packages, IOInterface $io = null)
+ {
+ $io->writeError([['DISABLING_EXTENSIONS', [], 1]], true);
+ $this->enabled_extensions = [];
+ foreach ($packages as $package => $version)
+ {
+ try
+ {
+ if ($this->extension_manager->is_enabled($package))
+ {
+ $this->enabled_extensions[] = $package;
+ $this->extension_manager->disable($package);
+ }
+ }
+ catch (\phpbb\exception\runtime_exception $e)
+ {
+ $io->writeError([[$e->getMessage(), $e->get_parameters(), 4]], true);
+ }
+ catch (\Exception $e)
+ {
+ $io->writeError([[$e->getMessage(), [], 4]], true);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function post_update(array $packages, IOInterface $io = null)
+ {
+ $io->writeError([['ENABLING_EXTENSIONS', [], 1]], true);
+ foreach ($this->enabled_extensions as $package)
+ {
+ try
+ {
+ $this->extension_manager->enable($package);
+ }
+ catch (\phpbb\exception\runtime_exception $e)
+ {
+ $io->writeError([[$e->getMessage(), $e->get_parameters(), 4]], true);
+ }
+ catch (\Exception $e)
+ {
+ $io->writeError([[$e->getMessage(), [], 4]], true);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove(array $packages, IOInterface $io = null)
+ {
+ $packages = $this->normalize_version($packages);
+
+ $not_installed = array_diff(array_keys($packages), array_keys($this->extension_manager->all_available()));
+ if (count($not_installed) !== 0)
+ {
+ throw new runtime_exception($this->exception_prefix, 'NOT_INSTALLED', [implode('|', array_keys($not_installed))]);
+ }
+
+ parent::remove($packages, $io);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function pre_remove(array $packages, IOInterface $io = null)
+ {
+ if ($this->purge_on_remove)
+ {
+ $io->writeError([['DISABLING_EXTENSIONS', [], 1]], true);
+ }
+
+ foreach ($packages as $package => $version)
+ {
+ try
+ {
+ if ($this->extension_manager->is_enabled($package))
+ {
+ if ($this->purge_on_remove)
+ {
+ $this->extension_manager->purge($package);
+ }
+ else
+ {
+ $this->extension_manager->disable($package);
+ }
+ }
+ }
+ catch (\phpbb\exception\runtime_exception $e)
+ {
+ $io->writeError([[$e->getMessage(), $e->get_parameters(), 4]], true);
+ }
+ catch (\Exception $e)
+ {
+ $io->writeError([[$e->getMessage(), [], 4]], true);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start_managing($package, $io)
+ {
+ if (!$this->extension_manager->is_available($package))
+ {
+ throw new runtime_exception($this->exception_prefix, 'NOT_INSTALLED', [$package]);
+ }
+
+ if ($this->is_managed($package))
+ {
+ throw new runtime_exception($this->exception_prefix, 'ALREADY_MANAGED', [$package]);
+ }
+
+ $enabled = false;
+ if ($this->extension_manager->is_enabled($package))
+ {
+ $enabled = true;
+ $io->writeError([['DISABLING_EXTENSIONS', [], 1]], true);
+ $this->extension_manager->disable($package);
+ }
+
+ $ext_path = $this->extension_manager->get_extension_path($package, true);
+ $backup_path = rtrim($ext_path, '/') . '__backup__';
+
+ try
+ {
+ $this->filesystem->rename($ext_path, $backup_path);
+ }
+ catch (filesystem_exception $e)
+ {
+ throw new runtime_exception($this->exception_prefix, 'CANNOT_MANAGE_FILESYSTEM_ERROR', [$package], $e);
+ }
+
+ try
+ {
+ $this->install((array) $package, $io);
+ $this->filesystem->remove($backup_path);
+ }
+ catch (runtime_exception $e)
+ {
+ $this->filesystem->rename($backup_path, $ext_path);
+ throw new runtime_exception($this->exception_prefix, 'CANNOT_MANAGE_INSTALL_ERROR', [$package], $e);
+ }
+ catch (filesystem_exception $e)
+ {
+ throw new managed_with_clean_error_exception($this->exception_prefix, 'MANAGED_WITH_CLEAN_ERROR', [$package, $backup_path], $e);
+ }
+
+ if ($enabled)
+ {
+ try
+ {
+ $io->writeError([['ENABLING_EXTENSIONS', [], 1]], true);
+ $this->extension_manager->enable($package);
+ }
+ catch (\Exception $e)
+ {
+ throw new managed_with_enable_error_exception($this->exception_prefix, 'MANAGED_WITH_ENABLE_ERROR', [$package], $e);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function check_requirements()
+ {
+ return parent::check_requirements() && $this->filesystem->is_writable($this->root_path . 'ext/');
+ }
+
+ /**
+ * Enable the extensions when installing
+ *
+ * Warning: Only the explicitly required extensions will be enabled
+ *
+ * @param bool $enable
+ */
+ public function set_enable_on_install($enable)
+ {
+ $this->enable_on_install = $enable;
+ }
+
+ /**
+ * Purge the extension when disabling it
+ *
+ * @param bool $purge
+ */
+ public function set_purge_on_remove($purge)
+ {
+ $this->purge_on_remove = $purge;
+ }
+}
diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php
new file mode 100644
index 0000000000..1bc2c8e6b4
--- /dev/null
+++ b/phpBB/phpbb/composer/installer.php
@@ -0,0 +1,716 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer;
+
+use Composer\Composer;
+use Composer\Factory;
+use Composer\IO\IOInterface;
+use Composer\IO\NullIO;
+use Composer\Json\JsonFile;
+use Composer\Package\BasePackage;
+use Composer\Package\CompletePackage;
+use Composer\Repository\ComposerRepository;
+use Composer\Semver\Constraint\ConstraintInterface;
+use Composer\Util\RemoteFilesystem;
+use phpbb\composer\io\null_io;
+use phpbb\config\config;
+use phpbb\exception\runtime_exception;
+use phpbb\filesystem\filesystem;
+use phpbb\request\request;
+use Seld\JsonLint\ParsingException;
+use phpbb\filesystem\helper as filesystem_helper;
+
+/**
+ * Class to install packages through composer while freezing core dependencies.
+ */
+class installer
+{
+ const PHPBB_TYPES = 'phpbb-extension,phpbb-style,phpbb-language';
+
+ /**
+ * @var array Repositories to look packages from
+ */
+ protected $repositories = [];
+
+ /**
+ * @var bool Indicates whether packagist usage is allowed or not
+ */
+ protected $packagist = false;
+
+ /**
+ * @var string Composer filename used to manage the packages
+ */
+ protected $composer_filename = 'composer-ext.json';
+
+ /**
+ * @var string Directory where to install packages vendors
+ */
+ protected $packages_vendor_dir = 'vendor-ext/';
+
+ /**
+ * @var string Minimum stability
+ */
+ protected $minimum_stability = 'stable';
+
+ /**
+ * @var string phpBB root path
+ */
+ protected $root_path;
+
+ /**
+ * @var string Stores the original working directory in case it has been changed through move_to_root()
+ */
+ private $original_cwd;
+
+ /**
+ * @var array Stores the content of the ext json file before generate_ext_json_file() overrides it
+ */
+ private $ext_json_file_backup;
+
+ /**
+ * @var request phpBB request object
+ */
+ private $request;
+
+ /**
+ * @param string $root_path phpBB root path
+ * @param filesystem $filesystem Filesystem object
+ * @param request $request phpBB request object
+ * @param config $config Config object
+ */
+ public function __construct($root_path, filesystem $filesystem, request $request, config $config = null)
+ {
+ if ($config)
+ {
+ $repositories = json_decode($config['exts_composer_repositories'], true);
+
+ if (is_array($repositories) && !empty($repositories))
+ {
+ $this->repositories = (array) $repositories;
+ }
+
+ $this->packagist = (bool) $config['exts_composer_packagist'];
+ $this->composer_filename = $config['exts_composer_json_file'];
+ $this->packages_vendor_dir = $config['exts_composer_vendor_dir'];
+ $this->minimum_stability = $config['exts_composer_minimum_stability'];
+ }
+
+ $this->root_path = $root_path;
+ $this->request = $request;
+
+ putenv('COMPOSER_HOME=' . filesystem_helper::realpath($root_path) . '/store/composer');
+ }
+
+ /**
+ * Update the current installed set of packages
+ *
+ * @param array $packages Packages to install.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param array $whitelist White-listed packages (packages that can be installed/updated/removed)
+ * @param IOInterface $io IO object used for the output
+ *
+ * @throws runtime_exception
+ */
+ public function install(array $packages, $whitelist, IOInterface $io = null)
+ {
+ $this->wrap(function() use ($packages, $whitelist, $io) {
+ $this->do_install($packages, $whitelist, $io);
+ });
+ }
+
+ /**
+ * Update the current installed set of packages
+ *
+ * /!\ Doesn't change the current working directory
+ *
+ * @param array $packages Packages to install.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param array $whitelist White-listed packages (packages that can be installed/updated/removed)
+ * @param IOInterface $io IO object used for the output
+ *
+ * @throws runtime_exception
+ */
+ protected function do_install(array $packages, $whitelist, IOInterface $io = null)
+ {
+ if (!$io)
+ {
+ $io = new null_io();
+ }
+
+ $this->generate_ext_json_file($packages);
+
+ $composer = Factory::create($io, $this->get_composer_ext_json_filename(), false);
+ $install = \Composer\Installer::create($io, $composer);
+
+ $composer->getDownloadManager()->setOutputProgress(false);
+
+ $install
+ ->setVerbose(true)
+ ->setPreferSource(false)
+ ->setPreferDist(true)
+ ->setDevMode(false)
+ ->setUpdate(true)
+ ->setUpdateWhitelist($whitelist)
+ ->setWhitelistDependencies(false)
+ ->setIgnorePlatformRequirements(false)
+ ->setOptimizeAutoloader(true)
+ ->setDumpAutoloader(true)
+ ->setPreferStable(true)
+ ->setRunScripts(false)
+ ->setDryRun(false);
+
+ try
+ {
+ $result = $install->run();
+ }
+ catch (\Exception $e)
+ {
+ $this->restore_ext_json_file();
+
+ throw new runtime_exception('COMPOSER_CANNOT_INSTALL', [], $e);
+ }
+
+ if ($result !== 0)
+ {
+ $this->restore_ext_json_file();
+
+ throw new runtime_exception($io->get_composer_error(), []);
+ }
+ }
+
+ /**
+ * Returns the list of currently installed packages
+ *
+ * @param string|array $types Returns only the packages with the given type(s)
+ *
+ * @return array The installed packages associated to their version.
+ *
+ * @throws runtime_exception
+ */
+ public function get_installed_packages($types)
+ {
+ return $this->wrap(function() use ($types) {
+ return $this->do_get_installed_packages($types);
+ });
+ }
+
+ /**
+ * Returns the list of currently installed packages
+ *
+ * /!\ Doesn't change the current working directory
+ *
+ * @param string|array $types Returns only the packages with the given type(s)
+ *
+ * @return array The installed packages associated to their version.
+ */
+ protected function do_get_installed_packages($types)
+ {
+ $types = (array) $types;
+
+ try
+ {
+ $io = new NullIO();
+ $composer = Factory::create($io, $this->get_composer_ext_json_filename(), false);
+
+ $installed = [];
+
+ /** @var \Composer\Package\Link[] $required_links */
+ $required_links = $composer->getPackage()->getRequires();
+ $installed_packages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
+
+ foreach ($installed_packages as $package)
+ {
+ if (in_array($package->getType(), $types, true))
+ {
+ $version = array_key_exists($package->getName(), $required_links) ?
+ $required_links[$package->getName()]->getPrettyConstraint() : '*';
+ $installed[$package->getName()] = $version;
+ }
+ }
+
+ return $installed;
+ }
+ catch (\Exception $e)
+ {
+ return [];
+ }
+ }
+
+ /**
+ * Gets the list of the available packages of the configured type in the configured repositories
+ *
+ * /!\ Doesn't change the current working directory
+ *
+ * @param string $type Returns only the packages with the given type
+ *
+ * @return array The name of the available packages, associated to their definition. Ordered by name.
+ *
+ * @throws runtime_exception
+ */
+ public function get_available_packages($type)
+ {
+ return $this->wrap(function() use ($type) {
+ return $this->do_get_available_packages($type);
+ });
+ }
+
+ /**
+ * Gets the list of the available packages of the configured type in the configured repositories
+ *
+ * @param string $type Returns only the packages with the given type
+ *
+ * @return array The name of the available packages, associated to their definition. Ordered by name.
+ */
+ protected function do_get_available_packages($type)
+ {
+ try
+ {
+ $this->generate_ext_json_file($this->do_get_installed_packages(explode(',', self::PHPBB_TYPES)));
+
+ $io = new NullIO();
+ $composer = Factory::create($io, $this->get_composer_ext_json_filename(), false);
+
+ /** @var ConstraintInterface $core_constraint */
+ $core_constraint = $composer->getPackage()->getRequires()['phpbb/phpbb']->getConstraint();
+ $core_stability = $composer->getPackage()->getMinimumStability();
+
+ $available = [];
+
+ $compatible_packages = [];
+ $repositories = $composer->getRepositoryManager()->getRepositories();
+
+ /** @var \Composer\Repository\RepositoryInterface $repository */
+ foreach ($repositories as $repository)
+ {
+ try
+ {
+ if ($repository instanceof ComposerRepository && $repository->hasProviders())
+ {
+ // Special case for packagist which exposes an api to retrieve all packages of a given type.
+ // For the others composer repositories with providers we can't do anything. It would be too slow.
+
+ $r = new \ReflectionObject($repository);
+ $repo_url = $r->getProperty('url');
+ $repo_url->setAccessible(true);
+
+ if ($repo_url->getValue($repository) === 'http://packagist.org')
+ {
+ $url = 'https://packagist.org/packages/list.json?type=' . $type;
+ $rfs = new RemoteFilesystem($io);
+ $hostname = parse_url($url, PHP_URL_HOST) ?: $url;
+ $json = $rfs->getContents($hostname, $url, false);
+
+ /** @var \Composer\Package\PackageInterface $package */
+ foreach (JsonFile::parseJson($json, $url)['packageNames'] as $package)
+ {
+ $versions = $repository->findPackages($package);
+ $compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $package, $versions);
+ }
+ }
+ }
+ else
+ {
+ // Pre-filter repo packages by their type
+ $packages = [];
+ /** @var \Composer\Package\PackageInterface $package */
+ foreach ($repository->getPackages() as $package)
+ {
+ if ($package->getType() === $type)
+ {
+ $packages[$package->getName()][] = $package;
+ }
+ }
+
+ // Filter the compatibles versions
+ foreach ($packages as $package => $versions)
+ {
+ $compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $package, $versions);
+ }
+ }
+ }
+ catch (\Exception $e)
+ {
+ // If a repo fails, just skip it.
+ continue;
+ }
+ }
+
+ foreach ($compatible_packages as $name => $versions)
+ {
+ // Determine the highest version of the package
+ /** @var CompletePackage $highest_version */
+ $highest_version = null;
+
+ /** @var CompletePackage $version */
+ foreach ($versions as $version)
+ {
+ if (!$highest_version || version_compare($version->getVersion(), $highest_version->getVersion(), '>'))
+ {
+ $highest_version = $version;
+ }
+ }
+
+ // Generates the entry
+ $available[$name] = [];
+ $available[$name]['name'] = $highest_version->getPrettyName();
+ $available[$name]['display_name'] = $highest_version->getExtra()['display-name'];
+ $available[$name]['composer_name'] = $highest_version->getName();
+ $available[$name]['version'] = $highest_version->getPrettyVersion();
+
+ if ($version instanceof CompletePackage)
+ {
+ $available[$name]['description'] = $highest_version->getDescription();
+ $available[$name]['url'] = $highest_version->getHomepage();
+ $available[$name]['authors'] = $highest_version->getAuthors();
+ }
+ else
+ {
+ $available[$name]['description'] = '';
+ $available[$name]['url'] = '';
+ $available[$name]['authors'] = [];
+ }
+ }
+
+ usort($available, function($a, $b)
+ {
+ return strcasecmp($a['display_name'], $b['display_name']);
+ });
+
+ return $available;
+ }
+ catch (\Exception $e)
+ {
+ return [];
+ }
+ }
+
+ /**
+ * Checks the requirements of the manager and returns true if it can be used.
+ *
+ * @return bool
+ */
+ public function check_requirements()
+ {
+ $filesystem = new \phpbb\filesystem\filesystem();
+
+ return $filesystem->is_writable([
+ $this->root_path . $this->composer_filename,
+ $this->root_path . $this->packages_vendor_dir,
+ $this->root_path . substr($this->composer_filename, 0, -5) . '.lock',
+ ]);
+ }
+
+ /**
+ * Updates $compatible_packages with the versions of $versions compatibles with the $core_constraint
+ *
+ * @param array $compatible_packages List of compatibles versions
+ * @param ConstraintInterface $core_constraint Constraint against the phpBB version
+ * @param string $core_stability Core stability
+ * @param string $package_name Considered package
+ * @param array $versions List of available versions
+ *
+ * @return array
+ */
+ private function get_compatible_versions(array $compatible_packages, ConstraintInterface $core_constraint, $core_stability, $package_name, array $versions)
+ {
+ $core_stability_value = BasePackage::$stabilities[$core_stability];
+
+ /** @var \Composer\Package\PackageInterface $version */
+ foreach ($versions as $version)
+ {
+ try
+ {
+ if (BasePackage::$stabilities[$version->getStability()] > $core_stability_value)
+ {
+ continue;
+ }
+
+ if (array_key_exists('phpbb/phpbb', $version->getRequires()))
+ {
+ /** @var ConstraintInterface $package_constraint */
+ $package_constraint = $version->getRequires()['phpbb/phpbb']->getConstraint();
+
+ if (!$package_constraint->matches($core_constraint))
+ {
+ continue;
+ }
+ }
+
+ $compatible_packages[$package_name][] = $version;
+ }
+ catch (\Exception $e)
+ {
+ // Do nothing (to log when a true debug logger is available)
+ }
+ }
+
+ return $compatible_packages;
+ }
+
+ /**
+ * Generates and write the json file used to install the set of packages
+ *
+ * @param array $packages Packages to update.
+ * Each entry may be a name or an array associating a version constraint to a name
+ */
+ protected function generate_ext_json_file(array $packages)
+ {
+ $io = new NullIO();
+
+ $composer = Factory::create($io, null, false);
+
+ $core_packages = $this->get_core_packages($composer);
+
+ // The composer/installers package must be installed on his own and not provided by the existing autoloader
+ $core_replace = $core_packages;
+ unset($core_replace['composer/installers']);
+
+ $ext_json_data = [
+ 'require' => array_merge(
+ ['php' => $this->get_core_php_requirement($composer)],
+ $core_packages,
+ $this->get_extra_dependencies(),
+ $packages),
+ 'replace' => $core_replace,
+ 'repositories' => $this->get_composer_repositories(),
+ 'config' => [
+ 'vendor-dir'=> $this->packages_vendor_dir,
+ ],
+ 'minimum-stability' => $this->minimum_stability,
+ ];
+
+ $this->ext_json_file_backup = null;
+ $json_file = new JsonFile($this->get_composer_ext_json_filename());
+
+ try
+ {
+ $ext_json_file_backup = $json_file->read();
+ }
+ catch (ParsingException $e)
+ {
+ $ext_json_file_backup = '{}';
+
+ $lockFile = new JsonFile(substr($this->get_composer_ext_json_filename(), 0, -5) . '.lock');
+ $lockFile->write([]);
+ }
+
+ $json_file->write($ext_json_data);
+ $this->ext_json_file_backup = $ext_json_file_backup;
+ }
+
+ /**
+ * Restore the json file overridden by generate_ext_json_file()
+ */
+ protected function restore_ext_json_file()
+ {
+ if ($this->ext_json_file_backup)
+ {
+ try
+ {
+ $json_file = new JsonFile($this->get_composer_ext_json_filename());
+ $json_file->write($this->ext_json_file_backup);
+ }
+ catch (\Exception $e)
+ {
+ }
+
+ $this->ext_json_file_backup = null;
+ }
+ }
+
+ /**
+ * Get the core installed packages
+ *
+ * @param Composer $composer Composer object to load the dependencies
+ * @return array The core packages with their version
+ */
+ protected function get_core_packages(Composer $composer)
+ {
+ $core_deps = [];
+ $packages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
+
+ foreach ($packages as $package)
+ {
+ $core_deps[$package->getName()] = $package->getPrettyVersion();
+ }
+
+ $core_deps['phpbb/phpbb'] = PHPBB_VERSION;
+
+ return $core_deps;
+ }
+
+ /**
+ * Get the PHP version required by the core
+ *
+ * @param Composer $composer Composer object to load the dependencies
+ * @return string The PHP version required by the core
+ */
+ protected function get_core_php_requirement(Composer $composer)
+ {
+ return $composer->getLocker()->getLockData()['platform']['php'];
+ }
+
+ /**
+ * Generate the repositories entry of the packages json file
+ *
+ * @return array repositories entry
+ */
+ protected function get_composer_repositories()
+ {
+ $repositories = [];
+
+ if (!$this->packagist)
+ {
+ $repositories[]['packagist'] = false;
+ }
+
+ foreach ($this->repositories as $repository)
+ {
+ if (preg_match('#^' . get_preg_expression('url') . '$#iu', $repository))
+ {
+ $repositories[] = [
+ 'type' => 'composer',
+ 'url' => $repository,
+ ];
+ }
+ }
+
+ return $repositories;
+ }
+
+ /**
+ * Get the name of the json file used for the packages.
+ *
+ * @return string The json filename
+ */
+ protected function get_composer_ext_json_filename()
+ {
+ return $this->composer_filename;
+ }
+
+ /**
+ * Get extra dependencies required to install the packages
+ *
+ * @return array Array of composer dependencies
+ */
+ protected function get_extra_dependencies()
+ {
+ return [];
+ }
+
+ /**
+ * Sets the customs repositories
+ *
+ * @param array $repositories An array of composer repositories to use
+ */
+ public function set_repositories(array $repositories)
+ {
+ $this->repositories = $repositories;
+ }
+
+ /**
+ * Allow or disallow packagist
+ *
+ * @param boolean $packagist
+ */
+ public function set_packagist($packagist)
+ {
+ $this->packagist = $packagist;
+ }
+
+ /**
+ * Sets the name of the managed packages' json file
+ *
+ * @param string $composer_filename
+ */
+ public function set_composer_filename($composer_filename)
+ {
+ $this->composer_filename = $composer_filename;
+ }
+
+ /**
+ * Sets the location of the managed packages' vendors
+ *
+ * @param string $packages_vendor_dir
+ */
+ public function set_packages_vendor_dir($packages_vendor_dir)
+ {
+ $this->packages_vendor_dir = $packages_vendor_dir;
+ }
+
+ /**
+ * Sets the phpBB root path
+ *
+ * @param string $root_path
+ */
+ public function set_root_path($root_path)
+ {
+ $this->root_path = $root_path;
+ }
+
+ /**
+ * Change the current directory to phpBB root
+ */
+ protected function move_to_root()
+ {
+ if ($this->original_cwd === null)
+ {
+ $this->original_cwd = getcwd();
+ chdir($this->root_path);
+ }
+ }
+
+ /**
+ * Restore the current working directory if move_to_root() have been called
+ */
+ protected function restore_cwd()
+ {
+ if ($this->original_cwd)
+ {
+ chdir($this->original_cwd);
+ $this->original_cwd = null;
+ }
+ }
+
+ /**
+ * Wraps a callable in order to adjust the context needed by composer
+ *
+ * @param callable $callable
+ *
+ * @return mixed
+ */
+ protected function wrap(callable $callable)
+ {
+ // The composer installers works with a path relative to the current directory
+ $this->move_to_root();
+
+ // The composer installers uses some super globals
+ $super_globals = $this->request->super_globals_disabled();
+ $this->request->enable_super_globals();
+
+ try
+ {
+ return $callable();
+ }
+ finally
+ {
+ $this->restore_cwd();
+
+ if ($super_globals)
+ {
+ $this->request->disable_super_globals();
+ }
+ }
+ }
+}
diff --git a/phpBB/phpbb/composer/io/console_io.php b/phpBB/phpbb/composer/io/console_io.php
new file mode 100644
index 0000000000..5239b050bb
--- /dev/null
+++ b/phpBB/phpbb/composer/io/console_io.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\io;
+
+use Composer\IO\ConsoleIO;
+use phpbb\language\language;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class console_io extends ConsoleIO implements io_interface
+{
+ use translate_composer_trait;
+
+ /**
+ * Constructor.
+ *
+ * @param InputInterface $input The input instance
+ * @param OutputInterface $output The output instance
+ * @param HelperSet $helperSet The helperSet instance
+ * @param language $language Language object
+ */
+ public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet, language $language)
+ {
+ $this->language = $language;
+
+ parent::__construct($input, $output, $helperSet);
+ }
+}
diff --git a/phpBB/phpbb/composer/io/html_output_formatter.php b/phpBB/phpbb/composer/io/html_output_formatter.php
new file mode 100644
index 0000000000..588be30a21
--- /dev/null
+++ b/phpBB/phpbb/composer/io/html_output_formatter.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\io;
+
+class html_output_formatter extends \Composer\Console\HtmlOutputFormatter
+{
+ protected static $availableForegroundColors = [
+ 30 => 'black',
+ 31 => 'red',
+ 32 => 'green',
+ 33 => 'orange',
+ 34 => 'blue',
+ 35 => 'magenta',
+ 36 => 'cyan',
+ 37 => 'white',
+ ];
+
+ protected static $availableBackgroundColors = [
+ 40 => 'black',
+ 41 => 'red',
+ 42 => 'green',
+ 43 => 'yellow',
+ 44 => 'blue',
+ 45 => 'magenta',
+ 46 => 'cyan',
+ 47 => 'white',
+ ];
+
+ protected static $availableOptions
+ = [
+ 1 => 'bold',
+ 4 => 'underscore',
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function format($message)
+ {
+ $formatted = parent::format($message);
+
+ return preg_replace_callback("{[\033\e]\[([0-9;]+)m(.*?)[\033\e]\[[0-9;]+m}s", [$this, 'formatHtml'], $formatted);
+ }
+
+ protected function formatHtml($matches)
+ {
+ $out = '<span style="';
+ foreach (explode(';', $matches[1]) as $code)
+ {
+ if (isset(self::$availableForegroundColors[$code]))
+ {
+ $out .= 'color:' . self::$availableForegroundColors[$code] . ';';
+ }
+ else if (isset(self::$availableBackgroundColors[$code]))
+ {
+ $out .= 'background-color:' . self::$availableBackgroundColors[$code] . ';';
+ }
+ else if (isset(self::$availableOptions[$code]))
+ {
+ switch (self::$availableOptions[$code])
+ {
+ case 'bold':
+ $out .= 'font-weight:bold;';
+ break;
+
+ case 'underscore':
+ $out .= 'text-decoration:underline;';
+ break;
+ }
+ }
+ }
+
+ return $out . '">' . $matches[2] . '</span>';
+ }
+}
diff --git a/phpBB/phpbb/composer/io/io_interface.php b/phpBB/phpbb/composer/io/io_interface.php
new file mode 100644
index 0000000000..a1d41122cb
--- /dev/null
+++ b/phpBB/phpbb/composer/io/io_interface.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\io;
+
+use Composer\IO\IOInterface;
+
+interface io_interface extends IOInterface
+{
+ /**
+ * Returns the composer errors that occurred since the last tcall of the method.
+ *
+ * @return string
+ */
+ public function get_composer_error();
+}
diff --git a/phpBB/phpbb/composer/io/null_io.php b/phpBB/phpbb/composer/io/null_io.php
new file mode 100644
index 0000000000..9a667ca097
--- /dev/null
+++ b/phpBB/phpbb/composer/io/null_io.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\io;
+
+use Composer\IO\NullIO;
+
+class null_io extends NullIO implements io_interface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function get_composer_error()
+ {
+ return '';
+ }
+}
diff --git a/phpBB/phpbb/composer/io/translate_composer_trait.php b/phpBB/phpbb/composer/io/translate_composer_trait.php
new file mode 100644
index 0000000000..444cf38e6b
--- /dev/null
+++ b/phpBB/phpbb/composer/io/translate_composer_trait.php
@@ -0,0 +1,245 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\io;
+
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Trait to translate the composer Output
+ */
+trait translate_composer_trait
+{
+ /**
+ * @var \phpbb\language\language
+ */
+ protected $language;
+
+ /**
+ * @var array
+ */
+ protected $composer_error = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($messages, $newline = true, $verbosity = self::NORMAL)
+ {
+ $messages = (array) $messages;
+ $translated_messages = [];
+
+ foreach ($messages as $message)
+ {
+ $level = 0;
+ if (is_array($message))
+ {
+ $lang_key = $message[0];
+ $parameters = $message[1];
+ if (count($message) > 2)
+ {
+ $level = $message[2];
+ }
+ }
+ else
+ {
+ $lang_key = $message;
+ $parameters = [];
+ }
+
+ $message = trim($this->strip_format($lang_key), "\n\r");
+
+ if ($this->output->getVerbosity() === OutputInterface::VERBOSITY_DEBUG)
+ {
+ // Do nothing
+ }
+ else if (strpos($message, 'Deleting ') === 0)
+ {
+ $elements = explode(' ', $message);
+ $lang_key = 'COMPOSER_DELETING';
+ $parameters = [$elements[1]];
+ }
+
+ $translated_message = $this->language->lang_array($lang_key, $parameters);
+
+ switch ($level)
+ {
+ case 1:
+ $translated_message = '<info>' . $translated_message . '</info>';
+ break;
+ case 2:
+ $translated_message = '<comment>' . $translated_message . '</comment>';
+ break;
+ case 3:
+ $translated_message = '<warning>' . $translated_message . '</warning>';
+ break;
+ case 4:
+ $translated_message = '<error>' . $translated_message . '</error>';
+ break;
+ }
+
+ $translated_messages[] = $translated_message;
+ }
+
+ parent::write($translated_messages, $newline);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function writeError($messages, $newline = true, $verbosity = self::NORMAL)
+ {
+ $messages = (array) $messages;
+ $translated_messages = [];
+
+ foreach ($messages as $message)
+ {
+ $level = 0;
+ if (is_array($message))
+ {
+ $lang_key = $message[0];
+ $parameters = $message[1];
+ if (count($message) > 2)
+ {
+ $level = $message[2];
+ }
+ }
+ else
+ {
+ $lang_key = $message;
+ $parameters = [];
+ }
+
+ $message = trim($this->strip_format($lang_key), "\n\r");
+
+ if ($message === 'Your requirements could not be resolved to an installable set of packages.')
+ {
+ $this->composer_error[] = ['COMPOSER_ERROR_CONFLICT', []];
+
+ if ($this->output->getVerbosity() < OutputInterface::VERBOSITY_DEBUG)
+ {
+ continue;
+ }
+ }
+ else if (strpos($message, ' Problem ') === 0)
+ {
+ if ($this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE)
+ {
+ continue;
+ }
+
+ $lang_key = "\n" . htmlentities($message) . "\n";
+ $level = 4;
+ }
+ else if ($message === 'Updating dependencies')
+ {
+ $lang_key = 'COMPOSER_UPDATING_DEPENDENCIES';
+ $level = 1;
+ }
+ else if ($message === 'Loading composer repositories with package information')
+ {
+ $lang_key = 'COMPOSER_LOADING_REPOSITORIES';
+ $level = 1;
+ }
+ else if (strpos($message, 'could not be fully loaded, package information was loaded from the local cache and may be out of date') !== false)
+ {
+ $end_repo = strpos($message, 'could not be fully loaded, package information was loaded from the local cache and may be out of date');
+ $repo = substr($message, 0, $end_repo - 1);
+
+ $lang_key = 'COMPOSER_REPOSITORY_UNAVAILABLE';
+ $parameters = [$repo];
+ $level = 3;
+ }
+ else if (strpos($message, 'file could not be downloaded') !== false)
+ {
+ continue;
+ }
+ else if (strpos($message, ' - Installing ') === 0)
+ {
+ $elements = explode(' ', $message);
+ $lang_key = 'COMPOSER_INSTALLING_PACKAGE';
+ $parameters = [$elements[4], trim($elements[5], '()')];
+ }
+ else if ($message === 'Nothing to install or update')
+ {
+ $lang_key = 'COMPOSER_UPDATE_NOTHING';
+ $level = 3;
+ }
+ else if ($message === ' Downloading')
+ {
+ continue;
+ }
+ else if ($message === ' Loading from cache')
+ {
+ continue;
+ }
+ else if ($message === 'Writing lock file')
+ {
+ continue;
+ }
+ else if ($message === ' Extracting archive')
+ {
+ continue;
+ }
+ else if (empty($message))
+ {
+ continue;
+ }
+
+ $translated_message = $this->language->lang_array($lang_key, $parameters);
+
+ switch ($level)
+ {
+ case 1:
+ $translated_message = '<info>' . $translated_message . '</info>';
+ break;
+ case 2:
+ $translated_message = '<comment>' . $translated_message . '</comment>';
+ break;
+ case 3:
+ $translated_message = '<warning>' . $translated_message . '</warning>';
+ break;
+ case 4:
+ $translated_message = '<error>' . $translated_message . '</error>';
+ break;
+ }
+
+ $translated_messages[] = $translated_message;
+ }
+
+ parent::writeError($translated_messages, $newline);
+ }
+
+ public function get_composer_error()
+ {
+ $error = '';
+ foreach ($this->composer_error as $error_line)
+ {
+ $error .= $this->language->lang_array($error_line[0], $error_line[1]);
+ $error .= "\n";
+ }
+
+ $this->composer_error = [];
+
+ return $error;
+ }
+
+ protected function strip_format($message)
+ {
+ return str_replace([
+ '<info>', '</info>',
+ '<warning>', '</warning>',
+ '<comment>', '</comment>',
+ '<error>', '</error>',
+ ], '', $message);
+ }
+}
diff --git a/phpBB/phpbb/composer/io/web_io.php b/phpBB/phpbb/composer/io/web_io.php
new file mode 100644
index 0000000000..4eab3d099a
--- /dev/null
+++ b/phpBB/phpbb/composer/io/web_io.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer\io;
+
+use Composer\IO\BufferIO;
+use phpbb\language\language;
+use Symfony\Component\Console\Formatter\OutputFormatterInterface;
+use Symfony\Component\Console\Output\StreamOutput;
+
+class web_io extends BufferIO implements io_interface
+{
+ use translate_composer_trait;
+
+ /**
+ * @param language $language Language object
+ * @param string $input Input string
+ * @param int $verbosity Verbosity level
+ * @param OutputFormatterInterface $formatter Output formatter
+ */
+ public function __construct(language $language, $input = '', $verbosity = StreamOutput::VERBOSITY_NORMAL, OutputFormatterInterface $formatter = null)
+ {
+ $this->language = $language;
+
+ parent::__construct($input, $verbosity, $formatter);
+ }
+}
diff --git a/phpBB/phpbb/composer/manager.php b/phpBB/phpbb/composer/manager.php
new file mode 100644
index 0000000000..43ea8a2503
--- /dev/null
+++ b/phpBB/phpbb/composer/manager.php
@@ -0,0 +1,332 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer;
+
+use Composer\IO\IOInterface;
+use phpbb\cache\driver\driver_interface;
+use phpbb\composer\exception\runtime_exception;
+
+/**
+ * Class to manage packages through composer.
+ */
+class manager implements manager_interface
+{
+ /**
+ * @var installer Composer packages installer
+ */
+ protected $installer;
+
+ /**
+ * @var driver_interface Cache instance
+ */
+ protected $cache;
+
+ /**
+ * @var string Type of packages (phpbb-packages per example)
+ */
+ protected $package_type;
+
+ /**
+ * @var string Prefix used for the exception's language string
+ */
+ protected $exception_prefix;
+
+ /**
+ * @var array Caches the managed packages list (for the current type)
+ */
+ private $managed_packages;
+
+ /**
+ * @var array Caches the managed packages list (for all phpBB types)
+ */
+ private $all_managed_packages;
+
+ /**
+ * @var array Caches the available packages list
+ */
+ private $available_packages;
+
+ /**
+ * @param installer $installer Installer object
+ * @param driver_interface $cache Cache object
+ * @param string $package_type Composer type of managed packages
+ * @param string $exception_prefix Exception prefix to use
+ */
+ public function __construct(installer $installer, driver_interface $cache, $package_type, $exception_prefix)
+ {
+ $this->installer = $installer;
+ $this->cache = $cache;
+ $this->package_type = $package_type;
+ $this->exception_prefix = $exception_prefix;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function install(array $packages, IOInterface $io = null)
+ {
+ $packages = $this->normalize_version($packages);
+
+ $already_managed = array_intersect(array_keys($this->get_managed_packages()), array_keys($packages));
+ if (count($already_managed) !== 0)
+ {
+ throw new runtime_exception($this->exception_prefix, 'ALREADY_INSTALLED', [implode('|', $already_managed)]);
+ }
+
+ $this->pre_install($packages, $io);
+
+ $managed_packages = array_merge($this->get_all_managed_packages(), $packages);
+ ksort($managed_packages);
+
+ $this->installer->install($managed_packages, array_keys($packages), $io);
+
+ $this->post_install($packages, $io);
+
+ $this->managed_packages = null;
+ }
+
+ /**
+ * Hook called before installing the packages
+ *
+ * @param array $packages Packages to update.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param IOInterface $io IO object used for the output
+ */
+ protected function pre_install(array $packages, IOInterface $io = null)
+ {
+ }
+
+ /**
+ * Hook called after installing the packages
+ *
+ * @param array $packages Packages to update.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param IOInterface $io IO object used for the output
+ */
+ protected function post_install(array $packages, IOInterface $io = null)
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function update(array $packages, IOInterface $io = null)
+ {
+ $packages = $this->normalize_version($packages);
+
+ $not_managed = array_diff_key($packages, $this->get_managed_packages());
+ if (count($not_managed) !== 0)
+ {
+ throw new runtime_exception($this->exception_prefix, 'NOT_MANAGED', [implode('|', array_keys($not_managed))]);
+ }
+
+ $this->pre_update($packages, $io);
+
+ $managed_packages = array_merge($this->get_all_managed_packages(), $packages);
+ ksort($managed_packages);
+
+ $this->installer->install($managed_packages, array_keys($packages), $io);
+
+ $this->post_update($packages, $io);
+ }
+
+ /**
+ * Hook called before updating the packages
+ *
+ * @param array $packages Packages to update.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param IOInterface $io IO object used for the output
+ */
+ protected function pre_update(array $packages, IOInterface $io = null)
+ {
+ }
+
+ /**
+ * Hook called after updating the packages
+ *
+ * @param array $packages Packages to update.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param IOInterface $io IO object used for the output
+ */
+ protected function post_update(array $packages, IOInterface $io = null)
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove(array $packages, IOInterface $io = null)
+ {
+ $packages = $this->normalize_version($packages);
+
+ $not_managed = array_diff_key($packages, $this->get_managed_packages());
+ if (count($not_managed) !== 0)
+ {
+ throw new runtime_exception($this->exception_prefix, 'NOT_MANAGED', [implode('|', array_keys($not_managed))]);
+ }
+
+ $this->pre_remove($packages, $io);
+
+ $managed_packages = array_diff_key($this->get_all_managed_packages(), $packages);
+ ksort($managed_packages);
+
+ $this->installer->install($managed_packages, array_keys($packages), $io);
+
+ $this->post_remove($packages, $io);
+
+ $this->managed_packages = null;
+ }
+
+ /**
+ * Hook called before removing the packages
+ *
+ * @param array $packages Packages to update.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param IOInterface $io IO object used for the output
+ */
+ protected function pre_remove(array $packages, IOInterface $io = null)
+ {
+ }
+
+ /**
+ * Hook called after removing the packages
+ *
+ * @param array $packages Packages to update.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param IOInterface $io IO object used for the output
+ */
+ protected function post_remove(array $packages, IOInterface $io = null)
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function is_managed($package)
+ {
+ return array_key_exists($package, $this->get_managed_packages());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get_managed_packages()
+ {
+ if ($this->managed_packages === null)
+ {
+ $this->managed_packages = $this->installer->get_installed_packages($this->package_type);
+ }
+
+ return $this->managed_packages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get_all_managed_packages()
+ {
+ if ($this->all_managed_packages === null)
+ {
+ $this->all_managed_packages = $this->installer->get_installed_packages(explode(',', installer::PHPBB_TYPES));
+ }
+
+ return $this->all_managed_packages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get_available_packages()
+ {
+ if ($this->available_packages === null)
+ {
+ $this->available_packages = $this->cache->get('_composer_' . $this->package_type . '_available');
+
+ if (!$this->available_packages)
+ {
+ $this->available_packages = $this->installer->get_available_packages($this->package_type);
+ $this->cache->put('_composer_' . $this->package_type . '_available', $this->available_packages, 24*60*60);
+ }
+ }
+
+ return $this->available_packages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset_cache()
+ {
+ $this->cache->destroy('_composer_' . $this->package_type . '_available');
+
+ $this->available_packages = null;
+ $this->managed_packages = null;
+ $this->all_managed_packages = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start_managing($package, $io)
+ {
+ throw new \phpbb\exception\runtime_exception('COMPOSER_UNSUPPORTED_OPERATION', (array) $this->package_type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function check_requirements()
+ {
+ return $this->installer->check_requirements();
+ }
+
+ /**
+ * Normalize a packages/version array. Every entry can have 3 different forms:
+ * - $package => $version
+ * - $indice => $package:$version
+ * - $indice => $package
+ * They are converted to he form:
+ * - $package => $version ($version is set to '*' for the third form)
+ *
+ * @param array $packages
+ *
+ * @return array
+ */
+ protected function normalize_version(array $packages)
+ {
+ $normalized_packages = [];
+
+ foreach ($packages as $package => $version)
+ {
+ if (is_numeric($package))
+ {
+ if (strpos($version, ':') !== false)
+ {
+ $parts = explode(':', $version);
+ $normalized_packages[$parts[0]] = $parts[1];
+ }
+ else
+ {
+ $normalized_packages[$version] = '*';
+ }
+ }
+ else
+ {
+ $normalized_packages[$package] = $version;
+ }
+ }
+
+ return $normalized_packages;
+ }
+}
diff --git a/phpBB/phpbb/composer/manager_interface.php b/phpBB/phpbb/composer/manager_interface.php
new file mode 100644
index 0000000000..3cb401f2b6
--- /dev/null
+++ b/phpBB/phpbb/composer/manager_interface.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\composer;
+
+use Composer\IO\IOInterface;
+use phpbb\composer\exception\runtime_exception;
+
+/**
+ * Class to manage packages through composer.
+ */
+interface manager_interface
+{
+ /**
+ * Installs (if necessary) a set of packages
+ *
+ * @param array $packages Packages to install.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param IOInterface $io IO object used for the output
+ *
+ * @throws runtime_exception
+ */
+ public function install(array $packages, IOInterface $io = null);
+
+ /**
+ * Updates or installs a set of packages
+ *
+ * @param array $packages Packages to update.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param IOInterface $io IO object used for the output
+ *
+ * @throws runtime_exception
+ */
+ public function update(array $packages, IOInterface $io = null);
+
+ /**
+ * Removes a set of packages
+ *
+ * @param array $packages Packages to remove.
+ * Each entry may be a name or an array associating a version constraint to a name
+ * @param IOInterface $io IO object used for the output
+ *
+ * @throws runtime_exception
+ */
+ public function remove(array $packages, IOInterface $io = null);
+
+ /**
+ * Tells whether or not a package is managed by Composer.
+ *
+ * @param string $packages Package name
+ *
+ * @return bool
+ */
+ public function is_managed($packages);
+
+ /**
+ * Returns the list of managed packages for the current type
+ *
+ * @return array The managed packages associated to their version.
+ */
+ public function get_managed_packages();
+
+ /**
+ * Returns the list of managed packages for all phpBB types
+ *
+ * @return array The managed packages associated to their version.
+ */
+ public function get_all_managed_packages();
+
+ /**
+ * Returns the list of available packages
+ *
+ * @return array The name of the available packages, associated to their definition. Ordered by name.
+ */
+ public function get_available_packages();
+
+ /**
+ * Reset the cache
+ */
+ public function reset_cache();
+
+ /**
+ * Start managing a manually installed package
+ *
+ * Remove a package installed manually and reinstall it using composer.
+ *
+ * @param string $package Package to manage
+ * @param IOInterface $io IO object used for the output
+ *
+ * @throws runtime_exception
+ */
+ public function start_managing($package, $io);
+
+ /**
+ * Checks the requirements of the manager and returns true if it can be used.
+ *
+ * @return bool
+ */
+ public function check_requirements();
+}