package WebGUI::Asset::File::GalleryFile::Photo; =head1 LEGAL ------------------------------------------------------------------- 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 ------------------------------------------------------------------- =cut use strict; use Moose; use WebGUI::Definition::Asset; extends 'WebGUI::Asset::File::GalleryFile'; define assetName => ['assetName', 'Asset_Photo']; define icon => 'photo.gif'; define tableName => 'Photo'; property exifData => ( fieldType => 'text', noFormPost => 1, default => undef, ); property location => ( fieldType => 'text', label => ['editForm location','Asset_Photo'], default => undef, ); use Carp qw( carp croak ); use Image::ExifTool qw( :Public ); use JSON qw/ to_json from_json /; use URI::Escape; use WebGUI::DateTime; use WebGUI::Friends; use WebGUI::Utility; use WebGUI::Storage; =head1 NAME WebGUI::Asset::File::GalleryFile::Photo =head1 DESCRIPTION =head1 SYNOPSIS use WebGUI::Asset::File::GalleryFile::Photo =head1 DIAGNOSTICS =head2 Geometry '...' is invalid. Skipping. makeResolutions will not pass invalid geometries to WebGUI::Storage::resize(). Valid geometries are one of the following forms: ^\d+$ ^\d*x\d*$ These geometries are exactly as understood by ImageMagick. =head1 METHODS These methods are available from this class: =cut #---------------------------------------------------------------------------- =head2 applyConstraints ( options ) Apply the constraints to the original file. Called automatically by C and C. This is a sort of catch-all method for applying things to the file after it's uploaded. This method simply calls other methods to do its work. C is a hash reference of options and is currently not used. =cut sub applyConstraints { my $self = shift; my $options = shift; my $gallery = $self->getGallery; # Update the asset's size and make a thumbnail my $maxImageSize = $gallery->imageViewSize || $self->session->setting->get("maxImageSize"); my $parameters = $self->parameters; my $storage = $self->getStorageLocation; my $file = $self->filename; # Make resolutions before fixing image, so that we can get higher quality # resolutions $self->makeResolutions; # adjust density before size, so that the dimensions won't change $storage->resize( $file, undef, undef, $gallery->imageDensity ); $storage->adjustMaxImageSize($file, $maxImageSize); $self->generateThumbnail; $self->setSize; $self->updateExifDataFromFile; $self->SUPER::applyConstraints( $options ); } #------------------------------------------------------------------- =head2 generateThumbnail ( ) Generates a thumbnail for this image. =cut sub generateThumbnail { my $self = shift; $self->getStorageLocation->generateThumbnail( $self->filename, $self->getGallery->imageThumbnailSize, ); return; } #---------------------------------------------------------------------------- =head2 getDownloadFileUrl ( resolution ) Get the absolute URL to download the requested resolution. Will croak if the resolution doesn't exist. =cut sub getDownloadFileUrl { my $self = shift; my $resolution = shift; croak "Photo->getDownloadFileUrl: resolution must be defined" unless $resolution; croak "Photo->getDownloadFileUrl: resolution doesn't exist for this Photo" unless grep /$resolution/, @{ $self->getResolutions }; return $self->getStorageLocation->getUrl( $resolution . ".jpg" ); } #---------------------------------------------------------------------------- =head2 getEditFormUploadControl Returns the HTML to display the current photo, if it has one, and a file chooser to either upload one, or replace the current one. Subclasses the master class to change i18n labels. =cut sub getEditFormUploadControl { my $self = shift; my $session = $self->session; my $i18n = WebGUI::International->new($session, 'Asset_File'); my $html = ''; if ($self->filename ne "") { $html .= WebGUI::Form::readOnly( $session, { value => '

'.$self->filename.' '.$self->filename.'

