]> git.donarmstrong.com Git - deb_pkgs/libapache-gallery-perl.git/blob - lib/Apache/Gallery.pm
add updates for -4
[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         
544                 # Create cache dir if not existing
545                 my @tmp = split (/\//, $filename);
546                 my $picfilename = pop @tmp;
547                 my $path = (join "/", @tmp)."/";
548                 my $cache_path = cache_dir($r, 1);
549
550                 unless (-d $cache_path) {
551                         unless (create_cache($r, $cache_path)) {
552                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
553                         }
554                 }
555
556                 my ($orig_width, $orig_height, $type) = imgsize($filename);
557
558                 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
559
560                 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
561
562                 my $cached = get_scaled_picture_name($filename, $image_width, $height);
563                 
564                 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
565
566                 my %templates = create_templates({layout         => "$tpl_dir/layout.tpl",
567                                                   picture        => "$tpl_dir/showpicture.tpl",
568                                                   navpicture     => "$tpl_dir/navpicture.tpl",
569                                                   info           => "$tpl_dir/info.tpl",
570                                                   scale          => "$tpl_dir/scale.tpl",
571                                                   scaleactive    => "$tpl_dir/scaleactive.tpl",
572                                                   orig           => "$tpl_dir/orig.tpl",
573                                                   refresh        => "$tpl_dir/refresh.tpl",
574                                                   interval       => "$tpl_dir/interval.tpl",
575                                                   intervalactive => "$tpl_dir/intervalactive.tpl",
576                                                   slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
577                                                   slideshowoff   => "$tpl_dir/slideshowoff.tpl",
578                                                   pictureinfo    => "$tpl_dir/pictureinfo.tpl",
579                                                   nopictureinfo  => "$tpl_dir/nopictureinfo.tpl",
580                                                  });
581
582                 my %tpl_vars;
583
584                 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ? 
585                         "$orig_width x $orig_height" : "$image_width x $height";
586
587                 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
588                 $tpl_vars{META} = " ";
589                 $tpl_vars{RESOLUTION} = $resolution;
590                 $tpl_vars{MENU} = generate_menu($r);
591                 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
592                 $tpl_vars{URI} = $r->uri();
593         
594                 my $exif_mode = $r->dir_config('GalleryEXIFMode');
595                 unless ($exif_mode) {
596                         $exif_mode = 'namevalue';
597                 }
598
599                 unless (opendir(DATADIR, $path)) {
600                         show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
601                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
602                 }
603                 my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
604                 closedir(DATADIR);
605                 @pictures = gallerysort($r, @pictures);
606
607                 $tpl_vars{TOTAL} = scalar @pictures;
608
609                 my $prevpicture;
610                 my $nextpicture;
611         
612                 for (my $i=0; $i <= $#pictures; $i++) {
613                         if ($pictures[$i] eq $picfilename) {
614
615                                 $tpl_vars{NUMBER} = $i+1;
616
617                                 $prevpicture = $pictures[$i-1];
618                                 my $displayprev = ($i>0 ? 1 : 0);
619
620                                 if ($r->dir_config("GalleryWrapNavigation")) {
621                                         $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
622                                         $displayprev = 1;
623                                 }
624                                 if ($prevpicture and $displayprev) {
625                                         my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
626                                         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);      
627                                         my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
628                                         my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
629                                         my %nav_vars;
630                                         $nav_vars{URL}       = uri_escape($prevpicture, $escape_rule);
631                                         $nav_vars{FILENAME}  = $prevpicture;
632                                         $nav_vars{WIDTH}     = $width;
633                                         $nav_vars{PICTURE}   = uri_escape(".cache/$cached", $escape_rule);
634                                         $nav_vars{DIRECTION} = "&laquo; <u>p</u>rev";
635                                         $nav_vars{ACCESSKEY} = "P";
636                                         $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
637                                 }
638                                 else {
639                                         $tpl_vars{BACK} = "&nbsp;";
640                                 }
641
642                                 $nextpicture = $pictures[$i+1];
643                                 if ($r->dir_config("GalleryWrapNavigation")) {
644                                         $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
645                                 }       
646
647                                 if ($nextpicture) {
648                                         my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
649                                         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);      
650                                         my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
651                                         my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
652                                         my %nav_vars;
653                                         $nav_vars{URL}       = uri_escape($nextpicture, $escape_rule);
654                                         $nav_vars{FILENAME}  = $nextpicture;
655                                         $nav_vars{WIDTH}     = $width;
656                                         $nav_vars{PICTURE}   = uri_escape(".cache/$cached", $escape_rule);
657                                         $nav_vars{DIRECTION} = "<u>n</u>ext &raquo;";
658                                         $nav_vars{ACCESSKEY} = "N";
659
660                                         $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
661                                         $tpl_vars{NEXTURL}   = uri_escape($nextpicture, $escape_rule);
662                                 }
663                                 else {
664                                         $tpl_vars{NEXT} = "&nbsp;";
665                                         $tpl_vars{NEXTURL}   = '#';
666                                 }
667                         }
668                 }
669
670                 my $foundcomment = 0;
671                 if (-f $path . '/' . $picfilename . '.comment') {
672                         my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
673                         $foundcomment = 1;
674                         $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
675                         $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
676                 } elsif ($r->dir_config('GalleryCommentExifKey')) {
677                         my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
678                         $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
679                 } else {
680                         $tpl_vars{COMMENT} = '';
681                 }
682
683                 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
684                 my $foundinfo = 0;
685                 my $exifvalues;
686                 foreach (@infos) {
687         
688                         my ($human_key, $exif_key) = (split " => ")[0,1];
689                         my $value = $imageinfo->{$human_key};
690                         if (defined($value)) {
691
692                                 $foundinfo = 1;
693
694                                 if ($exif_mode eq 'namevalue') {
695                                         my %info_vars;
696                                         $info_vars{KEY} = $human_key;
697                                         $info_vars{VALUE} = $value;
698                                         $tpl_vars{INFO} .=  $templates{info}->fill_in(HASH => \%info_vars);
699                                 }
700
701                                 if ($exif_mode eq 'variables') {
702                                         $tpl_vars{"EXIF_".uc($exif_key)} = $value;
703                                 }
704
705                                 if ($exif_mode eq 'values') {
706                                         $exifvalues .= "| ".$value." ";
707                                 }
708
709                         } 
710
711                 }
712
713                 if ($exif_mode eq 'values') {
714                         if (defined($exifvalues)) {
715                                 $tpl_vars{EXIFVALUES} = $exifvalues;
716                         }
717                         else {
718                                 $tpl_vars{EXIFVALUES} = "";
719                         }
720                 }
721
722                 if ($foundcomment and !$foundinfo) {
723                         $tpl_vars{INFO} = "";
724                 }
725
726                 if ($exif_mode ne 'namevalue') {
727                         $tpl_vars{INFO} = "";
728                 }
729
730                 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
731
732                         $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
733
734                         unless (defined($exifvalues)) {
735                                 $tpl_vars{EXIFVALUES} = "";
736                         }
737
738                 }
739                 else {
740                         $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
741                 }
742
743                 # Fill in sizes and determine if any are smaller than the
744                 # actual image. If they are, $scaleable=1
745                 my $scaleable = 0;
746                 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
747                 foreach my $size (@sizes) {
748                         if ($size<=$original_size) {
749                                 my %sizes_vars;
750                                 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
751                                 $sizes_vars{SIZE}     = $size;
752                                 $sizes_vars{WIDTH}    = $size;
753                                 if ($width == $size) {
754                                         $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
755                                 }
756                                 else {
757                                         $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
758                                 }
759                                 $scaleable = 1;
760                         }
761                 }
762
763                 unless ($scaleable) {
764                         my %sizes_vars;
765                         $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
766                         $sizes_vars{SIZE}     = $original_size;
767                         $sizes_vars{WIDTH}    = $original_size;
768                         $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
769                 }
770
771                 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
772
773                 if ($r->dir_config('GalleryAllowOriginal')) {
774                         $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
775                 }
776
777                 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
778                 foreach my $interval (@slideshow_intervals) {
779
780                         my %slideshow_vars;
781                         $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
782                         $slideshow_vars{SECONDS} = $interval;
783                         $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
784
785                         if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
786                                 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
787                         }
788                         else {
789
790                                 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
791
792                         }
793                 }
794
795                 if ($cgi->param('slideshow') and $nextpicture) {
796
797                         $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
798
799                         unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
800                                 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
801                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
802                         }
803
804                         $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
805                         $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
806                         $tpl_vars{INTERVAL} = $cgi->param('slideshow');
807                         $tpl_vars{META} .=  $templates{refresh}->fill_in(HASH => \%tpl_vars);
808
809                 }
810                 else {
811                         $tpl_vars{SLIDESHOW} .=  $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
812                 }
813
814                 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
815                 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
816
817                 $r->content_type('text/html');
818                 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
819
820                 if (!$::MP2) {
821                         $r->send_http_header;
822                 }
823
824                 $r->print($tpl_vars{MAIN});
825                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
826
827         }
828
829 }
830
831 sub send_file {
832     my ($r,$file) = @_;
833     my $subr = $r->lookup_file($file);
834     $r->content_type($subr->content_type());
835     if ($::MP2) {
836         my $fileinfo = stat($file);
837
838         my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
839         if ($r->headers_in->{"If-None-Match"} eq $nonce) {
840             return Apache2::Const::HTTP_NOT_MODIFIED();
841         }
842
843         if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
844             return Apache2::Const::HTTP_NOT_MODIFIED();
845         }
846
847         $r->headers_out->{"Content-Length"} = $fileinfo->size; 
848         $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime); 
849         $r->headers_out->{"ETag"} = $nonce;
850         $r->sendfile($file);
851         return Apache2::Const::OK();
852     }
853     else {
854         $r->path_info('');
855         $r->filename($file);
856         return Apache::Constants::DECLINED();
857     }
858 }
859
860 sub cache_dir {
861
862         my ($r, $strip_filename) = @_;
863
864         my $cache_root;
865
866         unless ($r->dir_config('GalleryCacheDir')) {
867
868                 $cache_root = '/var/cache/www/';
869                 if ($r->server->is_virtual) {
870                         $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
871                 } else {
872                         $cache_root = File::Spec->catdir($cache_root, $r->location);
873                 }
874
875         } else {
876
877                 $cache_root = $r->dir_config('GalleryCacheDir');
878
879         }
880
881         # If the uri contains .cache we need to remove it
882         my $uri = $r->uri;
883         $uri =~ s/\.cache//;
884
885         my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
886         # We don't need a volume as this is a relative path
887
888         if ($strip_filename) {
889                 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
890         } else {
891                 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
892         }
893 }
894
895 sub create_cache {
896
897         my ($r, $path) = @_;
898
899                 unless (mkdirhier ($path)) {
900                         show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
901                         return 0;
902                 }
903
904         return 1;
905 }
906
907 sub mkdirhier {
908
909         my $dir = shift;
910
911         unless (-d $dir) {
912
913                 unless (mkdir($dir, 0755)) {
914                         my $parent = $dir;
915                         $parent =~ s/\/[^\/]*$//;
916
917                         mkdirhier($parent);
918
919                         mkdir($dir, 0755);
920                 }
921         }
922 }
923
924 sub get_scaled_picture_name {
925
926         my ($fullpath, $width, $height) = @_;
927
928         my (undef, undef, $type) = imgsize($fullpath);
929
930         my @dirs = split(/\//, $fullpath);
931         my $filename = pop(@dirs);
932         my $newfilename;
933
934         if (grep $type eq $_, qw(PPM TIF GIF)) {
935                 $newfilename = $width."x".$height."-".$filename;
936                 # needs to be configurable
937                 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
938         } else {
939                 $newfilename = $width."x".$height."-".$filename;
940         }
941
942         return $newfilename;
943         
944 }
945
946 sub scale_picture {
947
948         my ($r, $fullpath, $width, $height, $imageinfo) = @_;
949
950         my @dirs = split(/\//, $fullpath);
951         my $filename = pop(@dirs);
952
953         my ($orig_width, $orig_height, $type) = imgsize($fullpath);
954
955         my $cache = cache_dir($r, 1);
956
957         my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
958
959         if (($width > $orig_width) && ($height > $orig_height)) {
960                 # Run it through the resize code anyway to get watermarks
961                 $width = $orig_width;
962                 $height = $orig_height;
963         }
964
965         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
966
967         # Do we want to generate a new file in the cache?
968         my $scale = 1;
969
970         if (-f $cache."/".$newfilename) {       
971                 $scale = 0;
972
973                 # Check to see if the image has changed
974                 my $filestat = stat($fullpath);
975                 my $cachestat = stat($cache."/".$newfilename);
976                 if ($filestat->mtime >= $cachestat->mtime) {
977                         $scale = 1;
978                 }       
979
980                 # Check to see if the .rotate file has been added or changed
981                 if (-f $fullpath . ".rotate") {
982                         my $rotatestat = stat($fullpath . ".rotate");
983                         if ($rotatestat->mtime > $cachestat->mtime) {
984                                 $scale = 1;
985                         }       
986                 }               
987                 # Check to see if the copyrightimage has been added or changed
988                 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
989                         unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
990                                 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
991                                 if ($copyrightstat->mtime > $cachestat->mtime) {
992                                         $scale = 1;
993                                 }       
994                         }
995                 }       
996
997         }       
998
999         if ($scale) {
1000
1001                 my $newpath = $cache."/".$newfilename;
1002                 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
1003                 my $quality = $r->dir_config('GalleryQuality');
1004
1005                 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
1006
1007                         resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
1008
1009                 } else {
1010
1011                         resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, 
1012                                 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''), 
1013                                 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''), 
1014                                 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''), 
1015                                 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''), 
1016                                 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''), 
1017                                 ($r->dir_config('GalleryTTFSize') ?  $r->dir_config('GalleryTTFSize') : ''),
1018                                 ($r->dir_config('GalleryCopyrightBackgroundColor') ?  $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1019                                 $quality);
1020
1021                 }
1022         }
1023
1024         return $newfilename;
1025
1026 }
1027
1028 sub get_thumbnailsize {
1029         my ($r, $orig_width, $orig_height) = @_;
1030
1031         my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1032
1033         if (defined($gallerythumbnailsize)) {
1034                 warn("Invalid setting for GalleryThumbnailSize") unless
1035                         $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1036         }
1037
1038         my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ?  $gallerythumbnailsize : "100x75");
1039
1040         my $width = $thumbnailwidth;
1041         my $height = $thumbnailheight;
1042
1043         # If the image is rotated, flip everything around.
1044         if (defined $r->dir_config('GalleryThumbnailSizeLS')
1045         and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1046         and $orig_width < $orig_height) {
1047                 
1048                 $width = $thumbnailheight;
1049                 $height = $thumbnailwidth;
1050         }
1051
1052         my $scale = ($orig_width ? $width/$orig_width : 1);
1053
1054         if ($orig_height) {
1055                 if ($orig_height * $scale > $thumbnailheight) {
1056                         $scale = $height/$orig_height;
1057                         $width = $orig_width * $scale;
1058                 }
1059         }
1060
1061         $height = $orig_height * $scale;
1062
1063         $height = floor($height);
1064         $width  = floor($width);
1065
1066         return ($width, $height);
1067 }
1068
1069 sub get_image_display_size {
1070         my ($cgi, $r, $orig_width, $orig_height) = @_;
1071
1072         my $width = $orig_width;
1073
1074         my $original_size=$orig_height;
1075         if ($orig_width>$orig_height) {
1076                 $original_size=$orig_width;
1077         }
1078
1079         # Check if the selected width is allowed
1080         my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1081
1082         my %cookies = fetch CGI::Cookie;
1083
1084         if ($cgi->param('width')) {
1085                 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1086                         show_error($r, 200, "Invalid width", "The specified width is invalid");
1087                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1088                 }
1089
1090                 $width = $cgi->param('width');
1091                 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1092                 $r->headers_out->{'Set-Cookie'} = $cookie;
1093
1094         } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1095
1096                 $width = $cookies{'GallerySize'}->value;
1097
1098         } else {
1099                 $width = $sizes[0];
1100         }       
1101
1102         my $scale;
1103         my $image_width;
1104         if ($orig_width<$orig_height) {
1105                 $scale = ($orig_height ? $width/$orig_height: 1);
1106                 $image_width=$width*$orig_width/$orig_height;
1107         }
1108         else {
1109                 $scale = ($orig_width ? $width/$orig_width : 1);
1110                 $image_width = $width;
1111         }
1112
1113         my $height = $orig_height * $scale;
1114
1115         $image_width = floor($image_width);
1116         $width       = floor($width);
1117         $height      = floor($height);
1118
1119         return ($image_width, $width, $height, $original_size);
1120 }
1121
1122 sub get_imageinfo {
1123         my ($r, $file, $type, $width, $height) = @_;
1124         my $imageinfo = {};
1125         if ($type eq 'Data stream is not a known image file format') {
1126                 # should never be reached, this is supposed to be handled outside of here
1127                 log_error("Something was fishy with the type of the file $file\n");
1128         } else { 
1129
1130                 # Some files, like TIFF, PNG, GIF do not have EXIF info 
1131                 # embedded but use .thm files instead.
1132                 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1133
1134                 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1135                 # info using Image::Info
1136                 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1137                         # Only for files that natively keep the EXIF info in the same file
1138                         $imageinfo = image_info($file);
1139                 }
1140         }
1141
1142         unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1143                 $imageinfo->{width} = $width;
1144                 $imageinfo->{height} = $height;
1145         }
1146
1147         my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1148         foreach (@infos) {
1149                 
1150                 my ($human_key, $exif_key) = (split " => ")[0,1];
1151                 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1152                         my $value = "";
1153                         if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') { 
1154                                 $value = $imageinfo->{$exif_key}->as_string;
1155                         } 
1156                         elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1157                                 foreach my $element (@{$imageinfo->{$exif_key}}) {
1158                                         if (ref($element) eq 'ARRAY') {
1159                                                 foreach (@{$element}) {
1160                                                         $value .= $_ . ' ';
1161                                                 }
1162                                         } 
1163                                         elsif (ref($element) eq 'HASH') {
1164                                                 $value .= "<br />{ ";
1165                                         foreach (sort keys %{$element}) {
1166                                                         $value .= "$_ = " . $element->{$_} . ' ';
1167                                                 }
1168                                         $value .= "} ";
1169                                         } 
1170                                         else {
1171                                                 $value .= $element;
1172                                         }
1173                                         $value .= ' ';
1174                                 }
1175                         } 
1176                         else {
1177                                 my $exif_value = $imageinfo->{$exif_key};
1178                                 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1179                                         my %flashmodes = (
1180                                                 "0"  => "No",
1181                                                 "1"  => "Yes",
1182                                                 "9"  => "Yes",
1183                                                 "16" => "No (Compulsory) Should be External Flash",
1184                                                 "17" => "Yes (External)",
1185                                                 "24" => "No",
1186                                                 "25" => "Yes (Auto)",
1187                                                 "73" => "Yes (Compulsory, Red Eye Reducing)",
1188                                                 "89" => "Yes (Auto, Red Eye Reducing)"
1189                                         );
1190                                         $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1191                                 }
1192                                 $value = $exif_value;
1193                         }
1194                         if ($exif_key eq 'MeteringMode') {
1195                                 my $exif_value = $imageinfo->{$exif_key};
1196                                 if ($exif_value =~ /^\d+$/) {
1197                                         my %meteringmodes = (
1198                                                 '0' => 'unknown',
1199                                                 '1' => 'Average',
1200                                                 '2' => 'CenterWeightedAverage',
1201                                                 '3' => 'Spot',
1202                                                 '4' => 'MultiSpot',
1203                                                 '5' => 'Pattern',
1204                                                 '6' => 'Partial',
1205                                                 '255' => 'Other'
1206                                         );
1207                                         $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1208                                 }
1209                                 $value = $exif_value;
1210                                 
1211                         }
1212                         if ($exif_key eq 'LightSource') {
1213                                 my $exif_value = $imageinfo->{$exif_key};
1214                                 if ($exif_value =~ /^\d+$/) {
1215                                         my %lightsources = (
1216                                                 '0' => 'unknown',
1217                                                 '1' => 'Daylight',
1218                                                 '2' => 'Fluorescent',
1219                                                 '3' => 'Tungsten (incandescent light)',
1220                                                 '4' => 'Flash',
1221                                                 '9' => 'Fine weather',
1222                                                 '10' => 'Cloudy weather',
1223                                                 '11' => 'Shade',
1224                                                 '12' => 'Daylight fluorescent',
1225                                                 '13' => 'Day white fluorescent',
1226                                                 '14' => 'Cool white fluorescent',
1227                                                 '15' => 'White fluorescent',
1228                                                 '17' => 'Standard light A',
1229                                                 '18' => 'Standard light B',
1230                                                 '19' => 'Standard light C',
1231                                                 '20' => 'D55',
1232                                                 '21' => 'D65',
1233                                                 '22' => 'D75',
1234                                                 '23' => 'D50',
1235                                                 '24' => 'ISO studio tungsten',
1236                                                 '255' => 'other light source'
1237                                         );
1238                                         $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1239                                 }
1240                                 $value = $exif_value;
1241                         }
1242                         if ($exif_key eq 'FocalLength') {
1243                                 if ($value =~ /^(\d+)\/(\d+)$/) {
1244                                         $value = eval { $1 / $2 };
1245                                         if ($@) {
1246                                                 $value = $@;
1247                                         } else {
1248                                                 $value = int($value + 0.5) . "mm";
1249
1250                                         }
1251                                 }
1252                         }
1253                         if ($exif_key eq 'ShutterSpeedValue') {
1254                                 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1255                                         $value = eval { $1 / $2 };
1256                                         if ($@) {
1257                                                 $value = $@;
1258                                         } else {
1259                                                 eval {
1260                                                         $value = 1/(exp($value*log(2)));
1261                                                         if ($value < 1) {
1262                                                                 $value = "1/" . (int((1/$value)));
1263                                                         } else {
1264                                                                 $value = int($value*10)/10; 
1265                                                         }
1266                                                 };
1267                                                 if ($@) {
1268                                                         $value = $@;
1269                                                 } else {
1270                                                         $value = $value . " sec";
1271                                                 }
1272                                         }
1273                                 }
1274                         }
1275                         if ($exif_key eq 'ApertureValue') {
1276                                 if ($value =~ /^(\d+)\/(\d+)$/) {
1277                                         $value = eval { $1 / $2 };
1278                                         if ($@) {
1279                                                 $value = $@;
1280                                         } else {
1281                                                 # poor man's rounding
1282                                                 $value = int(exp($value*log(2)*0.5)*10)/10;
1283                                                 $value = "f" . $value;
1284                                         }
1285                                 }
1286                         }
1287                         if ($exif_key eq 'FNumber') {
1288                                 if ($value =~ /^(\d+)\/(\d+)$/) {
1289                                         $value = eval { $1 / $2 };
1290                                         if ($@) {
1291                                                 $value = $@;
1292                                         } else {
1293                                                 $value = int($value*10+0.5)/10;
1294                                                 $value = "f" . $value;
1295                                         }
1296                                 }
1297                         }
1298                         $imageinfo->{$human_key} = $value;
1299                 } 
1300         }
1301
1302         if ($r->dir_config('GalleryUseFileDate') &&
1303                 ($r->dir_config('GalleryUseFileDate') eq '1'
1304                 || !$imageinfo->{"Picture Taken"} )) {
1305
1306                 my $st = stat($file);
1307                 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1308         }
1309
1310         return $imageinfo;
1311 }
1312
1313 sub get_imageinfo_from_thm_file {
1314
1315         my ($file, $width, $height) = @_;
1316
1317         my $imageinfo = undef;
1318         # Windows based file extensions are often .THM, so check 
1319         # for both .thm and .THM
1320         my $unix_file = $file;
1321         my $windows_file = $file;
1322         $unix_file =~ s/\.(\w+)$/.thm/;
1323         $windows_file =~ s/\.(\w+)$/.THM/;
1324
1325         if (-e $unix_file && -f $unix_file && -r $unix_file) {
1326                 $imageinfo = image_info($unix_file);
1327                 $imageinfo->{width} = $width;
1328                 $imageinfo->{height} = $height;
1329         }
1330         elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1331                 $imageinfo = image_info($windows_file);
1332                 $imageinfo->{width} = $width;
1333                 $imageinfo->{height} = $height;
1334         }
1335
1336         return $imageinfo;
1337 }
1338
1339
1340 sub readfile_getnum {
1341         my ($r, $imageinfo, $filename) = @_;
1342
1343         my $rotate = 0;
1344
1345         print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1346         # Check to see if the image contains the Orientation EXIF key,
1347         # but allow user to override using rotate
1348         if (!defined($r->dir_config("GalleryAutoRotate")) 
1349                 || $r->dir_config("GalleryAutoRotate") eq "1") {
1350                 if (defined($imageinfo->{Orientation})) {
1351                         print STDERR $imageinfo->{Orientation}."\n";
1352                         if ($imageinfo->{Orientation} eq 'right_top') {
1353                                 $rotate=1;
1354                         }       
1355                         elsif ($imageinfo->{Orientation} eq 'left_bot') {
1356                                 $rotate=3;
1357                         }
1358                 }
1359         }
1360
1361         if (open(FH, "<$filename")) {
1362                 my $temp = <FH>;
1363                 chomp($temp);
1364                 close(FH);
1365                 unless ($temp =~ /^\d$/) {
1366                         $rotate = 0;
1367                 }
1368                 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1369                         $rotate = 0;
1370                 }
1371                 $rotate = $temp;
1372         }
1373
1374         return $rotate;
1375 }
1376
1377 sub get_filecontent {
1378         my $file = shift;
1379         open(FH, $file) or return undef;
1380         my $content = '';
1381         {
1382                 local $/;
1383                 $content = <FH>;
1384         }
1385         close(FH);
1386         return $content;
1387 }
1388
1389 sub get_comment {
1390         my $filename = shift;
1391         my $comment_ref = {};
1392         $comment_ref->{TITLE} = undef;
1393         $comment_ref->{COMMENT} = '';
1394
1395         open(FH, $filename) or return $comment_ref;
1396         my $title = <FH>;
1397         if ($title =~ m/^TITLE: (.*)$/) {
1398                 chomp($comment_ref->{TITLE} = $1);
1399         } 
1400         else {
1401                 $comment_ref->{COMMENT} = $title;
1402         }
1403
1404         while (<FH>) {
1405                 chomp;
1406                 $comment_ref->{COMMENT} .= $_;
1407         }
1408         close(FH);
1409
1410         return $comment_ref;
1411 }
1412
1413 sub show_error {
1414
1415         my ($r, $statuscode, $errortitle, $error) = @_;
1416
1417         my $tpl = $r->dir_config('GalleryTemplateDir');
1418
1419         my %templates = create_templates({layout => "$tpl/layout.tpl",
1420                                           error  => "$tpl/error.tpl",
1421                                          });
1422
1423         my %tpl_vars;
1424         $tpl_vars{TITLE}      = "Error! $errortitle";
1425         $tpl_vars{META}       = "";
1426         $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1427         $tpl_vars{ERROR}      = $error;
1428
1429         $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1430
1431         $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1432
1433         $r->status($statuscode);
1434         $r->content_type('text/html');
1435
1436         $r->print($tpl_vars{PAGE});
1437
1438 }
1439
1440 sub generate_menu {
1441
1442         my $r = shift;
1443
1444         my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1445         my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1446
1447         my $subr = $r->lookup_uri($r->uri);
1448         my $filename = $subr->filename;
1449
1450         my @links = split (/\//, $r->uri);
1451         my $uri = $r->uri;
1452         $uri =~ s/^$root_path//g;
1453
1454         @links = split (/\//, $uri);
1455
1456         # Get the full path of the base directory
1457         my $dirname;
1458         {
1459                 my @direlem = split (/\//, $filename);
1460                 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1461                         $dirname .= shift(@direlem) . '/';
1462                 }
1463                 chop $dirname;
1464         }
1465
1466         my $picturename;
1467         if (-f $filename) {
1468                 $picturename = pop(@links);     
1469         }
1470
1471         if ($r->uri eq $root_path) {
1472                 return qq{ <a href="$root_path">$root_text</a> };
1473         }
1474
1475         my $menu;
1476         my $menuurl = $root_path;
1477         foreach my $link (@links) {
1478
1479                 $menuurl .= $link."/";
1480                 my $linktext = $link;
1481                 unless (length($link)) {
1482                         $linktext = "$root_text ";
1483                 }
1484                 else {
1485                         
1486                         $dirname = File::Spec->catdir($dirname, $link);
1487
1488                         if (-e $dirname . ".folder") {
1489                                 $linktext = get_filecontent($dirname . ".folder");
1490                         }
1491                 }
1492
1493                 if ("$root_path$uri" eq $menuurl) {
1494                         $menu .= "$linktext  / ";
1495                 }
1496                 else {
1497                         $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1498                 }
1499
1500         }
1501
1502         if (-f $filename) {
1503                 $menu .= $picturename;
1504         }
1505         else {
1506
1507                 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1508                         $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1509                         $menu .= "?select=1\">[select]</a> ";
1510                 }
1511         }
1512
1513         return $menu;
1514 }
1515
1516 sub resizepicture {
1517         my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1518
1519         # Load image
1520         my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1521
1522         # Scale image
1523         $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1524
1525         # Rotate image
1526         if ($rotate != 0) {
1527                 $image->image_orientate($rotate);
1528         }
1529
1530         # blend copyright image onto image
1531         if ($copyrightfile ne '') {
1532                 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1533                         my $x = $image->get_width();
1534                         my $y = $image->get_height();
1535                         my $logox = $logo->get_width();
1536                         my $logoy = $logo->get_height();
1537                         $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1538                 }
1539                 else {
1540                         log_error("GalleryCopyrightImage $copyrightfile was not found");
1541                 }
1542         }
1543
1544         if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1545                 if (!-d $GalleryTTFDir) {
1546
1547                         log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1548
1549                 } elsif ($GalleryCopyrightText eq '') {
1550
1551                         log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1552
1553                 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1554
1555                         log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1556
1557                 } else {
1558  
1559                         $GalleryTTFFile =~ s/\.TTF$//i;
1560                         $image->add_font_path("$GalleryTTFDir");
1561
1562                         $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1563                         my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1564                         my $x = $image->get_width();
1565                         my $y = $image->get_height();
1566
1567                         my $offset = 3;
1568
1569                         if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1570                                 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1571                                         my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1572                                         $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1573                                         $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1574                                 }
1575                                 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1576                                 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1577                                 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1578                         } else {
1579                                 log_error("Text is to big for the picture.\n");
1580                         }
1581                 }
1582         }
1583
1584         if ($quality && $quality =~ m/^\d+$/) {
1585                 $image->set_quality($quality);
1586         }
1587
1588         $image->save($outfile);
1589
1590 }
1591
1592 sub gallerysort {
1593         my $r=shift;
1594         my @files=@_;
1595         my $sortby = $r->dir_config('GallerySortBy');
1596         my $filename=$r->lookup_uri($r->uri)->filename;
1597         $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1598         if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1599                 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1600         } else {
1601                 @files = sort @files;
1602         }
1603         return @files;
1604 }
1605
1606 # Create Text::Template objects used by Apache::Gallery. Takes a
1607 # hashref of template_name, template_filename pairs, and returns a
1608 # list of template_name, texttemplate_object pairs.
1609 sub create_templates {
1610      my $templates = shift;
1611
1612      # This routine is called whenever a template has an error. Prints
1613      # the error to STDERR and sticks the error in the output
1614      sub tt_broken {
1615           my %args = @_;
1616           # Pull out the name and filename from the arg option [see
1617           # Text::Template for details]
1618           @args{qw(name file)} = @{$args{arg}};
1619           print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1620           # Don't include the file name in the output, as the user can see this.
1621           return qq(<!-- Template $args{name} is broken: $args{error} -->);
1622      }
1623
1624
1625
1626      my %texttemplate_objects;
1627
1628      for my $template_name (keys %$templates) {
1629           my $tt_obj = Text::Template->new(TYPE   => 'FILE',
1630                                            SOURCE => $$templates{$template_name},
1631                                            BROKEN => \&tt_broken,
1632                                            BROKEN_ARG => [$template_name, $$templates{$template_name}],
1633                                           )
1634                or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1635           $texttemplate_objects{$template_name} = $tt_obj;
1636      }
1637      return %texttemplate_objects;
1638 }
1639
1640 sub log_error {
1641         if ($::MP2) {
1642                 Apache2::RequestUtil->request->log_error(shift());
1643         } else {
1644                 Apache->request->log_error(shift());
1645         }
1646 }
1647
1648 1;
1649
1650 =head1 NAME
1651
1652 Apache::Gallery - mod_perl handler to create an image gallery
1653
1654 =head1 SYNOPSIS
1655
1656 See the INSTALL file in the distribution for installation instructions.
1657
1658 =head1 DESCRIPTION
1659
1660 Apache::Gallery creates an thumbnail index of each directory and allows 
1661 viewing pictures in different resolutions. Pictures are resized on the 
1662 fly and cached. The gallery can be configured and customized in many ways
1663 and a custom copyright image can be added to all the images without
1664 modifying the original.
1665
1666 =head1 CONFIGURATION
1667
1668 In your httpd.conf you set the global options for the gallery. You can
1669 also override each of the options in .htaccess files in your gallery
1670 directories.
1671
1672 The options are set in the httpd.conf/.htaccess file using the syntax:
1673 B<PerlSetVar OptionName 'value'>
1674
1675 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1676
1677 =over 4
1678
1679 =item B<GalleryAutoRotate>
1680
1681 Some cameras, like the Canon G3, can detect the orientation of a 
1682 the pictures you take and will save this information in the 
1683 'Orientation' EXIF field. Apache::Gallery will then automatically
1684 rotate your images. 
1685
1686 This behavior is default but can be disabled by setting GalleryAutoRotate
1687 to 0.
1688
1689 =item B<GalleryCacheDir>
1690
1691 Directory where Apache::Gallery should create its cache with scaled
1692 pictures. The default is /var/cache/www/ . Here, a directory for each
1693 virtualhost or location will be created automatically. Make sure your
1694 webserver has write access to the CacheDir.
1695
1696 =item B<GalleryTemplateDir>
1697
1698 Full path to the directory where you placed the templates. This option
1699 can be used both in your global configuration and in .htaccess files,
1700 this way you can have different layouts in different parts of your 
1701 gallery.
1702
1703 No default value, this option is required.
1704
1705 =item B<GalleryInfo>
1706
1707 With this option you can define which EXIF information you would like
1708 to present from the image. The format is: '<MyName => KeyInEXIF, 
1709 MyOtherName => OtherKeyInEXIF'
1710
1711 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1712 and B<Camera>
1713
1714 You can view all the keys from the EXIF header using this perl-oneliner:
1715
1716 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1717
1718 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1719
1720 =item B<GallerySizes>
1721
1722 Defines which widths images can be scaled to. Images cannot be
1723 scaled to other widths than the ones you define with this option.
1724
1725 The default is '640 800 1024 1600'
1726
1727 =item B<GalleryThumbnailSize>
1728
1729 Defines the width and height of the thumbnail images. 
1730
1731 Defaults to '100x75'
1732
1733 =item B<GalleryThumbnailSizeLS>
1734
1735 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1736 the thumbnail image instead of the width and height.
1737
1738 Defaults to '0'.
1739
1740 =item B<GalleryCopyrightImage>
1741
1742 Image you want to blend into your images in the lower right
1743 corner. This could be a transparent png saying "copyright
1744 my name 2001".
1745
1746 Optional.
1747
1748 =item B<GalleryWrapNavigation>
1749
1750 Make the navigation in the picture view wrap around (So Next
1751 at the end displays the first picture, etc.)
1752
1753 Set to 1 or 0, default is 0
1754
1755 =item B<GalleryAllowOriginal>
1756
1757 Allow the user to download the Original picture without
1758 resizing or putting the CopyrightImage on it.
1759
1760 Set to 1 or 0, default is 0
1761
1762 =item B<GallerySlideshowIntervals>
1763
1764 With this option you can configure which intervals can be selected for
1765 a slideshow. The default is '3 5 10 15 30'
1766
1767 =item B<GallerySortBy>
1768
1769 Instead of the default filename ordering you can sort by any
1770 stat attribute. For example size, atime, mtime, ctime.
1771
1772 =item B<GalleryDirSortBy>
1773
1774 Set this variable to sort directories differently than other items,
1775 can be set to size, atime, mtime and ctime; setting any other value
1776 will revert to sorting by name.
1777
1778 =item B<GalleryMemoize>
1779
1780 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1781 when many people access the same images, but it will also cache EXIF
1782 data until the current Apache child dies.
1783
1784 =item B<GalleryUseFileDate>
1785
1786 Set this option to 1 to make A::G show the files timestamp
1787 instead of the EXIF value for "Picture taken".
1788
1789 =item B<GallerySelectionMode>
1790
1791 Enable the selection mode. Select images with checkboxes and
1792 get a list of filenames. 
1793
1794 =item B<GalleryEXIFMode>
1795
1796 You can choose how Apache::Gallery should display EXIF info
1797 from your images. 
1798
1799 The default setting is 'namevalue'. This setting will make 
1800 Apache::Gallery print out the names and values of the EXIF values 
1801 you configure with GalleryInfo. The information will be parsed into 
1802 $INFO in pictureinfo.tpl.  
1803
1804 You can also set it to 'values' which will make A::G parse
1805 the configured values into the var $EXIFVALUES as 'value | value | value'
1806
1807 If you set this option to 'variables' the items you configure in GalleryInfo 
1808 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase). 
1809 That means that with the default setting "Picture Taken => DateTimeOriginal, 
1810 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and 
1811 $EXIF_FLASH available to your templates. You can place them
1812 anywhere you want.
1813
1814 =item B<GalleryRootPath>
1815
1816 Change the location of gallery root. The default is ""
1817
1818 =item B<GalleryRootText>
1819
1820 Change the name that appears as the root element in the menu. The
1821 default is "root:"
1822
1823 =item B<GalleryMaxThumbnailsPerPage>
1824
1825 This options controls how many thumbnails should be displayed in a 
1826 page. It requires $BROWSELINKS to be in the index.tpl template file.
1827
1828 =item B<GalleryImgFile>
1829
1830 Pattern matching the files you want Apache::Gallery to view in the
1831 index as thumbnails. 
1832
1833 The default is '\.(jpe?g|png|tiff?|ppm)$'
1834
1835 =item B<GalleryDocFile>
1836
1837 Pattern matching the files you want Apache::Gallery to view in the index
1838 as normal files. All other filetypes will still be served by Apache::Gallery
1839 but are not visible in the index.
1840
1841 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1842
1843 =item B<GalleryTTFDir>
1844
1845 To use the GalleryCopyrightText feature you must set this option to the
1846 directory where your True Type fonts are stored. No default is set.
1847
1848 Example:
1849
1850         PerlSetVar      GalleryTTFDir '/usr/share/fonts/'
1851
1852 =item B<GalleryTTFFile>
1853
1854 To use the GalleryCopyrightText feature this option must be set to the
1855 name of the True Type font you wish to use. Example:
1856
1857         PerlSetVar      GalleryTTFFile 'verdanab.ttf'
1858
1859 =item B<GalleryTTFSize>
1860
1861 Configure the size of the CopyrightText that will be inserted as 
1862 copyright notice in the corner of your pictures.
1863
1864 Example:
1865
1866         PerlSetVar      GalleryTTFSize '10'
1867
1868 =item B<GalleryCopyrightText>
1869
1870 The text that will be inserted as copyright notice.
1871
1872 Example:
1873
1874         PerlSetVar      GalleryCopyrightText '(c) Michael Legart'
1875
1876 =item B<GalleryCopyrightColor>
1877
1878 The text color of your copyright notice.
1879
1880 Examples:
1881
1882 White:
1883         PerlSetVar      GalleryCopyrightColor '255,255,255,255'
1884
1885 Black:
1886         PerlSetVar      GalleryCopyrightColor '0,0,0,255'
1887
1888 Red:
1889         PerlSetVar      GalleryCopyrightColor '255,0,0,255'
1890
1891 Green:
1892         PerlSetVar      GalleryCopyrightColor '0,255,0,255'
1893
1894 Blue:
1895         PerlSetVar      GalleryCopyrightColor '0,0,255,255'
1896
1897 Transparent orange:
1898         PerlSetVar      GalleryCopyrightColor '255,127,0,127'
1899
1900 =item B<GalleryCopyrightBackgroundColor>
1901
1902 The background-color of a GalleryCopyrightText
1903
1904 r,g,b,a - for examples, see GalleryCopyrightColor
1905
1906 =item B<GalleryQuality>
1907
1908 The quality (1-100) of scaled images
1909
1910 This setting affects the quality of the scaled images.
1911 Set this to a low number to reduce the size of the scaled images.
1912 Remember to clear out your cache if you change this setting.
1913 Quality seems to default to 75, at least in the jpeg and png loader code in
1914 Imlib2 1.1.0.
1915
1916 Examples:
1917
1918 Quality at 50:
1919         PerlSetVar      GalleryQuality '50'
1920
1921 =item B<GalleryUnderscoresToSpaces>
1922
1923 Set this option to 1 to convert underscores to spaces in the listing
1924 of directory and file names, as well as in the alt attribute for HTML
1925 <img> tags.
1926
1927 =back
1928
1929 =over 4
1930
1931 =item B<GalleryCommentExifKey>
1932
1933 Set this option to e.g. ImageDescription to use this field as comments
1934 for images.
1935
1936 =item B<GalleryEnableMediaRss>
1937
1938 Set this option to 1 to enable generation of a media RSS feed. This
1939 can be used e.g. together with the PicLens plugin from http://piclens.com
1940
1941 =back
1942
1943 =head1 FEATURES
1944
1945 =over 4
1946
1947 =item B<Rotate images>
1948
1949 Some cameras, like the Canon G3, detects the orientation of a picture
1950 and adds this info to the EXIF header. Apache::Gallery detects this
1951 and automatically rotates images with this info.
1952
1953 If your camera does not support this, you can rotate the images 
1954 manually, This can also be used to override the rotate information
1955 from a camera that supports that. You can also disable this behavior
1956 with the GalleryAutoRotate option.
1957
1958 To use this functionality you have to create file with the name of the 
1959 picture you want rotated appended with ".rotate". The file should include 
1960 a number where these numbers are supported:
1961
1962         "1", rotates clockwise by 90 degree
1963         "2", rotates clockwise by 180 degrees
1964         "3", rotates clockwise by 270 degrees
1965
1966 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1967 create a file in the same directory called "Picture1234.jpg.rotate" with
1968 the number 1 inside of it.
1969
1970 =item B<Ignore directories/files>
1971
1972 To ignore a directory or a file (of any kind, not only images) you
1973 create a <directory|file>.ignore file.
1974
1975 =item B<Comments>
1976
1977 To include comments for a directory you create a <directory>.comment
1978 file where the first line can contain "TITLE: New title" which
1979 will be the title of the page, and a comment on the following 
1980 lines.
1981 To include comments for each picture you create files called 
1982 picture.jpg.comment where the first line can contain "TITLE: New
1983 title" which will be the title of the page, and a comment on the
1984 following lines.
1985
1986 Example:
1987
1988         TITLE: This is the new title of the page
1989         And this is the comment.<br />
1990         And this is line two of the comment.
1991
1992 The visible name of the folder is by default identical to the name of
1993 the folder, but can be changed by creating a file <directory>.folder
1994 with the visible name of the folder.
1995
1996 It is also possible to set GalleryCommentExifKey to the name of an EXIF
1997 field containing the comment, e.g. ImageDescription. The EXIF comment is
1998 overridden by the .comment file if it exists.
1999
2000 =back
2001
2002 =head1 DEPENDENCIES
2003
2004 =over 4
2005
2006 =item B<Perl 5>
2007
2008 =item B<Apache with mod_perl>
2009
2010 =item B<URI::Escape>
2011
2012 =item B<Image::Info>
2013
2014 =item B<Image::Size>
2015
2016 =item B<Text::Template>
2017
2018 =item B<Image::Imlib2>
2019
2020 =item B<X11 libraries>
2021 (ie, XFree86)
2022
2023 =item B<Imlib2>
2024 Remember the -dev package when using rpm, deb or other package formats!
2025
2026 =back
2027
2028 =head1 AUTHOR
2029
2030 Michael Legart <michael@legart.dk>
2031
2032 =head1 COPYRIGHT AND LICENSE
2033
2034 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2035
2036 Templates designed by Thomas Kjaer <tk@lnx.dk>
2037
2038 Apache::Gallery is free software and is released under the Artistic License.
2039 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2040
2041 The video icons are from the GNOME project. B<http://www.gnome.org/>
2042
2043 =head1 THANKS
2044
2045 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2046 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2047
2048 =head1 SEE ALSO
2049
2050 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2051 L<Image::Info>, and L<Image::Size>.
2052
2053 =cut