| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- <?php
- declare(strict_types=1);
- /**
- * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
- * Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
- *
- * Licensed under The MIT License
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- * @link https://cakephp.org CakePHP(tm) Project
- * @since 3.6.0
- * @license https://opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\Core;
- use Cake\Core\Exception\MissingPluginException;
- use Cake\Utility\Hash;
- use Countable;
- use Generator;
- use InvalidArgumentException;
- use Iterator;
- /**
- * Plugin Collection
- *
- * Holds onto plugin objects loaded into an application, and
- * provides methods for iterating, and finding plugins based
- * on criteria.
- *
- * This class implements the Iterator interface to allow plugins
- * to be iterated, handling the situation where a plugin's hook
- * method (usually bootstrap) loads another plugin during iteration.
- *
- * While its implementation supported nested iteration it does not
- * support using `continue` or `break` inside loops.
- */
- class PluginCollection implements Iterator, Countable
- {
- /**
- * Plugin list
- *
- * @var array<\Cake\Core\PluginInterface>
- */
- protected array $plugins = [];
- /**
- * Names of plugins
- *
- * @var array<string>
- */
- protected array $names = [];
- /**
- * Iterator position stack.
- *
- * @var array<int>
- */
- protected array $positions = [];
- /**
- * Loop depth
- *
- * @var int
- */
- protected int $loopDepth = -1;
- /**
- * Constructor
- *
- * @param array<\Cake\Core\PluginInterface> $plugins The map of plugins to add to the collection.
- */
- public function __construct(array $plugins = [])
- {
- foreach ($plugins as $plugin) {
- $this->add($plugin);
- }
- $this->loadConfig();
- }
- /**
- * Add plugins from config array.
- *
- * @param array $config Configuration array. For e.g.:
- * ```
- * [
- * 'Company/TestPluginThree',
- * 'TestPlugin' => ['onlyDebug' => true, 'onlyCli' => true],
- * 'Nope' => ['optional' => true],
- * 'Named' => ['routes' => false, 'bootstrap' => false],
- * ]
- * ```
- * @return void
- */
- public function addFromConfig(array $config): void
- {
- $debug = Configure::read('debug');
- $cli = PHP_SAPI === 'cli';
- foreach (Hash::normalize($config) as $name => $options) {
- $options = (array)$options;
- $onlyDebug = $options['onlyDebug'] ?? false;
- $onlyCli = $options['onlyCli'] ?? false;
- $optional = $options['optional'] ?? false;
- if (
- ($onlyDebug && !$debug)
- || ($onlyCli && !$cli)
- ) {
- continue;
- }
- try {
- $plugin = $this->create($name, $options);
- $this->add($plugin);
- } catch (MissingPluginException $e) {
- if (!$optional) {
- throw $e;
- }
- }
- }
- }
- /**
- * Load the path information stored in vendor/cakephp-plugins.php
- *
- * This file is generated by the cakephp/plugin-installer package and used
- * to locate plugins on the filesystem as applications can use `extra.plugin-paths`
- * in their composer.json file to move plugin outside of vendor/
- *
- * @internal
- * @return void
- */
- protected function loadConfig(): void
- {
- if (Configure::check('plugins')) {
- return;
- }
- $vendorFile = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php';
- if (!is_file($vendorFile)) {
- $vendorFile = dirname(dirname(dirname(dirname(__DIR__)))) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php';
- if (!is_file($vendorFile)) {
- Configure::write(['plugins' => []]);
- return;
- }
- }
- $config = require $vendorFile;
- Configure::write($config);
- }
- /**
- * Locate a plugin path by looking at configuration data.
- *
- * This will use the `plugins` Configure key, and fallback to enumerating `App::path('plugins')`
- *
- * This method is not part of the official public API as plugins with
- * no plugin class are being phased out.
- *
- * @param string $name The plugin name to locate a path for.
- * @return string
- * @throws \Cake\Core\Exception\MissingPluginException when a plugin path cannot be resolved.
- * @internal
- */
- public function findPath(string $name): string
- {
- // Ensure plugin config is loaded each time. This is necessary primarily
- // for testing because the Configure::clear() call in TestCase::tearDown()
- // wipes out all configuration including plugin paths config.
- $this->loadConfig();
- $path = Configure::read('plugins.' . $name);
- if ($path) {
- return $path;
- }
- $pluginPath = str_replace('/', DIRECTORY_SEPARATOR, $name);
- $paths = App::path('plugins');
- foreach ($paths as $path) {
- if (is_dir($path . $pluginPath)) {
- return $path . $pluginPath . DIRECTORY_SEPARATOR;
- }
- }
- throw new MissingPluginException(['plugin' => $name]);
- }
- /**
- * Add a plugin to the collection
- *
- * Plugins will be keyed by their names.
- *
- * @param \Cake\Core\PluginInterface $plugin The plugin to load.
- * @return $this
- */
- public function add(PluginInterface $plugin)
- {
- $name = $plugin->getName();
- $this->plugins[$name] = $plugin;
- $this->names = array_keys($this->plugins);
- return $this;
- }
- /**
- * Remove a plugin from the collection if it exists.
- *
- * @param string $name The named plugin.
- * @return $this
- */
- public function remove(string $name)
- {
- unset($this->plugins[$name]);
- $this->names = array_keys($this->plugins);
- return $this;
- }
- /**
- * Remove all plugins from the collection
- *
- * @return $this
- */
- public function clear()
- {
- $this->plugins = [];
- $this->names = [];
- $this->positions = [];
- $this->loopDepth = -1;
- return $this;
- }
- /**
- * Check whether the named plugin exists in the collection.
- *
- * @param string $name The named plugin.
- * @return bool
- */
- public function has(string $name): bool
- {
- return isset($this->plugins[$name]);
- }
- /**
- * Get the a plugin by name.
- *
- * If a plugin isn't already loaded it will be autoloaded on first access
- * and that plugins loaded this way may miss some hook methods.
- *
- * @param string $name The plugin to get.
- * @return \Cake\Core\PluginInterface The plugin.
- * @throws \Cake\Core\Exception\MissingPluginException when unknown plugins are fetched.
- */
- public function get(string $name): PluginInterface
- {
- if ($this->has($name)) {
- return $this->plugins[$name];
- }
- $plugin = $this->create($name);
- $this->add($plugin);
- return $plugin;
- }
- /**
- * Create a plugin instance from a name/classname and configuration.
- *
- * @param string $name The plugin name or classname
- * @param array<string, mixed> $config Configuration options for the plugin.
- * @return \Cake\Core\PluginInterface
- * @throws \Cake\Core\Exception\MissingPluginException When plugin instance could not be created.
- * @throws \InvalidArgumentException When class name cannot be found.
- * @psalm-param class-string<\Cake\Core\PluginInterface>|string $name
- */
- public function create(string $name, array $config = []): PluginInterface
- {
- if (str_contains($name, '\\')) {
- if (!class_exists($name)) {
- throw new InvalidArgumentException("Class `{$name}` does not exist.");
- }
- /** @var \Cake\Core\PluginInterface $plugin */
- $plugin = new $name($config);
- return $plugin;
- }
- $config += ['name' => $name];
- $namespace = str_replace('/', '\\', $name);
- $className = $namespace . '\\' . 'Plugin';
- // Check for [Vendor/]Foo/Plugin class
- if (!class_exists($className)) {
- $pos = strpos($name, '/');
- if ($pos === false) {
- $className = $namespace . '\\' . $name . 'Plugin';
- } else {
- $className = $namespace . '\\' . substr($name, $pos + 1) . 'Plugin';
- }
- // Check for [Vendor/]Foo/FooPlugin
- if (!class_exists($className)) {
- $className = BasePlugin::class;
- if (empty($config['path'])) {
- $config['path'] = $this->findPath($name);
- }
- }
- }
- /** @var class-string<\Cake\Core\PluginInterface> $className */
- return new $className($config);
- }
- /**
- * Implementation of Countable.
- *
- * Get the number of plugins in the collection.
- *
- * @return int
- */
- public function count(): int
- {
- return count($this->plugins);
- }
- /**
- * Part of Iterator Interface
- *
- * @return void
- */
- public function next(): void
- {
- $this->positions[$this->loopDepth]++;
- }
- /**
- * Part of Iterator Interface
- *
- * @return string
- */
- public function key(): string
- {
- return $this->names[$this->positions[$this->loopDepth]];
- }
- /**
- * Part of Iterator Interface
- *
- * @return \Cake\Core\PluginInterface
- */
- public function current(): PluginInterface
- {
- $position = $this->positions[$this->loopDepth];
- $name = $this->names[$position];
- return $this->plugins[$name];
- }
- /**
- * Part of Iterator Interface
- *
- * @return void
- */
- public function rewind(): void
- {
- $this->positions[] = 0;
- $this->loopDepth += 1;
- }
- /**
- * Part of Iterator Interface
- *
- * @return bool
- */
- public function valid(): bool
- {
- $valid = isset($this->names[$this->positions[$this->loopDepth]]);
- if (!$valid) {
- array_pop($this->positions);
- $this->loopDepth -= 1;
- }
- return $valid;
- }
- /**
- * Filter the plugins to those with the named hook enabled.
- *
- * @param string $hook The hook to filter plugins by
- * @return \Generator<\Cake\Core\PluginInterface> A generator containing matching plugins.
- * @throws \InvalidArgumentException on invalid hooks
- */
- public function with(string $hook): Generator
- {
- if (!in_array($hook, PluginInterface::VALID_HOOKS, true)) {
- throw new InvalidArgumentException("The `{$hook}` hook is not a known plugin hook.");
- }
- foreach ($this as $plugin) {
- if ($plugin->isEnabled($hook)) {
- yield $plugin;
- }
- }
- }
- }
|