' }); } # Control to upload a new file $html .= WebGUI::Form::file( $session, { name => 'newFile', label => $i18n->get('new file'), hoverHelp => $i18n->get('new file description'), }); return $html; } #---------------------------------------------------------------------------- =head2 getExifData ( ) Gets a hash reference of Exif data about this Photo. =cut sub getExifData { my $self = shift; return unless $self->exifData; # Our processing and eliminating of bad / unparsable keys # isn't perfect, so handle errors gracefully my $exif = eval { from_json( $self->exifData ) }; if ( $@ ) { $self->session->errorHandler->warn( "Could not parse JSON data for EXIF in Photo '" . $self->title . "' (" . $self->getId . "): " . $@ ); return; } return $exif; } #---------------------------------------------------------------------------- =head2 getResolutions ( ) Get an array reference of download resolutions that exist for this image. Does not include the web view image or the thumbnail image. =cut sub getResolutions { my $self = shift; my $storage = $self->getStorageLocation; # Return a list not including the web view image. return [ sort { $a <=> $b } grep { $_ ne $self->filename } @{ $storage->getFiles } ]; } #---------------------------------------------------------------------------- =head2 getStorageClass ( ) Get the WebGUI::Storage subclass name for this file. This file uses the Image class. =cut sub getStorageClass { return 'WebGUI::Storage'; } #---------------------------------------------------------------------------- =head2 getTemplateVars ( ) Get a hash reference of template variables shared by all views of this asset. =cut sub getTemplateVars { my $self = shift; my $session = $self->session; my $var = $self->SUPER::getTemplateVars; ### Download resolutions for my $resolution ( @{ $self->getResolutions } ) { my $label = $resolution; $label =~ s/\.[^.]+$//; my $downloadUrl = $self->getStorageLocation->getUrl( $resolution ); push @{ $var->{ resolutions_loop } }, { resolution => $label, url_download => $downloadUrl, }; $var->{ "resolution_" . $resolution } = $downloadUrl; } ### Format exif vars my $exif = $self->getExifData; for my $tag ( keys %$exif ) { # Hash of exif_tag => value $var->{ "exif_" . $tag } = $exif->{$tag}; # Loop of tag => "...", value => "..." push @{ $var->{exifLoop} }, { tag => $tag, value => $exif->{$tag} }; } return $var; } #---------------------------------------------------------------------------- =head2 getThumbnailUrl ( ) Get the URL to the thumbnail for this Photo. =cut sub getThumbnailUrl { my $self = shift; return $self->getStorageLocation->getThumbnailUrl( $self->filename ); } #---------------------------------------------------------------------------- =head2 makeResolutions ( [resolutions] ) Create the specified resolutions for this Photo. If resolutions is not defined, will get the resolutions to make from the Gallery this Photo is contained in. =cut sub makeResolutions { my $self = shift; my $resolutions = shift; my $error; croak "Photo->makeResolutions: resolutions must be an array reference" if $resolutions && ref $resolutions ne "ARRAY"; # Get default if necessary $resolutions ||= $self->getGallery->getImageResolutions; my $storage = $self->getStorageLocation; $self->session->errorHandler->info(" Making resolutions for '" . $self->filename . q{'}); for my $res ( @$resolutions ) { # carp if resolution is bad if ( $res !~ /^\d+$/ && $res !~ /^\d*x\d*/ ) { carp "Geometry '$res' is invalid. Skipping."; next; } my $newFilename = $res . ".jpg"; $storage->copyFile( $self->filename, $newFilename ); $storage->resize( $newFilename, $res, undef, $self->getGallery->imageDensity ); } } #---------------------------------------------------------------------------- =head2 processPropertiesFromFormPost ( ) Process the asset edit form. Make the default title into the file name minus the extention. =cut sub processPropertiesFromFormPost { my $self = shift; my $form = $self->session->form; my $errors = $self->SUPER::processPropertiesFromFormPost || []; # Return if errors return $errors if @$errors; ### Passes all checks # If no title was given, make it the file name if ( !$form->get('title') ) { my $title = $self->filename; $title =~ s/\.[^.]*$//; $title =~ tr/-/ /; # De-mangle the spaces at the expense of the dashes $self->update( { title => $title, menuTitle => $title, } ); # If this is a new Photo, change some other things too if ( $form->get('assetId') eq "new" ) { $self->update( { url => $self->session->url->urlize( join "/", $self->getParent->url, $title ), } ); } } return undef; } #---------------------------------------------------------------------------- =head2 setFile ( filename ) Extend the superclass setFile to automatically generate thumbnails. =cut sub setFile { my $self = shift; $self->SUPER::setFile(@_); $self->generateThumbnail; } #---------------------------------------------------------------------------- =head2 updateExifDataFromFile ( ) Gets the EXIF data from the uploaded image and store it in the database. =cut sub updateExifDataFromFile { my $self = shift; my $storage = $self->getStorageLocation; my $exifTool = Image::ExifTool->new; $exifTool->Options( PrintConv => 1 ); my $info = $exifTool->ImageInfo( $storage->getPath( $self->filename ) ); # Sanitize Exif data by removing keys with references as values for my $key ( keys %$info ) { if ( ref $info->{$key} ) { delete $info->{$key}; } } # Remove other, pointless, possibly harmful keys for my $key ( qw( Directory NativeDigest CameraID CameraType ) ) { delete $info->{ $key }; } $self->update({ exifData => to_json( $info ), }); } #---------------------------------------------------------------------------- =head2 www_download Download the Photo with the specified resolution. If no resolution specified, download the original file. =cut sub www_download { my $self = shift; return $self->session->privilege->insufficient unless $self->canView; my $storage = $self->getStorageLocation; $self->session->http->setMimeType( "image/jpeg" ); $self->session->http->setLastModified( $self->getContentLastModified ); my $resolution = $self->session->form->get("resolution"); if ($resolution) { return $storage->getFileContentsAsScalar( $resolution . ".jpg" ); } else { return $storage->getFileContentsAsScalar( $self->filename ); } } #---------------------------------------------------------------------------- =head2 www_edit ( ) Web facing method which is the default edit page This page is only available to those who can edit this Photo. =cut sub www_edit { my $self = shift; my $session = $self->session; my $form = $self->session->form; return $self->session->privilege->insufficient unless $self->canEdit; return $self->session->privilege->locked unless $self->canEditIfLocked; # Prepare the template variables # Cannot get all template vars since they require a storage location, doesn't work for # creating new assets. #my $var = $self->getTemplateVars; my $var = { url_addArchive => $self->getParent->getUrl('func=addArchive'), url_album => $self->getParent->getUrl('func=album'), }; # Process errors if any if ( $session->stow->get( 'editFormErrors' ) ) { for my $error ( @{ $session->stow->get( 'editFormErrors' ) } ) { push @{ $var->{ errors } }, { error => $error, }; } } if ( $form->get('func') eq "add" ) { $var->{ isNewPhoto } = 1; } # Generate the form if ($form->get("func") eq "add") { $var->{ form_start } = WebGUI::Form::formHeader( $session, { action => $self->getParent->getUrl('func=editSave;assetId=new;class='.__PACKAGE__), }) . WebGUI::Form::hidden( $session, { name => 'ownerUserId', value => $session->user->userId, }) ; } else { $var->{ form_start } = WebGUI::Form::formHeader( $session, { action => $self->getUrl('func=editSave'), }) . WebGUI::Form::hidden( $session, { name => 'ownerUserId', value => $self->ownerUserId, }) ; } $var->{ form_start } .= WebGUI::Form::hidden( $session, { name => "proceed", value => "showConfirmation", }); $var->{ form_end } = WebGUI::Form::formFooter( $session ); $var->{ form_submit } = WebGUI::Form::submit( $session, { name => "submit", value => "Save", }); $var->{ form_title } = WebGUI::Form::Text( $session, { name => "title", value => ( $form->get("title") || $self->title ), }); $self->getGallery; $var->{ form_synopsis } = WebGUI::Form::HTMLArea( $session, { name => "synopsis", value => ( $form->get("synopsis") || $self->synopsis ), richEditId => $self->getGallery->richEditIdFile, }); $var->{ form_photo } = $self->getEditFormUploadControl; $var->{ form_keywords } = WebGUI::Form::Text( $session, { name => "keywords", value => ( $form->get("keywords") || $self->keywords ), }); $var->{ form_location } = WebGUI::Form::Text( $session, { name => "location", value => ( $form->get("location") || $self->location ), }); $var->{ form_friendsOnly } = WebGUI::Form::yesNo( $session, { name => "friendsOnly", value => ( $form->get("friendsOnly") || $self->friendsOnly ), defaultValue => undef, }); return $self->processStyle( $self->processTemplate( $var, $self->getGallery->getTemplateIdEditFile ) ); } #---------------------------------------------------------------------------- =head2 www_showConfirmation ( ) Shows the confirmation message after adding / editing a gallery album. Provides links to view the photo and add more photos. =cut sub www_showConfirmation { my $self = shift; my $i18n = WebGUI::International->new( $self->session, 'Asset_Photo' ); return $self->processStyle( sprintf( $i18n->get('save message'), $self->getUrl, $self->getParent->getUrl('func=add;class='.__PACKAGE__), ) ); } 1;