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 controller will handle the editing of an item
23 * @package GalleryCore
24 * @subpackage UserInterface
25 * @author Bharat Mediratta <bharat@menalto.com>
26 * @version $Revision: 17679 $
27 */
28class ItemEditController extends GalleryController {
29
30    /**
31     * @see GalleryController::handleRequest
32     */
33    function handleRequest($form) {
34	global $gallery;
35
36	$editPlugin = GalleryUtilities::getRequestVariables('editPlugin');
37
38	list ($ret, $item) = $this->getItem();
39	if ($ret) {
40	    return array($ret, null);
41	}
42	$itemId = $item->getId();
43
44	/* Make sure we have permission to edit this item */
45	$ret = GalleryCoreApi::assertHasItemPermission($item->getId(), 'core.edit');
46	if ($ret) {
47	    return array($ret, null);
48	}
49
50	/* Check to see if we have a preferred source */
51	list ($ret, $preferredTable) =
52	    GalleryCoreApi::fetchPreferredsByItemIds(array($item->getId()));
53	if ($ret) {
54	    return array($ret, null);
55	}
56	$preferred = empty($preferredTable) ? null : array_shift($preferredTable);
57
58	/* Load the thumbnail */
59	list ($ret, $thumbnails) = GalleryCoreApi::fetchThumbnailsByItemIds(array($item->getId()));
60	if ($ret) {
61	    return array($ret, null);
62	}
63	$thumbnail = empty($thumbnails) ? null : array_shift($thumbnails);
64
65	/* Get all the edit options */
66	list ($ret, $optionInstances) =
67	    ItemEditOption::getAllOptions($editPlugin, $item, $thumbnail);
68	if ($ret) {
69	    return array($ret, null);
70	}
71
72	/* Load the correct edit plugin */
73	list ($ret, $plugin) =
74	    GalleryCoreApi::newFactoryInstanceById('ItemEditPlugin', $editPlugin);
75	if ($ret) {
76	    return array($ret, null);
77	}
78	if (!isset($plugin)) {
79	    return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER), null);
80	}
81
82	$status = array();
83	list ($ret, $error, $status['editMessage'], $requiresProgressBar) =
84	    $plugin->handleRequest($form, $item, $preferred);
85	if ($ret) {
86	    return array($ret, null);
87	}
88	if (!empty($requiresProgressBar)) {
89	    $results['delegate']['view'] = 'core.ProgressBar';
90	}
91
92	/* Now let each option process its data */
93	if (!isset($status['warning'])) {
94	    $status['warning'] = array();
95	}
96	if (isset($form['action']['save'])) {
97	    $progressBarOptions = array();
98	    foreach ($optionInstances as $option) {
99		if ($requiresProgressBar || $option->requiresProgressBar($form)) {
100		    $progressBarOptions[] = $option;
101		} else {
102		    list ($ret, $optionErrors, $optionWarnings) =
103			$option->handleRequestAfterEdit($form, $item, $preferred);
104		    if ($ret) {
105			return array($ret, null);
106		    }
107
108		    $error = array_merge($error, $optionErrors);
109		    $status['warning'] = array_merge($status['warning'], $optionWarnings);
110		}
111	    }
112
113	    if (empty($error) && $progressBarOptions) {
114		$templateAdapter =& $gallery->getTemplateAdapter();
115		$templateAdapter->registerTrailerCallback(
116		    array($this, 'runProgressBarOptions'),
117		    array($progressBarOptions, $form, $item, $preferred, $status, $editPlugin));
118		$results['delegate']['view'] = 'core.ProgressBar';
119	    }
120	}
121
122	if (empty($results['delegate'])) {
123	    /* It's not a progress bar view */
124	    if (empty($error)) {
125		$results['redirect'] = array('view' => 'core.ItemAdmin',
126					     'subView' => 'core.ItemEdit',
127					     'editPlugin' => $editPlugin,
128					     'itemId' => $item->getId());
129	    } else {
130		$results['delegate']['view'] = 'core.ItemAdmin';
131		$results['delegate']['subView'] = 'core.ItemEdit';
132		$results['delegate']['editPlugin'] = $editPlugin;
133	    }
134	}
135
136	$results['status'] = $status;
137	$results['error'] = $error;
138
139	return array(null, $results);
140    }
141
142    function runProgressBarOptions($options, $form, $item, $preferred, $status, $editPlugin) {
143	global $gallery;
144	$templateAdapter =& $gallery->getTemplateAdapter();
145
146	$error = array();
147	foreach ($options as $option) {
148	    $gallery->guaranteeTimeLimit(60);
149
150	    list ($ret, $optionErrors, $optionWarnings) =
151		$option->handleRequestAfterEdit($form, $item, $preferred);
152	    if ($ret) {
153		return $ret;
154	    }
155
156	    $error = array_merge($error, $optionErrors);
157	    $status['warning'] = array_merge($status['warning'], $optionWarnings);
158	}
159	$session =& $gallery->getSession();
160	$session->putStatus($status);
161
162	$redirect = array();
163	$redirect['view'] = 'core.ItemAdmin';
164	$redirect['subView'] = 'core.ItemEdit';
165	$redirect['itemId'] = $item->getId();
166	$redirect['editPlugin'] = $editPlugin;
167
168	$urlGenerator =& $gallery->getUrlGenerator();
169	$templateAdapter->completeProgressBar($urlGenerator->generateUrl($redirect));
170
171	return null;
172    }
173}
174
175/**
176 * This view will show options to edit an item
177 */
178class ItemEditView extends GalleryView {
179
180    /**
181     * Returns sizes for "maxlength" in forms
182     * @todo This function is duplicated in ItemEditCaptions.inc, so it needs to be consolidated
183     *       somehow.
184     * @param integer $value is the value from
185     * @return size for "maxlength" in form
186     * @access private
187     */
188    function _getSizesForMaxlength($value) {
189	switch ($value) {
190	case STORAGE_SIZE_SMALL:
191	    $size = 32;
192	    break;
193
194	case STORAGE_SIZE_MEDIUM:
195	    $size = 128;
196	    break;
197
198	case STORAGE_SIZE_LARGE:
199	    $size = 255;
200	    break;
201	}
202
203	return $size;
204    }
205
206    /**
207     * @see GalleryView::loadTemplate
208     */
209    function loadTemplate(&$template, &$form) {
210	global $gallery;
211
212	$editPlugin = GalleryUtilities::getRequestVariables('editPlugin');
213
214	list ($ret, $item, $wasSpecified) = $this->getItem();
215	if ($ret) {
216	    return array($ret, null);
217	}
218
219	/* Make sure we have permission to edit this item */
220	$ret = GalleryCoreApi::assertHasItemPermission($item->getId(), 'core.edit');
221	if ($ret) {
222	    return array($ret, null);
223	}
224
225	/* Load the thumbnail */
226	list ($ret, $thumbnails) = GalleryCoreApi::fetchThumbnailsByItemIds(array($item->getId()));
227	if ($ret) {
228	    return array($ret, null);
229	}
230	if (!empty($thumbnails)) {
231	    list ($ret, $thumbnail) = GalleryCoreApi::rebuildDerivativeCacheIfNotCurrent(
232						      $thumbnails[$item->getId()]->getId());
233	    if ($ret) {
234		/* Ignore thumbnail errors so we can edit items with broken thumbnail */
235	    }
236	} else {
237	    $thumbnail = null;
238	}
239
240	/* Get the edit plugins that support this item type */
241	list ($ret, $allPluginIds) =
242	    GalleryCoreApi::getAllFactoryImplementationIds('ItemEditPlugin');
243	if ($ret) {
244	    return array($ret, null);
245	}
246
247	$pluginInstances = array();
248	foreach (array_keys($allPluginIds) as $pluginId) {
249	    list ($ret, $plugin) =
250		GalleryCoreApi::newFactoryInstanceById('ItemEditPlugin', $pluginId);
251	    if ($ret) {
252		return array($ret, null);
253	    }
254
255	    if ($plugin->isSupported($item, $thumbnail)) {
256		$pluginInstances[$pluginId] = $plugin;
257	    }
258	}
259
260	/* Define the first edit plugin as the default in case we need to fall back. */
261	$editPluginIds = array_keys($pluginInstances);
262	$defaultEditPlugin = $editPluginIds[0];
263
264	/*
265	 * If the plugin is empty get it from the session.  If it's empty there, default to the
266	 * first plugin we find.  Either way, save the user's preference in the session.
267	 */
268	$session =& $gallery->getSession();
269	$editPluginSessionKey = 'core.view.ItemEdit.editPlugin.' . get_class($item);
270	if (empty($editPlugin)) {
271	    $editPlugin = $session->get($editPluginSessionKey);
272	    if (empty($editPlugin) || !in_array($editPlugin, array_keys($pluginInstances))) {
273		$editPlugin = $defaultEditPlugin;
274	    }
275	}
276	/* Input validation of the given editPlugin id. */
277	if (!isset($pluginInstances[$editPlugin])) {
278	    $editPlugin = $defaultEditPlugin;
279	}
280	$session->put($editPluginSessionKey, $editPlugin);
281
282	/* Get display data for all plugins */
283	$plugins = array();
284	foreach ($pluginInstances as $pluginId => $plugin) {
285	    list ($ret, $title) =  $plugin->getTitle();
286	    if ($ret) {
287		return array($ret, null);
288	    }
289	    $plugins[] = array('title' => $title,
290			       'id' => $pluginId,
291			       'isSelected' => ($pluginId == $editPlugin));
292	}
293
294	/* Record our item serial number in the form so that all plugins can use it */
295	$form['serialNumber'] = $item->getSerialNumber();
296
297	$ItemEdit = array();
298	$ItemEdit['editPlugin'] = $editPlugin;
299	$ItemEdit['plugins'] = $plugins;
300	$ItemEdit['itemTypeNames'] = $item->itemTypeName();
301	$ItemEdit['showEditThumbnail'] = $thumbnail != null;
302
303	list ($ret, $ItemEdit['isAdmin']) = GalleryCoreApi::isUserInSiteAdminGroup();
304	if ($ret) {
305	    return array($ret, null);
306	}
307
308	/* Let the plugin load its template data */
309	list ($ret, $ItemEdit['pluginFile'], $ItemEdit['pluginL10Domain']) =
310	    $pluginInstances[$editPlugin]->loadTemplate($template, $form, $item, $thumbnail);
311	if ($ret) {
312	    return array($ret, null);
313	}
314
315	/* Get all the edit options */
316	list ($ret, $optionInstances) =
317	    ItemEditOption::getAllOptions($editPlugin, $item, $thumbnail);
318	if ($ret) {
319	    return array($ret, null);
320	}
321
322	/* Now let all options load their template data */
323	$ItemEdit['options'] = array();
324	foreach ($optionInstances as $option) {
325	    list ($ret, $entry['file'], $entry['l10Domain']) =
326		$option->loadTemplate($template, $form, $item, $thumbnail);
327	    if ($ret) {
328		return array($ret, null);
329	    }
330	    if (!empty($entry['file'])) {
331		$ItemEdit['options'][] = $entry;
332	    }
333	}
334
335	list ($ret, $entityInfo) = GalleryCoreApi::describeEntity('GalleryItem');
336	if ($ret) {
337	    return array($ret, null);
338	}
339
340	$ItemEdit['fieldLengths'] = array();
341	$ItemEdit['fieldLengths']['title'] =
342	     $this->_getSizesForMaxLength($entityInfo['GalleryItem']['members']['title']['size']);
343
344	$template->setVariable('ItemEdit', $ItemEdit);
345	$template->setVariable('controller', 'core.ItemEdit');
346	return array(null, array('body' => 'modules/core/templates/ItemEdit.tpl'));
347    }
348
349    /**
350     * @see GalleryView::getViewDescription
351     */
352    function getViewDescription() {
353	global $gallery;
354
355	list ($ret, $item) = $this->getItem();
356	if ($ret) {
357	    return array($ret, null);
358	}
359
360	list ($ret, $core) = GalleryCoreApi::loadPlugin('module', 'core');
361	if ($ret) {
362	    return array($ret, null);
363	}
364
365	$itemTypeNames = $item->itemTypeName(true);
366
367	return array(null,
368		     $core->translate(array('text' => 'edit %s', 'arg1' => $itemTypeNames[1])));
369    }
370}
371
372/**
373 * Interface for plugins to the ItemEdit view and controller
374 * @abstract
375 */
376class ItemEditPlugin {
377    /**
378     * Does this plugin support the given item type?
379     *
380     * @param GalleryItem $item
381     * @param GalleryDerivative $thumbnail item's thumbnail
382     * @return boolean true if it's supported
383     */
384    function isSupported($item, $thumbnail) {
385	return false;
386    }
387
388    /**
389     * Load the template with data from this plugin
390     * @see GalleryView::loadTemplate
391     *
392     * @param GalleryTemplate $template
393     * @param array $form the form values
394     * @param GalleryItem $item
395     * @param GalleryDerivative $thumbnail item's thumbnail
396     * @return array GalleryStatus a status code
397     *               string the path to a template file to include
398     *               string localization domain for the template file
399     */
400    function loadTemplate(&$template, &$form, $item, $thumbnail) {
401	return array(GalleryCoreApi::error(ERROR_UNIMPLEMENTED), null, null);
402    }
403
404    /**
405     * Let the plugin handle the incoming request
406     * @see GalleryController::handleRequest
407     *
408     * @param array $form the form values
409     * @param GalleryItem $item
410     * @param GalleryDerivative $preferred item's preferred derivative, if there is one
411     * @return array GalleryStatus a status code
412     *               array error messages
413     *               string localized status message
414     *               boolean true if progress bar is needed
415     */
416    function handleRequest($form, &$item, &$preferred) {
417	return array(GalleryCoreApi::error(ERROR_UNIMPLEMENTED),
418		     null, null, null);
419    }
420
421    /**
422     * Return a localized title for this plugin, suitable for display to the user
423     *
424     * @return array GalleryStatus a status code
425     *               string localized title
426     */
427    function getTitle() {
428	return array(GalleryCoreApi::error(ERROR_UNIMPLEMENTED), null);
429    }
430
431    /**
432     * Check to see if a given operation is available for any of a set of mime types
433     *
434     * @param string $operation the operation (eg. 'rotate' or 'scale')
435     * @param array $mimeTypes
436     * @return array GalleryStatus a status code
437     *               bool true if any of the mime types are supported
438     * @access protected
439     */
440    function _checkForOperation($operation, $mimeTypes) {
441	foreach (array_unique($mimeTypes) as $mimeType) {
442	    list ($ret, $toolkit) = GalleryCoreApi::getToolkitByOperation($mimeType, $operation);
443	    if ($ret) {
444		return array($ret, null);
445	    }
446
447	    if (isset($toolkit)) {
448		break;
449	    }
450	}
451
452	return array(null, isset($toolkit));
453    }
454}
455
456/**
457 * Interface for options to the ItemEdit view and controller.
458 * Options allow us to provide extra UI in the views and extra processing in the controller so
459 * that we can add new functionality to various ItemEditPlugins
460 * @abstract
461 */
462class ItemEditOption {
463
464    /**
465     * Return all the available option plugins
466     *
467     * @param string $editPlugin name of ItemEditPlugin
468     * @param GalleryItem $item
469     * @param GalleryDerivative $thumbnail
470     * @return array GalleryStatus a status code
471     *               array ItemEditOption instances
472     * @static
473     */
474    function getAllOptions($editPlugin, $item, $thumbnail) {
475	list ($ret, $allOptionIds) =
476	    GalleryCoreApi::getAllFactoryImplementationIdsWithHint('ItemEditOption', $editPlugin);
477	if ($ret) {
478	    return array($ret, null);
479	}
480
481	$optionInstances = array();
482	foreach (array_keys($allOptionIds) as $optionId) {
483	    list ($ret, $option) =
484		GalleryCoreApi::newFactoryInstanceById('ItemEditOption', $optionId);
485	    if ($ret) {
486		return array($ret, null);
487	    }
488
489	    list ($ret, $isAppropriate) = $option->isAppropriate($item, $thumbnail);
490	    if ($ret) {
491		return array($ret, null);
492	    }
493
494	    if ($isAppropriate) {
495		$optionInstances[$optionId] = $option;
496	    }
497	}
498
499	return array(null, $optionInstances);
500    }
501
502    /**
503     * Load the template with data from this plugin
504     * @see GalleryView::loadTemplate
505     *
506     * @param GalleryTemplate $template
507     * @param array $form the form values
508     * @param GalleryItem $item
509     * @return array GalleryStatus a status code
510     *               string the path to a template file to include
511     *               string localization domain for the template file
512     */
513    function loadTemplate(&$template, &$form, $item, $thumbnail) {
514	return array(GalleryCoreApi::error(ERROR_UNIMPLEMENTED), null, null);
515    }
516
517    /**
518     * Let the plugin handle the incoming request.  We expect the $item to be locked.
519     * @see GalleryController::handleRequest
520     *
521     * @param array $form the form values
522     * @param GalleryItem $item reference to the item
523     * @param GalleryDerivative $preferred reference to preferred derivative
524     * @return array GalleryStatus a status code
525     *               array error messages
526     *               array localized warning messages
527     */
528    function handleRequestAfterEdit($form, &$item, &$preferred) {
529	return array(GalleryCoreApi::error(ERROR_UNIMPLEMENTED), null, null);
530    }
531
532    /**
533     * Is this option appropriate at this time?
534     *
535     * @param GalleryItem $item
536     * @param GalleryDerivative $thumbnail
537     * @return array GalleryStatus a status code
538     *               boolean true or false
539     */
540    function isAppropriate($item, $thumbnail) {
541	return array(null, false);
542    }
543
544    /**
545     * Will this task run so long that it requires a progress bar?
546     *
547     * @param array $form the state of the current form
548     * @return boolean
549     */
550    function requiresProgressBar($form) {
551	return false;
552    }
553}
554?>
555