1: <?php
2: /**
3: * CakePlugin class
4: *
5: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
7: *
8: * Licensed under The MIT License
9: * For full copyright and license information, please see the LICENSE.txt
10: * Redistributions of files must retain the above copyright notice.
11: *
12: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
13: * @link https://cakephp.org CakePHP(tm) Project
14: * @package Cake.Core
15: * @since CakePHP(tm) v 2.0.0
16: * @license https://opensource.org/licenses/mit-license.php MIT License
17: */
18:
19: /**
20: * CakePlugin is responsible for loading and unloading plugins.
21: *
22: * It also can retrieve plugin paths and load their bootstrap and routes files.
23: *
24: * @package Cake.Core
25: * @link https://book.cakephp.org/2.0/en/plugins.html
26: */
27: class CakePlugin {
28:
29: /**
30: * Holds a list of all loaded plugins and their configuration
31: *
32: * @var array
33: */
34: protected static $_plugins = array();
35:
36: /**
37: * Loads a plugin and optionally loads bootstrapping, routing files or loads an initialization function
38: *
39: * Examples:
40: *
41: * `CakePlugin::load('DebugKit');`
42: *
43: * Will load the DebugKit plugin and will not load any bootstrap nor route files.
44: *
45: * `CakePlugin::load('DebugKit', array('bootstrap' => true, 'routes' => true));`
46: *
47: * Will load the bootstrap.php and routes.php files.
48: *
49: * `CakePlugin::load('DebugKit', array('bootstrap' => false, 'routes' => true));`
50: *
51: * Will load routes.php file but not bootstrap.php.
52: *
53: * `CakePlugin::load('DebugKit', array('bootstrap' => array('config1', 'config2')));`
54: *
55: * Will load config1.php and config2.php files.
56: *
57: * `CakePlugin::load('DebugKit', array('bootstrap' => 'aCallableMethod'));`
58: *
59: * Will run the aCallableMethod function to initialize it.
60: *
61: * Bootstrap initialization functions can be expressed as a PHP callback type,
62: * including closures. Callbacks will receive two parameters
63: * (plugin name, plugin configuration).
64: *
65: * It is also possible to load multiple plugins at once. Examples:
66: *
67: * `CakePlugin::load(array('DebugKit', 'ApiGenerator'));`
68: *
69: * Will load the DebugKit and ApiGenerator plugins.
70: *
71: * `CakePlugin::load(array('DebugKit', 'ApiGenerator'), array('bootstrap' => true));`
72: *
73: * Will load bootstrap file for both plugins.
74: *
75: * ```
76: * CakePlugin::load(array(
77: * 'DebugKit' => array('routes' => true),
78: * 'ApiGenerator'
79: * ),
80: * array('bootstrap' => true)
81: * );
82: * ```
83: *
84: * Will only load the bootstrap for ApiGenerator and only the routes for DebugKit.
85: * By using the `path` option you can specify an absolute path to the plugin. Make
86: * sure that the path is slash terminated or your plugin will not be located properly.
87: *
88: * @param string|array $plugin name of the plugin to be loaded in CamelCase format or array or plugins to load
89: * @param array $config configuration options for the plugin
90: * @throws MissingPluginException if the folder for the plugin to be loaded is not found
91: * @return void
92: */
93: public static function load($plugin, $config = array()) {
94: if (is_array($plugin)) {
95: foreach ($plugin as $name => $conf) {
96: list($name, $conf) = (is_numeric($name)) ? array($conf, $config) : array($name, $conf);
97: static::load($name, $conf);
98: }
99: return;
100: }
101: $config += array('bootstrap' => false, 'routes' => false, 'ignoreMissing' => false);
102: if (empty($config['path'])) {
103: foreach (App::path('plugins') as $path) {
104: if (is_dir($path . $plugin)) {
105: static::$_plugins[$plugin] = $config + array('path' => $path . $plugin . DS);
106: break;
107: }
108:
109: //Backwards compatibility to make easier to migrate to 2.0
110: $underscored = Inflector::underscore($plugin);
111: if (is_dir($path . $underscored)) {
112: static::$_plugins[$plugin] = $config + array('path' => $path . $underscored . DS);
113: break;
114: }
115: }
116: } else {
117: static::$_plugins[$plugin] = $config;
118: }
119:
120: if (empty(static::$_plugins[$plugin]['path'])) {
121: throw new MissingPluginException(array('plugin' => $plugin));
122: }
123: if (!empty(static::$_plugins[$plugin]['bootstrap'])) {
124: static::bootstrap($plugin);
125: }
126: }
127:
128: /**
129: * Will load all the plugins located in the configured plugins folders
130: *
131: * If passed an options array, it will be used as a common default for all plugins to be loaded
132: * It is possible to set specific defaults for each plugins in the options array. Examples:
133: *
134: * ```
135: * CakePlugin::loadAll(array(
136: * array('bootstrap' => true),
137: * 'DebugKit' => array('routes' => true, 'bootstrap' => false),
138: * ));
139: * ```
140: *
141: * The above example will load the bootstrap file for all plugins, but for DebugKit it will only load
142: * the routes file and will not look for any bootstrap script. If you are loading
143: * many plugins that inconsistently support routes/bootstrap files, instead of detailing
144: * each plugin you can use the `ignoreMissing` option:
145: *
146: * ```
147: * CakePlugin::loadAll(array(
148: * 'ignoreMissing' => true,
149: * 'bootstrap' => true,
150: * 'routes' => true,
151: * ));
152: * ```
153: *
154: * The ignoreMissing option will do additional file_exists() calls but is simpler
155: * to use.
156: *
157: * @param array $options Options list. See CakePlugin::load() for valid options.
158: * @return void
159: */
160: public static function loadAll($options = array()) {
161: $plugins = App::objects('plugins');
162: foreach ($plugins as $plugin) {
163: $pluginOptions = isset($options[$plugin]) ? (array)$options[$plugin] : array();
164: if (isset($options[0])) {
165: $pluginOptions += $options[0];
166: }
167: static::load($plugin, $pluginOptions);
168: }
169: }
170:
171: /**
172: * Returns the filesystem path for a plugin
173: *
174: * @param string $plugin name of the plugin in CamelCase format
175: * @return string path to the plugin folder
176: * @throws MissingPluginException if the folder for plugin was not found or plugin has not been loaded
177: */
178: public static function path($plugin) {
179: if (empty(static::$_plugins[$plugin])) {
180: throw new MissingPluginException(array('plugin' => $plugin));
181: }
182: return static::$_plugins[$plugin]['path'];
183: }
184:
185: /**
186: * Loads the bootstrapping files for a plugin, or calls the initialization setup in the configuration
187: *
188: * @param string $plugin name of the plugin
189: * @return mixed
190: * @see CakePlugin::load() for examples of bootstrap configuration
191: */
192: public static function bootstrap($plugin) {
193: $config = static::$_plugins[$plugin];
194: if ($config['bootstrap'] === false) {
195: return false;
196: }
197: if (is_callable($config['bootstrap'])) {
198: return call_user_func_array($config['bootstrap'], array($plugin, $config));
199: }
200:
201: $path = static::path($plugin);
202: if ($config['bootstrap'] === true) {
203: return static::_includeFile(
204: $path . 'Config' . DS . 'bootstrap.php',
205: $config['ignoreMissing']
206: );
207: }
208:
209: $bootstrap = (array)$config['bootstrap'];
210: foreach ($bootstrap as $file) {
211: static::_includeFile(
212: $path . 'Config' . DS . $file . '.php',
213: $config['ignoreMissing']
214: );
215: }
216:
217: return true;
218: }
219:
220: /**
221: * Loads the routes file for a plugin, or all plugins configured to load their respective routes file
222: *
223: * @param string $plugin name of the plugin, if null will operate on all plugins having enabled the
224: * loading of routes files
225: * @return bool
226: */
227: public static function routes($plugin = null) {
228: if ($plugin === null) {
229: foreach (static::loaded() as $p) {
230: static::routes($p);
231: }
232: return true;
233: }
234: $config = static::$_plugins[$plugin];
235: if ($config['routes'] === false) {
236: return false;
237: }
238: return (bool)static::_includeFile(
239: static::path($plugin) . 'Config' . DS . 'routes.php',
240: $config['ignoreMissing']
241: );
242: }
243:
244: /**
245: * Returns true if the plugin $plugin is already loaded
246: * If plugin is null, it will return a list of all loaded plugins
247: *
248: * @param string $plugin Plugin name to check.
249: * @return mixed boolean true if $plugin is already loaded.
250: * If $plugin is null, returns a list of plugins that have been loaded
251: */
252: public static function loaded($plugin = null) {
253: if ($plugin) {
254: return isset(static::$_plugins[$plugin]);
255: }
256: $return = array_keys(static::$_plugins);
257: sort($return);
258: return $return;
259: }
260:
261: /**
262: * Forgets a loaded plugin or all of them if first parameter is null
263: *
264: * @param string $plugin name of the plugin to forget
265: * @return void
266: */
267: public static function unload($plugin = null) {
268: if ($plugin === null) {
269: static::$_plugins = array();
270: } else {
271: unset(static::$_plugins[$plugin]);
272: }
273: }
274:
275: /**
276: * Include file, ignoring include error if needed if file is missing
277: *
278: * @param string $file File to include
279: * @param bool $ignoreMissing Whether to ignore include error for missing files
280: * @return mixed
281: */
282: protected static function _includeFile($file, $ignoreMissing = false) {
283: if ($ignoreMissing && !is_file($file)) {
284: return false;
285: }
286: return include $file;
287: }
288:
289: }
290: