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