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 | $caption | |||||||||||||
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, " |