webgui/lib/WebGUI/Asset/File/GalleryFile/Photo.pm

623 lines
17 KiB
Perl

package WebGUI::Asset::File::GalleryFile::Photo;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 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 base 'WebGUI::Asset::File::GalleryFile';
use Carp qw( carp croak );
use Image::ExifTool qw( :Public );
use JSON qw/ encode_json decode_json /;
use URI::Escape;
use Tie::IxHash;
use WebGUI::DateTime;
use WebGUI::Friends;
use WebGUI::Utility;
use WebGUI::Storage::Image;
=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::Image::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 definition ( session, definition )
Define the properties of the Photo asset.
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my $i18n = __PACKAGE__->i18n($session);
tie my %properties, 'Tie::IxHash', (
exifData => {
defaultValue => undef,
},
location => {
defaultValue => undef,
},
);
push @{$definition}, {
assetName => $i18n->get('assetName'),
autoGenerateForms => 0,
icon => 'photo.gif',
tableName => 'Photo',
className => 'WebGUI::Asset::File::GalleryFile::Photo',
properties => \%properties,
};
return $class->SUPER::definition($session, $definition);
}
#----------------------------------------------------------------------------
=head2 applyConstraints ( options )
Apply the constraints to the original file. Called automatically by C<setFile>
and C<processPropertiesFromFormPost>.
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<options> is a hash reference of options and is currently not used.
=cut
sub applyConstraints {
my $self = shift;
my $gallery = $self->getGallery;
# Update the asset's size and make a thumbnail
my $maxImageSize = $self->getGallery->get("imageViewSize")
|| $self->session->setting->get("maxImageSize");
my $thumbnailSize = $self->getGallery->get("imageThumbnailSize")
|| $self->session->setting->get("thumbnailSize");
my $parameters = $self->get("parameters");
my $storage = $self->getStorageLocation;
my $file = $self->get("filename");
$storage->adjustMaxImageSize($file, $maxImageSize);
$self->generateThumbnail;
$self->setSize;
$self->makeResolutions;
$self->updateExifDataFromFile;
}
#-------------------------------------------------------------------
=head2 generateThumbnail ( )
Generates a thumbnail for this image.
=cut
sub generateThumbnail {
my $self = shift;
$self->getStorageLocation->generateThumbnail(
$self->get("filename"),
$self->getGallery->get("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 render the upload box and link to delete the existing
file, if necessary.
=cut
sub getEditFormUploadControl {
my $self = shift;
my $session = $self->session;
my $i18n = WebGUI::International->new($session, 'Asset_File');
my $html = '';
if ($self->get("filename") ne "") {
$html .= WebGUI::Form::readOnly( $session, {
label => $i18n->get('current file'),
hoverHelp => $i18n->get('current file description'),
value => '<p style="display:inline;vertical-align:middle;"><a href="'.$self->getFileUrl.'"><img src="'.$self->getThumbnailUrl.'" alt="'.$self->get("filename").'" style="border-style:none;vertical-align:middle;" /> '.$self->get("filename").'</a></p>'
});
}
# 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->get('exifData');
# Our processing and eliminating of bad / unparsable keys
# isn't perfect, so handle errors gracefully
my $exif = eval { decode_json( $self->get('exifData') ) };
if ( $@ ) {
$self->session->errorHandler->warn(
"Could not parse JSON data for EXIF in Photo '" . $self->get('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->get("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::Image';
}
#----------------------------------------------------------------------------
=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->get("filename")
);
}
#----------------------------------------------------------------------------
=head2 i18n ( [ session ] )
Get a WebGUI::International object for this class.
Can be called as a class method, in which case a WebGUI::Session object
must be passed in.
NOTE: This method can NOT be inherited, due to a current limitation
in the i18n system. You must ALWAYS call this with C<__PACKAGE__>
=cut
sub i18n {
my $self = shift;
my $session = shift;
return WebGUI::International->new($session, "Asset_Photo");
}
#----------------------------------------------------------------------------
=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->get("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->get("filename"), $newFilename );
$storage->resize( $newFilename, $res );
}
}
#----------------------------------------------------------------------------
=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->get('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->get('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->get('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 => encode_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->get("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->get('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->get("title") ),
});
$self->getGallery;
$var->{ form_synopsis }
= WebGUI::Form::HTMLArea( $session, {
name => "synopsis",
value => ( $form->get("synopsis") || $self->get("synopsis") ),
richEditId => $self->getGallery->get("richEditIdFile"),
});
$var->{ form_photo } = $self->getEditFormUploadControl;
$var->{ form_keywords }
= WebGUI::Form::Text( $session, {
name => "keywords",
value => ( $form->get("keywords") || $self->get("keywords") ),
});
$var->{ form_location }
= WebGUI::Form::Text( $session, {
name => "location",
value => ( $form->get("location") || $self->get("location") ),
});
$var->{ form_friendsOnly }
= WebGUI::Form::yesNo( $session, {
name => "friendsOnly",
value => ( $form->get("friendsOnly") || $self->get("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 = __PACKAGE__->i18n( $self->session );
return $self->processStyle(
sprintf( $i18n->get('save message'),
$self->getUrl,
$self->getParent->getUrl('func=add;class='.__PACKAGE__),
)
);
}
1;