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