1<?php
2/*
3 * Gallery - a web based photo album viewer and editor
4 * Copyright (C) 2000-2008 Bharat Mediratta
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or (at
9 * your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20
21/**
22 * This view lets you make very simple callbacks to the framework to get very specific data.
23 * Eventually this will probably get refactored into a much more sophisticated framework.
24 *
25 * @package GalleryCore
26 * @subpackage UserInterface
27 * @author Bharat Mediratta <bharat@menalto.com>
28 * @version $Revision: 17580 $
29 */
30class PluginCallbackView extends GalleryView {
31    /**
32     * @see GalleryView::isImmediate
33     */
34    function isImmediate() {
35	return true;
36    }
37
38    /**
39     * @see GalleryView::isControllerLike
40     */
41    function isControllerLike() {
42	return true;
43    }
44
45    /**
46     * @see GalleryView::renderImmediate
47     */
48    function renderImmediate($status, $error) {
49	global $gallery;
50	$session =& $gallery->getSession();
51	$storage =& $gallery->getStorage();
52
53	$command = GalleryUtilities::getRequestVariables('command');
54	if (!headers_sent()) {
55	    header("Content-type: text/plain; charset=UTF-8");
56	}
57
58	$result = array();
59	list ($ret, $beforeStates) = $this->getPluginStates();
60	if ($ret) {
61	    $result['status'] = 'error';
62	    $storage->rollbackTransaction();  /* ignore errors here */
63	    $ret->putInSession();
64	}
65
66	if (!$ret) {
67	    $ret = $this->handleCallback($command, $result);
68	    if ($ret) {
69		$result['status'] = 'error';
70		$storage->rollbackTransaction();  /* ignore errors here */
71		$ret->putInSession();
72	    } else {
73		/* Make sure this change is isolated from any potential failures we get below. */
74		$ret = $storage->checkPoint();
75		if ($ret) {
76		    $result['status'] = 'error';
77		    $ret->putInSession();
78		}
79	    }
80
81	    if ($result['status'] == 'redirect') {
82		$urlGenerator =& $gallery->getUrlGenerator();
83		$result['redirect'] =
84		    $urlGenerator->generateUrl($result['redirect'],
85			array('htmlEntities' => 0, 'forceServerRelativeUrl' => 1));
86	    }
87	}
88
89	if (!$ret) {
90	    list ($ret, $afterStates) = $this->getPluginStates();
91	    if ($ret) {
92		$result['status'] = 'error';
93		$storage->rollbackTransaction();  /* ignore errors here */
94		$ret->putInSession();
95	    } else {
96		$result = array_merge(
97		    $result, $this->calculateStateChanges($beforeStates, $afterStates));
98	    }
99	}
100
101	GalleryCoreApi::requireOnce('lib/JSON/JSON.php');
102	$json = new Services_JSON();
103	print $json->encode($result);
104	return null;
105    }
106
107    /**
108     * Given two sets of states, figure out what's changed from before to after.
109     *
110     * @param array $beforeStates (moduleId => state, ...)
111     * @param array $afterStates (moduleId => state, ...)
112     * @return array changed states (moduleId => state, ...)
113     * @static
114     */
115    function calculateStateChanges($beforeStates, $afterStates) {
116	$states = array();
117	$deleted = array();
118	foreach (array('module', 'theme') as $type) {
119	    foreach ($beforeStates[$type] as $moduleId => $state) {
120		if (!isset($afterStates[$type][$moduleId])) {
121		    $deleted[$type][$moduleId] = 1;
122		} else if ($afterStates[$type][$moduleId] != $state) {
123		    $states[$type][$moduleId] = $afterStates[$type][$moduleId];
124		}
125	    }
126	}
127	return array('states' => $states, 'deleted' => $deleted);
128    }
129
130    /**
131     * Handle the specific callback, and store its result in the given output array.
132     *
133     * @param string $command (eg. "installModule")
134     * @param array $result the location for result data to be sent back to the browser
135     * @return GalleryStatus a status code
136     * @static
137     */
138    function handleCallback($command, &$result) {
139	global $gallery;
140	$platform =& $gallery->getPlatform();
141
142	$ret = GalleryCoreApi::assertUserIsSiteAdministrator();
143	if ($ret) {
144	    return $ret;
145	}
146
147	$result = array();
148	list ($pluginType, $pluginId) =
149	    GalleryUtilities::getRequestVariables('pluginType', 'pluginId');
150
151	list ($ret, $plugin) = GalleryCoreApi::loadPlugin($pluginType, $pluginId, true);
152	if ($ret) {
153	    return $ret;
154	}
155
156	list ($ret, $isActive) = $plugin->isActive();
157	if ($ret) {
158	    return $ret;
159	}
160
161	switch($command) {
162	case 'activate':
163
164	    if ($pluginType == 'module') {
165		list ($ret, $needsConfiguration) = $plugin->needsConfiguration();
166		if ($ret) {
167		    return $ret;
168		}
169	    } else {
170		/* Themes don't need configuration */
171		$needsConfiguration = false;
172	    }
173
174	    if ($isActive || $needsConfiguration) {
175		/* UI shouldn't let us come here anyway */
176		$result['status'] = 'invalid';
177		return null;
178	    }
179
180	    list ($ret, $redirect) = $plugin->activate();
181	    if ($ret) {
182		return $ret;
183	    }
184
185	    if ($redirect) {
186		$result['status'] = 'redirect';
187		$result['redirect'] = $redirect;
188	    } else {
189		$result['status'] = 'success';
190	    }
191	    break;
192
193	case 'deactivate':
194	    if (!$isActive) {
195		/* UI shouldn't let us come here anyway */
196		$result['status'] = 'invalid';
197		return null;
198	    }
199
200	    if ($pluginType == 'theme') {
201		list ($ret, $defaultThemeId) = GalleryCoreApi::getPluginParameter(
202		    'module', 'core', 'default.theme');
203		if ($ret) {
204		    return $ret;
205		}
206
207		if ($plugin->getId() == $defaultThemeId) {
208		    /* UI shouldn't let us come here anyway */
209		    $result['status'] = 'invalid';
210		}
211	    }
212
213	    if (empty($result['status'])) {
214		list ($ret, $redirect) = $plugin->deactivate();
215		if ($ret) {
216		    return $ret;
217		}
218
219		if ($redirect) {
220		    $result['status'] = 'redirect';
221		    $result['redirect'] = $redirect;
222		} else {
223		    $result['status'] = 'success';
224		}
225	    }
226	    break;
227
228	case 'upgrade':
229	case 'install':
230	    $ret = $plugin->installOrUpgrade();
231	    if ($ret) {
232		return $ret;
233	    }
234
235	    if ($pluginType == 'module') {
236		list ($ret, $autoConfigured) = $plugin->autoConfigure();
237		if ($ret) {
238		    return $ret;
239		}
240	    } else {
241		/* Themes don't need this step */
242		$autoConfigured = true;
243	    }
244
245
246	    if ($autoConfigured) {
247		list ($ret, $redirect) = $plugin->activate();
248		if ($ret) {
249		    if ($ret->getErrorCode() & ERROR_CONFIGURATION_REQUIRED) {
250			/*
251			 * Some modules don't override autoConfigure which defaults to success.
252			 * Show the "Modules needs configuration" message.
253			 */
254		    } else {
255			return $ret;
256		    }
257		}
258
259		if ($redirect) {
260		    $result['status'] = 'redirect';
261		    $result['redirect'] = $redirect;
262		} else {
263		    $result['status'] = 'success';
264		}
265	    } else {
266		$result['status'] = 'success';
267	    }
268
269	    break;
270
271	case 'uninstall':
272	    if ($isActive) {
273		list ($ret, $redirect) = $plugin->deactivate();
274		if ($ret) {
275		    return $ret;
276		}
277	    } else {
278		$redirect = false;
279	    }
280
281	    if ($redirect) {
282		$result['status'] = 'redirect';
283		$results['redirect'] = $redirect;
284	    } else {
285		$ret = $plugin->uninstall();
286		if ($ret) {
287		    return $ret;
288		}
289		$result['status'] = 'success';
290	    }
291	    break;
292
293	case 'delete':
294	    if ($isActive) {
295		list ($ret, $redirect) = $plugin->deactivate();
296		if ($ret) {
297		    return $ret;
298		}
299	    } else {
300		$redirect = false;
301	    }
302
303	    if ($redirect) {
304		$result['status'] = 'redirect';
305		$results['redirect'] = $redirect;
306	    } else {
307		$ret = $plugin->uninstall();
308		if ($ret) {
309		    return $ret;
310		}
311
312		$path = sprintf(
313		    "%s%ss/%s", GalleryCoreApi::getCodeBasePath(), $pluginType, $pluginId);
314		$success = @$platform->recursiveRmdir($path);
315		if (!$success) {
316		    $result['status'] = 'fail';
317		} else {
318		    $ret = GalleryCoreApi::removeMapEntry(
319			'GalleryPluginPackageMap',
320			array('pluginType' => $pluginType, 'pluginId' => $pluginId));
321		    if ($ret) {
322			return $ret;
323		    }
324
325		    $result['status'] = 'success';
326		}
327	    }
328	    break;
329
330	case 'configure':
331	    $result['status'] = 'redirect';
332	    $result['redirect'] = array('view' => 'core.SiteAdmin',
333					'subView' => $plugin->getConfigurationView());
334	    break;
335	}
336
337	return null;
338    }
339
340    /**
341     * Get the state ('active', 'inactive', 'uninstalled', etc) of all modules
342     *
343     * @return array GalleryStatus a status code
344     *               array(moduleId => state, ...)
345     * @static
346     */
347    function getPluginStates() {
348	$states = array();
349
350	foreach (array('module', 'theme') as $type) {
351	    list ($ret, $pluginStatus) = GalleryCoreApi::fetchPluginStatus($type, true);
352	    if ($ret) {
353		return array($ret, null);
354	    }
355	    foreach ($pluginStatus as $pluginId => $status) {
356		list ($ret, $plugin) = GalleryCoreApi::loadPlugin($type, $pluginId, true);
357		if ($ret) {
358		    return array($ret, null);
359		}
360
361		list ($ret, $states[$type][$pluginId]) =
362		    $this->getPluginState($type, $plugin, $status);
363		if ($ret) {
364		    return array($ret, null);
365		}
366	    }
367	}
368
369	return array(null, $states);
370    }
371
372    /**
373     * Get the state ('active', 'inactive', 'uninstalled', etc) of a given module
374     *
375     * @param string $type ('module' or 'theme')
376     * @param GalleryPlugin $plugin
377     * @param array $status status of the plugin (from GalleryCoreApi::fetchPluginStatus)
378     * @return array GalleryStatus a status code
379     *               string a state
380     * @static
381     */
382    function getPluginState($type, $plugin, $status) {
383	if ($type == 'module' && $plugin->getId() == 'core') {
384	    return array(null, 'active');
385	}
386
387	$coreApiCompatible = GalleryUtilities::isCompatibleWithApi(
388	    $plugin->getRequiredCoreApi(), GalleryCoreApi::getApiVersion());
389
390	/* TODO: refactor this into type specific wrapper methods around getPluginState() */
391	switch ($type) {
392	case 'module':
393	    $pluginApiCompatible = GalleryUtilities::isCompatibleWithApi(
394		$plugin->getRequiredModuleApi(), GalleryModule::getApiVersion());
395	    break;
396
397	case 'theme':
398	    $pluginApiCompatible = GalleryUtilities::isCompatibleWithApi(
399		$plugin->getRequiredThemeApi(), GalleryTheme::getApiVersion());
400	    break;
401	}
402
403	if ($coreApiCompatible && $pluginApiCompatible) {
404	    if (empty($status['active'])) {
405		$version = $status['version'];
406		$state = 'inactive';
407
408		/*
409		 * If the database versions doesn't match the module
410		 * version, we need to get the user to install the module.
411		 */
412		if ($version != $plugin->getVersion()) {
413		    if (empty($version)) {
414			$state = 'uninstalled';
415		    } else {
416			$state = 'unupgraded';
417		    }
418		} else {
419		    if ($type == 'module') {
420			/*
421			 * The versions match, but the module can still demand
422			 * to be configured before being activated.
423			 */
424			list ($ret, $needsConfig) = $plugin->needsConfiguration();
425			if ($ret) {
426			    return array($ret, null);
427			}
428		    } else {
429			$needsConfig = false;
430		    }
431
432		    if ($needsConfig) {
433			$state = 'unconfigured';
434		    } else {
435			$state = 'inactive';
436		    }
437		}
438	    } else {
439		$state = 'active';
440	    }
441	} else {
442	    $state = 'incompatible';
443	}
444
445	return array(null, $state);
446    }
447}
448?>
449