| blib/lib/HTML/PhotoAlbum.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % | 
| statement | 21 | 220 | 9.5 | 
| branch | 0 | 106 | 0.0 | 
| condition | 0 | 59 | 0.0 | 
| subroutine | 7 | 16 | 43.7 | 
| pod | 3 | 5 | 60.0 | 
| total | 31 | 406 | 7.6 | 
| line | stmt | bran | cond | sub | pod | time | code | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | ||||||||||||||
| 2 |  # Copyright (c) 2001 Nathan Wiger  | 
|||||||||||||
| 3 | # Use "perldoc PhotoAlbum.pm" for documentation | |||||||||||||
| 4 | ||||||||||||||
| 5 | package HTML::PhotoAlbum; | |||||||||||||
| 6 | ||||||||||||||
| 7 | =head1 NAME | |||||||||||||
| 8 | ||||||||||||||
| 9 | HTML::PhotoAlbum - Create web photo albums and slideshows | |||||||||||||
| 10 | ||||||||||||||
| 11 | =head1 SYNOPSIS | |||||||||||||
| 12 | ||||||||||||||
| 13 | use HTML::PhotoAlbum; | |||||||||||||
| 14 | ||||||||||||||
| 15 | # Create a new album object, specifying the albums we have | |||||||||||||
| 16 | ||||||||||||||
| 17 | my $album = HTML::PhotoAlbum->new( | |||||||||||||
| 18 | albums => { | |||||||||||||
| 19 | sf_trip => 'San Francisco Trip', | |||||||||||||
| 20 | sjc_vac => 'San Jose Vacation', | |||||||||||||
| 21 | puppy_1 => 'Puppy - First Week', | |||||||||||||
| 22 | puppy_2 => 'Puppy - Second Week' | |||||||||||||
| 23 | } | |||||||||||||
| 24 | ); | |||||||||||||
| 25 | ||||||||||||||
| 26 | # By using the "selected" method, we can change what each one | |||||||||||||
| 27 | # looks like. However, note these if statements are optional! | |||||||||||||
| 28 | ||||||||||||||
| 29 | if ($album->selected eq 'sf_trip') { | |||||||||||||
| 30 | print $album->render( | |||||||||||||
| 31 | header => 1, | |||||||||||||
| 32 | eachrow => 3, | |||||||||||||
| 33 | eachpage => 12 | |||||||||||||
| 34 | ); | |||||||||||||
| 35 | } elsif ($album->selected eq 'sjc_vac') { | |||||||||||||
| 36 | print $album->render( | |||||||||||||
| 37 | header => 1, | |||||||||||||
| 38 | eachrow => 5, | |||||||||||||
| 39 | eachpage => 20, | |||||||||||||
| 40 | font_face => 'times' | |||||||||||||
| 41 | body_bgcolor => 'silver', | |||||||||||||
| 42 | ); | |||||||||||||
| 43 | } else { | |||||||||||||
| 44 | # Standard album just uses the defaults | |||||||||||||
| 45 | # You can leave out the if's above and just use this | |||||||||||||
| 46 | print $album->render(header => 1); | |||||||||||||
| 47 | } | |||||||||||||
| 48 | ||||||||||||||
| 49 | =head1 REQUIREMENTS | |||||||||||||
| 50 | ||||||||||||||
| 51 |  This module requires B | 
|||||||||||||
| 52 | ||||||||||||||
| 53 | =head1 DESCRIPTION | |||||||||||||
| 54 | ||||||||||||||
| 55 | Admittedly a somewhat special-purpose module, this is designed to | |||||||||||||
| 56 | dynamically create and display a photo album. Actually, it manages | |||||||||||||
| 57 | multiple photo albums, each of which can be independently formatted | |||||||||||||
| 58 | and navigated. | |||||||||||||
| 59 | ||||||||||||||
| 60 | Basic usage of this module amounts to the examples shown above. This | |||||||||||||
| 61 | module supports table-based thumbnail pages, auto-pagination, and slideshows. | |||||||||||||
| 62 | The HTML produced is fully-customizable. It should be all you need for | |||||||||||||
| 63 | creating online photo albums (besides the pictures, of course). | |||||||||||||
| 64 | ||||||||||||||
| 65 | The directory structure of a basic album looks like this: | |||||||||||||
| 66 | ||||||||||||||
| 67 | albums/ | |||||||||||||
| 68 | index.cgi (your script) | |||||||||||||
| 69 | hawaii_trip/ | |||||||||||||
| 70 | captions.txt (optional) | |||||||||||||
| 71 | intro.html (optional) | |||||||||||||
| 72 | image001.jpg | |||||||||||||
| 73 | image001.sm.jpg | |||||||||||||
| 74 | image002.gif | |||||||||||||
| 75 | image002-mini.jpg | |||||||||||||
| 76 | pict0003.jpeg | |||||||||||||
| 77 | pict0003.sm.png | |||||||||||||
| 78 | dsc00004.png | |||||||||||||
| 79 | dsc00004.thumb.gif | |||||||||||||
| 80 | xmas_2001/ | |||||||||||||
| 81 | captions.txt | |||||||||||||
| 82 | pic0001.jpg | |||||||||||||
| 83 | pic0001.sm.jpg | |||||||||||||
| 84 | pic0002.jpg | |||||||||||||
| 85 | pic0002.sm.jpg | |||||||||||||
| 86 | pic0004.png | |||||||||||||
| 87 | pic0004.mini.png | |||||||||||||
| 88 | ||||||||||||||
| 89 | You'll probably end up choosing just one naming scheme for your images, | |||||||||||||
| 90 |  but the point is that C | 
|||||||||||||
| 91 | all of them or any combination thereof. What happens is that the | |||||||||||||
| 92 | module looks in the dir that you specify and does an ASCII sort | |||||||||||||
| 93 | on the files. Anything that looks like a valid web image (ends in | |||||||||||||
| 94 | C<.jpe?g>, C<.gif>, or C<.png>) will be indexed and displayed. | |||||||||||||
| 95 | Then, it does basenames on the images and looks for their | |||||||||||||
| 96 | thumbnails, if present. If there are no thumbnails you get a generic | |||||||||||||
| 97 | link that says "Image 4" or whatever. | |||||||||||||
| 98 | ||||||||||||||
| 99 |  An optional C | 
|||||||||||||
| 100 | well. If this file is present, you can specify captions that will be | |||||||||||||
| 101 | placed beneath each of the images. For example: | |||||||||||||
| 102 | ||||||||||||||
| 103 | # Sample captions.txt file | |||||||||||||
| 104 | image001 Us atop Haleakala | |||||||||||||
| 105 | image002 Sunset from Maui | |||||||||||||
| 106 | pict0003 Hiking on Kauai | |||||||||||||
| 107 | dsc00004 Snorkeling on Hawaii | |||||||||||||
| 108 | ||||||||||||||
| 109 |  Also, if the optional C | 
|||||||||||||
| 110 | then that will be shown as the first page, with a link at the bottom | |||||||||||||
| 111 | that says "See the Pictures". This allows you to put introductory HTML | |||||||||||||
| 112 | to tell about your photos. You can put any HTML you want into this file. | |||||||||||||
| 113 | ||||||||||||||
| 114 | This module attempts to give you a lot of fine-grained control over | |||||||||||||
| 115 | image placement and layout while still keeping it simple. You should | |||||||||||||
| 116 | be able to place images and cells in tables fairly precisely. | |||||||||||||
| 117 | ||||||||||||||
| 118 | =cut | |||||||||||||
| 119 | ||||||||||||||
| 120 | 1 | 1 | 21483 | use 5.004; | ||||||||||
| 1 | 4 | |||||||||||||
| 1 | 54 | |||||||||||||
| 121 | 1 | 1 | 7 | use Carp; | ||||||||||
| 1 | 2 | |||||||||||||
| 1 | 82 | |||||||||||||
| 122 | 1 | 1 | 5 | use strict; | ||||||||||
| 1 | 11 | |||||||||||||
| 1 | 38 | |||||||||||||
| 123 | 1 | 1 | 6 | use vars qw($VERSION); | ||||||||||
| 1 | 2 | |||||||||||||
| 1 | 120 | |||||||||||||
| 124 | $VERSION = do { my @r=(q$Revision: 1.20 $=~/\d+/g); sprintf "%d."."%02d"x$#r,@r }; | |||||||||||||
| 125 | ||||||||||||||
| 126 | # Must twiddle CGI a lot so must include this | |||||||||||||
| 127 | 1 | 1 | 5608 | use CGI; | ||||||||||
| 1 | 24165 | |||||||||||||
| 1 | 7 | |||||||||||||
| 128 | 1 | 1 | 1137 | use CGI::FormBuilder; | ||||||||||
| 1 | 34221 | |||||||||||||
| 1 | 1019 | |||||||||||||
| 129 | ||||||||||||||
| 130 | # The global %CONFIG hash contains pairs of key/value thingies | |||||||||||||
| 131 | # that serve as defaults if stuff is not specified. | |||||||||||||
| 132 | ||||||||||||||
| 133 | my %CONFIG = ( | |||||||||||||
| 134 | dir => '.', | |||||||||||||
| 135 | header => 0, | |||||||||||||
| 136 | eachrow => 4, | |||||||||||||
| 137 | eachpage => 16, | |||||||||||||
| 138 | navbar => 1, | |||||||||||||
| 139 | navwrap => 0, | |||||||||||||
| 140 | navfull => 1, | |||||||||||||
| 141 | prevtext => 'Prev', | |||||||||||||
| 142 | nexttext => 'Next', | |||||||||||||
| 143 | linktext => 'Image', | |||||||||||||
| 144 | ||||||||||||||
| 145 | # Preset HTML options | |||||||||||||
| 146 | body_bgcolor => 'white', | |||||||||||||
| 147 | font_face => 'arial,helvetica', | |||||||||||||
| 148 | div_align => 'center', | |||||||||||||
| 149 | td_align => 'center', | |||||||||||||
| 150 | td_valign => 'top', | |||||||||||||
| 151 | ||||||||||||||
| 152 | # These are technically options but completely unsupported | |||||||||||||
| 153 | thumbs => [qw( .thumb .mini .sm | |||||||||||||
| 154 | -thumb -mini -sm | |||||||||||||
| 155 | _thumb _mini _sm )], | |||||||||||||
| 156 | images => [qw( .jpg .jpeg .gif .png | |||||||||||||
| 157 | .mpg .mpeg .avi .mpa )], | |||||||||||||
| 158 | intro => 'intro.html', | |||||||||||||
| 159 | captions => 'captions.txt', | |||||||||||||
| 160 | ||||||||||||||
| 161 | ); | |||||||||||||
| 162 | ||||||||||||||
| 163 | # Internal tag routines stolen from CGI::FormBuilder, which | |||||||||||||
| 164 | # in turn stole them from CGI.pm | |||||||||||||
| 165 | ||||||||||||||
| 166 | sub _escapeurl ($) { | |||||||||||||
| 167 | # minimalist, not 100% correct, URL escaping | |||||||||||||
| 168 | 0 | 0 | 0 | my $toencode = shift || return undef; | ||||||||||
| 169 | 0 | $toencode =~ s!([^a-zA-Z0-9_,.-/])!sprintf("%%%02x",ord($1))!eg; | ||||||||||||
| 0 | ||||||||||||||
| 170 | 0 | return $toencode; | ||||||||||||
| 171 | } | |||||||||||||
| 172 | ||||||||||||||
| 173 | sub _escapehtml ($) { | |||||||||||||
| 174 | 0 | 0 | 0 | defined(my $toencode = shift) or return; | ||||||||||
| 175 | # must do these in order or the browser won't decode right | |||||||||||||
| 176 | 0 | $toencode =~ s!&!&!g; | ||||||||||||
| 177 | 0 | $toencode =~ s! | ||||||||||||
| 178 | 0 | $toencode =~ s!>!>!g; | ||||||||||||
| 179 | 0 | $toencode =~ s!"!"!g; | ||||||||||||
| 180 | 0 | return $toencode; | ||||||||||||
| 181 | } | |||||||||||||
| 182 | ||||||||||||||
| 183 | sub _tag ($;@) { | |||||||||||||
| 184 | # called as _tag('tagname', %attr) | |||||||||||||
| 185 | # creates an HTML tag on the fly, quick and dirty | |||||||||||||
| 186 | 0 | 0 | 0 | my $name = shift || return; | ||||||||||
| 187 | 0 | my @tag = (); | ||||||||||||
| 188 | 0 | 0 | my @args = ref $_[0] eq 'HASH' ? %{$_[0]} : @_; | |||||||||||
| 0 | ||||||||||||||
| 189 | 0 | while (@args) { | ||||||||||||
| 190 | # this cleans out all the internal junk kept in each data | |||||||||||||
| 191 | # element, returning everything else (for an html tag) | |||||||||||||
| 192 | 0 | my $key = shift @args; | ||||||||||||
| 193 | 0 | my $val = _escapehtml shift @args; # minimalist HTML escaping | ||||||||||||
| 194 | 0 | 0 | 0 | next unless $key && $val; | ||||||||||
| 195 | 0 | push @tag, qq($key="$val"); | ||||||||||||
| 196 | } | |||||||||||||
| 197 | 0 | return '<' . join(' ', $name, sort @tag) . '>'; | ||||||||||||
| 198 | } | |||||||||||||
| 199 | ||||||||||||||
| 200 | sub _round (@) { | |||||||||||||
| 201 | 0 | 0 | my($int,$dec) = split '\.', shift; | |||||||||||
| 202 | 0 | 0 | $int++ if $dec >= 5; | |||||||||||
| 203 | 0 | return $int; | ||||||||||||
| 204 | } | |||||||||||||
| 205 | ||||||||||||||
| 206 | sub error_404 { | |||||||||||||
| 207 | 0 | 0 | 0 | my $self = shift; | ||||||||||
| 208 | 0 | 0 | my $mesg = shift || "The requested album or image was not found."; | |||||||||||
| 209 | 0 | my $real = shift; | ||||||||||||
| 210 | 0 | 0 | my $mail = $ENV{SERVER_ADMIN} =~ /\@/ | |||||||||||
| 211 | ? qq($ENV{SERVER_ADMIN}) | |||||||||||||
| 212 | : "the webmaster"; | |||||||||||||
| 213 | 0 |      print < | ||||||||||||
| 214 | Status: 404 Not Found | |||||||||||||
| 215 | Content-type: text/html | |||||||||||||
| 216 | ||||||||||||||
| 217 |   | 
|||||||||||||
| 218 | ||||||||||||||
| 219 |  404 Not Found | 
|||||||||||||
| 220 | $mesg | |||||||||||||
| 221 |   
  | 
|||||||||||||
| 222 | Click here to start over, or hit "Back" on your browser. | |||||||||||||
| 223 |   
  | 
|||||||||||||
| 224 | Please contact $mail for more details. | |||||||||||||
| 225 | ||||||||||||||
| 226 | EOH | |||||||||||||
| 227 | 0 | 0 | carp "[HTML::PhotoAlbum] $real" if $real; # optional message | |||||||||||
| 228 | 0 | exit 0; | ||||||||||||
| 229 | } | |||||||||||||
| 230 | ||||||||||||||
| 231 | sub file2hash ($) { | |||||||||||||
| 232 | 0 | 0 | 0 | my $self = shift; | ||||||||||
| 233 | 0 | my $file = shift; | ||||||||||||
| 234 | 0 | my %data = (); | ||||||||||||
| 235 | 0 | 0 | open FILE, "<$file" | |||||||||||
| 236 | or $self->error_404("Sorry, cannot access photo albums.", "Can't read $file: $!"); | |||||||||||||
| 237 | 0 |      while ( | 
||||||||||||
| 238 | 0 |  		warn " | 
||||||||||||
| 239 | 0 | 0 | 0 | next if /^\s*#/ || /^\s*$/; | ||||||||||
| 240 | 0 | chomp; | ||||||||||||
| 241 | 0 | my($k,$v) = split /\s+/, $_, 2; | ||||||||||||
| 242 | #$c =~ s!$image_pat!!; # lose any file suffix - slow | |||||||||||||
| 243 | # fix encoding of path | |||||||||||||
| 244 | ||||||||||||||
| 245 | 0 | 0 | carp "[HTML::PhotoAlbum] Warning: duplicate value for '$k' found in $file" if $data{$k}; | |||||||||||
| 246 | 0 | warn "\$data{$k} = $v;"; | ||||||||||||
| 247 | 0 | $data{$k} = $v; | ||||||||||||
| 248 | } | |||||||||||||
| 249 | 0 | close FILE; | ||||||||||||
| 250 | 0 | 0 | return wantarray ? %data : \%data; | |||||||||||
| 251 | } | |||||||||||||
| 252 | ||||||||||||||
| 253 | =head1 FUNCTIONS | |||||||||||||
| 254 | ||||||||||||||
| 255 | =head2 new(opt => val, opt => val) | |||||||||||||
| 256 | ||||||||||||||
| 257 |  Create a new C | 
|||||||||||||
| 258 |  you need to specify is the C | 
|||||||||||||
| 259 | which albums you're going to allow indexing: | |||||||||||||
| 260 | ||||||||||||||
| 261 | my $album = HTML::PhotoAlbum->new( | |||||||||||||
| 262 | albums => { | |||||||||||||
| 263 | dir1 => "My First Album", | |||||||||||||
| 264 | dir2 => "My Second Album" | |||||||||||||
| 265 | } | |||||||||||||
| 266 | ); | |||||||||||||
| 267 | ||||||||||||||
| 268 |  The C | 
|||||||||||||
| 269 | ||||||||||||||
| 270 | =over | |||||||||||||
| 271 | ||||||||||||||
| 272 | =item albums => { dir => 'Title', dir => 'Title' } | |||||||||||||
| 273 | ||||||||||||||
| 274 | This accepts a hashref holding subdir and title pairs. Each of | |||||||||||||
| 275 |  the subdirs must live beneath C<"."> (or whatever you set C | 
|||||||||||||
| 276 | to below). The title is what will be displayed as the album | |||||||||||||
| 277 | title both in the thumbnails page as well as the navigation bar. | |||||||||||||
| 278 | ||||||||||||||
| 279 | You can also specify a filename, in which case it will be read | |||||||||||||
| 280 | for the names of the albums. The format is the same as the | |||||||||||||
| 281 |  C | 
|||||||||||||
| 282 | ||||||||||||||
| 283 | # Sample albums.txt file | |||||||||||||
| 284 | sf_trip San Francisco Trip | |||||||||||||
| 285 | sjc_vac San Jose Vacation | |||||||||||||
| 286 | ||||||||||||||
| 287 | You would then use this like so: | |||||||||||||
| 288 | ||||||||||||||
| 289 | my $album = HTML::PhotoAlbum->new(albums => 'albums.txt'); | |||||||||||||
| 290 | ||||||||||||||
| 291 | If you have a lot of albums, this will allow less code maintenance | |||||||||||||
| 292 | in the long run. | |||||||||||||
| 293 | ||||||||||||||
| 294 | =item dir => $path | |||||||||||||
| 295 | ||||||||||||||
| 296 | The directory holding the images. This defaults to C<".">, meaning | |||||||||||||
| 297 | it assumes your CGI script lives at the top level of your albums | |||||||||||||
| 298 | directory (as shown above). If you mess with this, you must | |||||||||||||
| 299 | understand that this directory must be visible from the web as a | |||||||||||||
| 300 | URL. It is recommended that you don't mess with this. | |||||||||||||
| 301 | ||||||||||||||
| 302 | =back | |||||||||||||
| 303 | ||||||||||||||
| 304 | =cut | |||||||||||||
| 305 | ||||||||||||||
| 306 | sub new { | |||||||||||||
| 307 | 0 | 0 | 1 | my $class = shift; | ||||||||||
| 308 | 0 | 0 | my $self = bless {}, ref $class || $class; | |||||||||||
| 309 | 0 | $self->{opt} = { %CONFIG, @_ }; # remainder of args are key/val | ||||||||||||
| 310 | 0 | $self->{data} = []; # holds all the images/etc | ||||||||||||
| 311 | 0 | push @{$self->{data}}, []; # blank first element so data @ 1 | ||||||||||||
| 0 | ||||||||||||||
| 312 | ||||||||||||||
| 313 | 0 | $self->{cgi} = new CGI; | ||||||||||||
| 314 | 0 | $self->{script} = $self->{cgi}->script_name; | ||||||||||||
| 315 | ||||||||||||||
| 316 | # Check for whether our 'albums' option is a hashref or not; if not, | |||||||||||||
| 317 | # assume it's a filename and read it in verbatim | |||||||||||||
| 318 | 0 | 0 | unless (ref $self->{opt}{albums} eq 'HASH') { | |||||||||||
| 319 | 0 | $self->{opt}{albums} = $self->file2hash($self->{opt}{albums}); | ||||||||||||
| 320 | } | |||||||||||||
| 321 | ||||||||||||||
| 322 | # Populate our data if we have an album | |||||||||||||
| 323 | 0 | 0 | if (my $album = $self->{cgi}->param('album')) { | |||||||||||
| 324 | ||||||||||||||
| 325 | 1 | 1 | 1203 | use Data::Dumper; | ||||||||||
| 1 | 9860 | |||||||||||||
| 1 | 3300 | |||||||||||||
| 326 | 0 | warn Dumper($self->{opt}{albums}{$album}); | ||||||||||||
| 327 | ||||||||||||||
| 328 | # If not allowed, show forbidden | |||||||||||||
| 329 | 0 | 0 | $self->error_404("Sorry, that is not a valid photo album.", | |||||||||||
| 330 | "Album $album not specified in albums option to new()") | |||||||||||||
| 331 | unless $self->{opt}{albums}{$album}; | |||||||||||||
| 332 | ||||||||||||||
| 333 | # Always need the album dir | |||||||||||||
| 334 | 0 | my $albumdir = $self->{cgi}->unescape("$self->{opt}{dir}/$album"); | ||||||||||||
| 335 | ||||||||||||||
| 336 | # Now, try to get to directory and populate all our data | |||||||||||||
| 337 | # We must populate data before our navbar or else we won't | |||||||||||||
| 338 | # be able to know what we should be generating... | |||||||||||||
| 339 | 0 | 0 | opendir ALBUM, $albumdir or $self->error_404("Sorry, that is not a valid photo album.", | |||||||||||
| 340 | "Cannot read directory $albumdir: $!"); | |||||||||||||
| 341 | ||||||||||||||
| 342 | # We want to just get our images out | |||||||||||||
| 343 | 0 | my $image_pat = join '|', @{ $self->{opt}{images} }; | ||||||||||||
| 0 | ||||||||||||||
| 344 | 0 | my $thumb_pat = join '|', @{ $self->{opt}{thumbs} }; | ||||||||||||
| 0 | ||||||||||||||
| 345 | ||||||||||||||
| 346 | # Real quick - any captions.txt file? | |||||||||||||
| 347 | 0 | my %captions = (); | ||||||||||||
| 348 | 0 | 0 | if (-s "$albumdir/$self->{opt}{captions}") { | |||||||||||
| 349 | 0 | %captions = $self->file2hash("$albumdir/$self->{opt}{captions}"); | ||||||||||||
| 350 | } | |||||||||||||
| 351 | ||||||||||||||
| 352 | 0 | for my $image (sort grep /(?:$image_pat)$/, readdir ALBUM) { | ||||||||||||
| 353 | ||||||||||||||
| 354 | # skip thumbs (get below) | |||||||||||||
| 355 | 0 | 0 | next if $image =~ /(?:$thumb_pat)(?:$image_pat)$/; | |||||||||||
| 356 | ||||||||||||||
| 357 | # chop apart the image name into a basename and suffix | |||||||||||||
| 358 | 0 | my($basename, $suffix) = $image =~ /(.*?)($image_pat)$/; | ||||||||||||
| 359 | ||||||||||||||
| 360 | # Look for a thumbnail | |||||||||||||
| 361 | 0 | my $image = "$basename$suffix"; | ||||||||||||
| 362 | 0 | my $thumb = ''; | ||||||||||||
| 363 | 0 | for my $thsuf ( @{$self->{opt}{thumbs}} ) { | ||||||||||||
| 0 | ||||||||||||||
| 364 | 0 | 0 | if ( -s "$albumdir/$basename$thsuf$suffix" ) { | |||||||||||
| 365 | 0 | $thumb = "$albumdir/$basename$thsuf$suffix"; | ||||||||||||
| 366 | } | |||||||||||||
| 367 | } | |||||||||||||
| 368 | ||||||||||||||
| 369 | # check to see if we have a caption | |||||||||||||
| 370 | 0 | 0 | my $caption = $captions{$basename} || $self->{opt}{nocaption}; | |||||||||||
| 371 | ||||||||||||||
| 372 | # put all our thumbs onto an ordered array | |||||||||||||
| 373 | # each element of the array is an array ref which points | |||||||||||||
| 374 | # to the thumbnail name, the image name, and the caption | |||||||||||||
| 375 | 0 | push @{$self->{data}}, [ $thumb, $image, $caption ]; | ||||||||||||
| 0 | ||||||||||||||
| 376 | } | |||||||||||||
| 377 | 0 | closedir ALBUM; | ||||||||||||
| 378 | } | |||||||||||||
| 379 | ||||||||||||||
| 380 | 0 | return $self; | ||||||||||||
| 381 | } | |||||||||||||
| 382 | ||||||||||||||
| 383 | =head2 render(opt => val, opt => val) | |||||||||||||
| 384 | ||||||||||||||
| 385 |  The C | 
|||||||||||||
| 386 | for the actual pages. It returns a string, which can then be | |||||||||||||
| 387 | printed out like so: | |||||||||||||
| 388 | ||||||||||||||
| 389 | print $album->render(header => 1); | |||||||||||||
| 390 | ||||||||||||||
| 391 | This method takes a number of options which allow you to tweak | |||||||||||||
| 392 | the formatting of the HTML produced: | |||||||||||||
| 393 | ||||||||||||||
| 394 | =over | |||||||||||||
| 395 | ||||||||||||||
| 396 | =item eachrow => $num | |||||||||||||
| 397 | ||||||||||||||
| 398 | The number of images to put in each row of the thumbnail page. | |||||||||||||
| 399 | Defaults to 4. | |||||||||||||
| 400 | ||||||||||||||
| 401 | =item eachpage => $num | |||||||||||||
| 402 | ||||||||||||||
| 403 | The number of images to display on each thumbnail page. | |||||||||||||
| 404 |  Defaults to 16. This should be a multiple of C | 
|||||||||||||
| 405 | doesn't have to be. | |||||||||||||
| 406 | ||||||||||||||
| 407 | =item header => 1 | 0 | |||||||||||||
| 408 | ||||||||||||||
| 409 | If set to 1, a "Content-type" header and HTML title will be | |||||||||||||
| 410 | printed out, meaning you don't have to do this yourself. | |||||||||||||
| 411 | Defaults to 0. | |||||||||||||
| 412 | ||||||||||||||
| 413 | =item navwrap => 1 | 0 | |||||||||||||
| 414 | ||||||||||||||
| 415 | If set to 1, the navigation bar will wrap from last page to | |||||||||||||
| 416 | the first for both thumbnails and full-size images. Defaults | |||||||||||||
| 417 | to 0. | |||||||||||||
| 418 | ||||||||||||||
| 419 | =item navfull => 1 | 0 | |||||||||||||
| 420 | ||||||||||||||
| 421 |  If set to 0, then a navigation page will I | 
|||||||||||||
| 422 | for the full-size images. Instead, the thumbnail pages will | |||||||||||||
| 423 | link to the full-size images directly. | |||||||||||||
| 424 | ||||||||||||||
| 425 | =item linktext => $string | |||||||||||||
| 426 | ||||||||||||||
| 427 | Printed out followed by a number if no thumbnail is found. | |||||||||||||
| 428 | Defaults to "Image". | |||||||||||||
| 429 | ||||||||||||||
| 430 | =item nexttext => $string | |||||||||||||
| 431 | ||||||||||||||
| 432 | The text for the "next page" link. Defaults to "Next". Note | |||||||||||||
| 433 | you can do snazzy navigation by doing something tricky like | |||||||||||||
| 434 | this: | |||||||||||||
| 435 | ||||||||||||||
| 436 |      nexttext => " "  | 
|||||||||||||
| 437 | ||||||||||||||
| 438 | But don't tell anyone I said that. | |||||||||||||
| 439 | ||||||||||||||
| 440 | =item prevtext => $string | |||||||||||||
| 441 | ||||||||||||||
| 442 | The text for the "previous page" link. Defaults to "Prev". | |||||||||||||
| 443 | ||||||||||||||
| 444 | =back | |||||||||||||
| 445 | ||||||||||||||
| 446 | In addition, you can specify tags for any HTML element in one | |||||||||||||
| 447 |  of two ways. This is stolen directly from L | 
|||||||||||||
| 448 | First, you can specify them as "tag_attr", for example: | |||||||||||||
| 449 | ||||||||||||||
| 450 | body_alink => 'silver' # | |||||||||||||
| 451 | ||||||||||||||
| 452 | td_bgcolor => 'white' # | |||||||||||||
| 453 | ||||||||||||||
| 454 | font_face => 'arial', # | |||||||||||||
| 455 | font_size => '3' | |||||||||||||
| 456 | ||||||||||||||
| 457 | Or, you can point the tag name to an attr hashref. These would | |||||||||||||
| 458 | have the same effect as the above: | |||||||||||||
| 459 | ||||||||||||||
| 460 | body => { alink => 'silver' } | |||||||||||||
| 461 | ||||||||||||||
| 462 | td => { bgcolor => 'white' } | |||||||||||||
| 463 | ||||||||||||||
| 464 | font => { face => 'arial', size => 3 } | |||||||||||||
| 465 | ||||||||||||||
| 466 | These tags will then be changed appropriately in the HTML, allowing | |||||||||||||
| 467 | you to completely manipulate what the HTML that is printed out looks | |||||||||||||
| 468 | like. Several of these options are set by default to make the standard | |||||||||||||
| 469 | HTML look as nice as possible. | |||||||||||||
| 470 | ||||||||||||||
| 471 | =cut | |||||||||||||
| 472 | ||||||||||||||
| 473 | sub render { | |||||||||||||
| 474 | 0 | 0 | 1 | my $self = shift; | ||||||||||
| 475 | 0 | 0 | carp "Odd number of arguments passed into \$album->render" unless @_ % 2 == 0; | |||||||||||
| 476 | 0 | my %opt = ( %{$self->{opt}}, @_ ); # rest are option => 'value' pairs | ||||||||||||
| 0 | ||||||||||||||
| 477 | ||||||||||||||
| 478 | # lose fucking uninitialized warnings | |||||||||||||
| 479 | 0 | local $^W = 0; | ||||||||||||
| 480 | ||||||||||||||
| 481 | # We print out a navigational form up top of each page | |||||||||||||
| 482 | 0 | my $navform = CGI::FormBuilder->new(fields => [qw/album/], params => $self->{cgi}); | ||||||||||||
| 483 | ||||||||||||||
| 484 | # What will be printed out | |||||||||||||
| 485 | 0 | my @print = (); | ||||||||||||
| 486 | ||||||||||||||
| 487 | # Re-parse our %opt to look for things that resemble HTML tags, | |||||||||||||
| 488 | # since all our options are single words. Note that "htmltag => { hashref }" | |||||||||||||
| 489 | # is already implicitly handled by the simple %opt = assign way at the top. | |||||||||||||
| 490 | # All the "||=" parts are needed so that our defaults don't kill customs | |||||||||||||
| 491 | ||||||||||||||
| 492 | 0 | while (my($key, $value) = each %opt) { | ||||||||||||
| 493 | 0 | 0 | if ($key =~ /^([a-zA-Z]+)_(\w+)/) { | |||||||||||
| 0 | ||||||||||||||
| 0 | ||||||||||||||
| 0 | ||||||||||||||
| 0 | ||||||||||||||
| 0 | ||||||||||||||
| 494 | # split up based on _ | |||||||||||||
| 495 | 0 | 0 | $opt{$1}{$2} ||= $value; | |||||||||||
| 496 | } elsif ($key eq 'font') { | |||||||||||||
| 497 | 0 | 0 | $opt{font}{face} ||= $value; | |||||||||||
| 498 | } elsif ($key eq 'bgcolor') { | |||||||||||||
| 499 | 0 | 0 | $opt{body}{bgcolor} ||= $value; | |||||||||||
| 500 | } elsif ($key eq 'width') { | |||||||||||||
| 501 | 0 | 0 | $opt{table}{width} ||= $value; | |||||||||||
| 502 | } elsif ($key eq 'align') { | |||||||||||||
| 503 | 0 | 0 | $opt{div}{align} ||= $value; | |||||||||||
| 504 | } elsif ($key eq 'center') { | |||||||||||||
| 505 | # super-special, undocumented for a reason | |||||||||||||
| 506 | 0 | 0 | 0 | $opt{div}{align} ||= $value ? 'center' : 'left'; | ||||||||||
| 507 | } | |||||||||||||
| 508 | } | |||||||||||||
| 509 | ||||||||||||||
| 510 | # Get any album if present via CGI | |||||||||||||
| 511 | 0 | 0 | my $album = $navform->field('album') || ''; | |||||||||||
| 512 | ||||||||||||||
| 513 | # See if we have a name text | |||||||||||||
| 514 | 0 | 0 | 0 | my $name = $album ? $self->{opt}{albums}{$album} || ucfirst $album | ||||||||||
| 515 | : 'Select a Photo Album'; | |||||||||||||
| 516 | ||||||||||||||
| 517 | # Extra meta gunk if slideshow | |||||||||||||
| 518 | 0 | my $head = ''; | ||||||||||||
| 519 | # Print a header if requested | |||||||||||||
| 520 | 0 | 0 | if ($opt{header}) { | |||||||||||
| 521 | 0 |          push @print, < | ||||||||||||
| 522 | Content-type: text/html | |||||||||||||
| 523 | ||||||||||||||
| 524 | ||||||||||||||
| 525 |  $head | 
|||||||||||||
| 526 | EOF | |||||||||||||
| 527 | 0 | push @print, _tag('body', $opt{body}), | ||||||||||||
| 528 | _tag('div', $opt{div}), | |||||||||||||
| 529 | _tag('font', $opt{font}); | |||||||||||||
| 530 | } | |||||||||||||
| 531 | ||||||||||||||
| 532 | # Closing copyright message | |||||||||||||
| 533 | 0 |      my $close = _tag('div', $opt{div}) . < | ||||||||||||
| 534 |   Generated by  | 
|||||||||||||
| 535 | HTML::PhotoAlbum | |||||||||||||
| 536 | by Nateware | |||||||||||||
| 537 | ||||||||||||||
| 538 | EOF | |||||||||||||
| 539 | ||||||||||||||
| 540 | # Add album select form | |||||||||||||
| 541 | 0 | $navform->field(name => 'album', options => $self->{opt}{albums}, type => 'select'); | ||||||||||||
| 542 | 0 | push @print, $navform->render(reset => 0, submit => 'View'); | ||||||||||||
| 543 | ||||||||||||||
| 544 | # Do we have an album? If so, keep going, otherwise print generic text | |||||||||||||
| 545 | 0 | 0 | if (! $album) { | |||||||||||
| 546 | 0 | push @print, qq(Please select a photo album from the list above and click "View".\n); | ||||||||||||
| 547 | } else { | |||||||||||||
| 548 | ||||||||||||||
| 549 | # Always need the album dir | |||||||||||||
| 550 | 0 | my $albumdir = "$self->{opt}{dir}/$album"; | ||||||||||||
| 551 | ||||||||||||||
| 552 | 0 | 0 | 0 | if ($self->{cgi}->param('image') || $self->{cgi}->param('slideshow')) { | ||||||||||
| 553 | 0 | 0 | my $img = $self->{cgi}->param('image') || ($opt{eachpage} * ($self->{cgi}->param('page') - 1) + 1); | |||||||||||
| 554 | ||||||||||||||
| 555 | # Print a single image out | |||||||||||||
| 556 | 0 | my $data = $self->{data}[$img]; | ||||||||||||
| 557 | ||||||||||||||
| 558 | # If the image doesn't exist, show 404 | |||||||||||||
| 559 | 0 | 0 | $self->error_404("Sorry, image $img was not found in the $name photo album.") | |||||||||||
| 560 | unless ref $data; | |||||||||||||
| 561 | ||||||||||||||
| 562 | # Boundary checks for min/max image | |||||||||||||
| 563 | 0 | my $nextimg = $img + 1; | ||||||||||||
| 564 | 0 | my $previmg = $img - 1; | ||||||||||||
| 565 | 0 | my $numimgs = @{$self->{data}} - 1; # length | ||||||||||||
| 0 | ||||||||||||||
| 566 | ||||||||||||||
| 567 | # Setup links just like for pages | |||||||||||||
| 568 | 0 | my($prevlink, $nextlink); | ||||||||||||
| 569 | 0 | 0 | if ($nextimg > $numimgs) { | |||||||||||
| 570 | 0 | 0 | if ($opt{navwrap}) { | |||||||||||
| 571 | 0 | $nextimg = 1; | ||||||||||||
| 572 | 0 | $nextlink = qq($opt{nexttext}); | ||||||||||||
| 573 | } else { | |||||||||||||
| 574 | 0 | $nextimg = undef; | ||||||||||||
| 575 | 0 | $prevlink = qq($opt{nexttext}); | ||||||||||||
| 576 | } | |||||||||||||
| 577 | } else { | |||||||||||||
| 578 | 0 | $nextlink = qq($opt{nexttext}); | ||||||||||||
| 579 | } | |||||||||||||
| 580 | ||||||||||||||
| 581 | # Setup links just like for pages | |||||||||||||
| 582 | 0 | 0 | if ($previmg < 1){ | |||||||||||
| 583 | 0 | 0 | if ($opt{navwrap}) { | |||||||||||
| 584 | 0 | $previmg = $numimgs; | ||||||||||||
| 585 | 0 | $prevlink = qq($opt{prevtext}); | ||||||||||||
| 586 | } else { | |||||||||||||
| 587 | 0 | $previmg = undef; | ||||||||||||
| 588 | 0 | $prevlink = qq($opt{prevtext}); | ||||||||||||
| 589 | } | |||||||||||||
| 590 | } else { | |||||||||||||
| 591 | 0 | $prevlink = qq($opt{prevtext}); | ||||||||||||
| 592 | } | |||||||||||||
| 593 | ||||||||||||||
| 594 | # Print out slideshow stuff | |||||||||||||
| 595 | 0 | 0 | 0 | if ($self->{cgi}->param('slideshow') && $nextimg && $self->{cgi}->param('submit') ne 'Stop') { | ||||||||||
| 0 | ||||||||||||||
| 596 | 0 | my $sec = $self->{cgi}->param('slideshow'); | ||||||||||||
| 597 | 0 | push @print, qq( 598 | . qq(url=$self->{script}?album=$album&image=$nextimg&slideshow=$sec">); | |||||||||||
| 599 | } | |||||||||||||
| 600 | ||||||||||||||
| 601 | # Figure out what page we'd be one | |||||||||||||
| 602 | 0 | my $page = int(($img - 1) / $opt{eachpage}) + 1; | ||||||||||||
| 603 | ||||||||||||||
| 604 | # Now print out HTML, nice and simple | |||||||||||||
| 605 | 0 | 0 |              my $caption = $data->[2] ? " $data->[2]" : '';  | 
|||||||||||
| 606 | 0 |              push @print, < | ||||||||||||
| 607 |  $name - Image $img of $numimgs | 
|||||||||||||
| 608 |  $prevlink | Back to Page $page | $nextlink  
  | 
|||||||||||||
| 609 |   | 
|||||||||||||
| 610 | EOF | |||||||||||||
| 611 | ||||||||||||||
| 612 | } else { | |||||||||||||
| 613 | ||||||||||||||
| 614 | # Print the whole album w/ thumbs out | |||||||||||||
| 615 | 0 | my $numpages = _round @{$self->{data}} / $opt{eachpage}; | ||||||||||||
| 0 | ||||||||||||||
| 616 | ||||||||||||||
| 617 | # Setup a couple vars and a title | |||||||||||||
| 618 | 0 | my $page = 0; | ||||||||||||
| 619 | 0 | 0 | unless ($page = $self->{cgi}->param('page')) { | |||||||||||
| 620 | 0 | 0 | if (-f "$albumdir/$opt{intro}") { | |||||||||||
| 621 | 0 | 0 | if (open INTRO, "<$albumdir/$opt{intro}") { | |||||||||||
| 622 | 0 |                          push @print, '',  | 
||||||||||||
| 623 | 0 | push @print, _tag('div', $opt{div}), | ||||||||||||
| 624 |                              qq( See the Pictures\n);  | 
|||||||||||||
| 625 | 0 | push @print, $close; | ||||||||||||
| 626 | 0 | close INTRO; | ||||||||||||
| 627 | 0 | 0 | return wantarray ? @print : join '', @print; | |||||||||||
| 628 | } else { | |||||||||||||
| 629 | 0 | carp "[HTML::PhotoAlbum] Warning: $albumdir/$opt{intro} present but unreadable: $!"; | ||||||||||||
| 630 | } | |||||||||||||
| 631 | } | |||||||||||||
| 632 | 0 | $page = 1; | ||||||||||||
| 633 | } | |||||||||||||
| 634 | ||||||||||||||
| 635 | 0 | 0 | 0 | $self->error_404("Sorry, we could not find page $page of the $name photo album.") | ||||||||||
| 636 | unless $page >= 0 && $page <= $numpages; | |||||||||||||
| 637 | 0 |              push @print, "\n$name - Page $page of $numpages\n"; | 
||||||||||||
| 638 | ||||||||||||||
| 639 | # Print a navbar? | |||||||||||||
| 640 | 0 | 0 | if ($opt{navbar}) { | |||||||||||
| 641 | ||||||||||||||
| 642 | # We setup our pages, tweak our page CGI param, then generate query_string | |||||||||||||
| 643 | 0 | my $nextpage = $page + 1; | ||||||||||||
| 644 | 0 | my $prevpage = $page - 1; | ||||||||||||
| 645 | #push @print, "\n"; | |||||||||||||
| 646 | ||||||||||||||
| 647 | # Sanity check: See if the previous page is less than 1, | |||||||||||||
| 648 | 0 | my($prevlink, $nextlink); | ||||||||||||
| 649 | 0 | 0 | if ($page - 1 > 0) { | |||||||||||
| 0 | ||||||||||||||
| 650 | 0 | $prevlink = qq($opt{prevtext}); | ||||||||||||
| 651 | } elsif ($opt{navwrap}) { | |||||||||||||
| 652 | 0 | $prevlink = qq($opt{prevtext}); | ||||||||||||
| 653 | } else { | |||||||||||||
| 654 | 0 | $prevlink = qq($opt{prevtext}); | ||||||||||||
| 655 | } | |||||||||||||
| 656 | ||||||||||||||
| 657 | # And if the next page is bigger than how many we have | |||||||||||||
| 658 | 0 | 0 | if ($page == $numpages) { | |||||||||||
| 659 | 0 | 0 | if ($opt{navwrap}) { | |||||||||||
| 660 | 0 | $nextlink = qq($opt{nexttext}); | ||||||||||||
| 661 | } else { | |||||||||||||
| 662 | 0 | $nextlink = qq($opt{nexttext}); | ||||||||||||
| 663 | } | |||||||||||||
| 664 | } else { | |||||||||||||
| 665 | 0 | $nextlink = qq($opt{nexttext}); | ||||||||||||
| 666 | } | |||||||||||||
| 667 | ||||||||||||||
| 668 | # Finally, push together a list of page numbers | |||||||||||||
| 669 | 0 | my $pagelinks; | ||||||||||||
| 670 | 0 | for (my $i=1; $i <= $numpages; $i++) { | ||||||||||||
| 671 | #push @print, "\n"; | |||||||||||||
| 672 | 0 | 0 | if ($i == $page) { | |||||||||||
| 673 | 0 | $pagelinks .= qq( | $i); | ||||||||||||
| 674 | } else { | |||||||||||||
| 675 | 0 | $pagelinks .= qq( | $i); | ||||||||||||
| 676 | } | |||||||||||||
| 677 | } | |||||||||||||
| 678 | ||||||||||||||
| 679 | 0 |                  push @print, qq($prevlink $pagelinks | $nextlink  \n);  | 
||||||||||||
| 680 | } | |||||||||||||
| 681 | ||||||||||||||
| 682 | # Browsers should always render tables correctly based | |||||||||||||
| 683 | # on the individual | and | ||||||||||||
| 684 | 0 | push @print, _tag('table', $opt{table}); | ||||||||||||
| 685 | ||||||||||||||
| 686 | # Here we take a slice of the data based on our | |||||||||||||
| 687 | # page and eachpage definitions | |||||||||||||
| 688 | 0 | my $first_img = $opt{eachpage} * ($page - 1) + 1; | ||||||||||||
| 689 | 0 | my $last_img = $opt{eachpage} + $first_img - 1; | ||||||||||||
| 690 | #push @print, "\n"; | |||||||||||||
| 691 | ||||||||||||||
| 692 | 0 | my $i = 0; | ||||||||||||
| 693 | 0 | for my $data ( @{$self->{data}}[$first_img .. $last_img] ) { | ||||||||||||
| 0 | ||||||||||||||
| 694 | 0 | 0 | push @print, _tag('tr', $opt{tr}), "\n" if $i % $opt{eachrow} == 0; | |||||||||||
| 695 | ||||||||||||||
| 696 | # The for loop w/ slice will autoviv array elements if needed, so | |||||||||||||
| 697 | # we must explicitly check to see if there's really any data first | |||||||||||||
| 698 | 0 | 0 | if (ref $data) { | |||||||||||
| 699 | 0 | my $n = $first_img + $i; | ||||||||||||
| 700 | 0 | 0 | my $thlink = $data->[2] || "$opt{linktext} $n"; | |||||||||||
| 701 | 0 | my $caption = ''; | ||||||||||||
| 702 | 0 | 0 | if ($data->[0]) { | |||||||||||
| 703 | 0 | $opt{img}{src} = $data->[0]; | ||||||||||||
| 704 | 0 | $thlink = _tag('img', $opt{img}); | ||||||||||||
| 705 | 0 |                          $caption = qq( $data->[2]);  | 
||||||||||||
| 706 | } | |||||||||||||
| 707 | # This is the td for each image w/ a link to display | |||||||||||||
| 708 | 0 | push @print, _tag('td', $opt{td}), _tag('font', $opt{font}); | ||||||||||||
| 709 | ||||||||||||||
| 710 | # We change from an HTML nav page to a direct img link based on navfull | |||||||||||||
| 711 | 0 | 0 | my $imglink = $opt{navfull} | |||||||||||
| 712 | ? qq() | |||||||||||||
| 713 | : qq(); | |||||||||||||
| 714 | ||||||||||||||
| 715 | # Create the link | |||||||||||||
| 716 | 0 | push @print, qq($imglink$thlink$caption | \n);||||||||||||
| 717 | } else { | |||||||||||||
| 718 | 0 | push @print, qq( | \n); | |||||||||||
| 719 | } | |||||||||||||
| 720 | 0 | $i++; | ||||||||||||
| 721 | 0 | 0 | push @print, " | |||||||||||
| 722 | } | |||||||||||||
| 723 | ||||||||||||||
| 724 | # Close image table | |||||||||||||
| 725 | 0 | push @print, " |