diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 4aa41df86..b70054b86 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -14,6 +14,7 @@ - fixed #11589: Syndicated Content: Return raw text for sentence and word template variables - fixed #11573: user has no way of knowing what they are currently using - fixed #11603: Shelf, template variables for sub shelves + - added #11504: Allow search by location in the gallery 7.9.5 - Asset->www_copy now has a progress bar diff --git a/docs/upgrades/packages-7.9.6/root_import_gallery-templates_default-gallery-search.wgpkg b/docs/upgrades/packages-7.9.6/root_import_gallery-templates_default-gallery-search.wgpkg new file mode 100644 index 000000000..0fd040d1f Binary files /dev/null and b/docs/upgrades/packages-7.9.6/root_import_gallery-templates_default-gallery-search.wgpkg differ diff --git a/lib/WebGUI/Asset/File.pm b/lib/WebGUI/Asset/File.pm index d1eca73bd..563d75180 100644 --- a/lib/WebGUI/Asset/File.pm +++ b/lib/WebGUI/Asset/File.pm @@ -379,6 +379,7 @@ sub indexContent { my $self = shift; my $indexer = $self->SUPER::indexContent; $indexer->addFile($self->getStorageLocation->getPath($self->get("filename"))); + return $indexer; } diff --git a/lib/WebGUI/Asset/File/GalleryFile/Photo.pm b/lib/WebGUI/Asset/File/GalleryFile/Photo.pm index 7f1af4da8..9bb51f134 100644 --- a/lib/WebGUI/Asset/File/GalleryFile/Photo.pm +++ b/lib/WebGUI/Asset/File/GalleryFile/Photo.pm @@ -372,6 +372,22 @@ sub getThumbnailUrl { ); } +#------------------------------------------------------------------- + +=head2 indexContent ( ) + +Indexing the content of the Photo. See WebGUI::Asset::indexContent() for +additonal details. + +=cut + +sub indexContent { + my $self = shift; + my $indexer = $self->SUPER::indexContent; + $indexer->addKeywords($self->get("location")); + return $indexer; +} + #---------------------------------------------------------------------------- =head2 makeResolutions ( [resolutions] ) diff --git a/lib/WebGUI/Asset/Wobject/Gallery.pm b/lib/WebGUI/Asset/Wobject/Gallery.pm index 1374dff07..3a3b059c0 100644 --- a/lib/WebGUI/Asset/Wobject/Gallery.pm +++ b/lib/WebGUI/Asset/Wobject/Gallery.pm @@ -13,12 +13,15 @@ package WebGUI::Asset::Wobject::Gallery; use strict; use Class::C3; use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset::Wobject); + use JSON; use Tie::IxHash; +use WebGUI::HTML; use WebGUI::International; +use WebGUI::Search; use WebGUI::Utility; use XML::Simple; -use WebGUI::HTML; + =head1 NAME @@ -29,6 +32,7 @@ use WebGUI::HTML; =head1 DIAGNOSTICS =head1 METHODS +=cut #------------------------------------------------------------------- @@ -391,7 +395,13 @@ sub appendTemplateVarsSearchForm { name => "keywords", value => $form->get("keywords"), }); - + + $var->{ searchForm_location } + = WebGUI::Form::text( $session, { + name => "location", + value => $form->get("location"), + }); + # Search classes tie my %searchClassOptions, 'Tie::IxHash', ( 'WebGUI::Asset::File::GalleryFile::Photo' => $i18n->get("search class photo"), @@ -766,6 +776,7 @@ Other keys are valid, see C for details. sub getSearchPaginator { my $self = shift; my $rules = shift; + my $session = $self->session; $rules->{ lineage } = [ $self->get("lineage") ]; @@ -1344,45 +1355,61 @@ sub www_listAlbumsService { return JSON->new->pretty->encode($document); } + #---------------------------------------------------------------------------- -=head2 www_search ( ) +=head2 search ( ) -Search through the GalleryAlbums and files in this gallery. Show the form to -search and display the results if necessary. +Helper method for C containing all the search logic. Executes a +search depending on search form parameters. Returns undef if no search was +executed. Otherwise an array is returned containing the following elements: + +=head3 paginator + +A paginator object containing the search results. + +=head3 keywords + +Search keywords assembled from search form fields. =cut -sub www_search { +sub search { my $self = shift; my $session = $self->session; my $form = $session->form; my $db = $session->db; + my $columns; - my $var = $self->getTemplateVars; - # NOTE: Search form is added as part of getTemplateVars() - - # Get search results, if necessary. + # Check whether we have to do a search my $doSearch = ( - $form->get( 'basicSearch' ) || $form->get( 'keywords' ) - || $form->get( 'title' ) || $form->get( 'description' ) - || $form->get( 'userId' ) || $form->get( 'className' ) - || $form->get( 'creationDate_after' ) || $form->get( 'creationDate_before' ) + $form->get( 'basicSearch' ) || $form->get( 'keywords' ) + || $form->get( 'location' ) || $form->get( 'title' ) + || $form->get( 'description' ) || $form->get( 'userId' ) + || $form->get( 'className' ) || $form->get( 'creationDate_after' ) + || $form->get( 'creationDate_before' ) ); - if ( $doSearch ) { - # Keywords to search on - # Do not add a space to the + if ( $doSearch ) { + # Keywords to search on. my $keywords; - FORMVAR: foreach my $formVar (qw/ basicSearch keywords title description /) { + FORMVAR: foreach my $formVar (qw/ basicSearch keywords location title description /) { my $var = $form->get($formVar); next FORMVAR unless $var; $keywords = join ' ', $keywords, $var; } + # Remove leading whitespace + $keywords =~ s/^\s+//; # Build a where clause from the advanced options # Lineage search can capture gallery + # Note that adding criteria to the where clause alone will not work. If + # you want to cover additional properties you need to make sure that + # - the property is added to $keywords above + # - the property is included in index keywords by overriding the indexContent method of respective classes (usually Photo or GalleryFile) + # - the respective table is joined in (usually via joinClass parameter of getSearchPaginator) + # - the column containing the property is included in the query (usually via column parameter of getSearchPaginator) my $where = q{assetIndex.assetId <> '} . $self->getId . q{'}; if ( $form->get("title") ) { $where .= q{ AND assetData.title LIKE } @@ -1394,11 +1421,18 @@ sub www_search { . $db->quote( '%' . $form->get("description") . '%' ) ; } + if ( $form->get("location") && ( $form->get("className") eq 'WebGUI::Asset::File::GalleryFile::Photo' + || $form->get("className") eq '' ) ) { + $where .= q{ AND Photo.location LIKE } + . $db->quote( '%' . $form->get("location") . '%' ) + ; + push (@{$columns}, 'Photo.location'); + } if ( $form->get("userId") ) { $where .= q{ AND assetData.ownerUserId = } . $db->quote( $form->get("userId") ) ; - } + } my $oneYearAgo = WebGUI::DateTime->new( $session, time )->add( years => -1 )->epoch; my $dateAfter = $form->get("creationDate_after", "dateTime", $oneYearAgo); @@ -1412,45 +1446,76 @@ sub www_search { } # Classes - my $joinClass = [ + my $classes = [ 'WebGUI::Asset::Wobject::GalleryAlbum', 'WebGUI::Asset::File::GalleryFile::Photo', ]; if ( $form->get("className") ) { - $joinClass = [ $form->get('className') ]; + $classes = [ $form->get('className') ]; } - $where .= q{ AND assetIndex.className IN ( } . $db->quoteAndJoin( $joinClass ) . q{ ) }; - # Build a URL for the pagination my $url = $self->getUrl( 'func=search;' . 'basicSearch=' . $form->get('basicSearch') . ';' . 'keywords=' . $form->get('keywords') . ';' + . 'location=' . $form->get('location') . ';' . 'title=' . $form->get('title') . ';' . 'description=' . $form->get('description') . ';' . 'creationDate_after=' . $dateAfter . ';' . 'creationDate_before=' . $dateBefore . ';' . 'userId=' . $form->get("userId") . ';' ); - for my $class ( @$joinClass ) { + for my $class ( @$classes ) { $url .= 'className=' . $class . ';'; } - - my $p + + my $paginator = $self->getSearchPaginator( { url => $url, keywords => $keywords, where => $where, - joinClass => $joinClass, + classes => $classes, + joinClass => $classes, + columns => $columns, creationDate => $creationDate, - } ); + } ); + return ( $paginator, $keywords ); + } + + # Return undef to indicate that no search was executed + return undef; +} + + +#---------------------------------------------------------------------------- + +=head2 www_search ( ) + +Search through the GalleryAlbums and files in this gallery. Show the form to +search and display the results if necessary. + +=cut + +sub www_search { + my $self = shift; + my $session = $self->session; + + my $var = $self->getTemplateVars; + # NOTE: Search form is added as part of getTemplateVars() + + # Execute search and retrieve search result paginator and keywords + my ( $paginator, $keywords ) = $self->search; + + if( $paginator ) { + # Provide search keywords as template variable $var->{ keywords } = $keywords; - $p->appendTemplateVars( $var ); - for my $result ( @{ $p->getPageData } ) { + # Add search results + $paginator->appendTemplateVars( $var ); + for my $result ( @{ $paginator->getPageData } ) { my $asset = WebGUI::Asset->newByDynamicClass( $session, $result->{assetId} ); push @{ $var->{search_results} }, { %{ $asset->getTemplateVars }, diff --git a/lib/WebGUI/Help/Asset_Gallery.pm b/lib/WebGUI/Help/Asset_Gallery.pm index 4ef482f1e..05287dcab 100644 --- a/lib/WebGUI/Help/Asset_Gallery.pm +++ b/lib/WebGUI/Help/Asset_Gallery.pm @@ -29,6 +29,10 @@ our $HELP = { name => 'searchForm_keywords', description => 'helpvar searchForm_keywords', }, + { + name => 'searchForm_location', + description => 'helpvar searchForm_location', + }, { name => 'searchForm_className', description => 'helpvar searchForm_className', diff --git a/lib/WebGUI/i18n/English/Asset_Gallery.pm b/lib/WebGUI/i18n/English/Asset_Gallery.pm index e9ea7b5ab..af78f44ce 100644 --- a/lib/WebGUI/i18n/English/Asset_Gallery.pm +++ b/lib/WebGUI/i18n/English/Asset_Gallery.pm @@ -419,6 +419,11 @@ our $I18N = { message => 'Search by keyword.', lastUpdated => 0, }, + + 'helpvar searchForm_location' => { + message => 'Search by location.', + lastUpdated => 0, + }, 'helpvar searchForm_className' => { message => 'Search by class name.', @@ -616,6 +621,12 @@ our $I18N = { lastUpdated => 0, context => "Label for the 'Keywords' input for the search form. 'Tags' is used because Keywords may be confused with the generic, all-inclusive search box.", }, + + 'template search field location' => { + message => "Location", + lastUpdated => 0, + context => "Label for the 'Location' input for the search form.", + }, 'template search field className' => { message => "Search Type", diff --git a/t/Asset/Wobject/Gallery/search.t b/t/Asset/Wobject/Gallery/search.t new file mode 100644 index 000000000..b17b0d7c1 --- /dev/null +++ b/t/Asset/Wobject/Gallery/search.t @@ -0,0 +1,318 @@ +#------------------------------------------------------------------- +# 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"; + +# Test of the Gallery basic and advanced search. In non-live tests, the Gallery +# search is accessed via the "search" method. Form parameters are passed in via +# the pseudo request object of the test session. + +use Test::More; +use Test::Deep; + +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Asset::Wobject::Gallery; +use WebGUI::Asset::Wobject::GalleryAlbum; +use WebGUI::Asset::File::GalleryFile::Photo; +use WebGUI::DateTime; +use WebGUI::Session; + + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); + +$versionTag->set( { name=>"Gallery Search Test" } ); +addToCleanup( $versionTag ); + +# Create gallery and a single album +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); + +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + title => "album", + synopsis => "synopsis2", + keywords => "group2", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); +my $albumId = $album->getId; + +# Populate album with different photos +my $photo1 + = $album->addChild({ + className => "WebGUI::Asset::File::GalleryFile::Photo", + title => "photo1", + synopsis => "synopsis1", + keywords => "group1", + location => "Heidelberg", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); +my $id1 = $photo1->getId; + +my $photo2 + = $album->addChild({ + className => "WebGUI::Asset::File::GalleryFile::Photo", + title => "photo2", + synopsis => "synopsis2", + keywords => "group1", + location => "Mannheim", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); +my $id2 = $photo2->getId; + +my $photo3 + = $album->addChild({ + className => "WebGUI::Asset::File::GalleryFile::Photo", + title => "photo3", + synopsis => "synopsis1", + keywords => "group2", + location => "Mannheim", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); +my $id3 = $photo3->getId; + +# Commit all changes +$versionTag->commit; + +# Make gallery default asset +$session->asset( $gallery ); + +# Define some general variables +my $result; + + +#---------------------------------------------------------------------------- +# Tests +plan tests => 32; + +#---------------------------------------------------------------------------- +# Basic search + +note( "Basic gallery search" ); + +# Search by title + +my $hits = search( { basicSearch => "album" } ); +# Basic search will behave differently from advanced search. The album and all +# photos of the album will be returned, since the name of the album is added to +# index keywords of photos. +cmp_bag( $hits, [ $albumId, $id1, $id2, $id3 ], "Search for album entitled 'album' (basic search)" ); + +my $hits = search( { basicSearch => "photo1" } ); +cmp_bag( $hits, [ $id1 ], "Search for photo entitled 'photo1' (basic search)" ); + +my $hits = search( { title => "photo4" } ); +cmp_bag( $hits, [ ], "Search for non-existing photo entitled 'photo4' (basic search)" ); + +# Search by keywords + +my $hits = search( { basicSearch => "group1" } ); +cmp_bag( $hits, [ $id1, $id2 ], "Search for albums/photos with keywords 'group1' (basic search)" ); + +my $hits = search( { basicSearch => "group2" } ); +cmp_bag( $hits, [ $albumId, $id3 ], "Search for albums/photos with keywords 'group2' (basic search)" ); + +# Search by description + +my $hits = search( { basicSearch => "synopsis1" } ); +cmp_bag( $hits, [ $id1, $id3 ], "Search for albums/photos with synopsis 'synopsis1' (basic search)" ); + +my $hits = search( { basicSearch => "synopsis2" } ); +cmp_bag( $hits, [ $albumId, $id2 ], "Search for albums/photos with synopsis 'synopsis2' (basic search)" ); + + +# Warning: Tried to use 'here' and 'there' as locations for the following test. +# For unknown reasons the test failed. It seems that these and possibly other +# keywords are either filtered out by MySQL and/or are reserved words. Needs to +# be checked!!! + +my $hits = search( { basicSearch => "Mannheim" } ); +cmp_bag( $hits, [ $id2, $id3 ], "Search for photos taken at location 'Mannheim' (basic search)" ); + +my $hits = search( { basicSearch => "Heidelberg" } ); +cmp_bag( $hits, [ $id1 ], "Search for photos taken at location 'Heidelberg' (basic search)" ); + +# Search by multiple criteria + +my $hits = search({ basicSearch => "group1 synopsis1" }); +cmp_bag( $hits, [ $id1 ], "Search for photo with keywords 'group1' and synopsis 'synopsis1' (basic search)" ); + +my $hits = search({ basicSearch => "group2 Mannheim" }); +cmp_bag( $hits, [ $id3 ], "Search for photo with keywords 'group2' and location 'Mannheim' (basic search)" ); + +my $hits = search({ basicSearch => "synopsis1 Mannheim" }); +cmp_bag( $hits, [ $id3 ], "Search for photo with synopsis 'synopsis1' and location 'Mannheim' (basic search)" ); + + +#---------------------------------------------------------------------------- +# Advanced search + +note( "Advanced gallery search" ); + +my $hits = search( { } ); +cmp_bag( $hits, [ ], "Empty search (advanced search)" ); + +# Search by class + +my $hits = search( { className => "WebGUI::Asset::File::GalleryFile::Photo" } ); +cmp_bag( $hits, [ $id1, $id2, $id3 ], "Search for all photos (advanced search)" ); + +my $hits = search( { className => "WebGUI::Asset::Wobject::GalleryAlbum" } ); +cmp_bag( $hits, [ $albumId ], "Search for all albums (advanced search)" ); + +# Search by date + +my $oneYearAgo = WebGUI::DateTime->new( $session, time )->add( years => -1 )->epoch; +my $hits + = search({ + creationDate_after => $oneYearAgo, + creationDate_before => time(), + }); +cmp_bag( $hits, [ $albumId, $id1, $id2, $id3 ], "Search by date, all included (advanced search)" ); + +my $hits + = search({ + creationDate_after => time() + 1, + creationDate_before => time() + 1, + }); +cmp_bag( $hits, [ ], "Search by date, all excluded (advanced search)" ); + +# Search by title + +my $hits = search( { title => "album" } ); +cmp_bag( $hits, [ $albumId ], "Search for album entitled 'album' (advanced search)" ); + +my $hits = search( { title => "photo1" } ); +cmp_bag( $hits, [ $id1 ], "Search for photo entitled 'photo1' (advanced search)" ); + +my $hits = search( { title => "photo4" } ); +cmp_bag( $hits, [ ], "Search for non-existing photo entitled 'photo4' (advanced search)" ); + +# Search by keywords + +my $hits = search( { keywords => "group1" } ); +cmp_bag( $hits, [ $id1, $id2 ], "Search for albums/photos with keywords 'group1' (advanced search)" ); + +my $hits = search( { keywords => "group2" } ); +cmp_bag( $hits, [ $albumId, $id3 ], "Search for albums/photos with keywords 'group2' (advanced search)" ); + +my $hits = search( { keywords => "group3" } ); +cmp_bag( $hits, [ ], "Search for non-existing albums/photos with keywords 'group3' (advanced search)" ); + +# Search by description + +my $hits = search( { description => "synopsis1" } ); +cmp_bag( $hits, [ $id1, $id3 ], "Search for albums/photos with synopsis 'synopsis1' (advanced search)" ); + +my $hits = search( { description => "synopsis2" } ); +cmp_bag( $hits, [ $albumId, $id2 ], "Search for albums/photos with synopsis 'synopsis2' (advanced search)" ); + +my $hits = search( { description => "synopsis3" } ); +cmp_bag( $hits, [ ], "Search for non-existing albums/photos with synopsis 'synopsis3' (advanced search)" ); + +# Search by location +# Warning: Tried to use 'here' and 'there' as locations for the following test. +# For unknown reasons the test failed. It seems that these and possibly other +# keywords are either filtered out by MySQL and/or are reserved words. Needs to +# be checked!!! + +my $hits = search( { location => "Mannheim" } ); +cmp_bag( $hits, [ $id2, $id3 ], "Search for photos taken at location 'Mannheim' (advanced search)" ); + +my $hits = search( { location => "Heidelberg" } ); +cmp_bag( $hits, [ $id1 ], "Search for photos taken at location 'Heidelberg' (advanced search)" ); + +my $hits = search( { location => "Frankfurt" } ); +cmp_bag( $hits, [ ], "Search for non-existing photos taken at location 'Frankfurt' (advanced search)" ); + +# Search by multiple criteria + +my $hits + = search({ + keywords => "group1", + description => "synopsis1", + }); +cmp_bag( $hits, [ $id1 ], "Search for photo with keywords 'group1' and synopsis 'synopsis1' (advanced search)" ); + +my $hits + = search({ + keywords => "group2", + location => "Mannheim", + }); +cmp_bag( $hits, [ $id3 ], "Search for photo with keywords 'group2' and location 'Mannheim' (advanced search)" ); + +my $hits + = search({ + description => "synopsis1", + location => "Mannheim", + }); +cmp_bag( $hits, [ $id3 ], "Search for photo with synopsis 'synopsis1' and location 'Mannheim' (advanced search)" ); + + +#---------------------------------------------------------------------------- +# search( formParams ) +# Execute a search for photos and albums in the test gallery. +# +# Accepts a hash ref as single parameter. All key/value pairs in the hash are +# added as form parameters to the pseudo request object before the search is +# executed. See the Gallery search method for valid form fields. +# +# Returns a reference pointing an array containg the asset Ids of all hits. + +sub search { + my $formParams = shift; + my $hits = []; + + # Setup the mock request object + $session->request->method( 'GET' ); + $session->request->setup_param( $formParams ); + + # Call gallery search function + my ( $paginator, $keywords ) = $gallery->search; + # Return ref to empty array if search could not be executed + return $hits unless $paginator; + + # Extract asset Ids from search results and compile array. + for ( my $i = 1; $i <= $paginator->getNumberOfPages; $i++ ) { + for my $result ( @{ $paginator->getPageData( $i ) } ) { + push @{ $hits }, $result->{ assetId }; + } + } + return $hits; +}