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