diff --git a/lib/composer/bamarni/composer-bin-plugin/LICENSE b/lib/composer/bamarni/composer-bin-plugin/LICENSE
new file mode 100644
index 0000000000000..b5b6c2f85d417
--- /dev/null
+++ b/lib/composer/bamarni/composer-bin-plugin/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 Bilal Amarni
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/lib/composer/bamarni/composer-bin-plugin/README.md b/lib/composer/bamarni/composer-bin-plugin/README.md
new file mode 100644
index 0000000000000..005e4e1b49601
--- /dev/null
+++ b/lib/composer/bamarni/composer-bin-plugin/README.md
@@ -0,0 +1,223 @@
+# Composer bin plugin — Isolate your bin dependencies
+
+[![Package version](http://img.shields.io/packagist/v/bamarni/composer-bin-plugin.svg?style=flat-square)](https://packagist.org/packages/bamarni/composer-bin-plugin)
+[![Build Status](https://img.shields.io/travis/bamarni/composer-bin-plugin.svg?branch=master&style=flat-square)](https://travis-ci.org/bamarni/composer-bin-plugin?branch=master)
+[![License](https://img.shields.io/badge/license-MIT-red.svg?style=flat-square)](LICENSE)
+
+
+## Table of Contents
+
+1. [Why?](#why)
+1. [How does this plugin work?](#how-does-this-plugin-work)
+1. [Installation](#installation)
+1. [Usage](#usage)
+ 1. [Example](#example)
+ 1. [The `all` bin namespace](#the-all-bin-namespace)
+ 1. [What happens when symlink conflicts?](#what-happens-when-symlink-conflicts)
+1. [Tips](#tips)
+ 1. [Auto-installation](#auto-installation)
+ 1. [Disable links](#disable-links)
+ 1. [Change directory](#change-directory)
+ 1. [Reduce clutter](#reduce-clutter)
+1. [Related plugins](#related-plugins)
+
+
+## Why?
+
+In PHP, with Composer, your dependencies are flattened, which might result in conflicts. Most of the time those
+conflicts are legitimate and should be properly resolved. However you may have dev tools that you want to manage
+via Composer for convenience, but should not influence your project dependencies or for which conflicts don't make
+sense. For example: [EtsyPhan][1] and [PhpMetrics][2]. Installing one of those static analysis tools should not change
+your application dependencies, neither should it be a problem to install both of them at the same time.
+
+
+## How does this plugin work?
+
+It allows you to install your *bin vendors* in isolated locations, and still link them to your
+[bin-dir][3] (if you want to).
+
+This is done by registering a `bin` command, which can be used to run Composer commands inside a namespace.
+
+
+## Installation
+
+ # Globally
+ $ composer global require bamarni/composer-bin-plugin
+
+ # In your project
+ $ composer require --dev bamarni/composer-bin-plugin
+
+
+## Usage
+
+ $ composer bin [namespace] [composer_command]
+ $ composer global bin [namespace] [composer_command]
+
+
+### Example
+
+Let's install [Behat][4] and [PhpSpec][5] inside a `bdd` bin namespace, [EtsyPhan][1] in `etsy-phan` and [PhpMetrics][2]
+in `phpmetrics`:
+
+ $ composer bin bdd require behat/behat phpspec/phpspec
+ $ composer bin etsy-phan require etsy/phan
+ $ composer bin phpmetrics require phpmetrics/phpmetrics
+
+This command creates the following directory structure :
+
+ .
+ ├── composer.json
+ ├── composer.lock
+ ├── vendor/
+ │ └── bin
+ │ ├── behat -> ../../vendor-bin/bdd/vendor/behat/behat/bin/behat
+ │ ├── phpspec -> ../../vendor-bin/bdd/vendor/phpspec/phpspec/bin/phpspec
+ │ ├── phan -> ../../vendor-bin/etsy-phan/vendor/etsy/phan/phan
+ │ └── phpmetrics -> ../../vendor-bin/phpmetrics/vendor/phpmetrics/phpmetrics/bin/phpmetrics
+ └── vendor-bin/
+ └── bdd
+ │ ├── composer.json
+ │ ├── composer.lock
+ │ └── vendor/
+ │ ├── behat/
+ │ ├── phpspec/
+ │ └── ...
+ └── etsy-phan
+ │ ├── composer.json
+ │ ├── composer.lock
+ │ └── vendor/
+ │ ├── etsy/
+ │ └── ...
+ └── phpmetrics
+ ├── composer.json
+ ├── composer.lock
+ └── vendor/
+ ├── phpmetrics/
+ └── ...
+
+
+You can continue to run `vendor/bin/behat`, `vendor/bin/phpspec` and co. as before but they will be properly isolated.
+Also, `composer.json` and `composer.lock` files in each namespace will allow you to take advantage of automated dependency
+management as normally provided by Composer.
+
+### The `all` bin namespace
+
+The `all` bin namespace has a special meaning. It runs a command for all existing bin namespaces. For instance, the
+following command would update all your bins :
+
+ $ composer bin all update
+ Changed current directory to vendor-bin/phpspec
+ Loading composer repositories with package information
+ Updating dependencies (including require-dev)
+ Nothing to install or update
+ Generating autoload files
+ Changed current directory to vendor-bin/phpunit
+ Loading composer repositories with package information
+ Updating dependencies (including require-dev)
+ Nothing to install or update
+ Generating autoload files
+
+
+### What happens when symlink conflicts?
+
+If we take the case described in the [example section](#example), there might be more binaries linked due to
+the dependencies. For example [PhpMetrics][2] depends on [Nikic PHP-Parser][6] and as such you will also have `php-parse`
+in `.vendor/bin/`:
+
+ .
+ ├── composer.json
+ ├── composer.lock
+ ├── vendor/
+ │ └── bin
+ │ ├── phpmetrics -> ../../vendor-bin/phpmetrics/vendor/phpmetrics/phpmetrics/bin/phpmetrics
+ │ └── php-parse -> ../../vendor-bin/phpmetrics/vendor/nikic/PHP-Parser/bin/php-parsee
+ └── vendor-bin/
+ └── phpmetrics
+ ├── composer.json
+ ├── composer.lock
+ └── vendor/
+ ├── phpmetrics/
+ ├── nikic/
+ └── ...
+
+But what happens if another bin-namespace has a dependency using [Nikic PHP-Parser][6]? In that situation symlinks would
+collides and are not created (only the colliding ones).
+
+
+## Tips
+
+### Auto-installation
+
+For convenience, you can add the following script in your `composer.json` :
+
+```json
+{
+ "scripts": {
+ "bin": "echo 'bin not installed'",
+ "post-install-cmd": ["@composer bin all install --ansi"],
+ "post-update-cmd": ["@composer bin all update --ansi"]
+ }
+}
+```
+
+This makes sure all your bins are installed during `composer install` and updated during `composer update`.
+
+
+### Disable links
+
+By default, binaries of the sub namespaces are linked to the root one like described in [example](#example). If you
+wish to disable that behaviour, you can do so by adding a little setting in the extra config:
+
+```json
+{
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": false
+ }
+ }
+}
+```
+
+
+### Change directory
+
+By default, the packages are looked for in the `vendor-bin` directory. The location can be changed using `target-directory` value in the extra config:
+
+```json
+{
+ "extra": {
+ "bamarni-bin": {
+ "target-directory": "ci/vendor"
+ }
+ }
+}
+```
+
+### Reduce clutter
+
+You can add following line to your `.gitignore` file in order to avoid committing dependencies of your tools.
+
+```.gitignore
+/vendor-bin/**/vendor
+```
+
+Updating each tool can create many not legible changes in `composer.lock` files. You can use `.gitattributes` file in order
+to inform git that it shouldn't show diffs of `composer.lock` files.
+
+```.gitattributes
+vendor-bin/**/composer.lock binary
+```
+
+## Related plugins
+
+* [theofidry/composer-inheritance-plugin][7]: Opinionated version of [Wikimedia composer-merge-plugin][8] to work in pair with this plugin.
+
+
+[1]: https://github.com/etsy/phan
+[2]: https://github.com/phpmetrics/PhpMetrics
+[3]: https://getcomposer.org/doc/06-config.md#bin-dir
+[4]: http://behat.org
+[5]: http://phpspec.net
+[6]: https://github.com/nikic/PHP-Parser
+[7]: https://github.com/theofidry/composer-inheritance-plugin
+[8]: https://github.com/wikimedia/composer-merge-plugin
diff --git a/lib/composer/bamarni/composer-bin-plugin/composer.json b/lib/composer/bamarni/composer-bin-plugin/composer.json
new file mode 100644
index 0000000000000..89752c4531cf8
--- /dev/null
+++ b/lib/composer/bamarni/composer-bin-plugin/composer.json
@@ -0,0 +1,46 @@
+{
+ "name": "bamarni/composer-bin-plugin",
+ "type": "composer-plugin",
+ "description": "No conflicts for your bin dependencies",
+ "keywords": [
+ "composer",
+ "dependency",
+ "tool",
+ "isolation",
+ "conflict",
+ "executable"
+ ],
+ "license": "MIT",
+ "require": {
+ "php": "^5.5.9 || ^7.0 || ^8.0",
+ "composer-plugin-api": "^1.0 || ^2.0"
+ },
+ "require-dev": {
+ "composer/composer": "^1.0 || ^2.0",
+ "symfony/console": "^2.5 || ^3.0 || ^4.0"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "class": "Bamarni\\Composer\\Bin\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Bamarni\\Composer\\Bin\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Bamarni\\Composer\\Bin\\Tests\\": "tests"
+ }
+ },
+ "scripts": {
+ "post-install-cmd": [
+ "@composer bin phpunit install"
+ ],
+ "post-update-cmd": [
+ "@post-install-cmd"
+ ]
+ }
+}
diff --git a/lib/composer/bamarni/composer-bin-plugin/src/BinCommand.php b/lib/composer/bamarni/composer-bin-plugin/src/BinCommand.php
new file mode 100644
index 0000000000000..2ec71349e43b2
--- /dev/null
+++ b/lib/composer/bamarni/composer-bin-plugin/src/BinCommand.php
@@ -0,0 +1,161 @@
+setName('bin')
+ ->setDescription('Run a command inside a bin namespace')
+ ->setDefinition([
+ new InputArgument('namespace', InputArgument::REQUIRED),
+ new InputArgument('args', InputArgument::REQUIRED | InputArgument::IS_ARRAY),
+ ])
+ ->ignoreValidationErrors()
+ ;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ $config = new Config($this->getComposer());
+ $this->resetComposers($application = $this->getApplication());
+ /** @var ComposerApplication $application */
+
+ if ($config->binLinksAreEnabled()) {
+ putenv('COMPOSER_BIN_DIR='.$this->createConfig()->get('bin-dir'));
+ }
+
+ $vendorRoot = $config->getTargetDirectory();
+ $namespace = $input->getArgument('namespace');
+ $input = new StringInput(preg_replace(
+ sprintf('/bin\s+(--ansi\s)?%s(\s.+)/', preg_quote($namespace, '/')),
+ '$1$2',
+ (string) $input,
+ 1
+ ));
+
+ return ('all' !== $namespace)
+ ? $this->executeInNamespace($application, $vendorRoot.'/'.$namespace, $input, $output)
+ : $this->executeAllNamespaces($application, $vendorRoot, $input, $output)
+ ;
+ }
+
+ /**
+ * @param ComposerApplication $application
+ * @param string $binVendorRoot
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ *
+ * @return int Exit code
+ */
+ private function executeAllNamespaces(ComposerApplication $application, $binVendorRoot, InputInterface $input, OutputInterface $output)
+ {
+ $binRoots = glob($binVendorRoot.'/*', GLOB_ONLYDIR);
+ if (empty($binRoots)) {
+ $this->getIO()->writeError('Couldn\'t find any bin namespace.');
+
+ return 0; // Is a valid scenario: the user may not have setup any bin namespace yet
+ }
+
+ $originalWorkingDir = getcwd();
+ $exitCode = 0;
+ foreach ($binRoots as $binRoot) {
+ $exitCode += $this->executeInNamespace($application, $binRoot, $input, $output);
+
+ chdir($originalWorkingDir);
+ $this->resetComposers($application);
+ }
+
+ return min($exitCode, 255);
+ }
+
+ /**
+ * @param ComposerApplication $application
+ * @param string $namespace
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ *
+ * @return int Exit code
+ */
+ private function executeInNamespace(ComposerApplication $application, $namespace, InputInterface $input, OutputInterface $output)
+ {
+ if (!file_exists($namespace)) {
+ mkdir($namespace, 0777, true);
+ }
+
+ $this->chdir($namespace);
+
+ // some plugins require access to composer file e.g. Symfony Flex
+ if (!file_exists(Factory::getComposerFile())) {
+ file_put_contents(Factory::getComposerFile(), '{}');
+ }
+
+ $input = new StringInput((string) $input . ' --working-dir=.');
+
+ $this->getIO()->writeError('Run with ' . $input->__toString() . '', true, IOInterface::VERBOSE);
+
+ return $application->doRun($input, $output);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function isProxyCommand()
+ {
+ return true;
+ }
+
+ /**
+ * Resets all Composer references in the application.
+ *
+ * @param ComposerApplication $application
+ */
+ private function resetComposers(ComposerApplication $application)
+ {
+ $application->resetComposer();
+ foreach ($this->getApplication()->all() as $command) {
+ if ($command instanceof BaseCommand) {
+ $command->resetComposer();
+ }
+ }
+ }
+
+ private function chdir($dir)
+ {
+ chdir($dir);
+ $this->getIO()->writeError('Changed current directory to ' . $dir . '', true, IOInterface::VERBOSE);
+ }
+
+ private function createConfig()
+ {
+ $config = Factory::createConfig();
+
+ $file = new JsonFile(Factory::getComposerFile());
+ if (!$file->exists()) {
+ return $config;
+ }
+ $file->validateSchema(JsonFile::LAX_SCHEMA);
+
+ $config->merge($file->read());
+
+ return $config;
+ }
+}
diff --git a/lib/composer/bamarni/composer-bin-plugin/src/CommandProvider.php b/lib/composer/bamarni/composer-bin-plugin/src/CommandProvider.php
new file mode 100644
index 0000000000000..2e8235fd02e08
--- /dev/null
+++ b/lib/composer/bamarni/composer-bin-plugin/src/CommandProvider.php
@@ -0,0 +1,16 @@
+getPackage()->getExtra();
+ $this->config = array_merge(
+ [
+ 'bin-links' => true,
+ 'target-directory' => 'vendor-bin',
+ ],
+ isset($extra['bamarni-bin']) ? $extra['bamarni-bin'] : []
+ );
+ }
+
+ /**
+ * @return bool
+ */
+ public function binLinksAreEnabled()
+ {
+ return true === $this->config['bin-links'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getTargetDirectory()
+ {
+ return $this->config['target-directory'];
+ }
+}
diff --git a/lib/composer/bamarni/composer-bin-plugin/src/Plugin.php b/lib/composer/bamarni/composer-bin-plugin/src/Plugin.php
new file mode 100644
index 0000000000000..238df6fac84c0
--- /dev/null
+++ b/lib/composer/bamarni/composer-bin-plugin/src/Plugin.php
@@ -0,0 +1,42 @@
+ 'Bamarni\Composer\Bin\CommandProvider',
+ ];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function deactivate(Composer $composer, IOInterface $io)
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function uninstall(Composer $composer, IOInterface $io)
+ {
+ }
+}
diff --git a/lib/public/Util.php b/lib/public/Util.php
index f77f8a58370b7..d0b23ddd3e057 100644
--- a/lib/public/Util.php
+++ b/lib/public/Util.php
@@ -232,6 +232,8 @@ public static function addScript($application, $file = null, $afterAppId = null)
/**
* Return the list of scripts injected to the page
+ * @return array
+ * @since 24.0.0
*/
public static function getScripts(): array {
// merging first and last data set
diff --git a/tests/lib/UtilTest.php b/tests/lib/UtilTest.php
index ca7a4ad144233..1b430fd7ecd6f 100644
--- a/tests/lib/UtilTest.php
+++ b/tests/lib/UtilTest.php
@@ -225,30 +225,35 @@ protected function setUp(): void {
\OC_Util::$scripts = [];
\OC_Util::$styles = [];
+ self::invokePrivate(\OCP\Util::class, 'scripts', [[]]);
}
protected function tearDown(): void {
parent::tearDown();
\OC_Util::$scripts = [];
\OC_Util::$styles = [];
+ self::invokePrivate(\OCP\Util::class, 'scripts', [[]]);
}
public function testAddScript() {
- \OC_Util::addScript('core', 'myFancyJSFile1');
- \OC_Util::addScript('myApp', 'myFancyJSFile2');
- \OC_Util::addScript('core', 'myFancyJSFile0', true);
- \OC_Util::addScript('core', 'myFancyJSFile10', true);
+ \OCP\Util::addScript('core', 'myFancyJSFile1');
+ \OCP\Util::addScript('files', 'myFancyJSFile2', 'core');
+ \OCP\Util::addScript('myApp', 'myFancyJSFile3');
+ \OCP\Util::addScript('core', 'myFancyJSFile4');
+ // after itself
+ \OCP\Util::addScript('core', 'myFancyJSFile5', 'core');
// add duplicate
- \OC_Util::addScript('core', 'myFancyJSFile1');
+ \OCP\Util::addScript('core', 'myFancyJSFile1');
$this->assertEquals([
- 'core/js/myFancyJSFile10',
- 'core/js/myFancyJSFile0',
'core/js/myFancyJSFile1',
+ 'core/js/myFancyJSFile4',
+ 'files/js/myFancyJSFile2',
+ 'core/js/myFancyJSFile5',
+ 'files/l10n/en',
'myApp/l10n/en',
- 'myApp/js/myFancyJSFile2',
- ], \OC_Util::$scripts);
- $this->assertEquals([], \OC_Util::$styles);
+ 'myApp/js/myFancyJSFile3',
+ ], array_values(\OCP\Util::getScripts()));
}
public function testAddVendorScript() {