PluginCollection.php 6.8 KB

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