1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * Redistributions of files must retain the above copyright notice.
8: *
9: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
10: * @link https://cakephp.org CakePHP(tm) Project
11: * @since 3.6.0
12: * @license https://opensource.org/licenses/mit-license.php MIT License
13: */
14: namespace Cake\Core;
15:
16: use Cake\Core\Exception\MissingPluginException;
17: use Countable;
18: use InvalidArgumentException;
19: use Iterator;
20:
21: /**
22: * Plugin Collection
23: *
24: * Holds onto plugin objects loaded into an application, and
25: * provides methods for iterating, and finding plugins based
26: * on criteria.
27: *
28: * This class implements the Iterator interface to allow plugins
29: * to be iterated, handling the situation where a plugin's hook
30: * method (usually bootstrap) loads another plugin during iteration.
31: */
32: class PluginCollection implements Iterator, Countable
33: {
34: /**
35: * Plugin list
36: *
37: * @var array
38: */
39: protected $plugins = [];
40:
41: /**
42: * Names of plugins
43: *
44: * @var array
45: */
46: protected $names = [];
47:
48: /**
49: * Iterator position.
50: *
51: * @var int
52: */
53: protected $position = 0;
54:
55: /**
56: * Constructor
57: *
58: * @param array $plugins The map of plugins to add to the collection.
59: */
60: public function __construct(array $plugins = [])
61: {
62: foreach ($plugins as $plugin) {
63: $this->add($plugin);
64: }
65: $this->loadConfig();
66: }
67:
68: /**
69: * Load the path information stored in vendor/cakephp-plugins.php
70: *
71: * This file is generated by the cakephp/plugin-installer package and used
72: * to locate plugins on the filesystem as applications can use `extra.plugin-paths`
73: * in their composer.json file to move plugin outside of vendor/
74: *
75: * @internal
76: * @return void
77: */
78: protected function loadConfig()
79: {
80: if (Configure::check('plugins')) {
81: return;
82: }
83: $vendorFile = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php';
84: if (!file_exists($vendorFile)) {
85: $vendorFile = dirname(dirname(dirname(dirname(__DIR__)))) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php';
86: if (!file_exists($vendorFile)) {
87: Configure::write(['plugins' => []]);
88:
89: return;
90: }
91: }
92:
93: $config = require $vendorFile;
94: Configure::write($config);
95: }
96:
97: /**
98: * Locate a plugin path by looking at configuration data.
99: *
100: * This will use the `plugins` Configure key, and fallback to enumerating `App::path('Plugin')`
101: *
102: * This method is not part of the official public API as plugins with
103: * no plugin class are being phased out.
104: *
105: * @param string $name The plugin name to locate a path for. Will return '' when a plugin cannot be found.
106: * @return string
107: * @throws \Cake\Core\Exception\MissingPluginException when a plugin path cannot be resolved.
108: * @internal
109: */
110: public function findPath($name)
111: {
112: $this->loadConfig();
113:
114: $path = Configure::read('plugins.' . $name);
115: if ($path) {
116: return $path;
117: }
118:
119: $pluginPath = str_replace('/', DIRECTORY_SEPARATOR, $name);
120: $paths = App::path('Plugin');
121: foreach ($paths as $path) {
122: if (is_dir($path . $pluginPath)) {
123: return $path . $pluginPath . DIRECTORY_SEPARATOR;
124: }
125: }
126:
127: throw new MissingPluginException(['plugin' => $name]);
128: }
129:
130: /**
131: * Add a plugin to the collection
132: *
133: * Plugins will be keyed by their names.
134: *
135: * @param \Cake\Core\PluginInterface $plugin The plugin to load.
136: * @return $this
137: */
138: public function add(PluginInterface $plugin)
139: {
140: $name = $plugin->getName();
141: $this->plugins[$name] = $plugin;
142: $this->names = array_keys($this->plugins);
143:
144: return $this;
145: }
146:
147: /**
148: * Remove a plugin from the collection if it exists.
149: *
150: * @param string $name The named plugin.
151: * @return $this
152: */
153: public function remove($name)
154: {
155: unset($this->plugins[$name]);
156: $this->names = array_keys($this->plugins);
157:
158: return $this;
159: }
160:
161: /**
162: * Remove all plugins from the collection
163: *
164: * @return $this
165: */
166: public function clear()
167: {
168: $this->plugins = [];
169:
170: return $this;
171: }
172:
173: /**
174: * Check whether the named plugin exists in the collection.
175: *
176: * @param string $name The named plugin.
177: * @return bool
178: */
179: public function has($name)
180: {
181: return isset($this->plugins[$name]);
182: }
183:
184: /**
185: * Get the a plugin by name
186: *
187: * @param string $name The plugin to get.
188: * @return \Cake\Core\PluginInterface The plugin.
189: * @throws \Cake\Core\Exception\MissingPluginException when unknown plugins are fetched.
190: */
191: public function get($name)
192: {
193: if (!$this->has($name)) {
194: throw new MissingPluginException(['plugin' => $name]);
195: }
196:
197: return $this->plugins[$name];
198: }
199:
200: /**
201: * Part of Iterator Interface
202: *
203: * @return void
204: */
205: public function next()
206: {
207: $this->position++;
208: }
209:
210: /**
211: * Part of Iterator Interface
212: *
213: * @return string
214: */
215: public function key()
216: {
217: return $this->names[$this->position];
218: }
219:
220: /**
221: * Part of Iterator Interface
222: *
223: * @return \Cake\Core\PluginInterface
224: */
225: public function current()
226: {
227: $name = $this->names[$this->position];
228:
229: return $this->plugins[$name];
230: }
231:
232: /**
233: * Part of Iterator Interface
234: *
235: * @return void
236: */
237: public function rewind()
238: {
239: $this->position = 0;
240: }
241:
242: /**
243: * Part of Iterator Interface
244: *
245: * @return bool
246: */
247: public function valid()
248: {
249: return $this->position < count($this->plugins);
250: }
251:
252: /**
253: * Implementation of Countable.
254: *
255: * Get the number of plugins in the collection.
256: *
257: * @return int
258: */
259: public function count()
260: {
261: return count($this->plugins);
262: }
263:
264: /**
265: * Filter the plugins to those with the named hook enabled.
266: *
267: * @param string $hook The hook to filter plugins by
268: * @return \Generator A generator containing matching plugins.
269: * @throws \InvalidArgumentException on invalid hooks
270: */
271: public function with($hook)
272: {
273: if (!in_array($hook, PluginInterface::VALID_HOOKS)) {
274: throw new InvalidArgumentException("The `{$hook}` hook is not a known plugin hook.");
275: }
276: foreach ($this as $plugin) {
277: if ($plugin->isEnabled($hook)) {
278: yield $plugin;
279: }
280: }
281: }
282: }
283: