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