PageRenderTime 42ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/Acl/Lib/AclExtras.php

https://github.com/kareypowell/croogo
PHP | 482 lines | 307 code | 43 blank | 132 comment | 42 complexity | a90674cc67256e2dfb45250cc2f4cf2d MD5 | raw file
  1. <?php
  2. /**
  3. * Acl Extras Shell.
  4. *
  5. * Enhances the existing Acl Shell with a few handy functions
  6. *
  7. * Copyright 2008, Mark Story.
  8. * 694B The Queensway
  9. * toronto, ontario M8Y 1K9
  10. *
  11. * Licensed under The MIT License
  12. * Redistributions of files must retain the above copyright notice.
  13. *
  14. * @copyright Copyright 2008-2010, Mark Story.
  15. * @link http://mark-story.com
  16. * @author Mark Story <mark@mark-story.com>
  17. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  18. */
  19. App::uses('Controller', 'Controller');
  20. App::uses('ComponentCollection', 'Controller');
  21. App::uses('AclComponent', 'Controller/Component');
  22. App::uses('DbAcl', 'Model');
  23. App::uses('Shell', 'Console');
  24. /**
  25. * Shell for ACO extras
  26. *
  27. * @package Croogo.Acl.Lib
  28. */
  29. class AclExtras extends Object {
  30. /**
  31. * Contains instance of AclComponent
  32. *
  33. * @var AclComponent
  34. * @access public
  35. */
  36. public $Acl;
  37. /**
  38. * Contains arguments parsed from the command line.
  39. *
  40. * @var array
  41. * @access public
  42. */
  43. public $args;
  44. /**
  45. * Contains database source to use
  46. *
  47. * @var string
  48. * @access public
  49. */
  50. public $dataSource = 'default';
  51. /**
  52. * Root node name.
  53. *
  54. * @var string
  55. **/
  56. public $rootNode = 'controllers';
  57. /**
  58. * Internal Clean Actions switch
  59. *
  60. * @var boolean
  61. **/
  62. protected $_clean = false;
  63. /**
  64. * array of output messages
  65. */
  66. public $output = array();
  67. /**
  68. * array of error messages
  69. */
  70. public $errors = array();
  71. /**
  72. * count of newly created ACOs
  73. */
  74. public $created = 0;
  75. /**
  76. * list of ACO paths to skip
  77. */
  78. protected $_skipList = array(
  79. 'controllers/CroogoApp',
  80. 'controllers/CroogoError',
  81. 'controllers/Croogo/CroogoApp',
  82. 'controllers/Croogo/CroogoError',
  83. );
  84. /**
  85. * Start up And load Acl Component / Aco model
  86. *
  87. * @return void
  88. **/
  89. public function startup($controller = null) {
  90. if (!$controller) {
  91. $controller = new Controller(new CakeRequest());
  92. }
  93. $collection = new ComponentCollection();
  94. $this->Acl = new AclComponent($collection);
  95. $this->Acl->startup($controller);
  96. $this->Aco = $this->Acl->Aco;
  97. $this->controller = $controller;
  98. }
  99. public function out($msg) {
  100. $this->output[] = $msg;
  101. if (!empty($this->controller->Session)) {
  102. $this->controller->Session->setFlash($msg);
  103. } elseif (isset($this->Shell)) {
  104. return $this->Shell->out($msg);
  105. }
  106. }
  107. public function err($msg) {
  108. $this->errors[] = $msg;
  109. if (!empty($this->controller->Session)) {
  110. $this->controller->Session->setFlash($msg);
  111. } elseif (isset($this->Shell)) {
  112. return $this->Shell->err($msg);
  113. }
  114. }
  115. /**
  116. * Sync the ACO table
  117. *
  118. * @return void
  119. **/
  120. public function aco_sync($params = array()) {
  121. $this->_clean = true;
  122. return $this->aco_update($params);
  123. }
  124. /**
  125. * Sync the ACO table for contents
  126. *
  127. * @return bool
  128. */
  129. public function aco_update_contents($params = array()) {
  130. list($plugin, $model) = pluginSplit($this->args[0]);
  131. App::uses($model, $plugin . '.Model');
  132. if (!class_exists($model)) {
  133. $this->err(__d('croogo', 'Model %s cannot be found.', $model));
  134. return false;
  135. }
  136. $Model = ClassRegistry::init($model);
  137. $primaryKey = $Model->primaryKey;
  138. $rows = $Model->find('all');
  139. $root = $this->_checkNode('contents', 'contents', null);
  140. foreach ($rows as $row) {
  141. $alias = sprintf('%s.%s', $model, $row[$model][$primaryKey]);
  142. $path = sprintf('contents/%s', $alias);
  143. $acoNode = $this->Aco->node($path);
  144. if ($acoNode) {
  145. continue;
  146. }
  147. $this->Aco->create(array(
  148. 'parent_id' => $root['Aco']['id'],
  149. 'model' => $model,
  150. 'alias' => $alias,
  151. 'foreign_key' => $row[$model][$primaryKey],
  152. ));
  153. $acoNode = $this->Aco->save();
  154. $this->out(__d('croogo', 'Created Aco node: %s', $path), 1, Shell::VERBOSE);
  155. }
  156. return true;
  157. }
  158. /**
  159. * Updates the Aco Tree with new controller actions.
  160. *
  161. * @return void
  162. **/
  163. public function aco_update($params = array()) {
  164. $root = $this->_checkNode($this->rootNode, $this->rootNode, null);
  165. if (empty($params['plugin'])) {
  166. $controllers = $this->getControllerList();
  167. $this->_updateControllers($root, $controllers);
  168. $plugins = CakePlugin::loaded();
  169. } else {
  170. $plugin = $params['plugin'];
  171. if (!in_array($plugin, App::objects('plugin')) || !CakePlugin::loaded($plugin)) {
  172. $this->err(__d('croogo', '<error>Plugin %s not found or not activated</error>', $plugin));
  173. return false;
  174. }
  175. $plugins = array($params['plugin']);
  176. }
  177. foreach ($plugins as $plugin) {
  178. $controllers = $this->getControllerList($plugin);
  179. $path = $this->rootNode . '/' . $plugin;
  180. $pluginRoot = $this->_checkNode($path, $plugin, $root['Aco']['id']);
  181. $this->_updateControllers($pluginRoot, $controllers, $plugin);
  182. $this->_updateApiComponents($controllers, $plugin);
  183. }
  184. Cache::clearGroup('acl', 'permissions');
  185. if ($this->_clean) {
  186. $this->out(__d('croogo', '<success>Aco Sync Complete</success>'));
  187. } else {
  188. $this->out(__d('croogo', '<success>Aco Update Complete</success>'));
  189. }
  190. return true;
  191. }
  192. /**
  193. * Updates a collection of controllers.
  194. *
  195. * @param array $root Array or ACO information for root node.
  196. * @param array $controllers Array of Controllers
  197. * @param string $plugin Name of the plugin you are making controllers for.
  198. * @return void
  199. */
  200. protected function _updateControllers($root, $controllers, $plugin = null) {
  201. $dotPlugin = $pluginPath = $plugin;
  202. if ($plugin) {
  203. $dotPlugin .= '.';
  204. $pluginPath .= '/';
  205. }
  206. $appIndex = array_search($plugin . 'AppController', $controllers);
  207. if ($appIndex !== false) {
  208. App::uses($plugin . 'AppController', $dotPlugin . 'Controller');
  209. unset($controllers[$appIndex]);
  210. }
  211. // look at each controller
  212. foreach ($controllers as $controller) {
  213. App::uses($controller, $dotPlugin . 'Controller');
  214. $controllerName = preg_replace('/Controller$/', '', $controller);
  215. $path = $this->rootNode . '/' . $pluginPath . $controllerName;
  216. if (in_array($path, $this->_skipList)) {
  217. $this->out(__d('croogo', 'Skipped Aco node: <warning>%s</warning>', $path), 1, Shell::VERBOSE);
  218. continue;
  219. }
  220. $controllerNode = $this->_checkNode($path, $controllerName, $root['Aco']['id']);
  221. $this->_checkMethods($controller, $controllerName, $controllerNode, $pluginPath);
  222. }
  223. if ($this->_clean) {
  224. if (!$plugin) {
  225. $controllers = array_merge($controllers, App::objects('plugin', null, false));
  226. }
  227. $controllerFlip = array_flip($controllers);
  228. $this->Aco->id = $root['Aco']['id'];
  229. $controllerNodes = $this->Aco->children(null, true);
  230. foreach ($controllerNodes as $ctrlNode) {
  231. $alias = $ctrlNode['Aco']['alias'];
  232. $name = $alias . 'Controller';
  233. if (!isset($controllerFlip[$name]) && !isset($controllerFlip[$alias])) {
  234. if ($this->Aco->delete($ctrlNode['Aco']['id'])) {
  235. $this->out(__d('croogo',
  236. 'Deleted %s and all children',
  237. $this->rootNode . '/' . $ctrlNode['Aco']['alias']
  238. ), 1, Shell::VERBOSE);
  239. }
  240. }
  241. }
  242. }
  243. }
  244. protected function _checkApiMethods($plugin, $className, $apiComponent) {
  245. $Aco = ClassRegistry::init('Acl.AclAco');
  246. list($cPlugin, $component) = pluginSplit($apiComponent);
  247. $componentName = $component . 'Component';
  248. App::uses($componentName, $cPlugin . '.Controller/Component');
  249. $reflection = new ReflectionClass($componentName);
  250. $props = $reflection->getDefaultProperties();
  251. $version = str_replace('.', '_', $props['_apiVersion']);
  252. $methods = $props['_apiMethods'];
  253. foreach ($methods as $method) {
  254. $path = sprintf(
  255. 'api/%s/%s/%s/%s',
  256. $version, $plugin, $className, $method
  257. );
  258. $node = $Aco->node($path);
  259. if (empty($node)) {
  260. $aco = $Aco->createFromPath($path);
  261. $this->created++;
  262. $this->out(__d('croogo', 'Created Aco node: <success>%s</success>', $path), 1, Shell::VERBOSE);
  263. continue;
  264. }
  265. if ($this->_clean) {
  266. $path = sprintf('api/%s/%s/%s', $version, $plugin, $className);
  267. $node = $Aco->node($path);
  268. $actionNodes = $this->Aco->children($node[0]['Aco']['id']);
  269. foreach ($actionNodes as $action) {
  270. if (in_array($action['Aco']['alias'], $methods)) {
  271. continue;
  272. }
  273. $this->Aco->id = $action['Aco']['id'];
  274. if ($this->Aco->delete()) {
  275. $acoPath = $path . '/' . $action['Aco']['alias'];
  276. $this->out(__d('croogo', 'Deleted Aco node: <warning>%s</warning>', $acoPath), 1, Shell::VERBOSE);
  277. }
  278. }
  279. }
  280. }
  281. }
  282. protected function _updateApiComponents($controllers, $plugin) {
  283. $hook = 'Hook.controller_properties.%s._apiComponents';
  284. foreach ($controllers as $controller) {
  285. $controllerName = str_replace('Controller', '', $controller);
  286. $path = sprintf($hook, $controllerName);
  287. $apiComponents = Configure::read($path);
  288. if (!is_array($apiComponents)) {
  289. continue;
  290. }
  291. foreach ($apiComponents as $apiComponent => $setting) {
  292. $this->_checkApiMethods($plugin, $controllerName, $apiComponent);
  293. }
  294. }
  295. }
  296. /**
  297. * Get a list of controllers in the app and plugins.
  298. *
  299. * Returns an array of path => import notation.
  300. *
  301. * @param string $plugin Name of plugin to get controllers for
  302. * @return array
  303. **/
  304. public function getControllerList($plugin = null) {
  305. $excludes = array('CakeErrorController');
  306. if (!$plugin) {
  307. $controllers = App::objects('Controller', null, false);
  308. } else {
  309. $controllers = App::objects($plugin . '.Controller', null, false);
  310. }
  311. $controllers = array_diff($controllers, $excludes);
  312. return $controllers;
  313. }
  314. /**
  315. * Check a node for existance, create it if it doesn't exist.
  316. *
  317. * @param string $path
  318. * @param string $alias
  319. * @param int $parentId
  320. * @return array Aco Node array
  321. */
  322. protected function _checkNode($path, $alias, $parentId = null) {
  323. $node = $this->Aco->node($path);
  324. if (!$node) {
  325. $this->Aco->create(array('parent_id' => $parentId, 'model' => null, 'alias' => $alias));
  326. $node = $this->Aco->save();
  327. $node['Aco']['id'] = $this->Aco->id;
  328. $this->created++;
  329. $this->out(__d('croogo', 'Created Aco node: <success>%s</success>', $path), 1, Shell::VERBOSE);
  330. } else {
  331. $node = $node[0];
  332. }
  333. return $node;
  334. }
  335. /**
  336. * Get a list of registered callback methods
  337. */
  338. protected function _getCallbacks($className) {
  339. $callbacks = array();
  340. $reflection = new ReflectionClass($className);
  341. if ($reflection->isAbstract()) {
  342. return $callbacks;
  343. }
  344. try {
  345. $method = $reflection->getMethod('implementedEvents');
  346. } catch (ReflectionException $e) {
  347. return $callbacks;
  348. }
  349. $method = $reflection->getMethod('implementedEvents');
  350. if (version_compare(phpversion(), '5.4', '>=')) {
  351. $object = $reflection->newInstanceWithoutConstructor();
  352. } else {
  353. $object = unserialize(
  354. sprintf('O:%d:"%s":0:{}', strlen($className), $className)
  355. );
  356. }
  357. $implementedEvents = $method->invoke($object);
  358. foreach ($implementedEvents as $event => $callable) {
  359. if (is_string($callable)) {
  360. $callbacks[] = $callable;
  361. }
  362. if (is_array($callable) && isset($callable['callable'])) {
  363. $callbacks[] = $callable['callable'];
  364. }
  365. }
  366. return $callbacks;
  367. }
  368. /**
  369. * Check and Add/delete controller Methods
  370. *
  371. * @param string $controller
  372. * @param array $node
  373. * @param string $plugin Name of plugin
  374. * @return void
  375. */
  376. protected function _checkMethods($className, $controllerName, $node, $pluginPath = false) {
  377. $excludes = array('afterConstruct', 'securityError');
  378. $excludes += $this->_getCallbacks($className);
  379. $baseMethods = get_class_methods('Controller');
  380. $actions = get_class_methods($className);
  381. $methods = array_diff($actions, $baseMethods);
  382. $methods = array_diff($methods, $excludes);
  383. foreach ($methods as $action) {
  384. if (strpos($action, '_', 0) === 0) {
  385. continue;
  386. }
  387. $path = $this->rootNode . '/' . $pluginPath . $controllerName . '/' . $action;
  388. $this->_checkNode($path, $action, $node['Aco']['id']);
  389. }
  390. if ($this->_clean) {
  391. $actionNodes = $this->Aco->children($node['Aco']['id']);
  392. $methodFlip = array_flip($methods);
  393. foreach ($actionNodes as $action) {
  394. if (!isset($methodFlip[$action['Aco']['alias']])) {
  395. $this->Aco->id = $action['Aco']['id'];
  396. if ($this->Aco->delete()) {
  397. $path = $this->rootNode . '/' . $controllerName . '/' . $action['Aco']['alias'];
  398. $this->out(__d('croogo', 'Deleted Aco node: <warning>%s</warning>', $path), 1, Shell::VERBOSE);
  399. }
  400. }
  401. }
  402. }
  403. return true;
  404. }
  405. /**
  406. * Verify a Acl Tree
  407. *
  408. * @param string $type The type of Acl Node to verify
  409. * @access public
  410. * @return void
  411. */
  412. public function verify() {
  413. $type = Inflector::camelize($this->args[0]);
  414. $return = $this->Acl->{$type}->verify();
  415. if ($return === true) {
  416. $this->out(__d('croogo', '<success>Tree is valid and strong</success>'));
  417. } else {
  418. $this->err(print_r($return, true));
  419. return false;
  420. }
  421. }
  422. /**
  423. * Recover an Acl Tree
  424. *
  425. * @param string $type The Type of Acl Node to recover
  426. * @access public
  427. * @return void
  428. */
  429. public function recover() {
  430. $type = Inflector::camelize($this->args[0]);
  431. $return = $this->Acl->{$type}->recover();
  432. if ($return === true) {
  433. $this->out(__d('croogo', 'Tree has been recovered, or tree did not need recovery.'));
  434. } else {
  435. $this->err(__d('croogo', '<error>Tree recovery failed.</error>'));
  436. return false;
  437. }
  438. }
  439. }