PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/sally/backend/lib/Controller/Addon.php

https://bitbucket.org/SallyCMS/trunk
PHP | 499 lines | 329 code | 93 blank | 77 comment | 40 complexity | 0ef6c6c098b60b4e9bd127502fb7ae1b MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright (c) 2012, webvariants GbR, http://www.webvariants.de
  4. *
  5. * This file is released under the terms of the MIT license. You can find the
  6. * complete text in the attached LICENSE file or online at:
  7. *
  8. * http://www.opensource.org/licenses/mit-license.php
  9. */
  10. class sly_Controller_Addon extends sly_Controller_Backend implements sly_Controller_Interface {
  11. protected $addon = false;
  12. private $init = 0;
  13. protected function init() {
  14. if ($this->init++) return;
  15. if (!$this->getRequest()->isAjax()) {
  16. $layout = sly_Core::getLayout();
  17. $layout->pageHeader(t('addons'));
  18. }
  19. }
  20. /**
  21. * Get current addOn
  22. *
  23. * @return string the addOn or null
  24. */
  25. protected function getAddOn() {
  26. if ($this->addon === false) {
  27. extract($this->getServices());
  28. $addon = $this->getRequest()->request('addon', 'string', '');
  29. if (empty($addon)) {
  30. throw new sly_Exception(t('addon_not_given'));
  31. }
  32. if (!$aservice->isRegistered($addon)) {
  33. throw new sly_Exception(t('addon_not_found', $addon));
  34. }
  35. $this->addon = $addon;
  36. }
  37. return $this->addon;
  38. }
  39. /**
  40. * Set current addOn
  41. *
  42. * @param string $addon addOn name
  43. */
  44. protected function setAddOn($addon) {
  45. $this->addon = $addon;
  46. }
  47. /**
  48. * Returns the commonly used services
  49. *
  50. * @return array {aservice: addon-service, manager: addon-manager, pservice: package-service}
  51. */
  52. protected function getServices() {
  53. return array(
  54. 'aservice' => sly_Service_Factory::getAddOnService(),
  55. 'manager' => sly_Service_Factory::getAddOnManagerService(),
  56. 'pservice' => sly_Service_Factory::getAddOnPackageService()
  57. );
  58. }
  59. /**
  60. * index action
  61. */
  62. public function indexAction() {
  63. $this->init();
  64. $data = $this->buildDataList();
  65. $data = $this->resolveParentRelationships($data);
  66. $this->render('addon/list.phtml', array(
  67. 'tree' => $data,
  68. 'stati' => $this->buildStatusList($data)
  69. ), false);
  70. }
  71. public function installAction() {
  72. try {
  73. $this->call('install', 'installed', true);
  74. $this->call('activate', 'activated');
  75. }
  76. catch (Exception $e) {
  77. sly_Core::getFlashMessage()->appendWarning($e->getMessage());
  78. }
  79. return $this->sendResponse();
  80. }
  81. public function uninstallAction() {
  82. try {
  83. $this->call('uninstall', 'uninstalled');
  84. }
  85. catch (Exception $e) {
  86. sly_Core::getFlashMessage()->appendWarning($e->getMessage());
  87. }
  88. return $this->sendResponse();
  89. }
  90. public function activateAction() {
  91. try {
  92. $this->call('activate', 'activated');
  93. }
  94. catch (Exception $e) {
  95. sly_Core::getFlashMessage()->appendWarning($e->getMessage());
  96. }
  97. return $this->sendResponse();
  98. }
  99. public function deactivateAction() {
  100. try {
  101. $this->call('deactivate', 'deactivated');
  102. }
  103. catch (Exception $e) {
  104. sly_Core::getFlashMessage()->appendWarning($e->getMessage());
  105. }
  106. return $this->sendResponse();
  107. }
  108. public function reinitAction() {
  109. try {
  110. $this->call('copyAssets', 'assets_copied');
  111. }
  112. catch (Exception $e) {
  113. sly_Core::getFlashMessage()->appendWarning($e->getMessage());
  114. }
  115. return $this->sendResponse();
  116. }
  117. public function fullinstallAction() {
  118. $this->init();
  119. try {
  120. $todo = $this->getInstallList($this->getAddOn());
  121. extract($this->getServices());
  122. if (!empty($todo)) {
  123. // pretend that we're about to work on this now
  124. $addon = reset($todo);
  125. $this->setAddOn($addon);
  126. // if not installed, install it
  127. if (!$aservice->isInstalled($addon)) {
  128. $this->call('install', 'installed', true);
  129. }
  130. // if not activated and install went OK, activate it
  131. if (!$aservice->isAvailable($addon)) {
  132. $this->call('activate', 'activated');
  133. }
  134. // redirect to the next addOn
  135. if (count($todo) > 1) {
  136. return $this->sendResponse(false);
  137. }
  138. }
  139. }
  140. catch (Exception $e) {
  141. sly_Core::getFlashMessage()->appendWarning($e->getMessage());
  142. }
  143. return $this->sendResponse();
  144. }
  145. public function checkPermission($action) {
  146. $user = sly_Util_User::getCurrentUser();
  147. if (!$user || (!$user->isAdmin() && !$user->hasRight('pages', 'addons'))) {
  148. return false;
  149. }
  150. return true;
  151. }
  152. protected function call($method, $i18n, $silent = false) {
  153. extract($this->getServices());
  154. $addon = $this->getAddOn();
  155. // check token here instead of in checkPermission() to handle the exception
  156. // ourselves and send a proper JSON response
  157. sly_Util_Csrf::checkToken();
  158. switch ($method) {
  159. case 'install':
  160. $manager->install($addon, true, sly_DB_Persistence::getInstance());
  161. break;
  162. case 'uninstall':
  163. $manager->uninstall($addon, sly_DB_Persistence::getInstance());
  164. break;
  165. default:
  166. $manager->$method($addon);
  167. }
  168. if (!$silent) {
  169. sly_Core::getFlashMessage()->appendInfo(t('addon_'.$i18n, $addon));
  170. }
  171. }
  172. private function sendResponse($finished = true) {
  173. if ($this->getRequest()->isAjax()) {
  174. $data = $this->buildDataList();
  175. $data = $this->resolveParentRelationships($data);
  176. $flash = sly_Core::getFlashMessage();
  177. $msgs = $flash->getMessages(sly_Util_FlashMessage::TYPE_WARNING);
  178. foreach ($msgs as $idx => $list) {
  179. $msgs[$idx] = is_array($list) ? implode('<br />', $list) : $list;
  180. }
  181. $content = array(
  182. 'status' => empty($msgs),
  183. 'stati' => $this->buildStatusList($data),
  184. 'message' => implode('<br />', $msgs),
  185. 'finished' => $finished
  186. );
  187. $flash->clear();
  188. $response = sly_Core::getResponse();
  189. $response->setContentType('application/json', 'UTF-8');
  190. $response->setContent(json_encode($content));
  191. return $response;
  192. }
  193. return $this->redirectResponse();
  194. }
  195. /**
  196. * @param string $addon
  197. * @return array
  198. */
  199. private function getAddOnDetails($addon) {
  200. static $reqCache = array();
  201. static $depCache = array();
  202. extract($this->getServices());
  203. if (!isset($reqCache[$addon])) {
  204. $reqCache[$addon] = $aservice->getRequirements($addon);
  205. $depCache[$addon] = $aservice->getDependencies($addon, true, true);
  206. }
  207. $requirements = $reqCache[$addon];
  208. $dependencies = $depCache[$addon];
  209. $missing = array();
  210. $required = $aservice->isRequired($addon) !== false;
  211. $installed = $aservice->isInstalled($addon);
  212. $activated = $installed ? $aservice->isActivated($addon) : false;
  213. $compatible = $aservice->isCompatible($addon);
  214. $version = $pservice->getVersion($addon);
  215. $parent = $pservice->getParent($addon);
  216. $author = sly_Helper_Package::getSupportPage($addon);
  217. $usable = $compatible ? $this->canBeUsed($addon) : false;
  218. if ($parent !== null) {
  219. // do not allow to nest more than one level
  220. $exists = $pservice->exists($parent);
  221. $hasGrand = $exists ? $pservice->getParent($parent) : false;
  222. if (!$exists || $hasGrand) {
  223. $parent = null;
  224. }
  225. else {
  226. $requirements[] = $parent;
  227. $requirements = array_unique($requirements);
  228. }
  229. }
  230. foreach ($requirements as $req) {
  231. if (!$aservice->isAvailable($req)) $missing[] = $req;
  232. }
  233. return compact('requirements', 'dependencies', 'missing', 'required', 'installed', 'activated', 'compatible', 'usable', 'version', 'author', 'parent');
  234. }
  235. /**
  236. * Check whether a package can be used
  237. *
  238. * To make this method return true, all required packages must be present,
  239. * compatible and themselves be usable.
  240. *
  241. * @param string $package
  242. * @return boolean
  243. */
  244. private function canBeUsed($package) {
  245. extract($this->getServices());
  246. if (!$pservice->exists($package)) return false;
  247. if (!$aservice->isCompatible($package)) return false;
  248. $requirements = $aservice->getRequirements($package, false);
  249. foreach ($requirements as $requirement) {
  250. if (!$this->canBeUsed($requirement)) return false;
  251. }
  252. return true;
  253. }
  254. /**
  255. * Determine what packages to install
  256. *
  257. * This method will walk through all requirements and collect a list of
  258. * packages that need to be installed to install the $addon. The list
  259. * is ordered ($addon is always the last element). Already activated
  260. * packages will not be included (so the result can be empty if $addon
  261. * is also already activated).
  262. *
  263. * @param string $addon addon name
  264. * @param array $list current stack (used internally)
  265. * @return array install list
  266. */
  267. private function getInstallList($addon, array $list = array()) {
  268. extract($this->getServices());
  269. $idx = array_search($addon, $list);
  270. $requirements = $aservice->getRequirements($addon);
  271. if ($idx !== false) {
  272. unset($list[$idx]);
  273. $list = array_values($list);
  274. }
  275. if (!$aservice->isAvailable($addon)) {
  276. array_unshift($list, $addon);
  277. }
  278. foreach ($requirements as $requirement) {
  279. $list = $this->getInstallList($requirement, $list);
  280. }
  281. return $list;
  282. }
  283. private function buildDataList() {
  284. extract($this->getServices());
  285. $result = array();
  286. foreach ($aservice->getRegisteredAddOns() as $addon) {
  287. $details = $this->getAddOnDetails($addon);
  288. $details['children'] = array();
  289. $result[$addon] = $details;
  290. }
  291. return $result;
  292. }
  293. private function buildStatusList(array $packageData) {
  294. $result = array();
  295. foreach ($packageData as $package => $info) {
  296. $classes = array('sly-addon');
  297. // build class list for all relevant stati
  298. if (!empty($info['children'])) {
  299. $classes[] = 'ch1'; // children yes
  300. foreach ($info['children'] as $childInfo) {
  301. if ($childInfo['activated']) {
  302. $classes[] = 'ca1';
  303. $classes[] = 'd1'; // assume implicit dependency of packages from their parent packages
  304. break;
  305. }
  306. }
  307. }
  308. else {
  309. $classes[] = 'ch0'; // childen no
  310. }
  311. // if there are no active children, dependency status is based on required status
  312. if (!in_array('ca1', $classes)) {
  313. $classes[] = 'd'.intval($info['required']);
  314. }
  315. else {
  316. $classes[] = 'ca0'; // children active no
  317. }
  318. $classes[] = 'i'.intval($info['installed']);
  319. $classes[] = 'a'.intval($info['activated']);
  320. $classes[] = 'c'.intval($info['compatible']);
  321. $classes[] = 'r'.intval($info['requirements']);
  322. $classes[] = 'ro'.(empty($info['missing']) ? 1 : 0);
  323. $classes[] = 'u'.intval($info['usable']);
  324. $result[$package] = array(
  325. 'classes' => implode(' ', $classes),
  326. 'deps' => $this->buildDepsInfo($info)
  327. );
  328. foreach ($info['children'] as $childPkg => $childInfo) {
  329. $classes = array('sly-addon-child');
  330. $childInfo['requirements'][] = $package;
  331. $childInfo['requirements'] = array_unique($childInfo['requirements']);
  332. $classes[] = 'i'.intval($childInfo['installed']);
  333. $classes[] = 'a'.intval($childInfo['activated']);
  334. $classes[] = 'd'.intval($childInfo['required']);
  335. $classes[] = 'c'.intval($childInfo['compatible']);
  336. $classes[] = 'r'.intval($childInfo['requirements']);
  337. $classes[] = 'ro'.(empty($childInfo['missing']) ? 1 : 0);
  338. $classes[] = 'u'.intval($childInfo['usable']);
  339. $result[$childPkg] = array(
  340. 'classes' => implode(' ', $classes),
  341. 'deps' => $this->buildDepsInfo($childInfo)
  342. );
  343. }
  344. }
  345. return $result;
  346. }
  347. /**
  348. * Build a HTML string describing the requirements and dependencies
  349. *
  350. * @param array $info addOn info
  351. * @return string
  352. */
  353. private function buildDepsInfo(array $info) {
  354. $texts = array();
  355. if ($info['required']) {
  356. if (count($info['dependencies']) === 1) {
  357. $text = t('is_required', reset($info['dependencies']));
  358. }
  359. else {
  360. $list = sly_Util_String::humanImplode($info['dependencies']);
  361. $text = t('is_required', count($info['dependencies']));
  362. $text = '<span title="'.sly_html($list).'">'.$text.'</span>';
  363. }
  364. $texts[] = $text;
  365. }
  366. if ($info['requirements']) {
  367. if (count($info['requirements']) === 1) {
  368. $text = t('requires').' '.reset($info['requirements']);
  369. }
  370. else {
  371. $list = sly_Util_String::humanImplode($info['requirements']);
  372. $text = t('requires').' '.count($info['requirements']);
  373. $text = '<span title="'.sly_html($list).'">'.$text.'</span>';
  374. }
  375. $texts[] = $text;
  376. }
  377. if (empty($texts)) {
  378. return t('no_dependencies');
  379. }
  380. return implode(' &amp; ', $texts);
  381. }
  382. /**
  383. * Transform addOn list by using parents
  384. *
  385. * This method scans through the flat list of addOns and moved all addOns,
  386. * that have a "parent" declaration into the parent's "children" key. This is
  387. * done to make the view simpler.
  388. *
  389. * @param array $data
  390. * @return array
  391. */
  392. private function resolveParentRelationships(array $data) {
  393. do {
  394. $changes = false;
  395. foreach ($data as $package => $info) {
  396. if ($info['parent']) {
  397. $data[$info['parent']]['children'][$package] = $info;
  398. unset($data[$package]);
  399. $changes = true;
  400. break;
  401. }
  402. }
  403. } while ($changes);
  404. return $data;
  405. }
  406. }