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 moving one or more items from one album to another.
23 * @package GalleryCore
24 * @subpackage UserInterface
25 * @author Ernesto Baschny <ernst@baschny.de>
26 * @version $Revision: 18138 $
27 */
28class ItemMoveController extends GalleryController {
29
30    /**
31     * @see GalleryController::handleRequest
32     * @todo Scalability - Don't load all album-items into memory. Need a way to load the items
33     *       incrementally, e.g. with an AJAX-powered tree widget.
34     */
35    function handleRequest($form) {
36	global $gallery;
37
38	list ($ret, $item) = $this->getItem();
39	if ($ret) {
40	    return array($ret, null);
41	}
42	$itemId = $item->getId();
43
44	$status = $error = array();
45	if (isset($form['action']['move'])) {
46
47	    /* First check if everything would be okay with the change */
48	    $canAddItem = $canAddAlbum = false;
49	    if (!empty($form['destination'])) {
50		/* Check if we can add albums or items here */
51		$newParentId = $form['destination'];
52		list ($ret, $permissions) = GalleryCoreApi::getPermissions($newParentId);
53		if ($ret) {
54		    return array($ret, null);
55		}
56
57		if (!isset($permissions['core.view'])) {
58		    /* Avoid information disclosure, act as if the item didn't exist. */
59		    return array(GalleryCoreApi::error(ERROR_MISSING_OBJECT), null);
60		}
61
62		$canAddItem = isset($permissions['core.addDataItem']);
63		$canAddAlbum = isset($permissions['core.addAlbumItem']);
64		if (!$canAddAlbum && !$canAddItem) {
65		    $error[] = 'form[error][destination][permission]';
66		}
67
68		/* Load destination parent ids: we don't want recursive moves */
69		list ($ret, $newParentAncestorIds) =
70		    GalleryCoreApi::fetchParentSequence($newParentId);
71		if ($ret) {
72		    return array($ret, null);
73		}
74		$newParentAncestorIds[] = $newParentId;
75	    } else {
76		$error[] = 'form[error][destination][empty]';
77	    }
78
79	    if (empty($error) && !empty($form['selectedIds'])) {
80		$selectedIds = array_keys($form['selectedIds']);
81
82		/* Load the source items */
83		list ($ret, $selectedItems) =
84		    GalleryCoreApi::loadEntitiesById($selectedIds, 'GalleryItem');
85		if ($ret) {
86		    return array($ret, null);
87		}
88		$ret = GalleryCoreApi::studyPermissions($selectedIds);
89		if ($ret) {
90		    return array($ret, null);
91		}
92
93		foreach ($selectedItems as $selectedItem) {
94		    $selectedId = $selectedItem->getId();
95
96		    /* Can't move into a tree that is included in the source */
97		    if (in_array($selectedId, $newParentAncestorIds)) {
98			$error[] = 'form[error][source][' . $selectedId . '][selfMove]';
99			continue;
100		    }
101
102		    list ($ret, $permissions) = GalleryCoreApi::getPermissions($selectedId);
103		    if ($ret) {
104			return array($ret, null);
105		    }
106
107		    /* Can we delete this item from here? */
108		    if (!isset($permissions['core.delete'])) {
109			$error[] = 'form[error][source][' . $selectedId . '][permission][delete]';
110		    }
111
112		    /* Check if the destination allows this source to be added */
113		    if ($selectedItem->getCanContainChildren() && !$canAddAlbum) {
114			$error[] = 'form[error][source][' . $selectedId .
115				   '][permission][addAlbumItem]';
116		    } else if (!$selectedItem->getCanContainChildren() && !$canAddItem) {
117			$error[] = 'form[error][source][' . $selectedId .
118				   '][permission][addDataItem]';
119		    }
120		}
121	    }
122
123	    if (empty($error) && !empty($selectedIds) && !empty($newParentId)) {
124		$storage =& $gallery->getStorage();
125
126		/* Read lock old and new parent album, and all ancestor albums */
127		$lockIds = array();
128		list ($ret, $oldParents) = GalleryCoreApi::fetchParentSequence($itemId);
129		if ($ret) {
130		    return array($ret, null);
131		}
132		list ($ret, $newParents) = GalleryCoreApi::fetchParentSequence($newParentId);
133		if ($ret) {
134		    return array($ret, null);
135		}
136		$oldParents[] = $itemId;
137		$newParents[] = $newParentId;
138		list ($ret, $lockIds[]) = GalleryCoreApi::acquireReadLock(
139			array_unique(array_merge($oldParents, $newParents)));
140		if ($ret) {
141		    return array($ret, null);
142		}
143
144		/* Do the move / locking in batches to prevent too many open files issues */
145		$batchSize = 100;
146		$status['moved']['count'] = 0;
147		do {
148		    $currentItems = array_splice($selectedItems, 0, $batchSize);
149		    $currentIds = array();
150		    foreach ($currentItems as $item) {
151			$currentIds[] = $item->getId();
152		    }
153		    /* Write lock all the items we're moving */
154		    list ($ret, $currentLockId) = GalleryCoreApi::acquireWriteLock($currentIds);
155		    if ($ret) {
156			GalleryCoreApi::releaseLocks($lockIds);
157			return array($ret, null);
158		    }
159
160		    /* If we have no problems, do the moves */
161		    foreach ($currentItems as $selectedItem) {
162			$ret = $selectedItem->move($newParentId);
163			if ($ret) {
164			    $lockIds[] = $currentLockId;
165			    GalleryCoreApi::releaseLocks($lockIds);
166			    return array($ret, null);
167			}
168			$status['moved']['count']++;
169
170			$ret = $selectedItem->save();
171			if ($ret) {
172			    $lockIds[] = $currentLockId;
173			    GalleryCoreApi::releaseLocks($lockIds);
174			    return array($ret, null);
175			}
176
177			if (GalleryUtilities::isA($selectedItem, 'GalleryDataItem')) {
178			    /* Update for derivative preferences of new parent */
179			    $ret =
180				GalleryCoreApi::addExistingItemToAlbum($selectedItem, $newParentId);
181			    if ($ret) {
182				$lockIds[] = $currentLockId;
183				GalleryCoreApi::releaseLocks($lockIds);
184				return array($ret, null);
185			    }
186			}
187
188			$ret = $storage->checkPoint();
189			if ($ret) {
190			    GalleryCoreApi::releaseLocks($lockIds);
191			    return array($ret, null);
192			}
193		    }
194		    $ret = GalleryCoreApi::releaseLocks($currentLockId);
195		    if ($ret) {
196			GalleryCoreApi::releaseLocks($lockIds);
197			return array($ret, null);
198		    }
199		    $ret = $storage->checkPoint();
200		    if ($ret) {
201			GalleryCoreApi::releaseLocks($lockIds);
202			return array($ret, null);
203		    }
204		} while (!empty($selectedItems));
205
206		$ret = GalleryCoreApi::releaseLocks($lockIds);
207		if ($ret) {
208		    return array($ret, null);
209		}
210
211		list ($ret, $success) = GalleryCoreApi::guaranteeAlbumHasThumbnail($itemId);
212		if ($ret) {
213		    return array($ret, null);
214		}
215
216		/* Figure out where to redirect upon success */
217		$redirect['view'] = 'core.ItemAdmin';
218		$redirect['subView'] = 'core.ItemMove';
219		$redirect['itemId'] = $itemId;
220	    }
221	} else if (isset($form['action']['next'])) {
222	    $page = GalleryUtilities::getRequestVariables('page');
223	    list ($ret, $peerIds) =
224		GalleryCoreApi::fetchChildItemIdsWithPermission($itemId, 'core.delete');
225	    if ($ret) {
226		return array($ret, null);
227	    }
228
229	    $numPages = ceil(sizeof($peerIds) / $form['numPerPage']);
230
231	    $results['delegate']['itemId'] = $itemId;
232	    $results['delegate']['page'] = min($page + 1, $numPages);
233	} else if (isset($form['action']['previous'])) {
234	    $page = GalleryUtilities::getRequestVariables('page');
235	    $results['delegate']['itemId'] = $itemId;
236	    $results['delegate']['page'] = max($page - 1, 1);
237	} else if (isset($form['action']['cancel'])) {
238	    $results['return'] = true;
239	}
240
241	if (!empty($redirect)) {
242	    $results['redirect'] = $redirect;
243	} else {
244	    $results['delegate']['view'] = 'core.ItemAdmin';
245	    $results['delegate']['subView'] = 'core.ItemMove';
246	}
247	$results['status'] = $status;
248	$results['error'] = $error;
249
250	return array(null, $results);
251    }
252}
253
254/**
255 * This view will prompt for which items to move and where is the destination.
256 */
257class ItemMoveView extends GalleryView {
258
259    /**
260     * @see GalleryView::loadTemplate
261     */
262    function loadTemplate(&$template, &$form) {
263	global $gallery;
264
265	/* itemId is the album where we want to move items from */
266	list ($ret, $item) = $this->getItem();
267	if ($ret) {
268	    return array($ret, null);
269	}
270	$itemId = $item->getId();
271
272	list ($selectedId, $page) = GalleryUtilities::getRequestVariables('selectedId', 'page');
273	if ($form['formName'] != 'ItemMove') {
274	    /* First time around, load the form with item data */
275	    if ($selectedId) {
276		$form['selectedIds'][$selectedId] = true;
277	    }
278	    $form['destination'] = '';
279	    $form['formName'] = 'ItemMove';
280	    $form['numPerPage'] = 15;
281	}
282
283	/* Get all peers that we can delete */
284	list ($ret, $peerIds) = GalleryCoreApi::fetchChildItemIdsWithPermission(
285	    $itemId, array('core.delete', 'core.view'));
286	if ($ret) {
287	    return array($ret, null);
288	}
289
290	$albumIds = $albumTree = $selectedIds = $peers = $peerDescendentCounts = array();
291	$peerTypes = array('album' => array(), 'data' => array());
292	$numPages = 1;
293	if (!empty($peerIds)) {
294	    $numPages = ceil(sizeof($peerIds) / $form['numPerPage']);
295	    if (empty($page)) {
296		/* determine which page we're on */
297		$page = 1;
298		for ($i = 0; $i < sizeof($peerIds); $i++) {
299		    if ($peerIds[$i] == $selectedId) {
300			$page = ceil(($i + 1) / $form['numPerPage']);
301		    }
302		}
303	    }
304
305	    $start = $form['numPerPage'] * ($page - 1);
306	    $peerIds = array_slice($peerIds, $start, $form['numPerPage']);
307	    if (isset($form['selectedIds'])) {
308		$selectedIds = $form['selectedIds'];
309		foreach ($peerIds as $peerId) {
310		    if (isset($selectedIds[$peerId])) {
311			unset($selectedIds[$peerId]);
312		    }
313		}
314	    }
315
316	    /* Add any items with error messages that would otherwise not be shown */
317	    if (!empty($form['error']['source'])) {
318		foreach ($form['error']['source'] as $id => $tmp) {
319		    if (!in_array($id, $peerIds)) {
320			array_unshift($peerIds, $id);
321			unset($selectedIds[$id]);
322		    }
323		}
324	    }
325
326	    /* Load all the peers */
327	    list ($ret, $peerItems) = GalleryCoreApi::loadEntitiesById($peerIds, 'GalleryItem');
328	    if ($ret) {
329		return array($ret, null);
330	    }
331
332	    /* get peer thumbnails and resizes */
333	    list ($ret, $derivatives) = GalleryCoreApi::fetchDerivativesByItemIds($peerIds);
334	    if ($ret) {
335		return array($ret, null);
336	    }
337
338	    /* Build our peers table */
339	    $peers = array();
340	    foreach ($peerItems as $peerItem) {
341		$peers[$peerItem->getId()] = (array)$peerItem;
342		if (GalleryUtilities::isA($peerItem, 'GalleryAlbumItem')) {
343		    $peerTypes['album'][$peerItem->getId()] = 1;
344		} else {
345		    $peerTypes['data'][$peerItem->getId()] = 1;
346		}
347		$peers[$peerItem->getId()]['selected'] =
348		    isset($form['selectedIds'][$peerItem->getId()]);
349
350		/* While we're at it, attach thumbnails and resizes */
351		if (isset($derivatives[$peerItem->getId()])) {
352		    foreach ($derivatives[$peerItem->getId()] as $derivative) {
353			$type = $derivative->getDerivativeType();
354			if (empty($peers[$peerItem->getId()]['resize']) &&
355			        $type == DERIVATIVE_TYPE_IMAGE_RESIZE) {
356			    $peers[$peerItem->getId()]['resize'] = (array)$derivative;
357			} else if ($type == DERIVATIVE_TYPE_IMAGE_THUMBNAIL) {
358			    $peers[$peerItem->getId()]['thumbnail'] = (array)$derivative;
359			}
360		    }
361		}
362	    }
363
364	    /* Get child counts */
365	    if (!empty($peerTypes['album'])) {
366		list ($ret, $peerDescendentCounts) =
367		    GalleryCoreApi::fetchDescendentCounts(array_keys($peerTypes['album']));
368		if ($ret) {
369		    return array($ret, null);
370		}
371	    }
372
373	    /* Get ids of all albums where we can add new data items */
374	    list ($ret, $albumIds['addDataItem']) = GalleryCoreApi::fetchAllItemIds(
375		'GalleryAlbumItem', array('core.addDataItem', 'core.view'));
376	    if ($ret) {
377		return array($ret, null);
378	    }
379
380	    /* Get ids of all all albums where we can add new album items */
381	    list ($ret, $albumIds['addAlbumItem']) = GalleryCoreApi::fetchAllItemIds(
382		'GalleryAlbumItem', array('core.addAlbumItem', 'core.view'));
383	    if ($ret) {
384		return array($ret, null);
385	    }
386
387	    /* Merge them together to get the master list of ids */
388	    $albumIds['allIds'] = array_unique(array_merge($albumIds['addDataItem'],
389							   $albumIds['addAlbumItem']));
390
391	    /* Load all the album entities */
392	    list ($ret, $albums) =
393		GalleryCoreApi::loadEntitiesById($albumIds['allIds'], 'GalleryAlbumItem');
394	    if ($ret) {
395		return array($ret, null);
396	    }
397
398	    $albumTree = GalleryUtilities::createAlbumTree($albums);
399	}
400
401	$urlGenerator =& $gallery->getUrlGenerator();
402
403	$ItemMove = array();
404	$ItemMove['canCancel'] = (boolean)GalleryUtilities::getRequestVariables('return');
405	$ItemMove['albumIds'] = $albumIds;
406	$ItemMove['peers'] = $peers;
407	$ItemMove['peerTypes'] = $peerTypes;
408	$ItemMove['peerDescendentCounts'] = $peerDescendentCounts;
409	$ItemMove['albumTree'] = $albumTree;
410	$ItemMove['page'] = $page;
411	$ItemMove['numPages'] = $numPages;
412	$ItemMove['numPerPage'] = $form['numPerPage'];
413	$ItemMove['selectedIds'] = array_keys($selectedIds);
414	$ItemMove['selectedIdCount'] = count($selectedIds);
415
416	$template->setVariable('ItemMove', $ItemMove);
417	$template->setVariable('controller', 'core.ItemMove');
418	$template->javascript('lib/yui/yahoo-dom-event.js');
419	$template->javascript('lib/yui/container-min.js');
420	$template->javascript('lib/yui/treeview-min.js');
421	$template->style('modules/core/data/tree.css');
422	return array(null, array('body' => 'modules/core/templates/ItemMove.tpl'));
423    }
424
425    /**
426     * @see GalleryView::getViewDescription
427     */
428    function getViewDescription() {
429	list ($ret, $core) = GalleryCoreApi::loadPlugin('module', 'core');
430	if ($ret) {
431	    return array($ret, null);
432	}
433
434	return array(null, $core->translate('move item'));
435    }
436}
437?>
438