PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/mediastuttgart/sallycms-0.7
PHP | 489 lines | 322 code | 92 blank | 75 comment | 37 complexity | a6ae4a66af056270d50345905f87d0e7 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 (!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. switch ($method) {
  152. case 'install':
  153. $manager->install($addon, true, sly_DB_Persistence::getInstance());
  154. break;
  155. case 'uninstall':
  156. $manager->uninstall($addon, sly_DB_Persistence::getInstance());
  157. break;
  158. default:
  159. $manager->$method($addon);
  160. }
  161. sly_Core::getFlashMessage()->appendInfo(t('addon_'.$i18n, $addon));
  162. }
  163. private function sendResponse() {
  164. if (sly_get('json', 'boolean')) {
  165. header('Content-Type: application/json; charset=UTF-8');
  166. while (ob_get_level()) ob_end_clean();
  167. ob_start('ob_gzhandler');
  168. $data = $this->buildDataList();
  169. $data = $this->resolveParentRelationships($data);
  170. $flash = sly_Core::getFlashMessage();
  171. $msgs = $flash->getMessages(sly_Util_FlashMessage::TYPE_WARNING);
  172. foreach ($msgs as $idx => $list) {
  173. $msgs[$idx] = is_array($list) ? implode('<br />', $list) : $list;
  174. }
  175. $response = array(
  176. 'status' => empty($msgs),
  177. 'stati' => $this->buildStatusList($data),
  178. 'message' => implode('<br />', $msgs)
  179. );
  180. $flash->clear();
  181. print json_encode($response);
  182. die;
  183. }
  184. return $this->indexAction();
  185. }
  186. /**
  187. * @param string $addon
  188. * @return array
  189. */
  190. private function getAddOnDetails($addon) {
  191. static $reqCache = array();
  192. static $depCache = array();
  193. extract($this->getServices());
  194. if (!isset($reqCache[$addon])) {
  195. $reqCache[$addon] = $aservice->getRequirements($addon);
  196. $depCache[$addon] = $aservice->getDependencies($addon, true, true);
  197. }
  198. $requirements = $reqCache[$addon];
  199. $dependencies = $depCache[$addon];
  200. $missing = array();
  201. $required = $aservice->isRequired($addon) !== false;
  202. $installed = $aservice->isInstalled($addon);
  203. $activated = $installed ? $aservice->isActivated($addon) : false;
  204. $compatible = $aservice->isCompatible($addon);
  205. $version = $pservice->getVersion($addon);
  206. $parent = $pservice->getParent($addon);
  207. $author = sly_Helper_Package::getSupportPage($addon);
  208. $usable = $compatible ? $this->canBeUsed($addon) : false;
  209. if ($parent !== null) {
  210. // do not allow to nest more than one level
  211. $exists = $pservice->exists($parent);
  212. $hasGrand = $exists ? $pservice->getParent($parent) : false;
  213. if (!$exists || $hasGrand) {
  214. $parent = null;
  215. }
  216. else {
  217. $requirements[] = $parent;
  218. $requirements = array_unique($requirements);
  219. }
  220. }
  221. foreach ($requirements as $req) {
  222. if (!$aservice->isAvailable($req)) $missing[] = $req;
  223. }
  224. return compact('requirements', 'dependencies', 'missing', 'required', 'installed', 'activated', 'compatible', 'usable', 'version', 'author', 'parent');
  225. }
  226. /**
  227. * Check whether a package can be used
  228. *
  229. * To make this method return true, all required packages must be present,
  230. * compatible and themselves be usable.
  231. *
  232. * @param string $package
  233. * @return boolean
  234. */
  235. private function canBeUsed($package) {
  236. extract($this->getServices());
  237. if (!$pservice->exists($package)) return false;
  238. if (!$aservice->isCompatible($package)) return false;
  239. $requirements = $aservice->getRequirements($package, false);
  240. foreach ($requirements as $requirement) {
  241. if (!$this->canBeUsed($requirement)) return false;
  242. }
  243. return true;
  244. }
  245. /**
  246. * Determine what packages to install
  247. *
  248. * This method will walk through all requirements and collect a list of
  249. * packages that need to be installed to install the $addon. The list
  250. * is ordered ($addon is always the last element). Already activated
  251. * packages will not be included (so the result can be empty if $addon
  252. * is also already activated).
  253. *
  254. * @param string $addon addon name
  255. * @param array $list current stack (used internally)
  256. * @return array install list
  257. */
  258. private function getInstallList($addon, array $list = array()) {
  259. extract($this->getServices());
  260. $idx = array_search($addon, $list);
  261. $requirements = $aservice->getRequirements($addon);
  262. if ($idx !== false) {
  263. unset($list[$idx]);
  264. $list = array_values($list);
  265. }
  266. if (!$aservice->isAvailable($addon)) {
  267. array_unshift($list, $addon);
  268. }
  269. foreach ($requirements as $requirement) {
  270. $list = $this->getInstallList($requirement, $list);
  271. }
  272. return $list;
  273. }
  274. private function buildDataList() {
  275. extract($this->getServices());
  276. $result = array();
  277. foreach ($aservice->getRegisteredAddOns() as $addon) {
  278. $details = $this->getAddOnDetails($addon);
  279. $details['children'] = array();
  280. $result[$addon] = $details;
  281. }
  282. return $result;
  283. }
  284. private function buildStatusList(array $packageData) {
  285. $result = array();
  286. foreach ($packageData as $package => $info) {
  287. $classes = array('sly-addon');
  288. // build class list for all relevant stati
  289. if (!empty($info['children'])) {
  290. $classes[] = 'ch1'; // children yes
  291. foreach ($info['children'] as $childInfo) {
  292. if ($childInfo['activated']) {
  293. $classes[] = 'ca1';
  294. $classes[] = 'd1'; // assume implicit dependency of packages from their parent packages
  295. break;
  296. }
  297. }
  298. }
  299. else {
  300. $classes[] = 'ch0'; // childen no
  301. }
  302. // if there are no active children, dependency status is based on required status
  303. if (!in_array('ca1', $classes)) {
  304. $classes[] = 'd'.intval($info['required']);
  305. }
  306. else {
  307. $classes[] = 'ca0'; // children active no
  308. }
  309. $classes[] = 'i'.intval($info['installed']);
  310. $classes[] = 'a'.intval($info['activated']);
  311. $classes[] = 'c'.intval($info['compatible']);
  312. $classes[] = 'r'.intval($info['requirements']);
  313. $classes[] = 'ro'.(empty($info['missing']) ? 1 : 0);
  314. $classes[] = 'u'.intval($info['usable']);
  315. $result[$package] = array(
  316. 'classes' => implode(' ', $classes),
  317. 'deps' => $this->buildDepsInfo($info)
  318. );
  319. foreach ($info['children'] as $childPkg => $childInfo) {
  320. $classes = array('sly-addon-child');
  321. $childInfo['requirements'][] = $package;
  322. $childInfo['requirements'] = array_unique($childInfo['requirements']);
  323. $classes[] = 'i'.intval($childInfo['installed']);
  324. $classes[] = 'a'.intval($childInfo['activated']);
  325. $classes[] = 'd'.intval($childInfo['required']);
  326. $classes[] = 'c'.intval($childInfo['compatible']);
  327. $classes[] = 'r'.intval($childInfo['requirements']);
  328. $classes[] = 'ro'.(empty($childInfo['missing']) ? 1 : 0);
  329. $classes[] = 'u'.intval($childInfo['usable']);
  330. $result[$childPkg] = array(
  331. 'classes' => implode(' ', $classes),
  332. 'deps' => $this->buildDepsInfo($childInfo)
  333. );
  334. }
  335. }
  336. return $result;
  337. }
  338. /**
  339. * Build a HTML string describing the requirements and dependencies
  340. *
  341. * @param array $info addOn info
  342. * @return string
  343. */
  344. private function buildDepsInfo(array $info) {
  345. $texts = array();
  346. if ($info['required']) {
  347. if (count($info['dependencies']) === 1) {
  348. $text = t('is_required', reset($info['dependencies']));
  349. }
  350. else {
  351. $list = sly_Util_String::humanImplode($info['dependencies']);
  352. $text = t('is_required', count($info['dependencies']));
  353. $text = '<span title="'.sly_html($list).'">'.$text.'</span>';
  354. }
  355. $texts[] = $text;
  356. }
  357. if ($info['requirements']) {
  358. if (count($info['requirements']) === 1) {
  359. $text = t('requires').' '.reset($info['requirements']);
  360. }
  361. else {
  362. $list = sly_Util_String::humanImplode($info['requirements']);
  363. $text = t('requires').' '.count($info['requirements']);
  364. $text = '<span title="'.sly_html($list).'">'.$text.'</span>';
  365. }
  366. $texts[] = $text;
  367. }
  368. if (empty($texts)) {
  369. return t('no_dependencies');
  370. }
  371. return implode(' &amp; ', $texts);
  372. }
  373. /**
  374. * Transform addOn list by using parents
  375. *
  376. * This method scans through the flat list of addOns and moved all addOns,
  377. * that have a "parent" declaration into the parent's "children" key. This is
  378. * done to make the view simpler.
  379. *
  380. * @param array $data
  381. * @return array
  382. */
  383. private function resolveParentRelationships(array $data) {
  384. do {
  385. $changes = false;
  386. foreach ($data as $package => $info) {
  387. if ($info['parent']) {
  388. $data[$info['parent']]['children'][$package] = $info;
  389. unset($data[$package]);
  390. $changes = true;
  391. break;
  392. }
  393. }
  394. } while ($changes);
  395. return $data;
  396. }
  397. }