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