]> git.donarmstrong.com Git - deb_pkgs/libapache-gallery-perl.git/blob - lib/Apache/Gallery.pm
import Apache2::Const::REDIRECT
[deb_pkgs/libapache-gallery-perl.git] / lib / Apache / Gallery.pm
1 package Apache::Gallery;
2
3 # $Author: mil $ $Rev: 335 $
4 # $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 2011) $
5
6 use strict;
7
8 use vars qw($VERSION);
9
10 $VERSION = "1.0.2";
11
12 BEGIN {
13
14         if (exists($ENV{MOD_PERL_API_VERSION})
15                 and ($ENV{MOD_PERL_API_VERSION}==2)) {
16                 require mod_perl2;
17                 if ($mod_perl::VERSION >= 1.99 && $mod_perl::VERSION < 2.0) {
18                         die "mod_perl 2.0.0 or later is now required";
19                 }
20                 require Apache2::ServerRec;
21                 require Apache2::RequestRec;
22                 require Apache2::Log;
23                 require APR::Table;
24                 require Apache2::RequestIO;
25                 require Apache2::SubRequest;
26                 require Apache2::Const;
27         
28                 Apache2::Const->import(-compile => 'OK','DECLINED','FORBIDDEN',
29                                'NOT_FOUND','HTTP_NOT_MODIFIED', 'REDIRECT');
30
31                 $::MP2 = 1;
32         } else {
33                 require mod_perl;
34
35                 require Apache;
36                 require Apache::Constants;
37                 require Apache::Request;
38         
39                 Apache::Constants->import('OK','DECLINED','FORBIDDEN','NOT_FOUND');
40                 $::MP2 = 0;
41         }
42 }
43
44 use Image::Info qw(image_info);
45 use Image::Size qw(imgsize);
46 use Image::Imlib2;
47 use Text::Template;
48 use File::stat;
49 use File::Spec;
50 use POSIX qw(floor);
51 use URI::Escape;
52 use CGI;
53 use CGI::Cookie;
54 use Encode;
55 use HTTP::Date;
56 use Digest::MD5 qw(md5_base64);
57
58 use Data::Dumper;
59
60 # Regexp for escaping URI's
61 my $escape_rule = "^A-Za-z0-9\-_.!~*'()\/";
62 my $memoized;
63
64 sub handler {
65
66         my $r = shift or Apache2::RequestUtil->request();
67
68         unless (($r->method eq 'HEAD') or ($r->method eq 'GET')) {
69                 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
70         }
71
72         if ((not $memoized) and ($r->dir_config('GalleryMemoize'))) {
73                 require Memoize;
74                 Memoize::memoize('get_imageinfo');
75                 $memoized=1;
76         }
77
78         $r->headers_out->{"X-Powered-By"} = "apachegallery.dk $VERSION - Hest design!";
79         $r->headers_out->{"X-Gallery-Version"} = '$Rev: 335 $ $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 2011) $';
80
81         my $filename = $r->filename;
82         $filename =~ s/\/$//;
83         my $topdir = $filename;
84
85         my $media_rss_enabled = $r->dir_config('GalleryEnableMediaRss');
86
87         # Just return the http headers if the client requested that
88         if ($r->header_only) {
89
90                 if (!$::MP2) {
91                         $r->send_http_header;
92                 }
93
94                 if (-f $filename or -d $filename) {
95                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
96                 }
97                 else {
98                         return $::MP2 ? Apache2::Const::NOT_FOUND() : Apache::Constants::NOT_FOUND();
99                 }
100         }
101
102         my $cgi = new CGI;
103
104         # Handle selected images
105         if ($cgi->param('selection')) {
106                 my @selected = $cgi->param('selection');
107                 my $content = join "<br />\n",@selected;
108                 $r->content_type('text/html');
109                 $r->headers_out->{'Content-Length'} = length($content);
110
111                 if (!$::MP2) {
112                         $r->send_http_header;
113                 }
114
115                 $r->print($content);
116                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
117         }
118         
119         # Selectmode providing checkboxes beside all thumbnails
120         my $select_mode = $cgi->param('select');
121         
122         # Let Apache serve icons without us modifying the request
123         if ($r->uri =~ m/^\/icons/i) {
124             if ($r->uri =~ m/^\/icons\/gallery\/([^\/]+$)/i) {
125             $filename = "/usr/share/libapache-gallery-perl/icons/$1";
126             return send_file($r,$filename);
127             } else {
128             return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
129         }
130         }
131         # Lookup the file in the cache and scale the image if the cached
132         # image does not exist
133         if ($r->uri =~ m/\.cache\//i) {
134
135                 my $filename = $r->filename().$r->path_info();
136                 $filename =~ s/\.cache//;
137
138                 $filename =~ m/\/(\d+)x(\d+)\-/;
139                 my $image_width = $1;
140                 my $image_height = $2;
141
142                 $filename =~ s/\/(\d+)x(\d+)\-//;
143
144                 my ($width, $height, $type) = imgsize($filename);
145
146                 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
147         
148                 my $cached = scale_picture($r, $filename, $image_width, $image_height, $imageinfo);
149
150                 my $file = cache_dir($r, 0);
151                 $file =~ s/\.cache//;
152
153         return send_file($r,$file);
154
155                 
156         }
157
158         my $uri = $r->uri;
159         $uri =~ s/\/$//;
160
161         unless (-f $filename or -d $filename) {
162                 show_error($r, 404, "404!", "No such file or directory: ".uri_escape($r->uri, $escape_rule));
163                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
164         }
165
166         my $doc_pattern = $r->dir_config('GalleryDocFile');
167         unless ($doc_pattern) {
168                 $doc_pattern = '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
169         }
170         my $img_pattern = $r->dir_config('GalleryImgFile');
171         unless ($img_pattern) {
172                 $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
173         }
174
175         # Let Apache serve files we don't know how to handle anyway
176         if (-f $filename && $filename !~ m/$img_pattern/i) {
177                 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
178         }
179
180         if (-d $filename) {
181
182                 unless (-d cache_dir($r, 0)) {
183                         unless (create_cache($r, cache_dir($r, 0))) {
184                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
185                         }
186                 }
187
188                 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
189
190                 # Instead of reading the templates every single time
191                 # we need them, create a hash of template names and
192                 # the associated Text::Template objects.
193                 my %templates = create_templates({layout       => "$tpl_dir/layout.tpl",
194                                                   index        => "$tpl_dir/index.tpl",
195                                                   directory    => "$tpl_dir/directory.tpl",
196                                                   picture      => "$tpl_dir/picture.tpl",
197                                                   file         => "$tpl_dir/file.tpl",
198                                                   comment      => "$tpl_dir/dircomment.tpl",
199                                                   nocomment    => "$tpl_dir/nodircomment.tpl",
200                                                   rss          => "$tpl_dir/rss.tpl",
201                                                   rss_item     => "$tpl_dir/rss_item.tpl",
202                                                   navdirectory => "$tpl_dir/navdirectory.tpl",
203                                                  });
204
205
206
207
208                 my %tpl_vars;
209
210                 $tpl_vars{TITLE} = "Index of: $uri";
211
212                 if ($media_rss_enabled) {
213                         # Put the RSS feed on all directory listings
214                         $tpl_vars{META} = '<link rel="alternate" href="?rss=1" type="application/rss+xml" title="" id="gallery" />';
215                 }
216
217                 unless (opendir (DIR, $filename)) {
218                         show_error ($r, 500, $!, "Unable to access directory $filename: $!");
219                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
220                 }
221
222                 $tpl_vars{MENU} = generate_menu($r);
223
224                 $tpl_vars{FORM_BEGIN} = $select_mode?'<form method="post">':'';
225                 $tpl_vars{FORM_END}   = $select_mode?'<input type="submit" name="Get list" value="Get list"></form>':'';
226
227                 # Read, sort, and filter files
228                 my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
229
230                 @files=gallerysort($r, @files);
231
232                 my @downloadable_files;
233
234                 if (@files) {
235                         # Remove unwanted files from list
236                         my @new_files = ();
237                         foreach my $picture (@files) {
238
239                                 my $file = $topdir."/".$picture;
240
241                                 if ($file =~ /$img_pattern/i) {
242                                         push (@new_files, $picture);
243                                 }
244
245                                 if ($file =~ /$doc_pattern/i) {
246                                         push (@downloadable_files, $picture);
247                                 }
248
249                         }
250                         @files = @new_files;
251                 }
252
253                 # Read and sort directories
254                 rewinddir (DIR);
255                 my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
256                 my $dirsortby;
257                 if (defined($r->dir_config('GalleryDirSortBy'))) {
258                         $dirsortby=$r->dir_config('GalleryDirSortBy');
259                 } else {
260                         $dirsortby=$r->dir_config('GallerySortBy');
261                 }
262                 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
263                         @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
264                 } else {
265                         @directories = sort @directories;
266                 }
267
268                 closedir(DIR);
269
270
271                 # Combine directories and files to one listing
272                 my @listing;
273                 push (@listing, @directories);
274                 push (@listing, @files);
275                 push (@listing, @downloadable_files);
276                 
277                 if (@listing) {
278
279                         my $filelist;
280
281                         my $file_counter = 0;
282                         my $start_at = 1;
283                         my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
284
285                         if (defined($cgi->param('start'))) {
286                                 $start_at = $cgi->param('start');
287                                 if ($start_at < 1) {
288                                         $start_at = 1;
289                                 }
290                         }
291
292                         my $browse_links = "";
293                         if (defined($max_files)) {
294                         
295                                 for (my $i=1; $i<=scalar(@listing); $i++) {
296
297                                         my $from = $i;
298
299                                         my $to = $i+$max_files-1;
300                                         if ($to > scalar(@listing)) {
301                                                 $to = scalar(@listing);
302                                         }
303
304                                         if ($start_at < $from || $start_at > $to) {
305                                                 $browse_links .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
306                                         }
307                                         else {
308                                                 $browse_links .= "$from - $to ";
309                                         }
310
311                                         $i+=$max_files-1;
312
313                                 }
314
315                         }
316
317                         $tpl_vars{BROWSELINKS} = $browse_links;
318
319                         DIRLOOP:
320                         foreach my $file (@listing) {
321
322                                 $file_counter++;
323
324                                 if ($file_counter < $start_at) {
325                                         next;
326                                 }
327
328                                 if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
329                                         last DIRLOOP;
330                                 }
331
332                                 my $thumbfilename = $topdir."/".$file;
333
334                                 my $fileurl = $uri."/".$file;
335
336                                 # Debian bug #619625 <http://bugs.debian.org/619625>
337                                 if (-d $thumbfilename && ! -e $thumbfilename . ".ignore") {
338                                         my $dirtitle = '';
339                                         if (-e $thumbfilename . ".folder") {
340                                                 $dirtitle = get_filecontent($thumbfilename . ".folder");
341                                         }
342
343                                         $dirtitle = $dirtitle ? $dirtitle : $file;
344                                         $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
345
346                                         $tpl_vars{FILES} .=
347                                              $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
348                                                                                     FILE    => $dirtitle,
349                                                                                    }
350                                                                            );
351
352                                 }
353                                 # Debian bug #619625 <http://bugs.debian.org/619625>
354                                 elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i && ! -e $thumbfilename . ".ignore") {
355                                         my $type = lc($1);
356                                         my $stat = stat($thumbfilename);
357                                         my $size = $stat->size;
358                                         my $filetype;
359
360                                         if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
361                                                 $filetype = "video-$type";
362                                         } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
363                                                 $filetype = "text-$type";
364                                         } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
365                                                 $filetype = "sound-$type";
366                                         } elsif ($thumbfilename =~ m/$doc_pattern/i) {
367                                                 $filetype = "application-$type";
368                                         } else {
369                                                 $filetype = "unknown";
370                                         }
371
372                                         # Debian bug #337012 <http://bugs.debian.org/337012>
373                                         # not images
374                                         my $filetitle = $file;
375                                         if (-e $thumbfilename . ".file") {
376                                                 $filetitle = get_filecontent($thumbfilename . ".file");
377                                         }
378
379                                         # Debian bug #348724 <http://bugs.debian.org/348724>
380                                         $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
381
382                                         $tpl_vars{FILES} .=
383                                              $templates{file}->fill_in(HASH => {%tpl_vars,
384                                                                                 FILEURL => uri_escape($fileurl, $escape_rule),
385                                                                                 ALT => "Size: $size Bytes",
386                                                                                 FILE => $filetitle,
387                                                                                 TYPE => $type,
388                                                                                 FILETYPE => $filetype,
389                                                                                }
390                                                                       );
391                                 }
392                                 # Debian bug #619625 <http://bugs.debian.org/619625>
393                                 elsif (-f $thumbfilename && ! -e $thumbfilename . ".ignore") {
394
395                                         my ($width, $height, $type) = imgsize($thumbfilename);
396                                         next if $type eq 'Data stream is not a known image file format';
397
398                                         my @filetypes = qw(JPG TIF PNG PPM GIF);
399
400                                         next unless (grep $type eq $_, @filetypes);
401                                         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);        
402                                         my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
403                                         my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
404
405                                         my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
406
407                                         # Debian bug #337012 <http://bugs.debian.org/337012>
408                                         # HTML <img> tag, alt attribute
409                                         my $filetitle = $file;
410                                         if (-e $thumbfilename . ".file") {
411                                                 $filetitle = get_filecontent($thumbfilename . ".file");
412                                         }
413
414                                         # Debian bug #348724 <http://bugs.debian.org/348724>
415                                         $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
416
417                                         my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
418                                                          FILE    => $filetitle,
419                                                          DATE    => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
420                                                          SRC     => uri_escape($uri."/.cache/$cached", $escape_rule),
421                                                          HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
422                                                          WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
423                                                          SELECT  => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'">&nbsp;&nbsp;':'',);
424                                         $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
425                                                                                                  %file_vars,
426                                                                                                 },
427                                                                                        );
428
429                                         if ($media_rss_enabled) {
430                                                 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
431                                                 my %item_vars = ( 
432                                                         THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
433                                                         LINK      => uri_escape($fileurl, $escape_rule),
434                                                         TITLE     => $file,
435                                                         CONTENT   => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
436                                                 );
437                                                 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => { 
438                                                         %item_vars
439                                                 });
440                                         }
441                                 }
442                         }
443                 }
444                 else {
445                         $tpl_vars{FILES} = "No files found";
446                         $tpl_vars{BROWSELINKS} = "";
447                 }
448
449                 # Generate prev and next directory menu items
450                 $filename =~ m/(.*)\/.*?$/;
451                 my $parent_filename = $1;
452
453                 $r->document_root =~ m/(.*)\/$/;
454                 my $root_path = $1;
455                 print STDERR "$filename vs $root_path\n";
456                 if ($filename ne $root_path) {
457                         unless (opendir (PARENT_DIR, $parent_filename)) {
458                                 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
459                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
460                         }
461         
462                         # Debian bug #619625 <http://bugs.debian.org/619625>
463                         my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" && ! -e "$parent_filename/$_" . ".ignore" } readdir (PARENT_DIR);
464                         my $dirsortby;
465                         if (defined($r->dir_config('GalleryDirSortBy'))) {
466                                 $dirsortby=$r->dir_config('GalleryDirSortBy');
467                         } else {
468                                 $dirsortby=$r->dir_config('GallerySortBy');
469                         }
470                         if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
471                                 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
472                         } else {
473                                 @neighbour_directories = sort @neighbour_directories;
474                         }
475
476                         closedir(PARENT_DIR);
477
478                         my $neightbour_counter = 0;
479                         foreach my $neighbour_directory (@neighbour_directories) {
480                                 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
481                                         if ($neightbour_counter > 0) {
482                                                 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
483                                                 my $linktext = $neighbour_directories[$neightbour_counter-1];
484                                                 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
485                                                         $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
486                                                 }
487                                                 my %info = (
488                                                 URL => "../".$neighbour_directories[$neightbour_counter-1],
489                                                 LINK_NAME => "<<< $linktext",
490                                                 DIR_FILES => "",
491                                                 );
492                                                 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
493                                                 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
494
495                                         }
496                                         if ($neightbour_counter < scalar @neighbour_directories - 1) {
497                                                 my $linktext = $neighbour_directories[$neightbour_counter+1];
498                                                 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder") {
499                                                         $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
500                                                 }
501                                                 my %info = (
502                                                 URL => "../".$neighbour_directories[$neightbour_counter+1],
503                                                 LINK_NAME => "$linktext >>>",
504                                                 DIR_FILES => "",
505                                                 );
506                                                 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
507                                                 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
508                                         }
509                                 }
510                                 $neightbour_counter++;
511                         }
512                 }
513
514                 if (-f $topdir . '.comment') {
515                         my $comment_ref = get_comment($topdir . '.comment');
516                         my %comment_vars;
517                         $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
518                         $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
519                         $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
520                         $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
521                 } else {
522                         $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
523                 }
524
525                 if ($cgi->param('rss')) {
526                         $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
527                         $r->content_type('application/rss+xml');
528                 } else {
529                         $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
530                         $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
531                         $r->content_type('text/html');
532                 }
533
534                 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
535
536                 if (!$::MP2) {
537                         $r->send_http_header;
538                 }
539
540                 $r->print($tpl_vars{MAIN});
541                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
542
543         }
544         else {
545
546                 # original size
547                 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
548                         if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
549                                 $r->filename($filename);
550                                 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
551                         } else {
552                                 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
553                         }
554                 }
555         if (defined $ENV{QUERY_STRING} && $ENV{QUERY_STRING} eq 'thumbonly' &&
556             $r->dir_config('GalleryAllowThumbonly') &&
557             -f $filename) {
558
559             my ($width, $height, $type) = imgsize($filename);
560             my @filetypes = qw(JPG TIF PNG PPM GIF);
561             if (grep $type eq $_, @filetypes) {
562                 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
563                 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
564                 my $cached = get_scaled_picture_name($filename, $thumbnailwidth, $thumbnailheight);
565                 $r->headers_out->set(Location => uri_escape(".cache/$cached", $escape_rule));
566                 return $::MP2 ? Apache2::Const::REDIRECT() : Apache::Constants::REDIRECT();
567             }
568         }
569         
570                 # Create cache dir if not existing
571                 my @tmp = split (/\//, $filename);
572                 my $picfilename = pop @tmp;
573                 my $path = (join "/", @tmp)."/";
574                 my $cache_path = cache_dir($r, 1);
575
576                 unless (-d $cache_path) {
577                         unless (create_cache($r, $cache_path)) {
578                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
579                         }
580                 }
581
582                 my ($orig_width, $orig_height, $type) = imgsize($filename);
583
584                 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
585
586                 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
587
588                 my $cached = get_scaled_picture_name($filename, $image_width, $height);
589                 
590                 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
591
592                 my %templates = create_templates({layout         => "$tpl_dir/layout.tpl",
593                                                   picture        => "$tpl_dir/showpicture.tpl",
594                                                   navpicture     => "$tpl_dir/navpicture.tpl",
595                                                   info           => "$tpl_dir/info.tpl",
596                                                   scale          => "$tpl_dir/scale.tpl",
597                                                   scaleactive    => "$tpl_dir/scaleactive.tpl",
598                                                   orig           => "$tpl_dir/orig.tpl",
599                                                   refresh        => "$tpl_dir/refresh.tpl",
600                                                   interval       => "$tpl_dir/interval.tpl",
601                                                   intervalactive => "$tpl_dir/intervalactive.tpl",
602                                                   slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
603                                                   slideshowoff   => "$tpl_dir/slideshowoff.tpl",
604                                                   pictureinfo    => "$tpl_dir/pictureinfo.tpl",
605                                                   nopictureinfo  => "$tpl_dir/nopictureinfo.tpl",
606                                                  });
607
608                 my %tpl_vars;
609
610                 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ? 
611                         "$orig_width x $orig_height" : "$image_width x $height";
612
613                 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
614                 $tpl_vars{META} = " ";
615                 $tpl_vars{RESOLUTION} = $resolution;
616                 $tpl_vars{MENU} = generate_menu($r);
617                 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
618                 $tpl_vars{URI} = $r->uri();
619         
620                 my $exif_mode = $r->dir_config('GalleryEXIFMode');
621                 unless ($exif_mode) {
622                         $exif_mode = 'namevalue';
623                 }
624
625                 unless (opendir(DATADIR, $path)) {
626                         show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
627                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
628                 }
629                 my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
630                 closedir(DATADIR);
631                 @pictures = gallerysort($r, @pictures);
632
633                 $tpl_vars{TOTAL} = scalar @pictures;
634
635                 my $prevpicture;
636                 my $nextpicture;
637         
638                 for (my $i=0; $i <= $#pictures; $i++) {
639                         if ($pictures[$i] eq $picfilename) {
640
641                                 $tpl_vars{NUMBER} = $i+1;
642
643                                 $prevpicture = $pictures[$i-1];
644                                 my $displayprev = ($i>0 ? 1 : 0);
645
646                                 if ($r->dir_config("GalleryWrapNavigation")) {
647                                         $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
648                                         $displayprev = 1;
649                                 }
650                                 if ($prevpicture and $displayprev) {
651                                         my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
652                                         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);      
653                                         my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
654                                         my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
655                                         # Debian bug #337012 <http://bugs.debian.org/337012>
656                                         my $prevpicture_title = $prevpicture;
657                                         if (-e $path."/".$prevpicture . ".file") {
658                                                 $prevpicture_title = get_filecontent($path."/".$prevpicture . ".file");
659                                         }
660                                         my %nav_vars;
661                                         $nav_vars{URL}       = uri_escape($prevpicture, $escape_rule);
662                                         $nav_vars{FILENAME}  = $prevpicture_title;
663                                         $nav_vars{WIDTH}     = $width;
664                                         $nav_vars{PICTURE}   = uri_escape(".cache/$cached", $escape_rule);
665                                         $nav_vars{DIRECTION} = "&laquo; <u>p</u>rev";
666                                         $nav_vars{ACCESSKEY} = "P";
667                                         $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
668                                 }
669                                 else {
670                                         $tpl_vars{BACK} = "&nbsp;";
671                                 }
672
673                                 $nextpicture = $pictures[$i+1];
674                                 if ($r->dir_config("GalleryWrapNavigation")) {
675                                         $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
676                                 }       
677
678                                 if ($nextpicture) {
679                                         my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
680                                         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);      
681                                         my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
682                                         my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
683                                         # Debian bug #337012 <http://bugs.debian.org/337012>
684                                         my $nextpicture_title = $nextpicture;
685                                         if (-e $path."/".$nextpicture . ".file") {
686                                                 $nextpicture_title = get_filecontent($path."/".$nextpicture . ".file");
687                                         }
688                                         my %nav_vars;
689                                         $nav_vars{URL}       = uri_escape($nextpicture, $escape_rule);
690                                         $nav_vars{FILENAME}  = $nextpicture_title;
691                                         $nav_vars{WIDTH}     = $width;
692                                         $nav_vars{PICTURE}   = uri_escape(".cache/$cached", $escape_rule);
693                                         $nav_vars{DIRECTION} = "<u>n</u>ext &raquo;";
694                                         $nav_vars{ACCESSKEY} = "N";
695
696                                         $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
697                                         $tpl_vars{NEXTURL}   = uri_escape($nextpicture, $escape_rule);
698                                 }
699                                 else {
700                                         $tpl_vars{NEXT} = "&nbsp;";
701                                         $tpl_vars{NEXTURL}   = '#';
702                                 }
703                         }
704                 }
705
706                 my $foundcomment = 0;
707                 if (-f $path . '/' . $picfilename . '.comment') {
708                         my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
709                         $foundcomment = 1;
710                         $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
711                         $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
712                 } elsif ($r->dir_config('GalleryCommentExifKey')) {
713                         my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
714                         $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
715                 } else {
716                         $tpl_vars{COMMENT} = '';
717                 }
718
719                 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
720                 my $foundinfo = 0;
721                 my $exifvalues;
722                 foreach (@infos) {
723         
724                         my ($human_key, $exif_key) = (split " => ")[0,1];
725                         my $value = $imageinfo->{$human_key};
726                         if (defined($value)) {
727
728                                 $foundinfo = 1;
729
730                                 if ($exif_mode eq 'namevalue') {
731                                         my %info_vars;
732                                         $info_vars{KEY} = $human_key;
733                                         $info_vars{VALUE} = $value;
734                                         $tpl_vars{INFO} .=  $templates{info}->fill_in(HASH => \%info_vars);
735                                 }
736
737                                 if ($exif_mode eq 'variables') {
738                                         $tpl_vars{"EXIF_".uc($exif_key)} = $value;
739                                 }
740
741                                 if ($exif_mode eq 'values') {
742                                         $exifvalues .= "| ".$value." ";
743                                 }
744
745                         } 
746
747                 }
748
749                 if ($exif_mode eq 'values') {
750                         if (defined($exifvalues)) {
751                                 $tpl_vars{EXIFVALUES} = $exifvalues;
752                         }
753                         else {
754                                 $tpl_vars{EXIFVALUES} = "";
755                         }
756                 }
757
758                 if ($foundcomment and !$foundinfo) {
759                         $tpl_vars{INFO} = "";
760                 }
761
762                 if ($exif_mode ne 'namevalue') {
763                         $tpl_vars{INFO} = "";
764                 }
765
766                 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
767
768                         $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
769
770                         unless (defined($exifvalues)) {
771                                 $tpl_vars{EXIFVALUES} = "";
772                         }
773
774                 }
775                 else {
776                         $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
777                 }
778
779                 # Fill in sizes and determine if any are smaller than the
780                 # actual image. If they are, $scaleable=1
781                 my $scaleable = 0;
782                 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
783                 foreach my $size (@sizes) {
784                         if ($size<=$original_size) {
785                                 my %sizes_vars;
786                                 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
787                                 $sizes_vars{SIZE}     = $size;
788                                 $sizes_vars{WIDTH}    = $size;
789                                 if ($width == $size) {
790                                         $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
791                                 }
792                                 else {
793                                         $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
794                                 }
795                                 $scaleable = 1;
796                         }
797                 }
798
799                 unless ($scaleable) {
800                         my %sizes_vars;
801                         $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
802                         $sizes_vars{SIZE}     = $original_size;
803                         $sizes_vars{WIDTH}    = $original_size;
804                         $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
805                 }
806
807                 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
808
809                 if ($r->dir_config('GalleryAllowOriginal')) {
810                         $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
811                 }
812
813                 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
814                 foreach my $interval (@slideshow_intervals) {
815
816                         my %slideshow_vars;
817                         $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
818                         $slideshow_vars{SECONDS} = $interval;
819                         $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
820
821                         if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
822                                 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
823                         }
824                         else {
825
826                                 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
827
828                         }
829                 }
830
831                 if ($cgi->param('slideshow') and $nextpicture) {
832
833                         $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
834
835                         unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
836                                 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
837                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
838                         }
839
840                         $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
841                         $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
842                         $tpl_vars{INTERVAL} = $cgi->param('slideshow');
843                         $tpl_vars{META} .=  $templates{refresh}->fill_in(HASH => \%tpl_vars);
844
845                 }
846                 else {
847                         $tpl_vars{SLIDESHOW} .=  $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
848                 }
849
850                 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
851                 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
852
853                 $r->content_type('text/html');
854                 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
855
856                 if (!$::MP2) {
857                         $r->send_http_header;
858                 }
859
860                 $r->print($tpl_vars{MAIN});
861                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
862
863         }
864
865 }
866
867 sub send_file {
868     my ($r,$file) = @_;
869     my $subr = $r->lookup_file($file);
870     $r->content_type($subr->content_type());
871     if ($::MP2) {
872         my $fileinfo = stat($file);
873
874         my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
875         if ($r->headers_in->{"If-None-Match"} eq $nonce) {
876             return Apache2::Const::HTTP_NOT_MODIFIED();
877         }
878
879         if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
880             return Apache2::Const::HTTP_NOT_MODIFIED();
881         }
882
883         $r->headers_out->{"Content-Length"} = $fileinfo->size; 
884         $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime); 
885         $r->headers_out->{"ETag"} = $nonce;
886         $r->sendfile($file);
887         return Apache2::Const::OK();
888     }
889     else {
890         $r->path_info('');
891         $r->filename($file);
892         return Apache::Constants::DECLINED();
893     }
894 }
895
896 sub cache_dir {
897
898         my ($r, $strip_filename) = @_;
899
900         my $cache_root;
901
902         unless ($r->dir_config('GalleryCacheDir')) {
903
904                 $cache_root = '/var/cache/www/';
905                 if ($r->server->is_virtual) {
906                         $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
907                 } else {
908                         $cache_root = File::Spec->catdir($cache_root, $r->location);
909                 }
910
911         } else {
912
913                 $cache_root = $r->dir_config('GalleryCacheDir');
914
915         }
916
917         # If the uri contains .cache we need to remove it
918         my $uri = $r->uri;
919         $uri =~ s/\.cache//;
920
921         my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
922         # We don't need a volume as this is a relative path
923
924         if ($strip_filename) {
925                 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
926         } else {
927                 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
928         }
929 }
930
931 sub create_cache {
932
933         my ($r, $path) = @_;
934
935                 unless (mkdirhier ($path)) {
936                         show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
937                         return 0;
938                 }
939
940         return 1;
941 }
942
943 sub mkdirhier {
944
945         my $dir = shift;
946
947         unless (-d $dir) {
948
949                 unless (mkdir($dir, 0755)) {
950                         my $parent = $dir;
951                         $parent =~ s/\/[^\/]*$//;
952
953                         mkdirhier($parent);
954
955                         mkdir($dir, 0755);
956                 }
957         }
958 }
959
960 sub get_scaled_picture_name {
961
962         my ($fullpath, $width, $height) = @_;
963
964         my (undef, undef, $type) = imgsize($fullpath);
965
966         my @dirs = split(/\//, $fullpath);
967         my $filename = pop(@dirs);
968         my $newfilename;
969
970         if (grep $type eq $_, qw(PPM TIF GIF)) {
971                 $newfilename = $width."x".$height."-".$filename;
972                 # needs to be configurable
973                 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
974         } else {
975                 $newfilename = $width."x".$height."-".$filename;
976         }
977
978         return $newfilename;
979         
980 }
981
982 sub scale_picture {
983
984         my ($r, $fullpath, $width, $height, $imageinfo) = @_;
985
986         my @dirs = split(/\//, $fullpath);
987         my $filename = pop(@dirs);
988
989         my ($orig_width, $orig_height, $type) = imgsize($fullpath);
990
991         my $cache = cache_dir($r, 1);
992
993         my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
994
995         if (($width > $orig_width) && ($height > $orig_height)) {
996                 # Run it through the resize code anyway to get watermarks
997                 $width = $orig_width;
998                 $height = $orig_height;
999         }
1000
1001         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
1002
1003         # Do we want to generate a new file in the cache?
1004         my $scale = 1;
1005
1006         if (-f $cache."/".$newfilename) {       
1007                 $scale = 0;
1008
1009                 # Check to see if the image has changed
1010                 my $filestat = stat($fullpath);
1011                 my $cachestat = stat($cache."/".$newfilename);
1012                 if ($filestat->mtime >= $cachestat->mtime) {
1013                         $scale = 1;
1014                 }       
1015
1016                 # Check to see if the .rotate file has been added or changed
1017                 if (-f $fullpath . ".rotate") {
1018                         my $rotatestat = stat($fullpath . ".rotate");
1019                         if ($rotatestat->mtime > $cachestat->mtime) {
1020                                 $scale = 1;
1021                         }       
1022                 }               
1023                 # Check to see if the copyrightimage has been added or changed
1024                 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
1025                         unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
1026                                 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
1027                                 if ($copyrightstat->mtime > $cachestat->mtime) {
1028                                         $scale = 1;
1029                                 }       
1030                         }
1031                 }       
1032
1033         }       
1034
1035         if ($scale) {
1036
1037                 my $newpath = $cache."/".$newfilename;
1038                 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
1039                 my $quality = $r->dir_config('GalleryQuality');
1040
1041                 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
1042
1043                         resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
1044
1045                 } else {
1046
1047                         resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, 
1048                                 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''), 
1049                                 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''), 
1050                                 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''), 
1051                                 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''), 
1052                                 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''), 
1053                                 ($r->dir_config('GalleryTTFSize') ?  $r->dir_config('GalleryTTFSize') : ''),
1054                                 ($r->dir_config('GalleryCopyrightBackgroundColor') ?  $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1055                                 $quality);
1056
1057                 }
1058         }
1059
1060         return $newfilename;
1061
1062 }
1063
1064 sub get_thumbnailsize {
1065         my ($r, $orig_width, $orig_height) = @_;
1066
1067         my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1068
1069         if (defined($gallerythumbnailsize)) {
1070                 warn("Invalid setting for GalleryThumbnailSize") unless
1071                         $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1072         }
1073
1074         my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ?  $gallerythumbnailsize : "100x75");
1075
1076         my $width = $thumbnailwidth;
1077         my $height = $thumbnailheight;
1078
1079         # If the image is rotated, flip everything around.
1080         if (defined $r->dir_config('GalleryThumbnailSizeLS')
1081         and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1082         and $orig_width < $orig_height) {
1083                 
1084                 $width = $thumbnailheight;
1085                 $height = $thumbnailwidth;
1086         }
1087
1088         my $scale = ($orig_width ? $width/$orig_width : 1);
1089
1090         if ($orig_height) {
1091                 if ($orig_height * $scale > $thumbnailheight) {
1092                         $scale = $height/$orig_height;
1093                         $width = $orig_width * $scale;
1094                 }
1095         }
1096
1097         $height = $orig_height * $scale;
1098
1099         $height = floor($height);
1100         $width  = floor($width);
1101
1102         return ($width, $height);
1103 }
1104
1105 sub get_image_display_size {
1106         my ($cgi, $r, $orig_width, $orig_height) = @_;
1107
1108         my $width = $orig_width;
1109
1110         my $original_size=$orig_height;
1111         if ($orig_width>$orig_height) {
1112                 $original_size=$orig_width;
1113         }
1114
1115         # Check if the selected width is allowed
1116         my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1117
1118         my %cookies = fetch CGI::Cookie;
1119
1120         if ($cgi->param('width')) {
1121                 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1122                         show_error($r, 200, "Invalid width", "The specified width is invalid");
1123                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1124                 }
1125
1126                 $width = $cgi->param('width');
1127                 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1128                 $r->headers_out->{'Set-Cookie'} = $cookie;
1129
1130         } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1131
1132                 $width = $cookies{'GallerySize'}->value;
1133
1134         } else {
1135                 $width = $sizes[0];
1136         }       
1137
1138         my $scale;
1139         my $image_width;
1140         if ($orig_width<$orig_height) {
1141                 $scale = ($orig_height ? $width/$orig_height: 1);
1142                 $image_width=$width*$orig_width/$orig_height;
1143         }
1144         else {
1145                 $scale = ($orig_width ? $width/$orig_width : 1);
1146                 $image_width = $width;
1147         }
1148
1149         my $height = $orig_height * $scale;
1150
1151         $image_width = floor($image_width);
1152         $width       = floor($width);
1153         $height      = floor($height);
1154
1155         return ($image_width, $width, $height, $original_size);
1156 }
1157
1158 sub get_imageinfo {
1159         my ($r, $file, $type, $width, $height) = @_;
1160         my $imageinfo = {};
1161         if ($type eq 'Data stream is not a known image file format') {
1162                 # should never be reached, this is supposed to be handled outside of here
1163                 log_error("Something was fishy with the type of the file $file\n");
1164         } else { 
1165
1166                 # Some files, like TIFF, PNG, GIF do not have EXIF info 
1167                 # embedded but use .thm files instead.
1168                 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1169
1170                 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1171                 # info using Image::Info
1172                 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1173                         # Only for files that natively keep the EXIF info in the same file
1174                         $imageinfo = image_info($file);
1175                 }
1176         }
1177
1178         unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1179                 $imageinfo->{width} = $width;
1180                 $imageinfo->{height} = $height;
1181         }
1182
1183         my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1184         foreach (@infos) {
1185                 
1186                 my ($human_key, $exif_key) = (split " => ")[0,1];
1187                 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1188                         my $value = "";
1189                         if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') { 
1190                                 $value = $imageinfo->{$exif_key}->as_string;
1191                         } 
1192                         elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1193                                 foreach my $element (@{$imageinfo->{$exif_key}}) {
1194                                         if (ref($element) eq 'ARRAY') {
1195                                                 foreach (@{$element}) {
1196                                                         $value .= $_ . ' ';
1197                                                 }
1198                                         } 
1199                                         elsif (ref($element) eq 'HASH') {
1200                                                 $value .= "<br />{ ";
1201                                         foreach (sort keys %{$element}) {
1202                                                         $value .= "$_ = " . $element->{$_} . ' ';
1203                                                 }
1204                                         $value .= "} ";
1205                                         } 
1206                                         else {
1207                                                 $value .= $element;
1208                                         }
1209                                         $value .= ' ';
1210                                 }
1211                         } 
1212                         else {
1213                                 my $exif_value = $imageinfo->{$exif_key};
1214                                 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1215                                         my %flashmodes = (
1216                                                 "0"  => "No",
1217                                                 "1"  => "Yes",
1218                                                 "9"  => "Yes",
1219                                                 "16" => "No (Compulsory) Should be External Flash",
1220                                                 "17" => "Yes (External)",
1221                                                 "24" => "No",
1222                                                 "25" => "Yes (Auto)",
1223                                                 "73" => "Yes (Compulsory, Red Eye Reducing)",
1224                                                 "89" => "Yes (Auto, Red Eye Reducing)"
1225                                         );
1226                                         $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1227                                 }
1228                                 $value = $exif_value;
1229                         }
1230                         if ($exif_key eq 'MeteringMode') {
1231                                 my $exif_value = $imageinfo->{$exif_key};
1232                                 if ($exif_value =~ /^\d+$/) {
1233                                         my %meteringmodes = (
1234                                                 '0' => 'unknown',
1235                                                 '1' => 'Average',
1236                                                 '2' => 'CenterWeightedAverage',
1237                                                 '3' => 'Spot',
1238                                                 '4' => 'MultiSpot',
1239                                                 '5' => 'Pattern',
1240                                                 '6' => 'Partial',
1241                                                 '255' => 'Other'
1242                                         );
1243                                         $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1244                                 }
1245                                 $value = $exif_value;
1246                                 
1247                         }
1248                         if ($exif_key eq 'LightSource') {
1249                                 my $exif_value = $imageinfo->{$exif_key};
1250                                 if ($exif_value =~ /^\d+$/) {
1251                                         my %lightsources = (
1252                                                 '0' => 'unknown',
1253                                                 '1' => 'Daylight',
1254                                                 '2' => 'Fluorescent',
1255                                                 '3' => 'Tungsten (incandescent light)',
1256                                                 '4' => 'Flash',
1257                                                 '9' => 'Fine weather',
1258                                                 '10' => 'Cloudy weather',
1259                                                 '11' => 'Shade',
1260                                                 '12' => 'Daylight fluorescent',
1261                                                 '13' => 'Day white fluorescent',
1262                                                 '14' => 'Cool white fluorescent',
1263                                                 '15' => 'White fluorescent',
1264                                                 '17' => 'Standard light A',
1265                                                 '18' => 'Standard light B',
1266                                                 '19' => 'Standard light C',
1267                                                 '20' => 'D55',
1268                                                 '21' => 'D65',
1269                                                 '22' => 'D75',
1270                                                 '23' => 'D50',
1271                                                 '24' => 'ISO studio tungsten',
1272                                                 '255' => 'other light source'
1273                                         );
1274                                         $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1275                                 }
1276                                 $value = $exif_value;
1277                         }
1278                         if ($exif_key eq 'FocalLength') {
1279                                 if ($value =~ /^(\d+)\/(\d+)$/) {
1280                                         $value = eval { $1 / $2 };
1281                                         if ($@) {
1282                                                 $value = $@;
1283                                         } else {
1284                                                 $value = int($value + 0.5) . "mm";
1285
1286                                         }
1287                                 }
1288                         }
1289                         if ($exif_key eq 'ShutterSpeedValue') {
1290                                 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1291                                         $value = eval { $1 / $2 };
1292                                         if ($@) {
1293                                                 $value = $@;
1294                                         } else {
1295                                                 eval {
1296                                                         $value = 1/(exp($value*log(2)));
1297                                                         if ($value < 1) {
1298                                                                 $value = "1/" . (int((1/$value)));
1299                                                         } else {
1300                                                                 $value = int($value*10)/10; 
1301                                                         }
1302                                                 };
1303                                                 if ($@) {
1304                                                         $value = $@;
1305                                                 } else {
1306                                                         $value = $value . " sec";
1307                                                 }
1308                                         }
1309                                 }
1310                         }
1311                         if ($exif_key eq 'ApertureValue') {
1312                                 if ($value =~ /^(\d+)\/(\d+)$/) {
1313                                         $value = eval { $1 / $2 };
1314                                         if ($@) {
1315                                                 $value = $@;
1316                                         } else {
1317                                                 # poor man's rounding
1318                                                 $value = int(exp($value*log(2)*0.5)*10)/10;
1319                                                 $value = "f" . $value;
1320                                         }
1321                                 }
1322                         }
1323                         if ($exif_key eq 'FNumber') {
1324                                 if ($value =~ /^(\d+)\/(\d+)$/) {
1325                                         $value = eval { $1 / $2 };
1326                                         if ($@) {
1327                                                 $value = $@;
1328                                         } else {
1329                                                 $value = int($value*10+0.5)/10;
1330                                                 $value = "f" . $value;
1331                                         }
1332                                 }
1333                         }
1334                         $imageinfo->{$human_key} = $value;
1335                 } 
1336         }
1337
1338         if ($r->dir_config('GalleryUseFileDate') &&
1339                 ($r->dir_config('GalleryUseFileDate') eq '1'
1340                 || !$imageinfo->{"Picture Taken"} )) {
1341
1342                 my $st = stat($file);
1343                 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1344         }
1345
1346         return $imageinfo;
1347 }
1348
1349 sub get_imageinfo_from_thm_file {
1350
1351         my ($file, $width, $height) = @_;
1352
1353         my $imageinfo = undef;
1354         # Windows based file extensions are often .THM, so check 
1355         # for both .thm and .THM
1356         my $unix_file = $file;
1357         my $windows_file = $file;
1358         $unix_file =~ s/\.(\w+)$/.thm/;
1359         $windows_file =~ s/\.(\w+)$/.THM/;
1360
1361         if (-e $unix_file && -f $unix_file && -r $unix_file) {
1362                 $imageinfo = image_info($unix_file);
1363                 $imageinfo->{width} = $width;
1364                 $imageinfo->{height} = $height;
1365         }
1366         elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1367                 $imageinfo = image_info($windows_file);
1368                 $imageinfo->{width} = $width;
1369                 $imageinfo->{height} = $height;
1370         }
1371
1372         return $imageinfo;
1373 }
1374
1375
1376 sub readfile_getnum {
1377         my ($r, $imageinfo, $filename) = @_;
1378
1379         my $rotate = 0;
1380
1381         print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1382         # Check to see if the image contains the Orientation EXIF key,
1383         # but allow user to override using rotate
1384         if (!defined($r->dir_config("GalleryAutoRotate")) 
1385                 || $r->dir_config("GalleryAutoRotate") eq "1") {
1386                 if (defined($imageinfo->{Orientation})) {
1387                         print STDERR $imageinfo->{Orientation}."\n";
1388                         if ($imageinfo->{Orientation} eq 'right_top') {
1389                                 $rotate=1;
1390                         }       
1391                         elsif ($imageinfo->{Orientation} eq 'left_bot') {
1392                                 $rotate=3;
1393                         }
1394                 }
1395         }
1396
1397         if (open(FH, "<$filename")) {
1398                 my $temp = <FH>;
1399                 chomp($temp);
1400                 close(FH);
1401                 unless ($temp =~ /^\d$/) {
1402                         $rotate = 0;
1403                 }
1404                 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1405                         $rotate = 0;
1406                 }
1407                 $rotate = $temp;
1408         }
1409
1410         return $rotate;
1411 }
1412
1413 sub get_filecontent {
1414         my $file = shift;
1415         open(FH, $file) or return undef;
1416         my $content = '';
1417         {
1418                 local $/;
1419                 $content = <FH>;
1420         }
1421         close(FH);
1422         return $content;
1423 }
1424
1425 sub get_comment {
1426         my $filename = shift;
1427         my $comment_ref = {};
1428         $comment_ref->{TITLE} = undef;
1429         $comment_ref->{COMMENT} = '';
1430
1431         open(FH, $filename) or return $comment_ref;
1432         my $title = <FH>;
1433         if ($title =~ m/^TITLE: (.*)$/) {
1434                 chomp($comment_ref->{TITLE} = $1);
1435         } 
1436         else {
1437                 $comment_ref->{COMMENT} = $title;
1438         }
1439
1440         while (<FH>) {
1441                 chomp;
1442                 $comment_ref->{COMMENT} .= $_;
1443         }
1444         close(FH);
1445
1446         return $comment_ref;
1447 }
1448
1449 sub show_error {
1450
1451         my ($r, $statuscode, $errortitle, $error) = @_;
1452
1453         my $tpl = $r->dir_config('GalleryTemplateDir');
1454
1455         my %templates = create_templates({layout => "$tpl/layout.tpl",
1456                                           error  => "$tpl/error.tpl",
1457                                          });
1458
1459         my %tpl_vars;
1460         $tpl_vars{TITLE}      = "Error! $errortitle";
1461         $tpl_vars{META}       = "";
1462         $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1463         $tpl_vars{ERROR}      = $error;
1464
1465         $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1466
1467         $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1468
1469         $r->status($statuscode);
1470         $r->content_type('text/html');
1471
1472         $r->print($tpl_vars{PAGE});
1473
1474 }
1475
1476 sub generate_menu {
1477
1478         my $r = shift;
1479
1480         my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1481         my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1482
1483         my $subr = $r->lookup_uri($r->uri);
1484         my $filename = $subr->filename;
1485
1486         my @links = split (/\//, $r->uri);
1487         my $uri = $r->uri;
1488         $uri =~ s/^$root_path//g;
1489
1490         @links = split (/\//, $uri);
1491
1492         # Get the full path of the base directory
1493         my $dirname;
1494         {
1495                 my @direlem = split (/\//, $filename);
1496                 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1497                         $dirname .= shift(@direlem) . '/';
1498                 }
1499                 chop $dirname;
1500         }
1501
1502         my $picturename;
1503         if (-f $filename) {
1504                 $picturename = pop(@links);     
1505                 # Debian bug #337012 <http://bugs.debian.org/337012>
1506                 if (-e $filename . ".file") {
1507                         $picturename = get_filecontent($filename . ".file");
1508                 }
1509         }
1510
1511         if ($r->uri eq $root_path) {
1512                 return qq{ <a href="$root_path">$root_text</a> };
1513         }
1514
1515         my $menu;
1516         my $menuurl = $root_path;
1517         foreach my $link (@links) {
1518
1519                 $menuurl .= $link."/";
1520                 my $linktext = $link;
1521                 unless (length($link)) {
1522                         $linktext = "$root_text ";
1523                 }
1524                 else {
1525                         
1526                         $dirname = File::Spec->catdir($dirname, $link);
1527
1528                         if (-e $dirname . ".folder") {
1529                                 $linktext = get_filecontent($dirname . ".folder");
1530                         }
1531                 }
1532
1533                 if ("$root_path$uri" eq $menuurl) {
1534                         $menu .= "$linktext  / ";
1535                 }
1536                 else {
1537                         $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1538                 }
1539
1540         }
1541
1542         if (-f $filename) {
1543                 $menu .= $picturename;
1544         }
1545         else {
1546
1547                 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1548                         $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1549                         $menu .= "?select=1\">[select]</a> ";
1550                 }
1551         }
1552
1553         return $menu;
1554 }
1555
1556 sub resizepicture {
1557         my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1558
1559         # Load image
1560         my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1561
1562         # Scale image
1563         $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1564
1565         # Rotate image
1566         if ($rotate != 0) {
1567                 $image->image_orientate($rotate);
1568         }
1569
1570         # blend copyright image onto image
1571         if ($copyrightfile ne '') {
1572                 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1573                         my $x = $image->get_width();
1574                         my $y = $image->get_height();
1575                         my $logox = $logo->get_width();
1576                         my $logoy = $logo->get_height();
1577                         $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1578                 }
1579                 else {
1580                         log_error("GalleryCopyrightImage $copyrightfile was not found");
1581                 }
1582         }
1583
1584         if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1585                 if (!-d $GalleryTTFDir) {
1586
1587                         log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1588
1589                 } elsif ($GalleryCopyrightText eq '') {
1590
1591                         log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1592
1593                 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1594
1595                         log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1596
1597                 } else {
1598  
1599                         $GalleryTTFFile =~ s/\.TTF$//i;
1600                         $image->add_font_path("$GalleryTTFDir");
1601
1602                         $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1603                         my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1604                         my $x = $image->get_width();
1605                         my $y = $image->get_height();
1606
1607                         my $offset = 3;
1608
1609                         if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1610                                 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1611                                         my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1612                                         $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1613                                         $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1614                                 }
1615                                 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1616                                 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1617                                 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1618                         } else {
1619                                 log_error("Text is to big for the picture.\n");
1620                         }
1621                 }
1622         }
1623
1624         if ($quality && $quality =~ m/^\d+$/) {
1625                 $image->set_quality($quality);
1626         }
1627
1628         $image->save($outfile);
1629
1630 }
1631
1632 sub gallerysort {
1633         my $r=shift;
1634         my @files=@_;
1635         my $sortby = $r->dir_config('GallerySortBy');
1636         my $filename=$r->lookup_uri($r->uri)->filename;
1637         $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1638         if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1639                 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1640         } else {
1641                 @files = sort @files;
1642         }
1643         return @files;
1644 }
1645
1646 # Create Text::Template objects used by Apache::Gallery. Takes a
1647 # hashref of template_name, template_filename pairs, and returns a
1648 # list of template_name, texttemplate_object pairs.
1649 sub create_templates {
1650      my $templates = shift;
1651
1652      # This routine is called whenever a template has an error. Prints
1653      # the error to STDERR and sticks the error in the output
1654      sub tt_broken {
1655           my %args = @_;
1656           # Pull out the name and filename from the arg option [see
1657           # Text::Template for details]
1658           @args{qw(name file)} = @{$args{arg}};
1659           print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1660           # Don't include the file name in the output, as the user can see this.
1661           return qq(<!-- Template $args{name} is broken: $args{error} -->);
1662      }
1663
1664
1665
1666      my %texttemplate_objects;
1667
1668      for my $template_name (keys %$templates) {
1669           my $tt_obj = Text::Template->new(TYPE   => 'FILE',
1670                                            SOURCE => $$templates{$template_name},
1671                                            BROKEN => \&tt_broken,
1672                                            BROKEN_ARG => [$template_name, $$templates{$template_name}],
1673                                           )
1674                or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1675           $texttemplate_objects{$template_name} = $tt_obj;
1676      }
1677      return %texttemplate_objects;
1678 }
1679
1680 sub log_error {
1681         if ($::MP2) {
1682                 Apache2::RequestUtil->request->log_error(shift());
1683         } else {
1684                 Apache->request->log_error(shift());
1685         }
1686 }
1687
1688 1;
1689
1690 =head1 NAME
1691
1692 Apache::Gallery - mod_perl handler to create an image gallery
1693
1694 =head1 SYNOPSIS
1695
1696 See the INSTALL file in the distribution for installation instructions.
1697
1698 =head1 DESCRIPTION
1699
1700 Apache::Gallery creates an thumbnail index of each directory and allows 
1701 viewing pictures in different resolutions. Pictures are resized on the 
1702 fly and cached. The gallery can be configured and customized in many ways
1703 and a custom copyright image can be added to all the images without
1704 modifying the original.
1705
1706 =head1 CONFIGURATION
1707
1708 In your httpd.conf you set the global options for the gallery. You can
1709 also override each of the options in .htaccess files in your gallery
1710 directories.
1711
1712 The options are set in the httpd.conf/.htaccess file using the syntax:
1713 B<PerlSetVar OptionName 'value'>
1714
1715 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1716
1717 =over 4
1718
1719 =item B<GalleryAutoRotate>
1720
1721 Some cameras, like the Canon G3, can detect the orientation of a 
1722 the pictures you take and will save this information in the 
1723 'Orientation' EXIF field. Apache::Gallery will then automatically
1724 rotate your images. 
1725
1726 This behavior is default but can be disabled by setting GalleryAutoRotate
1727 to 0.
1728
1729 =item B<GalleryCacheDir>
1730
1731 Directory where Apache::Gallery should create its cache with scaled
1732 pictures. The default is /var/cache/www/ . Here, a directory for each
1733 virtualhost or location will be created automatically. Make sure your
1734 webserver has write access to the CacheDir.
1735
1736 =item B<GalleryTemplateDir>
1737
1738 Full path to the directory where you placed the templates. This option
1739 can be used both in your global configuration and in .htaccess files,
1740 this way you can have different layouts in different parts of your 
1741 gallery.
1742
1743 No default value, this option is required.
1744
1745 =item B<GalleryInfo>
1746
1747 With this option you can define which EXIF information you would like
1748 to present from the image. The format is: '<MyName => KeyInEXIF, 
1749 MyOtherName => OtherKeyInEXIF'
1750
1751 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1752 and B<Camera>
1753
1754 You can view all the keys from the EXIF header using this perl-oneliner:
1755
1756 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1757
1758 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1759
1760 =item B<GallerySizes>
1761
1762 Defines which widths images can be scaled to. Images cannot be
1763 scaled to other widths than the ones you define with this option.
1764
1765 The default is '640 800 1024 1600'
1766
1767 =item B<GalleryThumbnailSize>
1768
1769 Defines the width and height of the thumbnail images. 
1770
1771 Defaults to '100x75'
1772
1773 =item B<GalleryThumbnailSizeLS>
1774
1775 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1776 the thumbnail image instead of the width and height.
1777
1778 Defaults to '0'.
1779
1780 =item B<GalleryCopyrightImage>
1781
1782 Image you want to blend into your images in the lower right
1783 corner. This could be a transparent png saying "copyright
1784 my name 2001".
1785
1786 Optional.
1787
1788 =item B<GalleryWrapNavigation>
1789
1790 Make the navigation in the picture view wrap around (So Next
1791 at the end displays the first picture, etc.)
1792
1793 Set to 1 or 0, default is 0
1794
1795 =item B<GalleryAllowOriginal>
1796
1797 Allow the user to download the Original picture without
1798 resizing or putting the CopyrightImage on it.
1799
1800 Set to 1 or 0, default is 0
1801
1802 =item B<GalleryAllowThumbOnly>
1803
1804 If true, B<GalleryAllowThumbOnly> allows fooimg.jpg?thumbonly urls
1805 to output the thumbnail of the image. This is useful when including
1806 images in a blog (or similar).
1807
1808 Defaults to '0' (false).
1809
1810 =item B<GallerySlideshowIntervals>
1811
1812 With this option you can configure which intervals can be selected for
1813 a slideshow. The default is '3 5 10 15 30'
1814
1815 =item B<GallerySortBy>
1816
1817 Instead of the default filename ordering you can sort by any
1818 stat attribute. For example size, atime, mtime, ctime.
1819
1820 =item B<GalleryDirSortBy>
1821
1822 Set this variable to sort directories differently than other items,
1823 can be set to size, atime, mtime and ctime; setting any other value
1824 will revert to sorting by name.
1825
1826 =item B<GalleryMemoize>
1827
1828 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1829 when many people access the same images, but it will also cache EXIF
1830 data until the current Apache child dies.
1831
1832 =item B<GalleryUseFileDate>
1833
1834 Set this option to 1 to make A::G show the files timestamp
1835 instead of the EXIF value for "Picture taken".
1836
1837 =item B<GallerySelectionMode>
1838
1839 Enable the selection mode. Select images with checkboxes and
1840 get a list of filenames. 
1841
1842 =item B<GalleryEXIFMode>
1843
1844 You can choose how Apache::Gallery should display EXIF info
1845 from your images. 
1846
1847 The default setting is 'namevalue'. This setting will make 
1848 Apache::Gallery print out the names and values of the EXIF values 
1849 you configure with GalleryInfo. The information will be parsed into 
1850 $INFO in pictureinfo.tpl.  
1851
1852 You can also set it to 'values' which will make A::G parse
1853 the configured values into the var $EXIFVALUES as 'value | value | value'
1854
1855 If you set this option to 'variables' the items you configure in GalleryInfo 
1856 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase). 
1857 That means that with the default setting "Picture Taken => DateTimeOriginal, 
1858 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and 
1859 $EXIF_FLASH available to your templates. You can place them
1860 anywhere you want.
1861
1862 =item B<GalleryRootPath>
1863
1864 Change the location of gallery root. The default is ""
1865
1866 =item B<GalleryRootText>
1867
1868 Change the name that appears as the root element in the menu. The
1869 default is "root:"
1870
1871 =item B<GalleryMaxThumbnailsPerPage>
1872
1873 This options controls how many thumbnails should be displayed in a 
1874 page. It requires $BROWSELINKS to be in the index.tpl template file.
1875
1876 =item B<GalleryImgFile>
1877
1878 Pattern matching the files you want Apache::Gallery to view in the
1879 index as thumbnails. 
1880
1881 The default is '\.(jpe?g|png|tiff?|ppm)$'
1882
1883 =item B<GalleryDocFile>
1884
1885 Pattern matching the files you want Apache::Gallery to view in the index
1886 as normal files. All other filetypes will still be served by Apache::Gallery
1887 but are not visible in the index.
1888
1889 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1890
1891 =item B<GalleryTTFDir>
1892
1893 To use the GalleryCopyrightText feature you must set this option to the
1894 directory where your True Type fonts are stored. No default is set.
1895
1896 Example:
1897
1898         PerlSetVar      GalleryTTFDir '/usr/share/fonts/'
1899
1900 =item B<GalleryTTFFile>
1901
1902 To use the GalleryCopyrightText feature this option must be set to the
1903 name of the True Type font you wish to use. Example:
1904
1905         PerlSetVar      GalleryTTFFile 'verdanab.ttf'
1906
1907 =item B<GalleryTTFSize>
1908
1909 Configure the size of the CopyrightText that will be inserted as 
1910 copyright notice in the corner of your pictures.
1911
1912 Example:
1913
1914         PerlSetVar      GalleryTTFSize '10'
1915
1916 =item B<GalleryCopyrightText>
1917
1918 The text that will be inserted as copyright notice.
1919
1920 Example:
1921
1922         PerlSetVar      GalleryCopyrightText '(c) Michael Legart'
1923
1924 =item B<GalleryCopyrightColor>
1925
1926 The text color of your copyright notice.
1927
1928 Examples:
1929
1930 White:
1931         PerlSetVar      GalleryCopyrightColor '255,255,255,255'
1932
1933 Black:
1934         PerlSetVar      GalleryCopyrightColor '0,0,0,255'
1935
1936 Red:
1937         PerlSetVar      GalleryCopyrightColor '255,0,0,255'
1938
1939 Green:
1940         PerlSetVar      GalleryCopyrightColor '0,255,0,255'
1941
1942 Blue:
1943         PerlSetVar      GalleryCopyrightColor '0,0,255,255'
1944
1945 Transparent orange:
1946         PerlSetVar      GalleryCopyrightColor '255,127,0,127'
1947
1948 =item B<GalleryCopyrightBackgroundColor>
1949
1950 The background-color of a GalleryCopyrightText
1951
1952 r,g,b,a - for examples, see GalleryCopyrightColor
1953
1954 =item B<GalleryQuality>
1955
1956 The quality (1-100) of scaled images
1957
1958 This setting affects the quality of the scaled images.
1959 Set this to a low number to reduce the size of the scaled images.
1960 Remember to clear out your cache if you change this setting.
1961 Quality seems to default to 75, at least in the jpeg and png loader code in
1962 Imlib2 1.1.0.
1963
1964 Examples:
1965
1966 Quality at 50:
1967         PerlSetVar      GalleryQuality '50'
1968
1969 =item B<GalleryUnderscoresToSpaces>
1970
1971 Set this option to 1 to convert underscores to spaces in the listing
1972 of directory and file names, as well as in the alt attribute for HTML
1973 <img> tags.
1974
1975 =back
1976
1977 =over 4
1978
1979 =item B<GalleryCommentExifKey>
1980
1981 Set this option to e.g. ImageDescription to use this field as comments
1982 for images.
1983
1984 =item B<GalleryEnableMediaRss>
1985
1986 Set this option to 1 to enable generation of a media RSS feed. This
1987 can be used e.g. together with the PicLens plugin from http://piclens.com
1988
1989 =back
1990
1991 =head1 FEATURES
1992
1993 =over 4
1994
1995 =item B<Rotate images>
1996
1997 Some cameras, like the Canon G3, detects the orientation of a picture
1998 and adds this info to the EXIF header. Apache::Gallery detects this
1999 and automatically rotates images with this info.
2000
2001 If your camera does not support this, you can rotate the images 
2002 manually, This can also be used to override the rotate information
2003 from a camera that supports that. You can also disable this behavior
2004 with the GalleryAutoRotate option.
2005
2006 To use this functionality you have to create file with the name of the 
2007 picture you want rotated appended with ".rotate". The file should include 
2008 a number where these numbers are supported:
2009
2010         "1", rotates clockwise by 90 degree
2011         "2", rotates clockwise by 180 degrees
2012         "3", rotates clockwise by 270 degrees
2013
2014 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
2015 create a file in the same directory called "Picture1234.jpg.rotate" with
2016 the number 1 inside of it.
2017
2018 =item B<Ignore directories/files>
2019
2020 To ignore a directory or a file (of any kind, not only images) you
2021 create a <directory|file>.ignore file.
2022
2023 =item B<Comments>
2024
2025 To include comments for a directory you create a <directory>.comment
2026 file where the first line can contain "TITLE: New title" which
2027 will be the title of the page, and a comment on the following 
2028 lines.
2029 To include comments for each picture you create files called 
2030 picture.jpg.comment where the first line can contain "TITLE: New
2031 title" which will be the title of the page, and a comment on the
2032 following lines.
2033
2034 Example:
2035
2036         TITLE: This is the new title of the page
2037         And this is the comment.<br />
2038         And this is line two of the comment.
2039
2040 The visible name of the folder is by default identical to the name of
2041 the folder, but can be changed by creating a file <directory>.folder
2042 with the visible name of the folder.
2043
2044 Similarly, the visible name of any file is by default identical to the
2045 name of the file, but can be changed by creating a file <file>.file
2046 with the visible name of the file.
2047
2048 It is also possible to set GalleryCommentExifKey to the name of an EXIF
2049 field containing the comment, e.g. ImageDescription. The EXIF comment is
2050 overridden by the .comment file if it exists.
2051
2052 =back
2053
2054 =head1 DEPENDENCIES
2055
2056 =over 4
2057
2058 =item B<Perl 5>
2059
2060 =item B<Apache with mod_perl>
2061
2062 =item B<URI::Escape>
2063
2064 =item B<Image::Info>
2065
2066 =item B<Image::Size>
2067
2068 =item B<Text::Template>
2069
2070 =item B<Image::Imlib2>
2071
2072 =item B<X11 libraries>
2073 (ie, XFree86)
2074
2075 =item B<Imlib2>
2076 Remember the -dev package when using rpm, deb or other package formats!
2077
2078 =back
2079
2080 =head1 AUTHOR
2081
2082 Michael Legart <michael@legart.dk>
2083
2084 =head1 COPYRIGHT AND LICENSE
2085
2086 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2087
2088 Templates designed by Thomas Kjaer <tk@lnx.dk>
2089
2090 Apache::Gallery is free software and is released under the Artistic License.
2091 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2092
2093 The video icons are from the GNOME project. B<http://www.gnome.org/>
2094
2095 =head1 THANKS
2096
2097 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2098 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2099
2100 =head1 SEE ALSO
2101
2102 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2103 L<Image::Info>, and L<Image::Size>.
2104
2105 =cut