diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 1607a9bea..1b6c9ef50 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -1,4 +1,5 @@ 7.9.3 + - added #11007: Added drag'n'drop sorting in Gallery Album Edit View 7.9.2 - added: Workflow to extend recurring Calendar events 2 years from the diff --git a/docs/upgrades/packages-7.9.3/root_import_gallery-templates_admin.css.wgpkg b/docs/upgrades/packages-7.9.3/root_import_gallery-templates_admin.css.wgpkg new file mode 100644 index 000000000..8cbaecf00 Binary files /dev/null and b/docs/upgrades/packages-7.9.3/root_import_gallery-templates_admin.css.wgpkg differ diff --git a/docs/upgrades/packages-7.9.3/root_import_gallery-templates_default-gallery-edit-album.wgpkg b/docs/upgrades/packages-7.9.3/root_import_gallery-templates_default-gallery-edit-album.wgpkg new file mode 100644 index 000000000..58fd72be7 Binary files /dev/null and b/docs/upgrades/packages-7.9.3/root_import_gallery-templates_default-gallery-edit-album.wgpkg differ diff --git a/docs/upgrades/packages-7.9.3/root_import_gallery-templates_dragdropsorting.js.wgpkg b/docs/upgrades/packages-7.9.3/root_import_gallery-templates_dragdropsorting.js.wgpkg new file mode 100644 index 000000000..b28f5e0b5 Binary files /dev/null and b/docs/upgrades/packages-7.9.3/root_import_gallery-templates_dragdropsorting.js.wgpkg differ diff --git a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm index c63fdb29e..267817e4f 100644 --- a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm +++ b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm @@ -17,6 +17,7 @@ use Carp qw( croak ); use File::Find; use File::Spec; use File::Temp qw{ tempdir }; +use JSON; use Tie::IxHash; use WebGUI::International; use WebGUI::Utility; @@ -34,6 +35,7 @@ use Archive::Any; =head1 DIAGNOSTICS =head1 METHODS +=cut #------------------------------------------------------------------- @@ -1204,6 +1206,172 @@ sub www_deleteConfirm { #---------------------------------------------------------------------------- +=head2 www_ajax ( ) + +Generic AJAX service for gallery. + +Arguments are accepted in JSON format in the form variable C. The single +obligatory argument is C determining the service to be called. A list +of available services is given in the following. Additional arguments may be +required depending on the service. + +Results are returned in JSON format. The information returned depends on the +service called. Generally, success is indicated by a value of 0 in C. + +=head3 moveFile + +Service for changing the rank of files. Accepts the asset Id of the photo to be moved +in C. The asset Id of the photo to be replaced is specified in C +or C depending on the desired order. Returns -1 in C and an error +message in C if moving of the photo failed. + +=cut + +sub www_ajax { + my $self = shift; + my $session = $self->session; + my $form = $self->session->form; + my $result; + + # Get arguments encoded in json format + my $args = decode_json($form->get("args")); + + # Log some debug information + $session->log->debug("Ajax service called with args=" . $form->get("args")); + + # Process requests depending on action argument + SWITCH: { + + # Return if no action was specified + if ( $args->{action} eq '' ) { + $session->log->error("Call of ajax service without action argument."); + $result->{ errMessage } = "Action argument is missing."; + last; + } + + # ----- Move file action ----- + $args->{action} eq 'moveFile' && do { $result = $self->_moveFileAjaxRequest( $args ); last; }; + + # ----- Unkown action ----- + $session->log->error("Call of ajax service with unknown action '" . $args->{action} . "'."); + $result->{ errMessage } = "Action '" . $args->{action} ."' is unknown."; + } + + # Set error flag if error message exists + $result->{ err } = -1 if $result->{ errMessage }; + + # Return results encoded in json format + return encode_json( $result ); +} + + +#---------------------------------------------------------------------------- + +=head2 _moveFileAjaxRequest ( args ) + +AJAX service for changing the rank of single files. Returns a hash ref with +error information. Arguments passed to the ajax service are provided via the +hash ref C. Note that this is a private function owned by www_ajax. It +should not be used directly. + +=cut + +sub _moveFileAjaxRequest { + my $self = shift; + my $args = shift; + + my $session = $self->session; + my %result; + + # Return if current user is not allowed to edit this album + unless ( $self->canEdit ) { + $session->log->error("Call of moveFile action without having edit permission."); + $result{ errMessage } = "You do not have permission to move files."; + return \%result; + } + # Return if no target was specified + if ( $args->{target} eq '') { + $session->log->error("Call of moveFile action without target argument."); + $result{ errMessage } = "Target argument is missing."; + return \%result; + } + # Return if before or after argument is missing + unless( $args->{before} or $args->{after} ) { + $session->log->error("Call of moveFile action without before/after argument."); + $result{ errMessage } = "Before/after argument is missing."; + return \%result; + } + # Return if before and after arguments were specified + unless( $args->{before} xor $args->{after} ) { + $session->log->error("Call of moveFile action with before *and* after argument."); + $result{ errMessage } = "Both, before and after arguments were specified."; + return \%result; + } + + # Get Id of target photo and instantiate asset + my $targetId = $args->{target}; + my $target = WebGUI::Asset->newByDynamicClass( $session, $targetId ); + + # Return if target photo could not be instantiated + unless ( $target ) { + $session->log->error("Couldn't move file '$targetId' because we couldn't instantiate it."); + $result{ errMessage } = "ID of target file seems to be invalid."; + return \%result; + } + # Return if target is not a child of the current album + unless ( $target->getParent->getId eq $self->getId ) { + $session->log->error("Couldn't move file '$targetId' because it is not a child of this album."); + $result{ errMessage } = "ID of target file seems to be invalid."; + return \%result; + } + + my ($destId, $dest); + + # Instantiate file with ID in before/after argument + $destId = $args->{before} ? $args->{before} : $args->{after}; + $dest = WebGUI::Asset->newByDynamicClass( $session, $destId ); + + # Return if destination file could not be instantiated + unless ( $dest ) { + $session->log->error("Couldn't move file '$targetId' before/after file '$destId' because we couldn't instantiate the latter."); + $result{ errMessage } = "ID in before/after argument seems to be invalid."; + return \%result; + } + # Return if destination file is not a child of the current album + unless ( $dest->getParent->getId eq $self->getId ) { + $session->log->error("Couldn't move file '$targetId' before/after file '$destId' because the latter is not a child of the same album."); + $result{ errMessage } = "ID in before/after argument seems to be invalid."; + return \%result; + } + + # Check for use of after argument when lowering the rank + if ( $args->{after} && $target->getRank() > $dest->getRank() ) { + # Get ID of next sibling + $destId = $self->getNextFileId( $destId ); + # Instantiate next sibling + $dest = WebGUI::Asset->newByDynamicClass( $session, $destId ); + } + # Check for use of before argument when increasing the rank + if ( $args->{before} && $target->getRank() < $dest->getRank() ) { + # Get ID of previous sibling + $destId = $self->getPreviousFileId( $destId ); + # Instantiate previous sibling + $dest = WebGUI::Asset->newByDynamicClass( $session, $destId ); + } + + # Update rank of target photo + $target->setRank( $dest->getRank ); + + # Log some debug information + $session->log->debug("Successfully moved file '$targetId' before/after file '$destId'."); + + # Return reporting success + $result{ err } = 0; + return \%result; +} + +#---------------------------------------------------------------------------- + =head2 www_edit ( ) Show the form to add / edit a GalleryAlbum asset. diff --git a/t/Asset/Wobject/GalleryAlbum/ajax.t b/t/Asset/Wobject/GalleryAlbum/ajax.t new file mode 100644 index 000000000..679137595 --- /dev/null +++ b/t/Asset/Wobject/GalleryAlbum/ajax.t @@ -0,0 +1,264 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 Plain Black Corporation. +#------------------------------------------------------------------- +# Please read the legal notices (docs/legal.txt) and the license +# (docs/license.txt) that came with this distribution before using +# this software. +#------------------------------------------------------------------- +# http://www.plainblack.com info@plainblack.com +#------------------------------------------------------------------- + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../../lib"; + +## The goal of this test is to test the creation and deletion of album assets + +use JSON; +use WebGUI::Test; +use WebGUI::Session; +use Test::More; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); + +my %user; +$user{'1'} = WebGUI::User->new( $session, "new" ); +$user{'1'}->addToGroups( ['3'] ); # Admins +WebGUI::Test->usersToDelete($user{'1'}); +$user{'2'} = WebGUI::User->new( $session, "new" ); +WebGUI::Test->usersToDelete($user{'2'}); + +# Create everything as user no. 1 +$session->user({ user => $user{'1'} }); + +$versionTag->set({name=>"Album Test"}); + +# Create gallery and a single album +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdEdit => 3, # Admins + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); + +# Create 5 photos inside the gallery +my @photoId; + +for (my $i = 0; $i < 5; $i++) +{ + my $photo + = $album->addChild({ + className => "WebGUI::Asset::File::GalleryFile::Photo", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); + $photoId[$i] = $photo->getId; +} + +# Commit all changes +$versionTag->commit; + +# Make album default asset +$session->asset( $album ); + +# Define some general variables +my $result; + +#---------------------------------------------------------------------------- +# Tests +plan tests => 19; + +#---------------------------------------------------------------------------- +# Test module compiles okay +use_ok("WebGUI::Asset::Wobject::GalleryAlbum"); + +#---------------------------------------------------------------------------- +# Test calling without arguments + +diag("general testing"); + +# Provide no arguments at all +$result = callAjaxService({ }); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after call without arguments." ); + +#---------------------------------------------------------------------------- +# Test moveFile action with incomplete of invalid arguments + +diag("moveFile action"); + +# Omit target +$result = callAjaxService({ + action => 'moveFile', + after => $photoId[4], + }); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after request of moveFile action without 'target' specified." ); + + +# Omit before/after +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], +}); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after request of moveFile action without 'before/after' specified." ); + +# Specify invalid target ID +$result = callAjaxService({ + action => 'moveFile', + target => '123456', + after => $photoId[4], +}); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after request of moveFile action with invalid 'target' ID." ); + +# Specify invalid ID in after argument +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], + after => '123456', +}); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after request of moveFile action with invalid ID in 'after' argument." ); + +# Specify invalid ID in before argument +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], + before => '123456', +}); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after request of moveFile action with invalid ID in 'before' argument." ); + +# Specify non-child target ID +$result = callAjaxService({ + action => 'moveFile', + target => $album->getId, + after => $photoId[4], +}); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after request of moveFile action with non-child 'target' ID." ); + +# Specify non-child ID in after argument +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], + after => $album->getId, +}); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after request of moveFile action with non-child ID in 'after' argument." ); + +# Specify non-child ID in before argument +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], + before => $album->getId, +}); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after request of moveFile action with non-child ID in 'before' argument." ); + +#---------------------------------------------------------------------------- +# Test moving photos + +# Move photo no. 0 after photo no. 4 +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], + after => $photoId[4], +}); + +is($result->{ err }, 0, 'Moving of photo no. 0 after photo no. 4 successful.'); +is($album->getPreviousFileId($photoId[0]), $photoId[4], 'Photo no. 0 is after photo no. 4.'); + +# Move photo no. 0 before photo no. 1 (restore initial order) +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], + before => $photoId[1], + }); + +# Delete all stow variables. This is necessary or the list of file IDs will +# not get updated. +$session->stow->deleteAll; + +is($result->{ err }, 0, 'Moving of photo no. 0 before photo no. 1 successful.'); +is($album->getNextFileId($photoId[0]), $photoId[1], 'Photo no. 0 is before photo no. 1.'); + +# Move photo no. 0 before photo no. 0 +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], + before => $photoId[0], + }); + +$session->stow->deleteAll; + +is($result->{ err }, 0, 'Moving of photo no. 0 before photo no. 0 successful.'); +is($album->getNextFileId($photoId[0]), $photoId[1], 'Photo no. 0 is still before photo no. 1.'); + +# Move photo no. 0 after photo no. 0 +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], + after => $photoId[0], + }); + +$session->stow->deleteAll; + +is($result->{ err }, 0, 'Moving of photo no. 0 after photo no. 0 successful.'); +is($album->getNextFileId($photoId[0]), $photoId[1], 'Photo no. 0 is still before photo no. 1.'); + +# Try to move photo with insufficient permissions +$session->user({ user => $user{'2'} }); +$result = callAjaxService({ + action => 'moveFile', + target => $photoId[0], + after => $photoId[4], +}); +$session->user({ user => $user{'1'} }); + +ok( $result->{ err } != 0 && $result->{ errMessage }, "Error after request of moveFile action with insufficient permissions." ); + +#---------------------------------------------------------------------------- +# callAjaxService( args ) +# Makes a call to the www_ajax method of $album and returns the reply. The +# only argument is a hash ref pointing to arguments for the ajax service. +# The sub uses the global $session and $album variables. + +sub callAjaxService { + my $args = shift; + + # Setup the mock request object + $session->request->method('POST'); + $session->request->setup_body({ args => encode_json($args) }); + + # Call ajax service function and decode reply + return decode_json( $album->www_ajax() ); +} + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +}