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 addition of new items in the gallery
23 * @package GalleryCore
24 * @subpackage UserInterface
25 * @author Bharat Mediratta <bharat@menalto.com>
26 * @version $Revision: 17589 $
27 */
28class ItemAddController extends GalleryController {
29    /**
30     * ItemAddOption instances to use when handling this request.  Only used by test code.
31     *
32     * @var array (optionId => ItemAddOption) $_optionInstances
33     * @access private
34     */
35    var $_optionInstances;
36
37    /**
38     * Tests can use this method to hardwire a specific set of option instances to use.
39     * This avoids situations where some of the option instances will do unpredictable
40     * things and derail the tests.
41     *
42     * @param array $optionInstances (optionId => ItemAddOption, ...)
43     */
44    function setOptionInstances($optionInstances) {
45	$this->_optionInstances = $optionInstances;
46    }
47
48    /**
49     * @see GalleryController::handleRequest
50     */
51    function handleRequest($form) {
52	global $gallery;
53	$templateAdapter =& $gallery->getTemplateAdapter();
54
55	$addPluginId = GalleryUtilities::getRequestVariables('addPlugin');
56
57	/**
58	 * Special case for backwards-compatibility with the webdav module
59	 * @todo Remove on next major API change
60	 */
61	if ($addPluginId == 'ItemAddWebDav') {
62	    /* WebDAV used to do a static ::handleRequest() call. We need an instance. */
63	    $controller = new ItemAddController();
64	} else {
65	    $controller = $this;
66	}
67
68	list ($ret, $item) = $controller->getItem();
69	if ($ret) {
70	    return array($ret, null);
71	}
72	$itemId = $item->getId();
73
74	/* Make sure we have permission to add to this item */
75	$ret = GalleryCoreApi::assertHasItemPermission($itemId, 'core.addDataItem');
76	if ($ret) {
77	    return array($ret, null);
78	}
79
80	if (!$item->getCanContainChildren()) {
81	    return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER), null);
82	}
83
84	/* Load the correct add plugin */
85	list ($ret, $addPlugin) =
86	    GalleryCoreApi::newFactoryInstanceById('ItemAddPlugin', $addPluginId);
87	if ($ret) {
88	    return array($ret, null);
89	}
90
91	if (!isset($addPlugin)) {
92	    return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER), null);
93	}
94
95	/**
96	 * Special case for backwards-compatibility with the webdav module
97	 * @todo Remove on next major API change
98	 */
99	if ($addPluginId == 'ItemAddWebDav') {
100	    /* Don't output any HTML (progress-bar) */
101	    ob_start();
102	    $ret = $controller->handleRequestWithProgressBar($form, $item, $addPlugin);
103	    ob_end_clean();
104	    if ($ret) {
105		return array($ret, null);
106	    }
107
108	    $session =& $gallery->getSession();
109	    $results['status'] = $session->getStatus();
110	    $results['redirect'] = array('view' => 'core.ItemAdmin',
111					 'subView' => 'core.ItemAddConfirmation',
112					 'itemId' => $item->getId());
113	    $results['error'] = array();
114	    return array(null, $results);
115	}
116
117	/* Do the actual work in callback of a progress-bar view */
118	$templateAdapter->registerTrailerCallback(
119		    array($this, 'handleRequestWithProgressBar'),
120		    array($form, $item, $addPlugin));
121
122	return array(null, array('delegate' => array('view' => 'core.ProgressBar'),
123				 'status' => array(), 'error' => array()));
124    }
125
126    /**
127     * Handles the add item request and is expected to be called as a progress-bar callback.
128     * @param array $form
129     * @param GalleryItem $item The container to which we're adding child-items
130     * @param ItemAddPlugin $addPlugin The plugin that handles this add item request
131     * @return GalleryStatus
132     */
133    function handleRequestWithProgressBar($form, $item, $addPlugin) {
134	global $gallery;
135	$templateAdapter =& $gallery->getTemplateAdapter();
136	$urlGenerator =& $gallery->getUrlGenerator();
137	$phpVm = $gallery->getPhpVm();
138	$session =& $gallery->getSession();
139
140	$startTime = $phpVm->time();
141	/* Auto-redirect if we complete the request within this period. Else show the continueURL */
142	$autoRedirectSeconds = 15;
143
144	list ($ret, $this->_coreModule) = GalleryCoreApi::loadPlugin('module', 'core');
145	if ($ret) {
146	    return $ret;
147	}
148	$templateAdapter->updateProgressBar($this->_coreModule->translate('Adding items'), '', 0);
149
150	$error = array();
151	$addPluginId = GalleryUtilities::getRequestVariables('addPlugin');
152
153	list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($item->getId());
154	if ($ret) {
155	    return $ret;
156	}
157
158	/* Start the add process */
159	list ($ret, $error, $status) = $addPlugin->handleRequest($form, $item, $this);
160	if ($ret) {
161	    GalleryCoreApi::releaseLocks($lockId);
162	    return $ret;
163	}
164
165	if (empty($error)) {
166	    list ($ret, $error) = $this->postprocessItems($form, $status);
167	    if ($ret) {
168		GalleryCoreApi::releaseLocks($lockId);
169		return $ret;
170	    }
171	}
172
173	$ret = GalleryCoreApi::releaseLocks($lockId);
174	if ($ret) {
175	    return $ret;
176	}
177
178	if (!empty($error)) {
179	    /** @todo Should we remove all added items in case of a late form validation error? */
180	    if (!empty($status['addedFiles'])) {
181		$error[] = 'form[error][itemsAddedDespiteFormErrors]';
182	    }
183	    $session->put('itemAdd.error', $error);
184
185	    $doRedirect = true;
186	    $continueUrl = $urlGenerator->generateUrl(
187		array('view' => 'core.ItemAdmin', 'subView' => 'core.ItemAdd',
188		      'addPlugin' => $addPluginId, 'itemId' => $item->getId()),
189		array('forceFullUrl' => true));
190	} else {
191	    $session->putStatus($status);
192	    $doRedirect = ($phpVm->time() - $startTime) <= $autoRedirectSeconds;
193
194	    if (empty($status['addedFiles'])) {
195		/*
196		 * Append all form parameters for the next view request.  Some plugins submit a
197		 * first form to the controller only to forward the request to the view which might
198		 * depend on the same form parameters.
199		 */
200		$continueUrl = $urlGenerator->generateUrl(
201		    array('view' => 'core.ItemAdmin', 'subView' => 'core.ItemAdd',
202			  'addPlugin' => $addPluginId, 'itemId' => $item->getId(), 'form' => $form),
203		    array('forceFullUrl' => true));
204		$templateAdapter->updateProgressBar(
205		    $this->_coreModule->translate('Adding items'), '', 1);
206	    } else {
207		$continueUrl = $urlGenerator->generateUrl(
208		    array('view' => 'core.ItemAdmin', 'subView' => 'core.ItemAddConfirmation',
209			  'itemId' => $item->getId()), array('forceFullUrl' => true));
210	    }
211	}
212
213	$templateAdapter->completeProgressBar($continueUrl, $doRedirect);
214
215	return null;
216    }
217
218    /**
219     * Do post-processing which includes extracting archive-items and letting all ItemAddOption
220     * instances handle the added items.
221     *
222     * If called from an ItemAddPlugin, the plugin should stop adding items if $error is non-empty.
223     *
224     * @param array $form
225     * @param array $status An array including the list of all added items
226     * @see ItemAddPlugin::handleRequest() for the structure of the $status array
227     * @return array GalleryStatus
228     *               array $error request parameter errors
229     */
230    function postprocessItems($form, &$status) {
231	global $gallery;
232	$this->_templateAdapter =& $gallery->getTemplateAdapter();
233	$this->_storage =& $gallery->getStorage();
234
235	if (!isset($this->_coreModule)) {
236	    list ($ret, $this->_coreModule) = GalleryCoreApi::loadPlugin('module', 'core');
237	    if ($ret) {
238		return array($ret, null);
239	    }
240	}
241
242	$this->_processingItemsMessage = $this->_coreModule->translate('Processing items');
243
244	if (!isset($this->_optionInstances)) {
245	    list ($ret, $this->_optionInstances) = ItemAddOption::getAllAddOptions();
246	    if ($ret) {
247		return array($ret, null);
248	    }
249	}
250
251	if (!isset($this->_extractionToolkitMap)) {
252	    list ($ret, $extractToolkits) =
253		GalleryCoreApi::getToolkitOperationMimeTypes('extract');
254	    if ($ret) {
255		return array($ret, null);
256	    }
257	    $this->_extractionToolkitMap = array();
258	    foreach ($extractToolkits as $mimeType => $toolkitList) {
259		if (!empty($toolkitList)) {
260		    list ($ret, $this->_extractionToolkitMap[$mimeType]) =
261			GalleryCoreApi::getToolkitByOperation($mimeType, 'extract');
262		    if ($ret) {
263			return array($ret, null);
264		    }
265		}
266	    }
267	}
268
269	if (empty($status['addedFiles'])
270		|| empty($this->_extractionToolkitMap) && empty($this->_optionInstances)) {
271	    /* Nothing to do */
272	    return array(null, array());
273	}
274
275	$this->_templateAdapter->updateProgressBar($this->_processingItemsMessage, '', 0);
276
277	$ret = $this->_storage->checkPoint();
278	if ($ret) {
279	    return array($ret, null);
280	}
281	$gallery->guaranteeTimeLimit(60);
282
283	if (!isset($this->_processedItems)) {
284	    $this->_processedItems = array();
285	}
286	$errors = array();
287
288	/* The number of items a ItemAddOption should be able to process in less than 30 seconds */
289	$batchSize = 20;
290
291	/* Extract all archive-type items and call the ItemAddOption instances for postprocessing */
292	$itemsToProcess = $itemsToProcessKeyMap = array();
293	$i = 0;
294	do {
295	    $file =& $status['addedFiles'][$i];
296	    if (empty($file['id']) || isset($this->_processedItems[$i])) {
297		/* We couldn't add this file for whatever reason or it has been processed already */
298		continue;
299	    }
300	    list ($ret, $addedItem) = GalleryCoreApi::loadEntitiesById($file['id'], 'GalleryItem');
301	    if ($ret) {
302		return array($ret, null);
303	    }
304
305	    /* Check if we should extract individual files out of an archive */
306	    if (GalleryUtilities::isA($addedItem, 'GalleryDataItem')
307		    && isset($this->_extractionToolkitMap[$addedItem->getMimeType()])) {
308		list ($ret, $extractedItems) = $this->_extractAndAddFiles(
309		    $addedItem, $this->_extractionToolkitMap[$addedItem->getMimeType()]);
310		if ($ret) {
311		    return array($ret, null);
312		}
313		$ret = GalleryCoreApi::deleteEntityById($addedItem->getId(), 'GalleryItem');
314		if ($ret) {
315		    return array($ret, null);
316		}
317		/*
318		 * Remove this element from the status and use array_merge to append the extracted
319		 * items and to reindex the whole array to fill the gap we just created.
320		 */
321		unset($status['addedFiles'][$i--]);
322		$status['addedFiles'] = array_merge($status['addedFiles'], $extractedItems);
323
324		$gallery->guaranteeTimeLimit(30);
325		$ret = $this->_storage->checkPoint();
326		if ($ret) {
327		    return array($ret, null);
328		}
329	    } else {
330		/* This is not an archive, add it to our array of item objects */
331		$itemsToProcess[] = $addedItem;
332		/*
333		 * We can't index $itemsToProcess directly by $i because some options expect it to
334		 * be indexed from 0..n without holes.
335		 */
336		$itemsToProcessKeyMap[] = $i;
337	    }
338
339	    if (count($itemsToProcess) % $batchSize == 0
340		    || !isset($status['addedFiles'][$i+1]) && count($itemsToProcess)) {
341		/* Allow ItemAddOptions to process added item(s) */
342		$optionNumber = 0;
343		foreach ($this->_optionInstances as $option) {
344		    $this->_templateAdapter->updateProgressBar(
345			$this->_processingItemsMessage, '',
346			$optionNumber++ / count($this->_optionInstances));
347		    $gallery->guaranteeTimeLimit(60);
348
349		    list ($ret, $optionErrors, $optionWarnings) =
350			$option->handleRequestAfterAdd($form, $itemsToProcess);
351		    if ($ret) {
352			return array($ret, null);
353		    }
354
355		    $errors = array_merge($errors, $optionErrors);
356		    /* For each item, put the items warnings into our status array */
357		    foreach ($optionWarnings as $j => $messages) {
358			$key = $itemsToProcessKeyMap[$j];
359			if (!isset($status['addedFiles'][$key]['warnings'])) {
360			    $status['addedFiles'][$key]['warnings'] = array();
361			}
362			$status['addedFiles'][$key]['warnings'] =
363			    array_merge($status['addedFiles'][$key]['warnings'], $messages);
364		    }
365
366		    $ret = $this->_storage->checkPoint();
367		    if ($ret) {
368			return array($ret, null);
369		    }
370		}
371
372		foreach ($itemsToProcessKeyMap as $j) {
373		    $this->_processedItems[$j] = 1;
374		}
375		$itemsToProcess = $itemsToProcessKeyMap = array();
376		$gallery->guaranteeTimeLimit(60);
377		$this->_templateAdapter->updateProgressBar($this->_processingItemsMessage, '', 1);
378	    }
379	} while (isset($status['addedFiles'][++$i]));
380
381	$this->_templateAdapter->updateProgressBar($this->_processingItemsMessage, '', 1);
382
383	return array(null, $errors);
384    }
385
386    /**
387     * Extract files from an archive item and add new items to the same album.
388     * @param GalleryDataItem $archiveItem archive
389     * @param GalleryToolkit $toolkit toolkit that supports extract operation
390     * @return array GalleryStatus a status code
391     *               array of array('fileName' => '..', 'id' => ##, 'warnings' => array of string)
392     * @access private
393     */
394    function _extractAndAddFiles($archiveItem, $toolkit) {
395	global $gallery;
396	$this->_platform =& $gallery->getPlatform();
397
398	$this->_extractingArchiveMessage = $this->_coreModule->translate('Extracting archive');
399	$this->_templateAdapter->updateProgressBar($this->_extractingArchiveMessage, '', 0);
400	$gallery->guaranteeTimeLimit(120);
401
402	$parentId = $archiveItem->getParentId();
403	list ($ret, $hasAddAlbumPermission) =
404	    GalleryCoreApi::hasItemPermission($parentId, 'core.addAlbumItem');
405	if ($ret) {
406	    return array($ret, null);
407	}
408
409	list ($ret, $file) = $archiveItem->fetchPath();
410	if ($ret) {
411	    return array($ret, null);
412	}
413
414	$base = $this->_platform->tempnam($gallery->getConfig('data.gallery.tmp'), 'tmp_');
415	$tmpDir = $base . '.dir';
416	if (!$this->_platform->mkdir($tmpDir)) {
417	    return array(GalleryCoreApi::error(ERROR_PLATFORM_FAILURE), null);
418	}
419
420	list ($ret) = $toolkit->performOperation($archiveItem->getMimeType(), 'extract',
421						 $file, $tmpDir, array());
422	if ($ret) {
423	    @$this->_platform->recursiveRmdir($tmpDir);
424	    @$this->_platform->unlink($base);
425	    return array($ret, null);
426	}
427
428	/*
429	 * If archive title matches the filename or base filename then name new items
430	 * with the same strategy; otherwise just use the archive title.
431	 */
432	$archiveTitle = $archiveItem->getTitle();
433	$archiveName = $archiveItem->getPathComponent();
434	list ($archiveBase) = GalleryUtilities::getFileNameComponents($archiveName);
435	if ($archiveTitle == $archiveName) {
436	    $titleMode = 'file';
437	} else if ($archiveTitle == $archiveBase) {
438	    $titleMode = 'base';
439	} else {
440	    $titleMode = 'archive';
441	}
442
443	$this->_templateAdapter->updateProgressBar($this->_extractingArchiveMessage, '', 0.1);
444	$gallery->guaranteeTimeLimit(30);
445
446	$addedFiles = array();
447	$ret = $this->_recursiveAddDir(
448	    $tmpDir, $parentId, $addedFiles, $archiveItem, $titleMode, $hasAddAlbumPermission);
449	@$this->_platform->recursiveRmdir($tmpDir);
450	@$this->_platform->unlink($base);
451	if ($ret) {
452	    return array($ret, null);
453	}
454
455	$this->_templateAdapter->updateProgressBar($this->_extractingArchiveMessage, '', 1);
456
457	return array(null, $addedFiles);
458    }
459
460    /**
461     * Recursively add files from extracted archive.
462     * @return GalleryStatus a status code
463     * @access private
464     */
465    function _recursiveAddDir($dir, $parentId, &$addedFiles, &$archiveItem, $titleMode,
466			      $canAddAlbums) {
467	global $gallery;
468
469	$list = array();
470	$dh = $this->_platform->opendir($dir);
471	while (($file = $this->_platform->readdir($dh)) !== false) {
472	    if ($file != '.' && $file != '..') {
473		$list[] = $file;
474	    }
475	}
476	$this->_platform->closedir($dh);
477
478	foreach ($list as $filename) {
479	    $path = "$dir/$filename";
480	    if ($this->_platform->is_dir($path)) {
481		if ($canAddAlbums) {
482		    $title = $filename;
483		    GalleryUtilities::sanitizeInputValues($title);
484		    list ($ret, $album) = GalleryCoreApi::createAlbum(
485			$parentId, $filename, $title, '', '', '');
486		    if ($ret) {
487			return $ret;
488		    }
489		    list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($album->getId());
490		    if ($ret) {
491			return $ret;
492		    }
493		    $ret = $this->_recursiveAddDir($path, $album->getId(), $addedFiles,
494			$archiveItem, $titleMode, $canAddAlbums);
495		    if ($ret) {
496			GalleryCoreApi::releaseLocks($lockId);
497			return $ret;
498		    }
499		    $ret = GalleryCoreApi::releaseLocks($lockId);
500		    if ($ret) {
501			return $ret;
502		    }
503		    $newItem =& $album;
504		} else {
505		    /*
506		     * Flattening folder structure since we're not allowed to create albums.
507		     * Adding files but ignoring directories.
508		     */
509		    $ret = $this->_recursiveAddDir(
510			$path, $parentId, $addedFiles, $archiveItem, $titleMode, $canAddAlbums);
511		    if ($ret) {
512			return $ret;
513		    }
514		    $newItem = null;
515		}
516	    } else {
517		list ($ret, $mimeType) = GalleryCoreApi::getMimeType($filename);
518		if ($ret) {
519		    return $ret;
520		}
521		if ($titleMode == 'file') {
522		    $title = $filename;
523		    GalleryUtilities::sanitizeInputValues($title);
524		} else if ($titleMode == 'base') {
525		    list ($title) = GalleryUtilities::getFileNameComponents($filename);
526		    GalleryUtilities::sanitizeInputValues($title);
527		} else {
528		    $title = $archiveItem->getTitle();
529		}
530		list ($ret, $newItem) = GalleryCoreApi::addItemToAlbum(
531		    $path, $filename, $title, $archiveItem->getSummary(),
532		    $archiveItem->getDescription(), $mimeType, $parentId);
533		if ($ret) {
534		    return $ret;
535		}
536	    }
537
538	    if ($newItem) {
539		$sanitizedFilename = $filename;
540		GalleryUtilities::sanitizeInputValues($sanitizedFilename);
541		$addedFiles[] = array('fileName' => $sanitizedFilename, 'id' => $newItem->getId(),
542				      'warnings' => array());
543	    }
544	    if (count($addedFiles) % 10 == 0) {
545		/* The percentage isn't accurate at all, we just keep the visual feedback going */
546		$this->_templateAdapter->updateProgressBar($this->_extractingArchiveMessage, '',
547		    0.1 + 0.9 * count($addedFiles) / (count($list) + count($addedFiles)));
548		$gallery->guaranteeTimeLimit(30);
549		$ret = $this->_storage->checkPoint();
550		if ($ret) {
551		    return $ret;
552		}
553	    }
554	}
555
556	return null;
557    }
558}
559
560/**
561 * This view will show the selected plugin for adding items to the gallery
562 */
563class ItemAddView extends GalleryView {
564
565    /**
566     * @see GalleryView::loadTemplate
567     */
568    function loadTemplate(&$template, &$form) {
569	global $gallery;
570	$session =& $gallery->getSession();
571
572	$addPlugin = GalleryUtilities::getRequestVariables('addPlugin');
573
574	list ($ret, $item) = $this->getItem();
575	if ($ret) {
576	    return array($ret, null);
577	}
578	$itemId = $item->getId();
579
580	/* Make sure we have permission to add to this item */
581	$ret = GalleryCoreApi::assertHasItemPermission($itemId, 'core.addDataItem');
582	if ($ret) {
583	    return array($ret, null);
584	}
585
586	list ($ret, $isAdmin) = GalleryCoreApi::isUserInSiteAdminGroup();
587	if ($ret) {
588	    return array($ret, null);
589	}
590
591	/* Get all the add plugins */
592	list ($ret, $allPluginIds) =
593	    GalleryCoreApi::getAllFactoryImplementationIds('ItemAddPlugin');
594	if ($ret) {
595	    return array($ret, null);
596	}
597
598	$pluginInstances = array();
599	foreach (array_keys($allPluginIds) as $pluginId) {
600	    list ($ret, $plugin) =
601		GalleryCoreApi::newFactoryInstanceById('ItemAddPlugin', $pluginId);
602	    if ($ret) {
603		return array($ret, null);
604	    }
605
606	    list ($ret, $isAppropriate) = $plugin->isAppropriate();
607	    if ($ret) {
608		return array($ret, null);
609	    }
610
611	    if ($isAppropriate) {
612		$pluginInstances[$pluginId] = $plugin;
613	    }
614	}
615
616	/* Get all the add options */
617	list ($ret, $optionInstances) = ItemAddOption::getAllAddOptions();
618	if ($ret) {
619	    return array($ret, null);
620	}
621
622	/*
623	 * If the plugin is empty get it from the session.  If it's empty there,
624	 * default to the first plugin we find.  Either way, save the user's
625	 * preference in the session.
626	 */
627	$addPluginSessionKey = 'core.view.ItemAdd.addPlugin.' . get_class($item);
628	if (empty($addPlugin) || !isset($pluginInstances[$addPlugin])) {
629	    $addPlugin = $session->get($addPluginSessionKey);
630	    if (empty($addPlugin) || !isset($pluginInstances[$addPlugin])) {
631		$ids = array_keys($pluginInstances);
632		$addPlugin = $ids[0];
633	    }
634	}
635	$session->put($addPluginSessionKey, $addPlugin);
636
637	$errors = $session->get('itemAdd.error');
638	if (!empty($errors)) {
639	    $session->remove('itemAdd.error');
640	    /* Same logic as in main.php */
641	    foreach ($errors as $error) {
642		GalleryUtilities::putRequestVariable($error, 1);
643	    }
644	}
645
646	/* Get display data for all plugins */
647	$plugins = array();
648	foreach ($pluginInstances as $pluginId => $plugin) {
649	    list ($ret, $title) =  $plugin->getTitle();
650	    if ($ret) {
651		return array($ret, null);
652	    }
653	    $plugins[] = array('title' => $title,
654			       'id' => $pluginId,
655			       'isSelected' => ($pluginId == $addPlugin));
656	}
657
658	$ItemAdd = array();
659	$ItemAdd['addPlugin'] = $addPlugin;
660	$ItemAdd['plugins'] = $plugins;
661	$ItemAdd['isAdmin'] = $isAdmin;
662
663	/* Let the plugin load its template data */
664	list ($ret, $ItemAdd['pluginFile'], $ItemAdd['pluginL10Domain']) =
665	    $pluginInstances[$addPlugin]->loadTemplate($template, $form, $item);
666	if ($ret) {
667	    return array($ret, null);
668	}
669
670	/* Now let all options load their template data */
671	$ItemAdd['options'] = array();
672	foreach ($optionInstances as $option) {
673	    list ($ret, $entry['file'], $entry['l10Domain']) =
674		$option->loadTemplate($template, $form, $item);
675	    if ($ret) {
676		return array($ret, null);
677	    }
678	    if (!empty($entry['file'])) {
679		$ItemAdd['options'][] = $entry;
680	    }
681	}
682
683	/* Make sure that we've got some toolkits */
684	list ($ret, $operations) = GalleryCoreApi::getToolkitOperations('image/jpeg');
685	if ($ret) {
686	    return array($ret, null);
687	}
688
689	$ItemAdd['hasToolkit'] = false;
690	for ($i = 0; $i < sizeof($operations); $i++) {
691	    if ($operations[$i]['name'] == 'thumbnail') {
692		$ItemAdd['hasToolkit'] = true;
693		break;
694	    }
695	}
696
697	$template->setVariable('ItemAdd', $ItemAdd);
698	$template->setVariable('controller', 'core.ItemAdd');
699	return array(null,
700		     array('body' => 'modules/core/templates/ItemAdd.tpl'));
701    }
702
703    /**
704     * @see GalleryView::getViewDescription
705     */
706    function getViewDescription() {
707	list ($ret, $core) = GalleryCoreApi::loadPlugin('module', 'core');
708	if ($ret) {
709	    return array($ret, null);
710	}
711	return array(null, $core->translate('add items'));
712    }
713}
714
715
716/**
717 * Interface for plugins to the ItemAdd view and controller.
718 * Plugins provide alternate ways to add items into Gallery.
719 * @abstract
720 */
721class ItemAddPlugin {
722
723    /**
724     * Load the template with data from this plugin
725     * @see GalleryView::loadTemplate
726     *
727     * @param GalleryTemplate $template
728     * @param array $form the form values
729     * @param GalleryItem $item
730     * @return array GalleryStatus a status code
731     *               string the path to a template file to include
732     *               string localization domain for the template file
733     */
734    function loadTemplate(&$template, &$form, $item) {
735	return array(GalleryCoreApi::error(ERROR_UNIMPLEMENTED), null, null);
736    }
737
738    /**
739     * Let the plugin handle the incoming request
740     * @see GalleryController::handleRequest
741     *
742     * @param array $form the form values
743     * @param GalleryItem $item
744     * @param GalleryItemAddController $addController A reference to the ItemAddController
745     *               to be used for the post-processing calls.
746     * @return array GalleryStatus a status code
747     *               array error messages (request parameter errors). Stop processing on errors.
748     *               array status data, 'addedFiles' entry should contain:
749     *                                   array(array('fileName' => '...', 'id' => ##,
750     *                                               'warnings' => array of strings), ...)
751     */
752    function handleRequest($form, &$item, &$addController) {
753	return array(GalleryCoreApi::error(ERROR_UNIMPLEMENTED), null, null);
754    }
755
756    /**
757     * Return a localized title for this plugin, suitable for display to the user
758     *
759     * @return array GalleryStatus a status code
760     *               return-array (same as GalleryController::handleRequest)
761     */
762    function getTitle() {
763	return array(GalleryCoreApi::error(ERROR_UNIMPLEMENTED), null);
764    }
765
766    /**
767     * Is this plugin appropriate at this time?  Default is true.
768     *
769     * @return array GalleryStatus a status code
770     *               boolean true or false
771     */
772    function isAppropriate() {
773	return array(null, true);
774    }
775}
776
777/**
778 * Interface for options to the ItemAdd view and controller.
779 * Options allow us to provide extra UI in the views and extra processing in the controller so
780 * that we can add new functionality like watermarking, quotas, etc to every ItemAddPlugin
781 * @abstract
782 */
783class ItemAddOption {
784
785    /**
786     * Return all the available option plugins
787     *
788     * @return array GalleryStatus a status code
789     *               array ItemAddOption instances
790     * @static
791     */
792    function getAllAddOptions() {
793	/* Get all the option plugins */
794	list ($ret, $allOptionIds) =
795	    GalleryCoreApi::getAllFactoryImplementationIds('ItemAddOption');
796	if ($ret) {
797	    return array($ret, null);
798	}
799
800	$optionInstances = array();
801	foreach (array_keys($allOptionIds) as $optionId) {
802	    list ($ret, $option) =
803		GalleryCoreApi::newFactoryInstanceById('ItemAddOption', $optionId);
804	    if ($ret) {
805		return array($ret, null);
806	    }
807
808	    list ($ret, $isAppropriate) = $option->isAppropriate();
809	    if ($ret) {
810		return array($ret, null);
811	    }
812
813	    if ($isAppropriate) {
814		$optionInstances[$optionId] = $option;
815	    }
816	}
817
818	return array(null, $optionInstances);
819    }
820
821    /**
822     * Load the template with data from this plugin
823     * @see GalleryView::loadTemplate
824     *
825     * @param GalleryTemplate $template
826     * @param array $form the form values
827     * @param GalleryItem $item
828     * @return array GalleryStatus a status code
829     *               string the path to a template file to include
830     *               string localization domain for the template file
831     */
832    function loadTemplate(&$template, &$form, $item) {
833	return array(null, null, null);
834    }
835
836    /**
837     * Let the plugin handle the incoming request.  We expect the $items to be locked.
838     * @see GalleryController::handleRequest
839     *
840     * @param array $form the form values
841     * @param array $items GalleryDataItems
842     * @return array GalleryStatus a status code
843     *               array localized error messages.  Attempt to continue processing on errors since
844     *                     the items have already been added and post-processing will continue.
845     *               array localized warning messages
846     */
847    function handleRequestAfterAdd($form, $items) {
848	return array(GalleryCoreApi::error(ERROR_UNIMPLEMENTED), null, null);
849    }
850
851    /**
852     * Is this option appropriate at this time?
853     *
854     * @return array GalleryStatus a status code
855     *               boolean true or false
856     */
857    function isAppropriate() {
858	return array(null, false);
859    }
860}
861?>
862