PageRenderTime 64ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

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

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