PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/Extensions/Lib/CroogoPlugin.php

https://github.com/kareypowell/croogo
PHP | 784 lines | 489 code | 58 blank | 237 comment | 85 complexity | 8c3c840015803e3c0920968e896120eb MD5 | raw file
  1. <?php
  2. App::uses('CroogoEventManager', 'Croogo.Event');
  3. App::uses('ClassRegistry', 'Utility');
  4. App::uses('Folder', 'Utility');
  5. App::uses('Hash', 'Utility');
  6. App::uses('MigrationVersion', 'Migrations.Lib');
  7. /**
  8. * CroogoPlugin utility class
  9. *
  10. * @category Component
  11. * @package Croogo.Extensions.Lib
  12. * @version 1.4
  13. * @since 1.4
  14. * @author Fahad Ibnay Heylaal <contact@fahad19.com>
  15. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  16. * @link http://www.croogo.org
  17. */
  18. class CroogoPlugin extends Object {
  19. /**
  20. * List of migration errors
  21. * Updated in case of errors when running migrations
  22. *
  23. * @var array
  24. */
  25. public $migrationErrors = array();
  26. /**
  27. * PluginActivation class
  28. *
  29. * @var object
  30. */
  31. protected $_PluginActivation = null;
  32. /**
  33. * MigrationVersion class
  34. *
  35. * @var MigrationVersion
  36. */
  37. protected $_MigrationVersion = null;
  38. /**
  39. * Core plugins
  40. *
  41. * Typically these plugins must be active and should not be deactivated
  42. *
  43. * @var array
  44. * @access public
  45. */
  46. public $corePlugins = array(
  47. 'Acl',
  48. 'Croogo',
  49. 'Extensions',
  50. 'Migrations',
  51. 'Search',
  52. 'Settings',
  53. );
  54. /**
  55. * Bundled plugins providing core functionalities but could be deactivated
  56. *
  57. * @var array
  58. * @access public
  59. */
  60. public $bundledPlugins = array(
  61. 'Blocks',
  62. 'Comments',
  63. 'Contacts',
  64. 'FileManager',
  65. 'Meta',
  66. 'Menus',
  67. 'Nodes',
  68. 'Taxonomy',
  69. 'Users',
  70. );
  71. /**
  72. * __construct
  73. */
  74. public function __construct($migrationVersion = null) {
  75. if (!is_null($migrationVersion)) {
  76. $this->_MigrationVersion = $migrationVersion;
  77. }
  78. }
  79. /**
  80. * Get instance
  81. */
  82. public static function instance() {
  83. static $self = null;
  84. if ($self === null) {
  85. $self = new CroogoPlugin();
  86. }
  87. return $self;
  88. }
  89. /**
  90. * AppController setter
  91. *
  92. * @return void
  93. */
  94. public function setController(AppController $controller) {
  95. $this->_Controller = $controller;
  96. }
  97. /**
  98. * Get plugin aliases (folder names)
  99. *
  100. * @return array
  101. */
  102. public function getPlugins() {
  103. $plugins = array();
  104. $this->folder = new Folder;
  105. $pluginPaths = App::path('plugins');
  106. foreach ($pluginPaths as $pluginPath) {
  107. $this->folder->path = $pluginPath;
  108. if (!file_exists($this->folder->path)) {
  109. continue;
  110. }
  111. $pluginFolders = $this->folder->read();
  112. foreach ($pluginFolders[0] as $pluginFolder) {
  113. if (substr($pluginFolder, 0, 1) != '.') {
  114. if (!$this->_isCroogoPlugin($pluginPath, $pluginFolder)) {
  115. continue;
  116. }
  117. $plugins[$pluginFolder] = $pluginFolder;
  118. }
  119. }
  120. }
  121. return $plugins;
  122. }
  123. /**
  124. * Checks wether $pluginDir/$path is a Croogo plugin
  125. *
  126. * @param string $pluginDir plugin directory
  127. * @param string $path plugin alias
  128. * @return bool true if path is a Croogo plugin
  129. */
  130. protected function _isCroogoPlugin($pluginDir, $path) {
  131. $dir = $pluginDir . $path . DS;
  132. $composerFile = $dir . 'composer.json';
  133. if (file_exists($composerFile)) {
  134. $pluginData = json_decode(file_get_contents($composerFile), true);
  135. if (
  136. isset($pluginData['require']['croogo/croogo']) ||
  137. (isset($pluginData['type']) && $pluginData['type'] == 'croogo-plugin')
  138. ) {
  139. return true;
  140. }
  141. }
  142. if (file_exists($dir . 'Config' . DS . 'plugin.json')) {
  143. return true;
  144. }
  145. return false;
  146. }
  147. /**
  148. * Checks whether $plugin is builtin
  149. *
  150. * @param string $plugin plugin alias
  151. * @return boolean true if $plugin is builtin
  152. */
  153. protected function _isBuiltin($plugin) {
  154. return
  155. in_array($plugin, $this->bundledPlugins) ||
  156. in_array($plugin, $this->corePlugins);
  157. }
  158. /**
  159. * Get the content of plugin.json file of a plugin
  160. *
  161. * @param string $alias plugin folder name
  162. * @return array|bool array of plugin manifest or boolean false
  163. */
  164. public function getData($alias = null) {
  165. $pluginPaths = App::path('plugins');
  166. foreach ($pluginPaths as $pluginPath) {
  167. $active = $this->isActive($alias);
  168. $isCroogoPlugin = false;
  169. $manifestFile = $pluginPath . $alias . DS . 'Config' . DS . 'plugin.json';
  170. $hasManifest = file_exists($manifestFile);
  171. $composerFile = $pluginPath . $alias . DS . 'composer.json';
  172. $hasComposer = file_exists($composerFile);
  173. if ($hasManifest || $hasComposer) {
  174. $pluginData = array(
  175. 'name' => $alias,
  176. 'needMigration' => false,
  177. 'active' => $active,
  178. );
  179. if ($hasManifest) {
  180. $manifestData = json_decode(file_get_contents($manifestFile), true);
  181. if (empty($manifestData)) {
  182. $this->log($alias . 'plugin.json exists but cannot be decoded.');
  183. return $pluginData;
  184. }
  185. $pluginData = array_merge($manifestData, $pluginData);
  186. }
  187. if ($hasComposer) {
  188. $composerData = json_decode(file_get_contents($composerFile), true);
  189. $type = isset($composerData['type']) ? $composerData['type'] : null;
  190. $isCroogoPlugin =
  191. isset($composerData['require']['croogo/croogo']) ||
  192. $type == 'croogo-plugin';
  193. if ($isCroogoPlugin) {
  194. if (isset($composerData['name'])) {
  195. $composerData['vendor'] = $composerData['name'];
  196. unset($composerData['name']);
  197. }
  198. $pluginData = Hash::merge($pluginData, $composerData);
  199. }
  200. }
  201. $pluginData['needMigration'] = $this->needMigration($alias, $active);
  202. return $pluginData;
  203. } elseif ($this->_isBuiltin($alias)) {
  204. if ($this->needMigration($alias, $active)) {
  205. $pluginData = array(
  206. 'name' => $alias,
  207. 'description' => "Croogo $alias plugin",
  208. 'active' => true,
  209. 'needMigration' => true,
  210. );
  211. return $pluginData;
  212. }
  213. }
  214. }
  215. return false;
  216. }
  217. /**
  218. * Get the content of plugin.json file of a plugin
  219. *
  220. * @param string $alias plugin folder name
  221. * @return array
  222. * @deprecated use getData()
  223. */
  224. public function getPluginData($alias = null) {
  225. return $this->getData($alias);
  226. }
  227. /**
  228. * Get a list of plugins available with all available meta data.
  229. * Plugin without metadata are excluded.
  230. *
  231. * @return array array of plugins, listed according to bootstrap order
  232. */
  233. public function plugins() {
  234. $pluginAliases = $this->getPlugins();
  235. $allPlugins = array();
  236. foreach ($pluginAliases as $pluginAlias) {
  237. $allPlugins[$pluginAlias] = $this->getData($pluginAlias);
  238. }
  239. $activePlugins = array();
  240. $bootstraps = explode(',', Configure::read('Hook.bootstraps'));
  241. foreach ($bootstraps as $pluginAlias) {
  242. if ($pluginData = $this->getData($pluginAlias)) {
  243. $activePlugins[$pluginAlias] = $pluginData;
  244. }
  245. }
  246. $plugins = array();
  247. foreach ($activePlugins as $plugin => $pluginData) {
  248. $plugins[$plugin] = $pluginData;
  249. }
  250. $plugins = Hash::merge($plugins, $allPlugins);
  251. return $plugins;
  252. }
  253. /**
  254. * Check if plugin is dependent on any other plugin.
  255. * If yes, check if that plugin is available in plugins directory.
  256. *
  257. * @param string $plugin plugin alias
  258. * @return boolean
  259. */
  260. public function checkDependency($plugin = null) {
  261. $dependencies = $this->getDependencies($plugin);
  262. $pluginPaths = App::path('plugins');
  263. foreach ($dependencies as $p) {
  264. $check = false;
  265. foreach ($pluginPaths as $pluginPath) {
  266. if (is_dir($pluginPath . $p)) {
  267. $check = true;
  268. }
  269. }
  270. if (!$check) {
  271. return false;
  272. }
  273. }
  274. return true;
  275. }
  276. /**
  277. * getDependencies
  278. *
  279. * @param string $plugin plugin alias (underscrored)
  280. * @return array list of plugin that $plugin depends on
  281. */
  282. public function getDependencies($plugin) {
  283. $pluginData = $this->getData($plugin);
  284. if (!isset($pluginData['dependencies']['plugins'])) {
  285. $pluginData['dependencies']['plugins'] = array();
  286. }
  287. $dependencies = array();
  288. foreach ($pluginData['dependencies']['plugins'] as $i => $plugin) {
  289. $dependencies[] = Inflector::camelize($plugin);
  290. }
  291. return $dependencies;
  292. }
  293. /**
  294. * Check if plugin is dependent on any other plugin.
  295. * If yes, check if that plugin is available in plugins directory.
  296. *
  297. * @param string $plugin plugin alias (underscrored)
  298. * @return boolean
  299. */
  300. public function checkPluginDependency($plugin = null) {
  301. return $this->checkDependency($plugin);
  302. }
  303. /**
  304. * Check if plugin is active
  305. *
  306. * @param string $plugin Plugin name (underscored)
  307. * @return boolean
  308. */
  309. public function isActive($plugin) {
  310. $configureKeys = array(
  311. 'Hook.bootstraps',
  312. );
  313. $plugin = array(Inflector::underscore($plugin), Inflector::camelize($plugin));
  314. foreach ($configureKeys as $configureKey) {
  315. $hooks = explode(',', Configure::read($configureKey));
  316. foreach ($hooks as $hook) {
  317. if (in_array($hook, $plugin)) {
  318. return true;
  319. }
  320. }
  321. }
  322. return false;
  323. }
  324. /**
  325. * Check if plugin is active
  326. *
  327. * @param string $plugin Plugin name (underscored)
  328. * @return boolean
  329. * @deprecated use isActive()
  330. */
  331. public function pluginIsActive($plugin) {
  332. return $this->isActive($plugin);
  333. }
  334. /**
  335. * Check if a plugin need a database migration
  336. *
  337. * @param string $plugin Plugin name
  338. * @param string $isActive If the plugin is active
  339. * @return boolean
  340. */
  341. public function needMigration($plugin, $isActive) {
  342. $needMigration = false;
  343. if ($isActive) {
  344. $mapping = $this->_getMigrationVersion()->getMapping($plugin);
  345. $currentVersion = $this->_getMigrationVersion()->getVersion($plugin);
  346. if ($mapping) {
  347. $lastVersion = max(array_keys($mapping));
  348. $needMigration = ($lastVersion - $currentVersion != 0);
  349. }
  350. }
  351. return $needMigration;
  352. }
  353. /**
  354. * Migrate a plugin
  355. *
  356. * @param string $plugin Plugin name
  357. * @return boolean Success of the migration
  358. */
  359. public function migrate($plugin) {
  360. $success = false;
  361. $mapping = $this->_getMigrationVersion()->getMapping($plugin);
  362. if ($mapping) {
  363. $lastVersion = max(array_keys($mapping));
  364. $executionResult = $this->_MigrationVersion->run(array(
  365. 'version' => $lastVersion,
  366. 'type' => $plugin
  367. ));
  368. $success = $executionResult === true;
  369. if (!$success) {
  370. array_push($this->migrationErrors, $executionResult);
  371. }
  372. }
  373. return $success;
  374. }
  375. public function unmigrate($plugin) {
  376. $success = false;
  377. if ($this->_getMigrationVersion()->getMapping($plugin)) {
  378. $success = $this->_getMigrationVersion()->run(array(
  379. 'version' => 0,
  380. 'type' => $plugin,
  381. 'direction' => 'down'
  382. ));
  383. }
  384. return $success;
  385. }
  386. protected function _getMigrationVersion() {
  387. if (!($this->_MigrationVersion instanceof MigrationVersion)) {
  388. $this->_MigrationVersion = new MigrationVersion();
  389. }
  390. return $this->_MigrationVersion;
  391. }
  392. /**
  393. * Loads plugin's bootstrap.php file
  394. *
  395. * @param string $plugin Plugin name
  396. * @return void
  397. */
  398. public function addBootstrap($plugin) {
  399. $hookBootstraps = Configure::read('Hook.bootstraps');
  400. if (!$hookBootstraps) {
  401. $plugins = array();
  402. } else {
  403. $plugins = explode(',', $hookBootstraps);
  404. $names = array(Inflector::underscore($plugin), Inflector::camelize($plugin));
  405. if ($intersect = array_intersect($names, $plugins)) {
  406. $plugin = current($intersect);
  407. }
  408. }
  409. if (array_search($plugin, $plugins) !== false) {
  410. $plugins = (array)$hookBootstraps;
  411. } else {
  412. $plugins[] = $plugin;
  413. }
  414. $this->_saveBootstraps($plugins);
  415. }
  416. /**
  417. * Loads plugin's bootstrap.php file
  418. *
  419. * @param string $plugin Plugin name
  420. * @return void
  421. * @deprecated use addBootstrap($plugin)
  422. */
  423. public function addPluginBootstrap($plugin) {
  424. $this->addBootstrap($plugin);
  425. }
  426. /**
  427. * Plugin name will be removed from Hook.bootstraps
  428. *
  429. * @param string $plugin Plugin name
  430. * @return void
  431. */
  432. public function removeBootstrap($plugin) {
  433. $hookBootstraps = Configure::read('Hook.bootstraps');
  434. if (!$hookBootstraps) {
  435. return;
  436. }
  437. $plugins = explode(',', $hookBootstraps);
  438. $names = array(Inflector::underscore($plugin), Inflector::camelize($plugin));
  439. if ($intersect = array_intersect($names, $plugins)) {
  440. $plugin = current($intersect);
  441. $k = array_search($plugin, $plugins);
  442. unset($plugins[$k]);
  443. }
  444. $this->_saveBootstraps($plugins);
  445. }
  446. /**
  447. * Plugin name will be removed from Hook.bootstraps
  448. *
  449. * @param string $plugin Plugin name
  450. * @return void
  451. * @deprecated use removeBootstrap()
  452. */
  453. public function removePluginBootstrap($plugin) {
  454. $this->removeBootstrap($plugin);
  455. }
  456. /**
  457. * Get PluginActivation class
  458. *
  459. * @param string $plugin
  460. * @return object
  461. */
  462. public function getActivator($plugin = null) {
  463. $plugin = Inflector::camelize($plugin);
  464. if (!isset($this->_PluginActivation)) {
  465. $className = $plugin . 'Activation';
  466. $pluginPaths = App::path('plugins');
  467. foreach ($pluginPaths as $path) {
  468. $configFile = $path . DS . $plugin . DS . 'Config' . DS . $className . '.php';
  469. if (file_exists($configFile) && include $configFile) {
  470. $this->_PluginActivation = new $className;
  471. }
  472. }
  473. }
  474. return $this->_PluginActivation;
  475. }
  476. /**
  477. * Activate plugin
  478. *
  479. * @param string $plugin Plugin name
  480. * @return boolean true when successful, false or error message when failed
  481. */
  482. public function activate($plugin) {
  483. if (CakePlugin::loaded($plugin)) {
  484. return __d('croogo', 'Plugin "%s" is already active.', $plugin);
  485. }
  486. $pluginActivation = $this->getActivator($plugin);
  487. if (!isset($pluginActivation) ||
  488. (isset($pluginActivation) && method_exists($pluginActivation, 'beforeActivation') && $pluginActivation->beforeActivation($this->_Controller))) {
  489. $pluginData = $this->getData($plugin);
  490. $dependencies = true;
  491. if (!empty($pluginData['dependencies']['plugins'])) {
  492. foreach ($pluginData['dependencies']['plugins'] as $requiredPlugin) {
  493. $requiredPlugin = ucfirst($requiredPlugin);
  494. if (!CakePlugin::loaded($requiredPlugin)) {
  495. $dependencies = false;
  496. $missingPlugin = $requiredPlugin;
  497. break;
  498. }
  499. }
  500. }
  501. if ($dependencies) {
  502. $this->addBootstrap($plugin);
  503. CroogoPlugin::load($plugin);
  504. if (isset($pluginActivation) && method_exists($pluginActivation, 'onActivation')) {
  505. $pluginActivation->onActivation($this->_Controller);
  506. }
  507. Cache::delete('file_map', '_cake_core_');
  508. return true;
  509. } else {
  510. return __d('croogo', 'Plugin "%s" depends on "%s" plugin.', $plugin, $missingPlugin);
  511. }
  512. return __d('croogo', 'Plugin "%s" could not be activated. Please, try again.', $plugin);
  513. }
  514. }
  515. /**
  516. * Return a list of plugins that uses $plugin
  517. *
  518. * @return array|bool Boolean false or Array of plugin names
  519. */
  520. public function usedBy($plugin) {
  521. $deps = Configure::read('pluginDeps');
  522. if (empty($deps['usedBy'][$plugin])) {
  523. return false;
  524. }
  525. $usedBy = array_filter($deps['usedBy'][$plugin], array('CakePlugin', 'loaded'));
  526. if (!empty($usedBy)) {
  527. return $usedBy;
  528. }
  529. return false;
  530. }
  531. /**
  532. * Deactivate plugin
  533. *
  534. * @param string $plugin Plugin name
  535. * @return boolean true when successful, false or error message when failed
  536. */
  537. public function deactivate($plugin) {
  538. if (!CakePlugin::loaded($plugin)) {
  539. return __d('croogo', 'Plugin "%s" is not active.', $plugin);
  540. }
  541. $pluginActivation = $this->getActivator($plugin);
  542. if (!isset($pluginActivation) ||
  543. (isset($pluginActivation) && method_exists($pluginActivation, 'beforeDeactivation') && $pluginActivation->beforeDeactivation($this->_Controller))) {
  544. $this->removeBootstrap($plugin);
  545. if (isset($pluginActivation) && method_exists($pluginActivation, 'onDeactivation')) {
  546. $pluginActivation->onDeactivation($this->_Controller);
  547. }
  548. CroogoPlugin::unload($plugin);
  549. Cache::delete('file_map', '_cake_core_');
  550. return true;
  551. } else {
  552. return __d('croogo', 'Plugin could not be deactivated. Please, try again.');
  553. }
  554. }
  555. /**
  556. * Cache plugin dependency list
  557. */
  558. public static function cacheDependencies() {
  559. $pluginDeps = Cache::read('pluginDeps', 'cached_settings');
  560. if (!$pluginDeps) {
  561. $self = self::instance();
  562. $plugins = CakePlugin::loaded();
  563. $dependencies = $usedBy = array();
  564. foreach ($plugins as $plugin) {
  565. $dependencies[$plugin] = $self->getDependencies($plugin);
  566. foreach ($dependencies[$plugin] as $dependent) {
  567. $usedBy[$dependent][] = $plugin;
  568. }
  569. }
  570. $pluginDeps = compact('dependencies', 'usedBy');
  571. Cache::write('pluginDeps', $pluginDeps, 'cached_settings');
  572. }
  573. Configure::write('pluginDeps', $pluginDeps);
  574. }
  575. /**
  576. * Loads a plugin and optionally loads bootstrapping and routing files.
  577. *
  578. * This method is identical to CakePlugin::load() with extra functionality
  579. * that loads event configuration when Plugin/Config/events.php is present.
  580. *
  581. * @see CakePlugin::load()
  582. * @param mixed $plugin name of plugin, or array of plugin and its config
  583. * @return void
  584. */
  585. public static function load($plugin, $config = array()) {
  586. CakePlugin::load($plugin, $config);
  587. if (is_string($plugin)) {
  588. $plugin = array($plugin => $config);
  589. }
  590. Cache::delete('EventHandlers', 'cached_settings');
  591. foreach ($plugin as $name => $conf) {
  592. list($name, $conf) = (is_numeric($name)) ? array($conf, $config) : array($name, $conf);
  593. $file = CakePlugin::path($name) . 'Config' . DS . 'events.php';
  594. if (file_exists($file)) {
  595. Configure::load($name . '.events');
  596. }
  597. }
  598. }
  599. /**
  600. * Forgets a loaded plugin or all of them if first parameter is null
  601. *
  602. * This method is identical to CakePlugin::load() with extra functionality
  603. * that unregister event listeners when a plugin in unloaded.
  604. *
  605. * @see CakePlugin::unload()
  606. * @param string $plugin name of the plugin to forget
  607. * @return void
  608. */
  609. public static function unload($plugin) {
  610. $eventManager = CroogoEventManager::instance();
  611. if ($eventManager instanceof CroogoEventManager) {
  612. if ($plugin == null) {
  613. $activePlugins = CakePlugin::loaded();
  614. foreach ($activePlugins as $activePlugin) {
  615. $eventManager->detachPluginSubscribers($activePlugin);
  616. }
  617. } else {
  618. $eventManager->detachPluginSubscribers($plugin);
  619. }
  620. }
  621. CakePlugin::unload($plugin);
  622. Cache::delete('EventHandlers', 'cached_settings');
  623. }
  624. /**
  625. * Delete plugin
  626. *
  627. * @param string $plugin Plugin name
  628. * @return boolean true when successful, false or array of error messages when failed
  629. * @throws InvalidArgumentException
  630. */
  631. public function delete($plugin) {
  632. if (empty($plugin)) {
  633. throw new InvalidArgumentException(__d('croogo', 'Invalid plugin'));
  634. }
  635. $pluginPath = APP . 'Plugin' . DS . $plugin;
  636. if (is_link($pluginPath)) {
  637. return unlink($pluginPath);
  638. }
  639. $folder = new Folder();
  640. $result = $folder->delete($pluginPath);
  641. if ($result !== true) {
  642. return $folder->errors();
  643. }
  644. return true;
  645. }
  646. /**
  647. * Move plugin up or down in the bootstrap order
  648. *
  649. * @param string $dir valid values 'up' or 'down'
  650. * @param string $plugin plugin alias
  651. * @param array $bootstraps current bootstrap order
  652. * @return array|string array when successful, string contains error message
  653. */
  654. protected function _move($dir, $plugin, $bootstraps) {
  655. $index = array_search($plugin, $bootstraps);
  656. if ($dir === 'up') {
  657. if ($index) {
  658. $swap = $bootstraps[$index - 1];
  659. }
  660. if ($index == 0 || $this->_isBuiltin($swap)) {
  661. return __d('croogo', '%s is already at the first position', $plugin);
  662. }
  663. $before = array_slice($bootstraps, 0, $index - 1);
  664. $after = array_slice($bootstraps, $index + 1);
  665. $dependencies = $this->getDependencies($plugin);
  666. if (in_array($swap, $dependencies)) {
  667. return __d('croogo', 'Plugin %s depends on %s', $plugin, $swap);
  668. }
  669. $reordered = array_merge($before, (array)$plugin, (array)$swap);
  670. } elseif ($dir === 'down') {
  671. if ($index >= count($bootstraps) - 1) {
  672. return __d('croogo', '%s is already at the last position', $plugin);
  673. }
  674. $swap = $bootstraps[$index + 1];
  675. $before = array_slice($bootstraps, 0, $index);
  676. $after = array_slice($bootstraps, $index + 2);
  677. $dependencies = $this->getDependencies($swap);
  678. if (in_array($plugin, $dependencies)) {
  679. return __d('croogo', 'Plugin %s depends on %s', $swap, $plugin);
  680. }
  681. $reordered = array_merge($before, (array)$swap, (array)$plugin);
  682. } else {
  683. return __d('croogo', 'Invalid direction');
  684. }
  685. $reordered = array_merge($reordered, $after);
  686. if ($this->_isBuiltin($swap)) {
  687. return __d('croogo', 'Plugin %s cannot be reordered', $swap);
  688. }
  689. return $reordered;
  690. }
  691. /**
  692. * Write Hook.bootstraps settings to database and json file
  693. *
  694. * @param array $bootstrap array of plugin aliases
  695. * @return boolean
  696. * @throws CakeException
  697. */
  698. protected function _saveBootstraps($bootstraps) {
  699. static $Setting = null;
  700. if (empty($Setting)) {
  701. if (!Configure::read('Croogo.installed')) {
  702. throw new CakeException('Unable to save Hook.bootstraps when Croogo is not fully installed');
  703. }
  704. $Setting = ClassRegistry::init('Settings.Setting');
  705. }
  706. return $Setting->write('Hook.bootstraps', implode(',', $bootstraps));
  707. }
  708. /**
  709. * Move plugin in the bootstrap order
  710. *
  711. * @param string $dir direction 'up' or 'down'
  712. * @param string $plugin plugin alias
  713. * @param array $bootstraps array of plugin aliases
  714. * @return string|bool true when successful, string contains error message
  715. */
  716. public function move($dir, $plugin, $bootstraps = null) {
  717. if (empty($bootstraps)) {
  718. $bootstraps = explode(',', Configure::read('Hook.bootstraps'));
  719. }
  720. $reordered = $this->_move(strtolower($dir), $plugin, $bootstraps);
  721. if (is_string($reordered)) {
  722. return $reordered;
  723. }
  724. return $this->_saveBootstraps($reordered);
  725. }
  726. }