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