diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index e513e7f49..67e700b9e 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -21,6 +21,8 @@ - fixed #11563: Syndicated Content - descriptionFirstParagraph cuts off - fixed #11538: User invite mail: whitespace in message lost - fixed #11549: Shortcut Asset cannot override Page Layout + - added #11502: Gallery: Allow specification of location when uploading ZIP archives + - added #11517: Gallery: Sorting of files uploaded in zip archives 7.9.4 - We're shipping underscore.js now for its suite of extremely handy utility diff --git a/docs/upgrades/packages-7.9.5/root_import_gallery-templates_default-gallery-add-archive.wgpkg b/docs/upgrades/packages-7.9.5/root_import_gallery-templates_default-gallery-add-archive.wgpkg new file mode 100644 index 000000000..cf6bb0c81 Binary files /dev/null and b/docs/upgrades/packages-7.9.5/root_import_gallery-templates_default-gallery-add-archive.wgpkg differ diff --git a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm index 9ef7f408a..9d600c6f7 100644 --- a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm +++ b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm @@ -11,20 +11,25 @@ package WebGUI::Asset::Wobject::GalleryAlbum; #------------------------------------------------------------------- use strict; -use Class::C3; use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset::Wobject); + + +use Class::C3; use Carp qw( croak ); use File::Find; use File::Spec; use File::Temp qw{ tempdir }; +use Image::ExifTool qw( :Public ); use JSON; use Tie::IxHash; + use WebGUI::International; use WebGUI::Utility; use WebGUI::HTML; use WebGUI::ProgressBar; use WebGUI::Storage; +# Do not move this instruction! use Archive::Any; =head1 NAME @@ -108,6 +113,12 @@ The name of the file archive to import. A base set of properties to add to each file in the archive. +=head3 sortBy + +Order in which files should be added to the gallery album. Legal values are +'name', 'date' and 'fileOrder'. If missing or invalid, files will be added as +ordered in the archive (default; corresponding to 'fileOrder'). + =head3 $outputSub A callback to use for outputting data, most likely to a progress bar. It expects the @@ -120,6 +131,7 @@ sub addArchive { my $self = shift; my $filename = shift; my $properties = shift; + my $sortBy = shift; my $outputSub = shift || sub {}; my $gallery = $self->getParent; my $session = $self->session; @@ -139,6 +151,33 @@ sub addArchive { find( { wanted => $wanted, }, $tempdirName ); + + # Sort files by date + if ($sortBy eq 'date') { + # Hash for storing last modified timestamps + my %mtimes; + + my $exifTool = Image::ExifTool->new; + # Make ExifTool return epoch timestamps + $exifTool->Options('DateFormat', '%s'); + + # Iterate through all files + foreach my $file (@files) { + # Extract exif data from image + $exifTool->ExtractInfo( $file ); + # Get last modified timestamp from exif data + $mtimes{$file} = $exifTool->GetValue('ModifyDate'); + # Use last modified date of file as fallback + $mtimes{$file} = (stat($file))[9] unless $mtimes{$file}; + } + + # Sort files based on last modified timestamps + @files = sort { $mtimes{$a} <=> $mtimes{$b} } @files; + } + # Sort files by name + elsif ($sortBy eq 'name') { + @files = sort @files; + } for my $filePath (@files) { my ($volume, $directory, $filename) = File::Spec->splitpath( $filePath ); @@ -988,12 +1027,29 @@ sub www_addArchive { name => "keywords", value => ( $form->get("keywords") ), }); + + $var->{ form_location } + = WebGUI::Form::Text( $session, { + name => "location", + value => ( $form->get("location") ), + }); $var->{ form_friendsOnly } = WebGUI::Form::yesNo( $session, { name => "friendsOnly", value => ( $form->get("friendsOnly") ), }); + + $var->{ form_sortBy } + = WebGUI::Form::RadioList( $session, { + name => "sortBy", + value => [ "name" ], + options => { + name => $i18n->get("addArchive sortBy name", 'Asset_GalleryAlbum'), + date => $i18n->get("addArchive sortBy date", 'Asset_GalleryAlbum'), + fileOrder => $i18n->get("addArchive sortBy fileOrder", 'Asset_GalleryAlbum'), + }, + }); return $self->processStyle( $self->processTemplate($var, $self->getParent->get("templateIdAddArchive")) @@ -1011,26 +1067,39 @@ Process the form for adding an archive. sub www_addArchiveSave { my $self = shift; + # Return error message if the user viewing does not have permission to add files return $self->session->privilege->insufficient unless $self->canAddFile; my $session = $self->session; my $form = $self->session->form; my $i18n = WebGUI::International->new( $session, 'Asset_GalleryAlbum' ); my $pb = WebGUI::ProgressBar->new($session); + + # Retrieve properties and sort order from form variables my $properties = { keywords => $form->get("keywords"), + location => $form->get("location"), friendsOnly => $form->get("friendsOnly"), }; + my $sortBy = $form->get("sortBy"); + # Create progress bar to keep the connection alive $pb->start($i18n->get('Uploading archive'), $session->url->extras('adminConsole/assets.gif')); + + # Retrieve storage containing the uploaded archive my $storageId = $form->get("archive", "File"); my $storage = WebGUI::Storage->get( $session, $storageId ); if (!$storage) { return $pb->finish($self->getUrl('func=addArchive;error='.$i18n->get('addArchive error too big'))); } - my $filename = $storage->getPath( $storage->getFiles->[0] ); - eval { $self->addArchive( $filename, $properties, sub{ $pb->update(sprintf $i18n->get(shift), @_); }); }; + # Get the full path to the archive + my $filename = $storage->getPath( $storage->getFiles->[0] ); + + # Try to add files in archive as photos + eval { $self->addArchive( $filename, $properties, $sortBy, sub{ $pb->update(sprintf $i18n->get(shift), @_); }); }; + + # Delete storage containing archive $storage->delete; if ( my $error = $@ ) { return $pb->finish($self->getUrl('func=addArchive;error='.sprintf $i18n->get('addArchive error generic'), $error )); diff --git a/lib/WebGUI/Help/Asset_GalleryAlbum.pm b/lib/WebGUI/Help/Asset_GalleryAlbum.pm index b8a042a20..e334118cc 100644 --- a/lib/WebGUI/Help/Asset_GalleryAlbum.pm +++ b/lib/WebGUI/Help/Asset_GalleryAlbum.pm @@ -250,10 +250,18 @@ our $HELP = { name => 'form_keywords', description => 'helpvar form_keywords', }, + { + name => 'form_location', + description => 'helpvar form_location', + }, { name => 'form_friendsOnly', description => 'helpvar form_friendsOnly', }, + { + name => 'form_sortBy', + description => 'helpvar form_sortBy', + }, ], }, diff --git a/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm b/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm index fd784e874..895a39bb7 100644 --- a/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm +++ b/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm @@ -277,11 +277,21 @@ our $I18N = { message => 'The keywords for the files being uploaded.', lastUpdated => 0, }, + + 'helpvar form_location' => { + message => 'The location for the files being uploaded.', + lastUpdated => 0, + }, 'helpvar form_friendsOnly' => { message => 'Should the file be friends only?', lastUpdated => 0, }, + + 'helpvar form_sortBy' => { + message => 'Property according to which photos should be sorted.', + lastUpdated => 0, + }, 'helpvar url_yes' => { message => 'Confirm the delete of this Album.', @@ -451,13 +461,43 @@ our $I18N = { lastUpdated => 0, context => 'Label for the "keywords" field of the Add Archive page', }, - + + 'addArchive location' => { + message => 'Location', + lastUpdated => 0, + context => 'Label for the "location" field of the Add Archive page', + }, + 'addArchive friendsOnly' => { message => 'Friends Only', lastUpdated => 0, - context => 'Label for the "friends only" field of the Add Archive page', + context => 'Label for the "friendsOnly" field of the Add Archive page', + }, + + 'addArchive sortBy' => { + message => 'Sort By', + lastUpdated => 0, + context => 'Label for the "sortBy" field of the Add Archive page', }, + 'addArchive sortBy name' => { + message => 'Name', + lastUpdated => 0, + context => 'Label for the "Name" radio button', + }, + + 'addArchive sortBy date' => { + message => 'Date', + lastUpdated => 0, + context => 'Label for the "Date" radio button', + }, + + 'addArchive sortBy fileOrder' => { + message => 'File Order', + lastUpdated => 0, + context => 'Label for the "File Order" radio button', + }, + 'template addArchive title' => { message => "Add Zip Archive", lastUpdated => 0, diff --git a/t/Asset/Wobject/GalleryAlbum/addArchive.t b/t/Asset/Wobject/GalleryAlbum/addArchive.t index 9673d90c8..afa56d598 100644 --- a/t/Asset/Wobject/GalleryAlbum/addArchive.t +++ b/t/Asset/Wobject/GalleryAlbum/addArchive.t @@ -25,7 +25,10 @@ use Test::Deep; my $session = WebGUI::Test->session; my $node = WebGUI::Asset->getImportNode($session); my $versionTag = WebGUI::VersionTag->getWorking($session); -$versionTag->set({name=>"Album Test"}); + +$versionTag->set({name=>"Add Archive to Album Test"}); +addToCleanup($versionTag); + my $gallery = $node->addChild({ className => "WebGUI::Asset::Wobject::Gallery", @@ -35,6 +38,7 @@ my $gallery groupIdEdit => 3, # Admins ownerUserId => 3, # Admin }); + my $album = $gallery->addChild({ className => "WebGUI::Asset::Wobject::GalleryAlbum", @@ -46,48 +50,127 @@ my $album skipAutoCommitWorkflows => 1, }); -$album->addArchive( WebGUI::Test->getTestCollateralPath('elephant_images.zip') ); +# Properties applied to every photo in the archive +my $properties = { + keywords => "something", + location => "somewhere", + friendsOnly => "1", +}; + $versionTag->commit; + #---------------------------------------------------------------------------- # Tests -plan tests => 5; +plan tests => 11; + #---------------------------------------------------------------------------- # Test the addArchive sub # elephant_images.zip contains three jpgs: Aana1.jpg, Aana2.jpg, Aana3.jpg + +$versionTag = WebGUI::VersionTag->getWorking($session); +$album->addArchive( WebGUI::Test->getTestCollateralPath('elephant_images.zip'), $properties ); + my $images = $album->getLineage(['descendants'], { returnObjects => 1 }); is( scalar @$images, 3, "addArchive() adds one asset per image" ); -cmp_deeply( +cmp_bag( [ map { $_->get("filename") } @$images ], - bag( "Aana1.jpg", "Aana2.jpg", "Aana3.jpg" ), + [ "Aana1.jpg", "Aana2.jpg", "Aana3.jpg" ], + "Names of files attached to Photo assets match filenames in archive" ); -cmp_deeply( +cmp_bag( [ map { $_->get("title") } @$images ], - bag( "Aana1", "Aana2", "Aana3" ), + [ "Aana1", "Aana2", "Aana3" ], + "Titles of Photo assets match filenames in archive excluding extensions" ); -cmp_deeply( +cmp_bag( [ map { $_->get("menuTitle") } @$images ], - bag( "Aana1", "Aana2", "Aana3" ), + [ "Aana1", "Aana2", "Aana3" ], + "Menu titles of Photo assets match filenames in archive excluding extensions" ); -cmp_deeply( +cmp_bag( [ map { $_->get("url") } @$images ], - bag( + [ $session->url->urlize( $album->getUrl . "/Aana1" ), $session->url->urlize( $album->getUrl . "/Aana2" ), $session->url->urlize( $album->getUrl . "/Aana3" ), - ), + ], + "URLs of Photo assets match filenames in archive excluding extensions" ); +cmp_bag( + [ map { $_->get("keywords") } @$images ], + [ "something", "something", "something" ], + "Keywords of Photo assets match keywords in properties" +); + +cmp_bag( + [ map { $_->get("location") } @$images ], + [ "somewhere", "somewhere", "somewhere" ], + "Location of Photo assets match keywords in properties" +); + +cmp_bag( + [ map { $_->get("friendsOnly") } @$images ], + [ "1", "1", "1" ], + "Photo assets are viewable by friends only" +); + +# Empty gallery album +$versionTag->rollback; + +#---------------------------------------------------------------------------- +# Test the sorting option of addArchive sub +# gallery_archive_sorting_test.zip contains four jpgs: photo1.jpg, photo2.jpg, photo3.jpg and photo4.jpg +# The following test covers sorting by date, name and file order (default). + +$versionTag = WebGUI::VersionTag->getWorking($session); +# Add photos sorted by file order (default) +$album->addArchive( WebGUI::Test->getTestCollateralPath('gallery_archive_sorting_test.zip'), $properties, 'fileOrder' ); +# Get all children +my $images = $album->getLineage(['descendants'], { returnObjects => 1 }); +# Check order +cmp_deeply( + [ map { $_->get("filename") } @$images ], + [ "photo1.jpg", "photo2.jpg", "photo4.jpg", "photo3.jpg" ], + "Photos sorted by file order" +); +# Empty gallery album +$versionTag->rollback; + +$versionTag = WebGUI::VersionTag->getWorking($session); +# Add photos sorted by date +$album->addArchive( WebGUI::Test->getTestCollateralPath('gallery_archive_sorting_test.zip'), $properties, 'date' ); +# Get all children +my $images = $album->getLineage(['descendants'], { returnObjects => 1 }); +# Check order +cmp_deeply( + [ map { $_->get("filename") } @$images ], + [ "photo4.jpg", "photo1.jpg", "photo3.jpg", "photo2.jpg" ], + "Photos sorted by date" +); +# Empty gallery album +$versionTag->rollback; + +$versionTag = WebGUI::VersionTag->getWorking($session); +# Add photos sorted by name +$album->addArchive( WebGUI::Test->getTestCollateralPath('gallery_archive_sorting_test.zip'), $properties, 'name' ); +# Get all children +my $images = $album->getLineage(['descendants'], { returnObjects => 1 }); +# Check order +cmp_deeply( + [ map { $_->get("filename") } @$images ], + [ "photo1.jpg", "photo2.jpg", "photo3.jpg", "photo4.jpg" ], + "Photos sorted by name" +); +# Empty gallery album +$versionTag->rollback; + + #---------------------------------------------------------------------------- # Test the www_addArchive page - -#---------------------------------------------------------------------------- -# Cleanup -END { - $versionTag->rollback; -} diff --git a/t/supporting_collateral/gallery_archive_sorting_test.zip b/t/supporting_collateral/gallery_archive_sorting_test.zip new file mode 100644 index 000000000..1ccc6deff Binary files /dev/null and b/t/supporting_collateral/gallery_archive_sorting_test.zip differ