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

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

https://bitbucket.org/SallyCMS/0.6
PHP | 444 lines | 310 code | 93 blank | 41 comment | 42 complexity | 184d3222745a289ec6ccc43d4f765e0a MD5 | raw file
Possible License(s): BSD-3-Clause
  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 $func = '';
  12. protected $addons = null;
  13. protected $plugins = null;
  14. protected $addon = null;
  15. protected $plugin = null;
  16. protected $info = '';
  17. protected $warning = '';
  18. private $init = 0;
  19. protected function init() {
  20. if ($this->init++) return;
  21. if (!sly_get('json', 'boolean')) {
  22. $layout = sly_Core::getLayout();
  23. $layout->pageHeader(t('addons'));
  24. }
  25. $this->addons = sly_Service_Factory::getAddOnService();
  26. $this->plugins = sly_Service_Factory::getPluginService();
  27. $addon = sly_request('addon', 'string', '');
  28. $plugin = sly_request('plugin', 'string', '');
  29. $addons = $this->addons->getRegisteredAddOns();
  30. $this->addon = in_array($addon, $addons) ? $addon : null;
  31. $this->plugin = null;
  32. if ($this->addon) {
  33. $plugins = $this->plugins->getRegisteredPlugins($this->addon);
  34. $this->plugin = in_array($plugin, $plugins) ? $plugin : null;
  35. }
  36. else {
  37. unset($_REQUEST['func']);
  38. }
  39. }
  40. public function indexAction() {
  41. $this->init();
  42. $this->checkForNewComponents();
  43. $data = $this->buildDataList();
  44. print $this->render('addon/list.phtml', array(
  45. 'addons' => $this->addons,
  46. 'plugins' => $this->plugins,
  47. 'tree' => $data,
  48. 'stati' => $this->buildStatusList($data),
  49. 'info' => $this->info,
  50. 'warning' => $this->warning
  51. ));
  52. }
  53. protected function prepare() {
  54. return array(
  55. $this->plugin ? $this->plugins : $this->addons,
  56. $this->plugin ? array($this->addon, $this->plugin) : $this->addon
  57. );
  58. }
  59. protected function checkForNewComponents() {
  60. $config = sly_Core::config();
  61. $addons = $this->readAddOns();
  62. $plugins = array();
  63. foreach ($addons as $addon) {
  64. $plugins[$addon] = $this->readPlugins($addon);
  65. }
  66. // Vergleiche Addons aus dem Verzeichnis addons/ mit den Einträgen in addons.yaml.
  67. // Wenn ein Addon in der Datei fehlt oder nicht mehr vorhanden ist, ändere den Dateiinhalt.
  68. $knownAddons = $this->addons->getRegisteredAddOns();
  69. foreach (array_diff($addons, $knownAddons) as $addon){
  70. $this->addons->add($addon);
  71. }
  72. foreach (array_diff($knownAddons, $addons) as $addon){
  73. $this->addons->removeConfig($addon);
  74. }
  75. // dito für Plugins
  76. foreach ($addons as $addon) {
  77. $knownPlugins = $this->plugins->getRegisteredPlugins($addon);
  78. foreach (array_diff($plugins[$addon], $knownPlugins) as $plugin){
  79. $this->plugins->add(array($addon, $plugin));
  80. }
  81. foreach (array_diff($knownPlugins, $plugins[$addon]) as $plugin){
  82. $this->plugins->removeConfig(array($addon, $plugin));
  83. }
  84. }
  85. }
  86. protected function call($method, $i18n) {
  87. list($service, $component) = $this->prepare();
  88. $this->warning = $service->$method($component);
  89. if ($this->warning === true || $this->warning === 1) {
  90. $name = is_array($component) ? $component[0].'/'.$component[1] : $component;
  91. $this->info = t('component_'.$i18n, $name);
  92. $this->warning = '';
  93. }
  94. }
  95. public function installAction() {
  96. $this->init();
  97. $this->call('install', 'installed');
  98. if ($this->warning === '') {
  99. $this->call('activate', 'activated');
  100. }
  101. return $this->sendResponse();
  102. }
  103. public function uninstallAction() { $this->init(); $this->call('uninstall', 'uninstalled'); return $this->sendResponse(); }
  104. public function activateAction() { $this->init(); $this->call('activate', 'activated'); return $this->sendResponse(); }
  105. public function deactivateAction() { $this->init(); $this->call('deactivate', 'deactivated'); return $this->sendResponse(); }
  106. public function reinitAction() { $this->init(); $this->call('copyAssets', 'assets_copied'); return $this->sendResponse(); }
  107. public function fullinstallAction() {
  108. $this->init();
  109. list($service, $component) = $this->prepare();
  110. $todo = $this->getInstallList($component);
  111. if (!empty($todo)) {
  112. $now = reset($todo);
  113. // pretend that we're about to work on $now
  114. if (is_array($now)) {
  115. $this->addon = $now[0];
  116. $this->plugin = $now[1];
  117. }
  118. else {
  119. $this->addon = $now;
  120. $this->plugin = '';
  121. }
  122. list($service, $component) = $this->prepare();
  123. // if not installed, install it
  124. if (!$service->isInstalled($component)) {
  125. $this->call('install', 'installed');
  126. }
  127. // if not activated and install went OK, activate it
  128. if (!$service->isAvailable($component) && $this->warning === '') {
  129. $this->call('activate', 'activated');
  130. }
  131. // if everything worked out fine, we can redirect to the next component
  132. if ($this->warning === '' && count($todo) > 1) {
  133. sly_Util_HTTP::redirect($_SERVER['REQUEST_URI'], array(), '', 302);
  134. }
  135. }
  136. return $this->sendResponse();
  137. }
  138. public function checkPermission($action) {
  139. $user = sly_Util_User::getCurrentUser();
  140. return $user && ($user->isAdmin() || $user->hasRight('pages', 'addons'));
  141. }
  142. private function sendResponse() {
  143. if (sly_get('json', 'boolean')) {
  144. header('Content-Type: application/json; charset=UTF-8');
  145. while (ob_get_level()) ob_end_clean();
  146. ob_start('ob_gzhandler');
  147. $data = $this->buildDataList();
  148. $response = array(
  149. 'status' => !empty($this->info),
  150. 'stati' => $this->buildStatusList($data),
  151. 'message' => $this->warning
  152. );
  153. print json_encode($response);
  154. die;
  155. }
  156. return $this->indexAction();
  157. }
  158. private function readAddOns() {
  159. $dir = sly_Service_Factory::getAddOnService()->baseFolder(null);
  160. return $this->readDir($dir);
  161. }
  162. private function readPlugins($addon) {
  163. $dir = sly_Service_Factory::getPluginService()->baseFolder($addon);
  164. return $this->readDir($dir);
  165. }
  166. private function readDir($dir) {
  167. $dir = new sly_Util_Directory($dir);
  168. return $dir->exists() ? $dir->listPlain(false, true) : array();
  169. }
  170. private function getComponentDetails($component, $type) {
  171. static $reqCache = array();
  172. static $depCache = array();
  173. $service = $type === 'addon' ? $this->addons : $this->plugins;
  174. $key = is_array($component) ? $component[0].'/'.$component[1] : $component;
  175. if (!isset($reqCache[$key])) {
  176. $reqCache[$key] = $service->getRequirements($component);
  177. $depCache[$key] = $service->getDependencies($component);
  178. }
  179. $requirements = $reqCache[$key];
  180. $dependencies = $depCache[$key];
  181. $missing = array();
  182. $required = $service->isRequired($component) !== false;
  183. $installed = $service->isInstalled($component);
  184. $activated = $installed ? $service->isActivated($component) : false;
  185. $compatible = $service->isCompatible($component);
  186. $version = $service->getVersion($component);
  187. $author = $service->getSupportPageEx($component);
  188. $usable = $compatible ? $this->canBeUsed($component) : false;
  189. if (is_array($component)) {
  190. $requirements[] = $component[0];
  191. }
  192. foreach ($requirements as $idx => $req) {
  193. if (is_array($req)) {
  194. if (!$this->plugins->isAvailable($req)) $missing[] = implode('/', $req);
  195. $requirements[$idx] = implode('/', $req);
  196. }
  197. else {
  198. if (!$this->addons->isAvailable($req)) $missing[] = $req;
  199. }
  200. }
  201. return compact('requirements', 'dependencies', 'missing', 'required', 'installed', 'activated', 'compatible', 'usable', 'version', 'author');
  202. }
  203. /**
  204. * Check whether a component can be used
  205. *
  206. * To make this method return true, all required components must be present,
  207. * compatible and themselves be usable.
  208. *
  209. * @param mixed $component
  210. * @return boolean
  211. */
  212. private function canBeUsed($component) {
  213. $service = is_string($component) ? $this->addons : $this->plugins;
  214. if (!$service->exists($component)) return false;
  215. if (!$service->isCompatible($component)) return false;
  216. $requirements = $service->getRequirements($component);
  217. foreach ($requirements as $requirement) {
  218. if (!$this->canBeUsed($requirement)) return false;
  219. }
  220. return true;
  221. }
  222. /**
  223. * Determine what components to install
  224. *
  225. * This method will walk through all requirements and collect a list of
  226. * components that need to be installed to install the $component. The list
  227. * is ordered ($component is always the last element). Already activated
  228. * components will not be included (so the result can be empty if $component
  229. * is also already activated).
  230. *
  231. * @param mixed $component plugin or addOn
  232. * @param array $list current stack (used internally)
  233. * @return array install list
  234. */
  235. private function getInstallList($component, array $list = array()) {
  236. $service = is_string($component) ? $this->addons : $this->plugins;
  237. $idx = array_search($component, $list);
  238. $requirements = $service->getRequirements($component);
  239. if (is_array($component) && !in_array($component[0], $requirements)) {
  240. $requirements[] = $component[0];
  241. }
  242. if ($idx !== false) {
  243. unset($list[$idx]);
  244. $list = array_values($list);
  245. }
  246. if (!$service->isAvailable($component)) {
  247. array_unshift($list, $component);
  248. }
  249. foreach ($requirements as $requirement) {
  250. $list = $this->getInstallList($requirement, $list);
  251. }
  252. return $list;
  253. }
  254. private function buildDataList() {
  255. $addons = array();
  256. foreach ($this->addons->getRegisteredAddOns() as $addon) {
  257. $pluginList = $this->plugins->getRegisteredPlugins($addon);
  258. $plugins = array();
  259. foreach ($pluginList as $plugin) {
  260. $comp = array($addon, $plugin);
  261. $plugins[$plugin] = $this->getComponentDetails($comp, 'plugin');
  262. }
  263. $info = $this->getComponentDetails($addon, 'addon');
  264. $info['plugins'] = $plugins;
  265. $addons[$addon] = $info;
  266. }
  267. return $addons;
  268. }
  269. private function buildStatusList(array $dataList) {
  270. $result = array();
  271. foreach ($dataList as $addon => $aInfo) {
  272. $classes = array('sly-addon');
  273. // build class list for all relevant stati
  274. if (!empty($aInfo['plugins'])) {
  275. $classes[] = 'p1';
  276. foreach ($aInfo['plugins'] as $pInfo) {
  277. if ($pInfo['activated']) {
  278. $classes[] = 'pa1';
  279. $classes[] = 'd1'; // assume implicit dependency of plugins from their parent addOns
  280. break;
  281. }
  282. }
  283. }
  284. else {
  285. $classes[] = 'p0';
  286. }
  287. if (!in_array('pa1', $classes)) {
  288. $classes[] = 'd'.intval($aInfo['required']);
  289. }
  290. else {
  291. $classes[] = 'pa0';
  292. // foreach (array_keys($aInfo['plugins']) as $plugin) {
  293. // $aInfo['requirements'][] = $addon.'/'.$plugin;
  294. // }
  295. }
  296. $classes[] = 'i'.intval($aInfo['installed']);
  297. $classes[] = 'a'.intval($aInfo['activated']);
  298. $classes[] = 'c'.intval($aInfo['compatible']);
  299. $classes[] = 'r'.intval($aInfo['requirements']);
  300. $classes[] = 'ro'.(empty($aInfo['missing']) ? 1 : 0);
  301. $classes[] = 'u'.intval($aInfo['usable']);
  302. $result[$addon] = array(
  303. 'classes' => implode(' ', $classes),
  304. 'deps' => $this->buildDepsInfo($aInfo)
  305. );
  306. foreach ($aInfo['plugins'] as $plugin => $pInfo) {
  307. $key = $addon.'/'.$plugin;
  308. $classes = array('sly-plugin');
  309. $pInfo['requirements'][] = $addon;
  310. $pInfo['requirements'] = array_unique($pInfo['requirements']);
  311. $classes[] = 'i'.intval($pInfo['installed']);
  312. $classes[] = 'a'.intval($pInfo['activated']);
  313. $classes[] = 'd'.intval($pInfo['required']);
  314. $classes[] = 'c'.intval($pInfo['compatible']);
  315. $classes[] = 'r'.intval($pInfo['requirements']);
  316. $classes[] = 'ro'.(empty($pInfo['missing']) ? 1 : 0);
  317. $classes[] = 'u'.intval($pInfo['usable']);
  318. $result[$key] = array(
  319. 'classes' => implode(' ', $classes),
  320. 'deps' => $this->buildDepsInfo($pInfo)
  321. );
  322. }
  323. }
  324. return $result;
  325. }
  326. private function buildDepsInfo(array $info) {
  327. if ($info['required']) {
  328. $names = array();
  329. foreach ($info['dependencies'] as $comp) {
  330. $names[] = is_array($comp) ? reset($comp).' / '.end($comp) : $comp;
  331. }
  332. $isRequiredTitle = sly_html(t('is_required', sly_Util_String::humanImplode(array_slice($names, 0, 3))));
  333. }
  334. else {
  335. $isRequiredTitle = '';
  336. }
  337. if ($info['requirements']) {
  338. $names = array();
  339. foreach ($info['requirements'] as $comp) {
  340. $names[] = is_array($comp) ? reset($comp).' / '.end($comp) : $comp;
  341. }
  342. $requiresTitle = t('requires').' '.sly_Util_String::humanImplode(array_slice($names, 0, 3));
  343. }
  344. else {
  345. $requiresTitle = '';
  346. }
  347. $texts = array_filter(array($requiresTitle, $isRequiredTitle));
  348. if (empty($texts)) $texts[] = t('no_dependencies');
  349. return implode(' &amp; ', $texts);
  350. }
  351. }