diff --git a/phpBB/config/default/container/services_console.yml b/phpBB/config/default/container/services_console.yml index a1cc599aa7..3b05ef9d46 100644 --- a/phpBB/config/default/container/services_console.yml +++ b/phpBB/config/default/container/services_console.yml @@ -154,6 +154,7 @@ services: arguments: - @user - @ext.composer.manager + - @language tags: - { name: console.command } @@ -171,6 +172,7 @@ services: arguments: - @user - @ext.composer.manager + - @language tags: - { name: console.command } @@ -188,6 +190,7 @@ services: arguments: - @user - @ext.composer.manager + - @language tags: - { name: console.command } diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index 537cc1a5d1..7f341abd07 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -242,6 +242,13 @@ $lang = array_merge($lang, array( 'COLOUR_SWATCH' => 'Web-safe colour swatch', 'COMPOSER_UNSUPPORTED_OPERATION' => 'Operation unsupported for the package type ā€œ%sā€.', + 'COMPOSER_UPDATING_DEPENDENCIES' => 'Updating packages', + 'COMPOSER_LOADING_REPOSITORIES' => 'Loading remote packages information', + 'COMPOSER_ERROR_CONFLICT' => 'Your requirements could not be resolved to an installable set of packages.', + 'COMPOSER_REPOSITORY_UNAVAILABLE' => 'An error occurred when fetching the repository %s.', + 'COMPOSER_INSTALLING_PACKAGE' => ' - Installing %1$s (%2$s)', + 'COMPOSER_DELETING' => ' - Deleting %s', + 'COMPOSER_UPDATE_NOTHING' => 'Nothing to update', 'CONFIG_UPDATED' => 'Configuration updated successfully.', 'CRON_LOCK_ERROR' => 'Could not obtain cron lock.', diff --git a/phpBB/phpbb/composer/extension_manager.php b/phpBB/phpbb/composer/extension_manager.php index 360e2fca5b..a86b91fffa 100644 --- a/phpBB/phpbb/composer/extension_manager.php +++ b/phpBB/phpbb/composer/extension_manager.php @@ -13,6 +13,7 @@ namespace phpbb\composer; +use Composer\IO\IOInterface; use phpbb\composer\exception\managed_with_clean_error_exception; use phpbb\composer\exception\managed_with_enable_error_exception; use phpbb\composer\exception\managed_with_error_exception; @@ -54,7 +55,7 @@ class extension_manager extends manager /** * {@inheritdoc} */ - public function install(array $packages) + public function install(array $packages, IOInterface $io = null) { $packages = $this->normalize_version($packages); @@ -70,7 +71,7 @@ class extension_manager extends manager throw new runtime_exception($this->exception_prefix, 'ALREADY_INSTALLED_MANUALLY', [implode('|', array_keys($installed_manually))]); } - $this->do_install($packages); + $this->do_install($packages, $io); } /** diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index 94cf0c634c..498fb6cdcb 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -15,7 +15,7 @@ namespace phpbb\composer; use Composer\Composer; use Composer\Factory; -use Composer\IO\BufferIO; +use Composer\IO\IOInterface; use Composer\IO\NullIO; use Composer\Json\JsonFile; use Composer\Package\CompletePackage; @@ -25,7 +25,6 @@ use Composer\Repository\RepositoryInterface; use Composer\Util\RemoteFilesystem; use phpbb\config\config; use phpbb\exception\runtime_exception; -use Symfony\Component\Console\Output\OutputInterface; /** * Class to install packages through composer while freezing core dependencies. @@ -85,19 +84,27 @@ class installer * @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) + public function install(array $packages, $whitelist, IOInterface $io = null) { + if (!$io) + { + $io = new NullIO(); + } + $this->generate_ext_json_file($packages); $original_vendor_dir = getenv('COMPOSER_VENDOR_DIR'); putenv('COMPOSER_VENDOR_DIR=' . $this->root_path . $this->packages_vendor_dir); - $io = new BufferIO('', OutputInterface::VERBOSITY_DEBUG); $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) @@ -112,17 +119,12 @@ class installer ->setRunScripts(false) ->setDryRun(false); + $result = 0; try { - $install->run(); - $output = $io->getOutput(); - $error_pos = strpos($output, 'Your requirements could not be resolved to an installable set of packages.'); - - if ($error_pos) - { - // TODO Extract the precise error and use language string - throw new \RuntimeException(substr($output, $error_pos)); - } + $result = $install->run(); + //$output = $io->getOutput(); + //$error_pos = strpos($output, 'Your requirements could not be resolved to an installable set of packages.'); } catch (\Exception $e) { @@ -132,6 +134,11 @@ class installer { putenv('COMPOSER_VENDOR_DIR=' . $original_vendor_dir); } + + if ($result !== 0) + { + throw new runtime_exception($io->get_composer_error(), []); + } } /** @@ -154,7 +161,7 @@ class installer $composer = Factory::create($io, $this->get_composer_ext_json_filename(), false); $installed = []; - $packages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages(); + $packages = $composer->getPackage()->getRequires();//$composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages(); foreach ($packages as $package) { @@ -282,6 +289,7 @@ class installer $packages), 'replace' => $core_packages, 'repositories' => $this->get_composer_repositories(), + 'minimum-stability' => 'dev', ]; $json_file = new JsonFile($this->get_composer_ext_json_filename()); diff --git a/phpBB/phpbb/composer/io/console_io.php b/phpBB/phpbb/composer/io/console_io.php new file mode 100644 index 0000000000..bf1754d4c0 --- /dev/null +++ b/phpBB/phpbb/composer/io/console_io.php @@ -0,0 +1,217 @@ + + * @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 +{ + /** + * @var language + */ + protected $language; + + /** + * @var array + */ + protected $composer_error; + + /** + * Constructor. + * + * @param InputInterface $input The input instance + * @param OutputInterface $output The output instance + * @param HelperSet $helperSet The helperSet instance + */ + public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet, language $language) + { + $this->language = $language; + + parent::__construct($input, $output, $helperSet); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = true) + { + $messages = (array) $messages; + $translated_messages = []; + + foreach ($messages as $message) + { + $lang_key = $message; + $parameters = []; + $level = 0; + + $message = trim(strip_tags($message), "\n\r"); + + if (strpos($message, 'Deleting ') === 0) + { + $elements = explode(' ', $message); + $lang_key = 'COMPOSER_DELETING'; + $parameters = [$elements[1]]; + } + + //$translated_message = $this->language->lang_array($lang_key, $parameters); + $translated_message = call_user_func_array([$this->language, 'lang'], array_merge((array)$lang_key, $parameters)); + + switch ($level) + { + case 1: + $translated_message = '' . $translated_message . ''; + break; + case 2: + $translated_message = '' . $translated_message . ''; + break; + case 3: + $translated_message = '' . $translated_message . ''; + break; + case 4: + $translated_message = '' . $translated_message . ''; + break; + } + + $translated_messages[] = $translated_message; + } + + parent::write($translated_messages, $newline); + } + + /** + * {@inheritdoc} + */ + public function writeError($messages, $newline = true) + { + $messages = (array) $messages; + $translated_messages = []; + + foreach ($messages as $message) + { + $lang_key = $message; + $parameters = []; + $level = 0; + + $message = trim(strip_tags($message), "\n\r"); + + if (strpos($message, ' Problem ') === 0) + { + if ($this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) + { + continue; + } + + $lang_key = "\n" . $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 ($message === 'Your requirements could not be resolved to an installable set of packages.') + { + $this->composer_error[] = ['COMPOSER_ERROR_CONFLICT', []]; + continue; + } + 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 (empty($message)) + { + continue; + } + + //$translated_message = $this->language->lang_array($lang_key, $parameters); + $translated_message = call_user_func_array([$this->language, 'lang'], array_merge((array)$lang_key, $parameters)); + + switch ($level) + { + case 1: + $translated_message = '' . $translated_message . ''; + break; + case 2: + $translated_message = '' . $translated_message . ''; + break; + case 3: + $translated_message = '' . $translated_message . ''; + break; + case 4: + $translated_message = '' . $translated_message . ''; + 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 .= call_user_func_array([$this->language, 'lang'], array_merge((array)$error_line[0], $error_line[1])); + $error .= "\n"; + } + + $this->composer_error = []; + + return $error; + } +} diff --git a/phpBB/phpbb/composer/manager.php b/phpBB/phpbb/composer/manager.php index ed4ce6528b..5d942c11d0 100644 --- a/phpBB/phpbb/composer/manager.php +++ b/phpBB/phpbb/composer/manager.php @@ -13,6 +13,7 @@ namespace phpbb\composer; +use Composer\IO\IOInterface; use phpbb\composer\exception\runtime_exception; /** @@ -65,7 +66,7 @@ class manager implements manager_interface /** * {@inheritdoc} */ - public function install(array $packages) + public function install(array $packages, IOInterface $io = null) { $packages = $this->normalize_version($packages); @@ -75,20 +76,21 @@ class manager implements manager_interface throw new runtime_exception($this->exception_prefix, 'ALREADY_INSTALLED', [implode('|', $already_managed)]); } - $this->do_install($packages); + $this->do_install($packages, $io); } /** * Really install the packages. * * @param array $packages Packages to install. + * @param IOInterface $io IO object used for the output */ - protected function do_install($packages) + protected function do_install($packages, IOInterface $io = null) { $managed_packages = array_merge($this->get_all_managed_packages(), $packages); ksort($managed_packages); - $this->installer->install($managed_packages, array_keys($packages)); + $this->installer->install($managed_packages, array_keys($packages), $io); $this->managed_packages = null; } @@ -96,7 +98,7 @@ class manager implements manager_interface /** * {@inheritdoc} */ - public function update(array $packages) + public function update(array $packages, IOInterface $io = null) { $packages = $this->normalize_version($packages); @@ -110,13 +112,13 @@ class manager implements manager_interface $managed_packages = array_merge($this->get_all_managed_packages(), $packages); ksort($managed_packages); - $this->installer->install($managed_packages, array_keys($packages)); + $this->installer->install($managed_packages, array_keys($packages), $io); } /** * {@inheritdoc} */ - public function remove(array $packages) + public function remove(array $packages, IOInterface $io = null) { $packages = $this->normalize_version($packages); @@ -130,7 +132,7 @@ class manager implements manager_interface $managed_packages = array_diff_key($this->get_all_managed_packages(), $packages); ksort($managed_packages); - $this->installer->install($managed_packages, array_keys($packages)); + $this->installer->install($managed_packages, array_keys($packages), $io); $this->managed_packages = null; } diff --git a/phpBB/phpbb/composer/manager_interface.php b/phpBB/phpbb/composer/manager_interface.php index 13f9498b35..b5d0177342 100644 --- a/phpBB/phpbb/composer/manager_interface.php +++ b/phpBB/phpbb/composer/manager_interface.php @@ -6,6 +6,7 @@ * Time: 23:12 */ namespace phpbb\composer; +use Composer\IO\IOInterface; use phpbb\composer\exception\runtime_exception; @@ -19,30 +20,33 @@ interface manager_interface * * @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); + 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); + 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); + public function remove(array $packages, IOInterface $io = null); /** * Tells whether or not a package is managed by Composer. diff --git a/phpBB/phpbb/console/command/extension/install.php b/phpBB/phpbb/console/command/extension/install.php index 4d83fe2cef..aa9d5a6867 100644 --- a/phpBB/phpbb/console/command/extension/install.php +++ b/phpBB/phpbb/console/command/extension/install.php @@ -13,8 +13,10 @@ namespace phpbb\console\command\extension; -use phpbb\composer\manager; +use phpbb\composer\io\console_io; use phpbb\composer\manager_interface; +use phpbb\language\language; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -27,9 +29,15 @@ class install extends \phpbb\console\command\command */ protected $manager; - public function __construct(\phpbb\user $user, manager_interface $manager) + /** + * @var \phpbb\language\language + */ + protected $language; + + public function __construct(\phpbb\user $user, manager_interface $manager, language $language) { $this->manager = $manager; + $this->language = $language; parent::__construct($user); } @@ -60,11 +68,13 @@ class install extends \phpbb\console\command\command */ protected function execute(InputInterface $input, OutputInterface $output) { - $io = new SymfonyStyle($input, $output); + $output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow')); + $io = new SymfonyStyle($input, $output); + $composer_io = new console_io($input, $output, $this->getHelperSet(), $this->language); $extensions = $input->getArgument('extensions'); - $this->manager->install($extensions); + $this->manager->install($extensions, $composer_io); $io->success('All extensions installed'); diff --git a/phpBB/phpbb/console/command/extension/remove.php b/phpBB/phpbb/console/command/extension/remove.php index 16a3ad263f..d1f59dec5c 100644 --- a/phpBB/phpbb/console/command/extension/remove.php +++ b/phpBB/phpbb/console/command/extension/remove.php @@ -13,8 +13,10 @@ namespace phpbb\console\command\extension; -use phpbb\composer\manager; +use phpbb\composer\io\console_io; use phpbb\composer\manager_interface; +use phpbb\language\language; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -27,9 +29,15 @@ class remove extends \phpbb\console\command\command */ protected $manager; - public function __construct(\phpbb\user $user, manager_interface $manager) + /** + * @var \phpbb\language\language + */ + protected $language; + + public function __construct(\phpbb\user $user, manager_interface $manager, language $language) { $this->manager = $manager; + $this->language = $language; parent::__construct($user); } @@ -60,11 +68,13 @@ class remove extends \phpbb\console\command\command */ protected function execute(InputInterface $input, OutputInterface $output) { - $io = new SymfonyStyle($input, $output); + $output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow')); + $io = new SymfonyStyle($input, $output); + $composer_io = new console_io($input, $output, $this->getHelperSet(), $this->language); $extensions = $input->getArgument('extensions'); - $this->manager->remove($extensions); + $this->manager->remove($extensions, $composer_io); $io->success('All extensions removed'); diff --git a/phpBB/phpbb/console/command/extension/update.php b/phpBB/phpbb/console/command/extension/update.php index e3f8935985..7a38294ed4 100644 --- a/phpBB/phpbb/console/command/extension/update.php +++ b/phpBB/phpbb/console/command/extension/update.php @@ -13,8 +13,10 @@ namespace phpbb\console\command\extension; -use phpbb\composer\manager; +use phpbb\composer\io\console_io; use phpbb\composer\manager_interface; +use phpbb\language\language; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -27,9 +29,15 @@ class update extends \phpbb\console\command\command */ protected $manager; - public function __construct(\phpbb\user $user, manager_interface $manager) + /** + * @var \phpbb\language\language + */ + protected $language; + + public function __construct(\phpbb\user $user, manager_interface $manager, language $language) { $this->manager = $manager; + $this->language = $language; parent::__construct($user); } @@ -60,11 +68,13 @@ class update extends \phpbb\console\command\command */ protected function execute(InputInterface $input, OutputInterface $output) { - $io = new SymfonyStyle($input, $output); + $output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow')); + $io = new SymfonyStyle($input, $output); + $composer_io = new console_io($input, $output, $this->getHelperSet(), $this->language); $extensions = $input->getArgument('extensions'); - $this->manager->update($extensions); + $this->manager->update($extensions, $composer_io); $io->success('All extensions updated');