PageRenderTime 56ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/sly/Controller/Addon.php

https://bitbucket.org/SallyCMS/sallycms-backend
PHP | 495 lines | 325 code | 93 blank | 77 comment | 40 complexity | 6457b09d4f09c56df51717d958337158 MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright (c) 2013, 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' => $this->getContainer()->getAddOnService(),
  55. 'manager' => $this->getContainer()->getAddOnManagerService(),
  56. 'pservice' => $this->getContainer()->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 fullinstallAction() {
  109. $this->init();
  110. try {
  111. $todo = $this->getInstallList($this->getAddOn());
  112. extract($this->getServices());
  113. if (!empty($todo)) {
  114. // pretend that we're about to work on this now
  115. $addon = reset($todo);
  116. $this->setAddOn($addon);
  117. // if not installed, install it
  118. if (!$aservice->isInstalled($addon)) {
  119. $this->call('install', 'installed', true);
  120. }
  121. // if not activated and install went OK, activate it
  122. if (!$aservice->isAvailable($addon)) {
  123. $this->call('activate', 'activated');
  124. }
  125. // redirect to the next addOn
  126. if (count($todo) > 1) {
  127. return $this->sendResponse(false);
  128. }
  129. }
  130. }
  131. catch (Exception $e) {
  132. sly_Core::getFlashMessage()->appendWarning($e->getMessage());
  133. }
  134. return $this->sendResponse();
  135. }
  136. public function checkPermission($action) {
  137. $user = sly_Util_User::getCurrentUser();
  138. if (!$user || (!$user->isAdmin() && !$user->hasRight('pages', 'addons'))) {
  139. return false;
  140. }
  141. return true;
  142. }
  143. protected function call($method, $i18n, $silent = false) {
  144. extract($this->getServices());
  145. $addon = $this->getAddOn();
  146. $container = $this->getContainer();
  147. $persistence = $container->getPersistence();
  148. // check token here instead of in checkPermission() to handle the exception
  149. // ourselves and send a proper JSON response
  150. sly_Util_Csrf::checkToken();
  151. switch ($method) {
  152. case 'install':
  153. $manager->install($addon, true, $persistence, $container);
  154. break;
  155. case 'uninstall':
  156. $manager->uninstall($addon, $persistence, $container);
  157. break;
  158. case 'activate':
  159. $manager->$method($addon, $container);
  160. break;
  161. default:
  162. $manager->$method($addon);
  163. }
  164. if (!$silent) {
  165. $container->getFlashMessage()->appendInfo(t('addon_'.$i18n, $addon));
  166. }
  167. }
  168. private function sendResponse($finished = true) {
  169. if ($this->getRequest()->isAjax()) {
  170. $data = $this->buildDataList();
  171. $data = $this->resolveParentRelationships($data);
  172. $flash = sly_Core::getFlashMessage();
  173. $msgs = $flash->getMessages(sly_Util_FlashMessage::TYPE_WARNING);
  174. foreach ($msgs as $idx => $list) {
  175. $msgs[$idx] = is_array($list) ? implode('<br />', $list) : $list;
  176. }
  177. $content = array(
  178. 'status' => empty($msgs),
  179. 'stati' => $this->buildStatusList($data),
  180. 'message' => implode('<br />', $msgs),
  181. 'finished' => $finished
  182. );
  183. $flash->clear();
  184. $response = sly_Core::getResponse();
  185. $response->setContentType('application/json', 'UTF-8');
  186. $response->setContent(json_encode($content));
  187. return $response;
  188. }
  189. return $this->redirectResponse();
  190. }
  191. /**
  192. * @param string $addon
  193. * @return array
  194. */
  195. private function getAddOnDetails($addon) {
  196. static $reqCache = array();
  197. static $depCache = array();
  198. extract($this->getServices());
  199. if (!isset($reqCache[$addon])) {
  200. $reqCache[$addon] = $aservice->getRequirements($addon);
  201. $depCache[$addon] = $aservice->getDependencies($addon, true, true);
  202. }
  203. $requirements = $reqCache[$addon];
  204. $dependencies = $depCache[$addon];
  205. $missing = array();
  206. $required = $aservice->isRequired($addon) !== false;
  207. $installed = $aservice->isInstalled($addon);
  208. $activated = $installed ? $aservice->isActivated($addon) : false;
  209. $compatible = $aservice->isCompatible($addon);
  210. $version = $pservice->getVersion($addon);
  211. $parent = $pservice->getParent($addon);
  212. $author = sly_Helper_Package::getSupportPage($addon);
  213. $usable = $compatible ? $this->canBeUsed($addon) : false;
  214. if ($parent !== null) {
  215. // do not allow to nest more than one level
  216. $exists = $pservice->exists($parent);
  217. $hasGrand = $exists ? $pservice->getParent($parent) : false;
  218. if (!$exists || $hasGrand) {
  219. $parent = null;
  220. }
  221. else {
  222. $requirements[] = $parent;
  223. $requirements = array_unique($requirements);
  224. }
  225. }
  226. foreach ($requirements as $req) {
  227. if (!$aservice->isAvailable($req)) $missing[] = $req;
  228. }
  229. return compact('requirements', 'dependencies', 'missing', 'required', 'installed', 'activated', 'compatible', 'usable', 'version', 'author', 'parent');
  230. }
  231. /**
  232. * Check whether a package can be used
  233. *
  234. * To make this method return true, all required packages must be present,
  235. * compatible and themselves be usable.
  236. *
  237. * @param string $package
  238. * @return boolean
  239. */
  240. private function canBeUsed($package) {
  241. extract($this->getServices());
  242. if (!$pservice->exists($package)) return false;
  243. if (!$aservice->isCompatible($package)) return false;
  244. $requirements = $aservice->getRequirements($package, false);
  245. foreach ($requirements as $requirement) {
  246. if (!$this->canBeUsed($requirement)) return false;
  247. }
  248. return true;
  249. }
  250. /**
  251. * Determine what packages to install
  252. *
  253. * This method will walk through all requirements and collect a list of
  254. * packages that need to be installed to install the $addon. The list
  255. * is ordered ($addon is always the last element). Already activated
  256. * packages will not be included (so the result can be empty if $addon
  257. * is also already activated).
  258. *
  259. * @param string $addon addon name
  260. * @param array $list current stack (used internally)
  261. * @return array install list
  262. */
  263. private function getInstallList($addon, array $list = array()) {
  264. extract($this->getServices());
  265. $idx = array_search($addon, $list);
  266. $requirements = $aservice->getRequirements($addon);
  267. if ($idx !== false) {
  268. unset($list[$idx]);
  269. $list = array_values($list);
  270. }
  271. if (!$aservice->isAvailable($addon)) {
  272. array_unshift($list, $addon);
  273. }
  274. foreach ($requirements as $requirement) {
  275. $list = $this->getInstallList($requirement, $list);
  276. }
  277. return $list;
  278. }
  279. private function buildDataList() {
  280. extract($this->getServices());
  281. $result = array();
  282. foreach ($aservice->getRegisteredAddOns() as $addon) {
  283. $details = $this->getAddOnDetails($addon);
  284. $details['children'] = array();
  285. $result[$addon] = $details;
  286. }
  287. return $result;
  288. }
  289. private function buildStatusList(array $packageData) {
  290. $result = array();
  291. foreach ($packageData as $package => $info) {
  292. $classes = array('sly-addon');
  293. // build class list for all relevant stati
  294. if (!empty($info['children'])) {
  295. $classes[] = 'ch1'; // children yes
  296. foreach ($info['children'] as $childInfo) {
  297. if ($childInfo['activated']) {
  298. $classes[] = 'ca1';
  299. $classes[] = 'd1'; // assume implicit dependency of packages from their parent packages
  300. break;
  301. }
  302. }
  303. }
  304. else {
  305. $classes[] = 'ch0'; // childen no
  306. }
  307. // if there are no active children, dependency status is based on required status
  308. if (!in_array('ca1', $classes)) {
  309. $classes[] = 'd'.intval($info['required']);
  310. }
  311. else {
  312. $classes[] = 'ca0'; // children active no
  313. }
  314. $classes[] = 'i'.intval($info['installed']);
  315. $classes[] = 'a'.intval($info['activated']);
  316. $classes[] = 'c'.intval($info['compatible']);
  317. $classes[] = 'r'.intval($info['requirements']);
  318. $classes[] = 'ro'.(empty($info['missing']) ? 1 : 0);
  319. $classes[] = 'u'.intval($info['usable']);
  320. $result[$package] = array(
  321. 'classes' => implode(' ', $classes),
  322. 'deps' => $this->buildDepsInfo($info)
  323. );
  324. foreach ($info['children'] as $childPkg => $childInfo) {
  325. $classes = array('sly-addon-child');
  326. $childInfo['requirements'][] = $package;
  327. $childInfo['requirements'] = array_unique($childInfo['requirements']);
  328. $classes[] = 'i'.intval($childInfo['installed']);
  329. $classes[] = 'a'.intval($childInfo['activated']);
  330. $classes[] = 'd'.intval($childInfo['required']);
  331. $classes[] = 'c'.intval($childInfo['compatible']);
  332. $classes[] = 'r'.intval($childInfo['requirements']);
  333. $classes[] = 'ro'.(empty($childInfo['missing']) ? 1 : 0);
  334. $classes[] = 'u'.intval($childInfo['usable']);
  335. $result[$childPkg] = array(
  336. 'classes' => implode(' ', $classes),
  337. 'deps' => $this->buildDepsInfo($childInfo)
  338. );
  339. }
  340. }
  341. return $result;
  342. }
  343. /**
  344. * Build a HTML string describing the requirements and dependencies
  345. *
  346. * @param array $info addOn info
  347. * @return string
  348. */
  349. private function buildDepsInfo(array $info) {
  350. $texts = array();
  351. if ($info['required']) {
  352. if (count($info['dependencies']) === 1) {
  353. $text = t('is_required', reset($info['dependencies']));
  354. }
  355. else {
  356. $list = sly_Util_String::humanImplode($info['dependencies']);
  357. $text = t('is_required', count($info['dependencies']));
  358. $text = '<span title="'.sly_html($list).'">'.$text.'</span>';
  359. }
  360. $texts[] = $text;
  361. }
  362. if ($info['requirements']) {
  363. if (count($info['requirements']) === 1) {
  364. $text = t('requires').' '.reset($info['requirements']);
  365. }
  366. else {
  367. $list = sly_Util_String::humanImplode($info['requirements']);
  368. $text = t('requires').' '.count($info['requirements']);
  369. $text = '<span title="'.sly_html($list).'">'.$text.'</span>';
  370. }
  371. $texts[] = $text;
  372. }
  373. if (empty($texts)) {
  374. return t('no_dependencies');
  375. }
  376. return implode(' &amp; ', $texts);
  377. }
  378. /**
  379. * Transform addOn list by using parents
  380. *
  381. * This method scans through the flat list of addOns and moved all addOns,
  382. * that have a "parent" declaration into the parent's "children" key. This is
  383. * done to make the view simpler.
  384. *
  385. * @param array $data
  386. * @return array
  387. */
  388. private function resolveParentRelationships(array $data) {
  389. do {
  390. $changes = false;
  391. foreach ($data as $package => $info) {
  392. if ($info['parent']) {
  393. $data[$info['parent']]['children'][$package] = $info;
  394. unset($data[$package]);
  395. $changes = true;
  396. break;
  397. }
  398. }
  399. } while ($changes);
  400. return $data;
  401. }
  402. }