Plugin.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 2.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Core;
  16. use Cake\Core\ClassLoader;
  17. use Cake\Utility\Inflector;
  18. /**
  19. * Plugin is used to load and locate plugins.
  20. *
  21. * It also can retrieve plugin paths and load their bootstrap and routes files.
  22. *
  23. * @link http://book.cakephp.org/3.0/en/plugins.html
  24. */
  25. class Plugin {
  26. /**
  27. * Holds a list of all loaded plugins and their configuration
  28. *
  29. * @var array
  30. */
  31. protected static $_plugins = [];
  32. /**
  33. * Class loader instance
  34. *
  35. * @var \Cake\Core\ClassLoader
  36. */
  37. protected static $_loader;
  38. /**
  39. * Loads a plugin and optionally loads bootstrapping,
  40. * routing files or runs a initialization function.
  41. *
  42. * Plugins only need to be loaded if you want bootstrapping/routes/cli commands to
  43. * be exposed. If your plugin does not expose any of these features you do not need
  44. * to load them.
  45. *
  46. * This method does not configure any autoloaders. That must be done separately either
  47. * through composer, or your own code during App/Config/bootstrap.php.
  48. *
  49. * ## Examples:
  50. *
  51. * `Plugin::load('DebugKit')`
  52. *
  53. * Will load the DebugKit plugin and will not load any bootstrap nor route files.
  54. * However, the plugin will be part of the framework default routes, and have its
  55. * CLI tools (if any) available for use.
  56. *
  57. * `Plugin::load('DebugKit', ['bootstrap' => true, 'routes' => true])`
  58. *
  59. * Will load the bootstrap.php and routes.php files.
  60. *
  61. * `Plugin::load('DebugKit', ['bootstrap' => false, 'routes' => true])`
  62. *
  63. * Will load routes.php file but not bootstrap.php
  64. *
  65. * `Plugin::load('DebugKit', ['namespace' => 'Cake\DebugKit'])`
  66. *
  67. * Will load files on APP/Plugin/Cake/DebugKit/...
  68. *
  69. * Bootstrap initialization functions can be expressed as a PHP callback type,
  70. * including closures. Callbacks will receive two parameters (plugin name, plugin configuration)
  71. *
  72. * It is also possible to load multiple plugins at once. Examples:
  73. *
  74. * `Plugin::load(['DebugKit', 'ApiGenerator'])`
  75. *
  76. * Will load the DebugKit and ApiGenerator plugins.
  77. *
  78. * `Plugin::load(['DebugKit', 'ApiGenerator'], ['bootstrap' => true])`
  79. *
  80. * Will load bootstrap file for both plugins
  81. *
  82. * {{{
  83. * Plugin::load([
  84. * 'DebugKit' => ['routes' => true],
  85. * 'ApiGenerator'
  86. * ],
  87. * ['bootstrap' => true])
  88. * }}}
  89. *
  90. * Will only load the bootstrap for ApiGenerator and only the routes for DebugKit
  91. *
  92. * ## Configuration options
  93. *
  94. * - `bootstrap` - array - Whether or not you want the $plugin/Config/bootstrap.php file loaded.
  95. * - `routes` - boolean - Whether or not you want to load the $plugin/Config/routes.php file.
  96. * - `namespace` - string - A custom namespace for the plugin. It will default to the plugin name.
  97. * - `ignoreMissing` - boolean - Set to true to ignore missing bootstrap/routes files.
  98. * - `path` - string - The path the plugin can be found on. If empty the default plugin path (App.pluginPaths) will be used.
  99. * - `classBase` - The path relative to `path` which contains the folders with class files.
  100. * Defaults to "src".
  101. * - `autoload` - boolean - Whether or not you want an autoloader registered. This defaults to false. The framework
  102. * assumes you have configured autoloaders using composer. However, if your application source tree is made up of
  103. * plugins, this can be a useful option.
  104. *
  105. * @param string|array $plugin name of the plugin to be loaded in CamelCase format or array or plugins to load
  106. * @param array $config configuration options for the plugin
  107. * @throws \Cake\Core\Error\MissingPluginException if the folder for the plugin to be loaded is not found
  108. * @return void
  109. */
  110. public static function load($plugin, array $config = []) {
  111. if (is_array($plugin)) {
  112. foreach ($plugin as $name => $conf) {
  113. list($name, $conf) = (is_numeric($name)) ? [$conf, $config] : [$name, $conf];
  114. static::load($name, $conf);
  115. }
  116. return;
  117. }
  118. $config += [
  119. 'autoload' => false,
  120. 'bootstrap' => false,
  121. 'routes' => false,
  122. 'namespace' => str_replace('/', '\\', $plugin),
  123. 'classBase' => 'src',
  124. 'ignoreMissing' => false
  125. ];
  126. if (empty($config['path'])) {
  127. $paths = App::path('Plugin');
  128. foreach ($paths as $path) {
  129. $namespacePath = str_replace('\\', DS, $config['namespace']);
  130. $pluginPath = str_replace('/', DS, $plugin);
  131. if (is_dir($path . $pluginPath)) {
  132. $config += ['path' => $path . $pluginPath . DS];
  133. break;
  134. }
  135. if ($plugin !== $config['namespace'] && is_dir($path . $namespacePath)) {
  136. $config += ['path' => $path . $namespacePath . DS];
  137. break;
  138. }
  139. }
  140. }
  141. if (empty($config['path'])) {
  142. throw new Error\MissingPluginException(['plugin' => $plugin]);
  143. }
  144. $config['classPath'] = $config['path'] . $config['classBase'] . DS;
  145. static::$_plugins[$plugin] = $config;
  146. if ($config['bootstrap'] === true) {
  147. static::bootstrap($plugin);
  148. }
  149. if ($config['autoload'] === true) {
  150. if (empty(static::$_loader)) {
  151. static::$_loader = new ClassLoader;
  152. static::$_loader->register();
  153. }
  154. static::$_loader->addNamespace(
  155. $config['namespace'],
  156. $config['path'] . $config['classBase'] . DS
  157. );
  158. static::$_loader->addNamespace(
  159. $config['namespace'] . '\Test',
  160. $config['path'] . 'tests' . DS
  161. );
  162. }
  163. }
  164. /**
  165. * Will load all the plugins located in the default plugin folder.
  166. *
  167. * If passed an options array, it will be used as a common default for all plugins to be loaded
  168. * It is possible to set specific defaults for each plugins in the options array. Examples:
  169. *
  170. * {{{
  171. * Plugin::loadAll([
  172. * ['bootstrap' => true],
  173. * 'DebugKit' => ['routes' => true],
  174. * ]);
  175. * }}}
  176. *
  177. * The above example will load the bootstrap file for all plugins, but for DebugKit it will only load the routes file
  178. * and will not look for any bootstrap script.
  179. *
  180. * If a plugin has been loaded already, it will not be reloaded by loadAll().
  181. *
  182. * @param array $options Options.
  183. * @return void
  184. */
  185. public static function loadAll(array $options = []) {
  186. $plugins = App::objects('Plugin');
  187. foreach ($plugins as $p) {
  188. $opts = isset($options[$p]) ? $options[$p] : null;
  189. if ($opts === null && isset($options[0])) {
  190. $opts = $options[0];
  191. }
  192. if (isset(static::$_plugins[$p])) {
  193. continue;
  194. }
  195. static::load($p, (array)$opts);
  196. }
  197. }
  198. /**
  199. * Returns the filesystem path for a plugin
  200. *
  201. * @param string $plugin name of the plugin in CamelCase format
  202. * @return string path to the plugin folder
  203. * @throws \Cake\Core\Error\MissingPluginException if the folder for plugin was not found or plugin has not been loaded
  204. */
  205. public static function path($plugin) {
  206. if (empty(static::$_plugins[$plugin])) {
  207. throw new Error\MissingPluginException(['plugin' => $plugin]);
  208. }
  209. return static::$_plugins[$plugin]['path'];
  210. }
  211. /**
  212. * Returns the filesystem path for plugin's folder containing class folders.
  213. *
  214. * @param string $plugin name of the plugin in CamelCase format.
  215. * @return string Path to the plugin folder container class folders.
  216. * @throws \Cake\Core\Error\MissingPluginException If plugin has not been loaded.
  217. */
  218. public static function classPath($plugin) {
  219. if (empty(static::$_plugins[$plugin])) {
  220. throw new Error\MissingPluginException(['plugin' => $plugin]);
  221. }
  222. return static::$_plugins[$plugin]['classPath'];
  223. }
  224. /**
  225. * Return the namespace for a plugin
  226. *
  227. * If a plugin is unknown, the plugin name will be used as the namespace.
  228. * This lets you access vendor libraries or unloaded plugins using `Plugin.Class`.
  229. *
  230. * @param string $plugin name of the plugin in CamelCase format
  231. * @return string namespace to the plugin
  232. */
  233. public static function getNamespace($plugin) {
  234. if (empty(static::$_plugins[$plugin])) {
  235. return $plugin;
  236. }
  237. return static::$_plugins[$plugin]['namespace'];
  238. }
  239. /**
  240. * Loads the bootstrapping files for a plugin, or calls the initialization setup in the configuration
  241. *
  242. * @param string $plugin name of the plugin
  243. * @return mixed
  244. * @see Plugin::load() for examples of bootstrap configuration
  245. */
  246. public static function bootstrap($plugin) {
  247. $config = static::$_plugins[$plugin];
  248. if ($config['bootstrap'] === false) {
  249. return false;
  250. }
  251. $path = static::path($plugin);
  252. if ($config['bootstrap'] === true) {
  253. return static::_includeFile(
  254. $config['classPath'] . 'Config' . DS . 'bootstrap.php',
  255. $config['ignoreMissing']
  256. );
  257. }
  258. }
  259. /**
  260. * Loads the routes file for a plugin, or all plugins configured to load their respective routes file
  261. *
  262. * @param string $plugin name of the plugin, if null will operate on all plugins having enabled the
  263. * loading of routes files
  264. * @return bool
  265. */
  266. public static function routes($plugin = null) {
  267. if ($plugin === null) {
  268. foreach (static::loaded() as $p) {
  269. static::routes($p);
  270. }
  271. return true;
  272. }
  273. $config = static::$_plugins[$plugin];
  274. if ($config['routes'] === false) {
  275. return false;
  276. }
  277. return (bool)static::_includeFile(
  278. $config['classPath'] . 'Config' . DS . 'routes.php',
  279. $config['ignoreMissing']
  280. );
  281. }
  282. /**
  283. * Returns true if the plugin $plugin is already loaded
  284. * If plugin is null, it will return a list of all loaded plugins
  285. *
  286. * @param string $plugin Plugin name.
  287. * @return mixed boolean true if $plugin is already loaded.
  288. * If $plugin is null, returns a list of plugins that have been loaded
  289. */
  290. public static function loaded($plugin = null) {
  291. if ($plugin) {
  292. return isset(static::$_plugins[$plugin]);
  293. }
  294. $return = array_keys(static::$_plugins);
  295. sort($return);
  296. return $return;
  297. }
  298. /**
  299. * Forgets a loaded plugin or all of them if first parameter is null
  300. *
  301. * @param string $plugin name of the plugin to forget
  302. * @return void
  303. */
  304. public static function unload($plugin = null) {
  305. if ($plugin === null) {
  306. static::$_plugins = [];
  307. } else {
  308. unset(static::$_plugins[$plugin]);
  309. }
  310. }
  311. /**
  312. * Include file, ignoring include error if needed if file is missing
  313. *
  314. * @param string $file File to include
  315. * @param bool $ignoreMissing Whether to ignore include error for missing files
  316. * @return mixed
  317. */
  318. protected static function _includeFile($file, $ignoreMissing = false) {
  319. if ($ignoreMissing && !is_file($file)) {
  320. return false;
  321. }
  322. return include $file;
  323. }
  324. }