Building the Photo Album Application

The photo album application is made up of several pieces. First, we have the generate.pl program. This is a command-line program that photo-album administrators use to generate the photo album from a directory of images. This program takes the images in the directory, converts them to the different sizes, moves them to where they need to be for the Web server, and adds the information to our data- base. This program takes a little while to run because of the image conversions it is doing.

The other pieces of the application are the admin.cgi script, used to add descriptions and comments for the photos, and the index.cgi script, which displays the images and albums.

The index.cgi and admin.cgi are 95 percent the same program, but they are made into two separate programs so that you can put the admin.cgi program in a safe place while index.cgi is available to the visitors of your album.

Let’s take a look at the generate.pl program first, since it is what actually generates the photo album when passed a directory of images.

Something to be aware of with this program is that when you point it at a directory to add to your album, it traverses the subdirectories in the directory you point to. If you do not want this to happen, put your photos temporarily into a directory when you add them to the album. New copies of the images are added to the photo album directory, so you can remove the temporary files once the program has finished generating the files.

To run the program, just do this:

./generate.pl "album name" directory 

The program handles the rest. As we’ll see in a minute, however, there are several things to set in the program so that it knows where to put the files it generates.

This program uses a few new Perl modules we have not used before. Imager.pm handles our image manipulation; Image::Info is used to get the EXIF information (like the day it was taken, the shutter speed, etc.) embedded in JPEG files; File::Basename handles getting the filename for us from a complete path; and File::Find is used to get the files in the subdirectories. Imager.pm and Image::Info are available from CPAN or ActiveState if you are on Windows. File::Find and File::Basename should be part of your Perl installation.

generate.pl

start example
01: #!/usr/bin/perl -w 02: # generate.pl 03: use strict; 04: use Image::Info qw(image_info dim); 05: use File::Basename; 06: use File::Find; 07: use File::Copy; 08: use Imager; 09: use DBI; 
end example

Line 1 tells the system where to find Perl and turns on warnings. Notice that we do not turn on taint checking with the –T flag here. We do this because this program should only be run manually by photo album administrators. Since this is not a program the users run, it is safe to leave off the taint checking.

Line 2 is a comment about the program.

Line 3 turns on strict mode. This helps ensure that all variables are declared before they are used. Also, it prevents many common programming errors.

Line 4 loads the Image::Info module and imports the image_info and dim functions.

Line 5 loads the File::Basename module so that we can easily split directory and filenames.

Line 6 loads the File::Find module so that we can easily get files and process them from directories.

Line 7 loads the File::Copy module so that we can easily copy files from one location to another on the file system.

Line 8 loads the Imager module. This module allows us to manipulate images.

Line 9 loads the DBI module so that we have database connectivity.

10: my $info; 11: my $album_name = shift(@ARGV) || die "Which album name?"; 12: my $album_id   = time(); 

Line 10 declares a scalar variable we’ll be using in this program.

Line 11 declares a scalar variable named $album_name and shifts in the first value from @ARGV. If nothing was passed the program will die and ask which album name.

Line 12 declares a scalar variable named $album_id and populates it with the current time value. This is the unique id that we use for this album. This is not a very clever trick, but since this program takes quite a bit of time to run, there is not a very good chance that more than one album will be generated per second.

13: ### Begin user modifications 14: my $relative_dir = "/photos"; 15: my $album_dir    = "/var/www/html/photos"; 16: my @SIZES        = qw(100 640 800); 17: ### End of user modifications

Line 13 is a comment telling the user that the area of the program he or she may need to modify is beginning.

Line 14 declares a scalar variable named $relative_dir and sets it to the value "/photos". This is the relative directory that goes in the URL when the browser is viewing the image. This is not the actual path to the directory; rather, it is the URL path.

Line 15 declares a scalar variable named $album_dir and sets it to the value "/var/www/html/photos". This is the absolute path to the directory that the files for the photo album are copied into.

Line 16 declares an array named @SIZES and loads it with the values 100, 640, and 800. These are the widths of the images this program generates. The width of 100 is the width of the thumbnail images displayed on the main album page.

Line 17 is simply a comment telling the program user that the manual-modifications section has ended.

18: $album_name      =~ s/ /\_/g; 19: $album_dir      .= "/$album_name"; 20: my $rel_dir      = "$relative_dir/$album_name";

Line 18 replaces any spaces in the album name with the underscore character. The =~ on this line is called the binding operator. It binds the regular expression on the right to the variable on the left. This makes it so that the regular expression acts upon $album_name instead of the default variable of $_.

Line 19 adds /$album_name to the end of the album directory. This occurs so we can generate the directory we need.

Line 20 sets up the relative directory to the album and stores that value in $rel_dir.

21: mkdir($album_dir) || die "Can’t mkdir: $!"   unless –e $album_dir; 22: my $source_dir = shift(@ARGV) || ".";

Line 21 uses the mkdir function to create the new album directory. This line then checks to see if the directory was actually created with the –e (which means exists). If the directory does not exist, then the program will die and print out an error message.

Line 22 looks at the value passed on the command line to see what directory we want to get the photos from. @ARGV initially has the album name in it, but when we call the shift function on line 10, we remove that value from @ARGV. This line sets $source_dir to the current directory unless a directory name is passed on the command line.

23: my $dbh = DBI->connect("DBI:mysql:photoalbum",       "bookuser","testpass") 24:     or die("Cannot connect: $DBI::errstr");

Lines 23 and 24 call the DBI->connect function to create a connection to the data- base. If there is a problem creating the database connection, the die on line 24 will cause the program to exit and display an error message.

25: my @EXIF  = qw(width height resolution Flash Model  26:                Make MaxApertureValue FNumber DateTime 27:                ISOSpeedRatings ExposureTime FocalLength);

Lines 25–27 declare a new array named @EXIF and set it to the values we want to get from the image. EXIF information is image metadata that is written into JPEG files. Many digital cameras put EXIF information into the images when they write them. The preceding list of EXIF information is not complete; these are simply the fields that have been added to this program. The Image::Info module is how you get this data, and doing a perldoc Image::Info gets you a whole slew of information about what it can extract from a JPEG image.

28: Add_Album(); 29: find({wanted => \&Wanted, follow => 1}, $source_dir); 30: print "\nDone generating new album...\n\n";

Line 28 calls the Add_Album subroutine. This subroutine adds the album information to the database.

Line 29 calls the find function. The values passed to find are a hash of parameters, and the directory to look at.

wanted => \&Wanted tells the program that we need to call the Wanted subroutine for each file we find.

follow => 1 mainly causes symbolic links to be followed, but also has a useful side effect: By saying follow => 1, the File::Find function also populates the $File::Find::fulldir variable—which we need so that we can get the com- plete path to the item. Without this, we do not get the full path information, only the relative path information.

@ARGV is the directory that we are getting photos from.

Line 30 prints a message telling the user that the program is done running. That is it! Well, the rest of the stuff this program does is stored in the following subroutines.

31: sub Add_Album { 32:     my $sql = qq{INSERT INTO album 33:                  (album_id, name, comments) 34:                 VALUES (?,?,?)};

Line 31 begins the Add_Album subroutine. This subroutine adds the album information to the database.

Lines 32–34 make up the SQL statement needed to add the album information to the database. On line 33, we use placeholders (that is, ?’s) in the SQL statement so that we can pass the values when we call the execute the function.

35:     my $sth_album = $dbh->prepare($sql); 36:     $sth_album->execute($album_id, $album_name, ‘’) 37:         or die("ERROR! $DBI::errstr\n"); 38: }

Line 35 calls the prepare function to get this SQL statement ready for execution. The value returned from the prepare function is a handle to the statement, which we store in the $sth_album scalar variable.

Line 36 calls the execute function on the statement handle and passes in the album id, name, and an empty string for the comments. These values replace the placeholders that are in the SQL statement.

Line 37 is a continuation of line 35. This causes the program to exit and to print an error message if there is a problem executing the SQL statement.

Line 38 closes the Add_Album subroutine.

39: sub Update_Database { 40:     my $photo = shift; 41:     my $p_dir = "$rel_dir/$photo";

Line 39 begins the Update_Database subroutine. This subroutine updates the database with the photo information. It is called for each photo.

Line 40 uses the shift function to get the name of the photo we are dealing with.

Line 41 declares a variable named $p_dir (photo directory) and sets it to the relative directory name, with the name of the photo added to it.

42:     my $sql1  = qq{INSERT INTO photo 43:             (img_id, width, height, resolution, flash,  44:              model, make, aperture, fnumber, img_date,  45:              iso_speed, exposure, focal_length, 46:              img_title, img_location, album_id, comments) 47:             VALUES 48:             (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)};

Lines 42–48 make up the SQL statement to insert the photo information into the database. It is rather large, but there is nothing different about this SQL INSERT statement when we compare it with our smaller INSERT statements.

49:     my $sth_photo = $dbh->prepare($sql1); 50:     my $id = $info->{DateTime}; 51:     $id    =~ s/[ \:]//g; 52:     $id   .= time();

Line 49 calls the prepare function and stores the result into the $sth_photo variable.

Line 50 declares a variable named $id and sets it to the value at $info->{DateTime}. This should contain the date and time this picture was taken.

Line 51 removes any spaces or colons that are in the $id variable. The DateTime value we get on line 50 is in the form of 2002:05:13 15:32:54, so we want to remove the extra stuff.

Line 52 appends the current time onto the $id variable. This should pretty much ensure that the value in $id is unique. We need it to be unique because this is going to be the primary key of our table.

53:     $sth_photo->execute($id, @$info{@EXIF}, ‘untitled’, 54:                         $p_dir, $album_id, ‘’) 55:                or die("ERROR! $DBI::errstr");

Line 53 calls the execute function and passes it the values to fill in all of the placeholders. The @$info{@EXIF} here is of importance. This is called a hash slice because it takes only a portion, or slice, of the hash. The %info hash contains all of the information we have gathered so far for this photo. At this point, we need to add the EXIF information to the database. We can $info{width}, $info{height}, and so on, but that requires too much typing. Instead, let’s take advantage of what Perl can do for us. We already have the field names (hash keys) in an array named @EXIF. So, when we create the SQL statement on lines 42–48, we do so in the same order the @EXIF array is in. Then, by calling the %info hash with the @ in front of it, we create the hash slice that contains all of the values from the %info hash with the keys in the @EXIF array.

Line 54 continues the execute statement from line 53. It adds a few other values that replace the placeholders.

Line 55 calls the die function if there are any errors executing this SQL statement.

56:     print "Record inserted into database...\n"; 57: }

Line 56 prints a message to the user so that he or she can see that an item has been inserted into the database.

Line 57 ends the Update_Database subroutine.

58: sub Massage_Data { 59:     my @RATIONAL = qw(FNumber FocalLength);

Line 58 begins the Massage_Data subroutine. This subroutine just takes the data passed to it and manipulates it how we need to, then sends it back.

Line 59 declares a new array named @RATIONAL and adds two values to it. These are the values that need to be rational numbers.

60:     for(@RATIONAL) { 61:         $info->{$_} = Rational($info->{$_},1); 62:     }

Line 60 begins a for loop that iterates over each of the values in the @RATIONAL array. If you have more values that need to be rational numbers, simply add them to the array on line 58.

Line 61 sets the current value of the %info hash to the value returned by the call to the Rational function on the right. The 1 in this function call is the number of digits we want after the decimal.

Line 62 closes this for loop.

63:     $info->{‘MaxApertureValue’}  64:       = sprintf("%.1f", (1.4142 **              $info->{‘MaxApertureValue’} )); 65: }

Line 63 takes the value at $info->{MaxApertureValue} and performs a calculation on it. This converts the aperture value from a regular decimal to the form that is commonly seen and more widely recognized.

Line 64 is a continuation of line 63. This line formats the value to conform to common photographic terminology.

Line 65 closes the Massage_Data function.

66: sub Rational { 67:   my ($in, $dec)    = @_; 68:   my ($num, $denom) = split(‘/’, $in); 69:   my  $fmt          = ‘%.’ . $dec . ‘f’;

Line 66 begins the Rational subroutine. This subroutine is used to convert the numbers passed to it into rational numbers.

Line 67 reads in the value ($in) and the number of decimal places ($dec) to format this number to.

Line 68 uses the split function to split the value passed to the function into a numerator ($num) and denominator ($denom).

Line 69 creates the format string for the sprintf function. This creates the %.1f or %.2f , and so on, values that we use in the sprintf function call.

70: return($denom == 0 ? "Err" : sprintf($fmt, $num/$denom)); 71: }

Line 70 checks to see if the denominator is 0. If it is, we return "Err" because we cannot divide by 0. Otherwise, we return the value returned by the sprintf call to the right of the colon.

Line 71 ends the Rational subroutine.

72: sub Img_Copy { 73:     my $source_file = shift;

Line 72 begins the Img_Copy subroutine. This subroutine is simply used to copy the unmodified image file into the album directory.

Line 73 uses the shift function to read in the value passed to the subroutine and stores it in the variable named $source_file.

74:     $source_file =~ /^([\w\-\/]+)\.(\w{1,4})$/; 75:     my $dest_file = $1 . ‘.’ . $2;

Line 74 filters the filename to remove any characters we don’t want. This regular expression can be a bit confusing, so here is how it breaks down.

$file =~ 

Uses the binding operator (=~) to bind the variable $file to the regular expression.

/^ 

The / begins the regular expression, and the caret (^) means that the following pattern must occur at the beginning of the string.

 ([\w\-\/]+)

The parentheses () cause the regular expression engine to store the match into the variables $1, $2, $3, and so on. Each new pair of parentheses increments which variable it is stored in. Since this is the first set of parentheses, the result is stored in $1.

The square brackets here ([]) are a character class. This means that whatever is inside them matches—it is like matching anything from a group (for example, all brunettes in a crowd). The term brunette is the character class.

The \w matches any word character. A word character consists of all letters, numbers, and the underscore.

The \- matches the dash.

The \/ matches a forward slash.

And the + after the closing square bracket literally means "match one or more of these characters."

\.

Next, the \. matches the dot that separates the filename and extension.

(\w{1,4})

The next set in parentheses matches on the \w or word characters. The {1,4} means to match at least one, but no more than four, characters. The results of this match are stored in $2 since this is the second set of parentheses in this regular expression. We don’t want to match more than four characters because we are really only looking for .jpg and .jpeg. So, if you want, you can change this to {3,4} and it should still work as needed—matching at least three, but no more than four, characters.

$/;

The $ anchors this last pattern to the end of the string. This means we are looking for the file extension at the very end of the string (where it should be).

The / closes the regular expression, and the ; ends this statement.

So, this line should basically get rid of any extra dots in the filename or illegal characters.

Line 75 creates a string containing the value in $1, a dot, and the value in $2 and stores the string in $dest_file. The $dest_file variable now holds the file name.

76:    copy($source_file, "$album_dir/" .          basename($dest_file)) or die "Cannot copy: $!";

Line 76 uses the copy function from the File::Copy module to copy the file from the source to the destination. The basename($dest_file) ensures that we get just the base filename and not the complete path information.

77:     print "Done writing $dest_file...\n"; 78: }

Line 77 prints a message to the user, telling him or her that the new file is done being written.

Line 78 closes the Img_Copy subroutine.

79: sub Resize { 80:     my ($file, $xfac) = @_; 81:     my $extn;

Line 79 begins the Resize subroutine.

Line 80 declares two new variables named $file and $xfac and copies the values passed to the subroutine into them. $file is the file name, and $xfac is the width of the new image.

Line 81 declares a new variable named $extn.

82:     my $img = Imager->new(); 83:     $img->open(file => $file) or die $img->errstr();

Line 82 creates a new Imager object and stores a reference to it in the $img variable.

Line 83 opens the image file or aborts the program if there is a problem opening the file.

84:     $file =~ /^([\w\-\/]+)\.(\w{1,4})$/; 85:     $file = $1; 86:     $file = basename($file); 87:     $extn = $2; 88:     $xfac =~ /^([\d]+)$/ || die "Oops!"; 89:     $xfac = $1;

Line 84 does the same thing as line 74 of this program. It basically splits the filename into the name and extension.

Line 85 sets the $file variable to $1, which is the filename without the extension.

Line 86 calls the basename function to remove any path information from $file.

Line 87 sets the $extn variable to $2, which should contain the file extension at this point.

Line 88 passes the $xfac variable through the regular expression. This regular expression ensures that $xfac contains only digits (\d), and calls die if it finds anything other than digits in $xfac.

Line 89 sets $xfac to the value in $1. The parentheses in the regular expression on line 91 capture any data that matches—this is the data we are looking for.

90:     my $newimg = $img->copy(); 91:     $newimg    = $img->scale(xpixels => $xfac);

Line 90 creates a copy of the image we are working with and stores a handle to it in $newimg.

Line 91 scales the new image so that the X-axis is $xfac pixels wide. The Y-axis scales proportionally, so we don’t have to worry about it in this application.

92:    $newimg->write(file => "$album_dir/$file.$xfac.$extn")  93:        or die $img->errstr();

Line 92 writes the new file to our album directory. The new file has the X axis value between the filename and the extension. This way, we can easily determine the width of this file. We’ll also use this value later to grab the image of the correct size.

Line 93 calls die if there is a problem writing to the file.

94     print "Done writing $file.$xfac.$extn...\n"; 95: }

Line 94 prints a message telling the user that the file is done being written.

Line 95 closes this subroutine.

96: sub Wanted { 97:     if( (/\.jpg|\.jpeg$/i) ) { 98:         my $file      = $_;

Line 96 begins the Wanted subroutine. This is the subroutine that gets called by File::Find to traverse the subdirectories and find the files.

Line 97 checks to see if the current file contains .jpg or .jpeg. If it does, this block of code is entered.

Line 98 sets the variable $file to the current value in $_, which is the filename.

99:         $info = undef; 100:         $info = image_info($File::Find::fullname); 

Line 99 sets the $info variable to undef. We need to do this so that no data from a previous file remains referenced by $info.

Line 100 sets the $info variable to the values returned by a call to the image_info subroutine. The image_info subroutine is part of the Image::Info module.

101:         die "Can’t parse image info: $info->{error}\n" 102:             if($info->{error});

Line 101 calls the die function to cause the program to abort if the program encounters an error getting the image information.

Line 102 is the condition for line 103. This line allows line 103 to execute if there is a value in $info->{error}.

103:         for my $width (@SIZES) { 104:             Resize ($File::Find::fullname, $width); 105:         }

Line 103 begins a for loop that loops through each value in the @SIZES array. Each time through the array, the current value is stored into $width. This array contains the image sizes that we want to generate from the original images and to store in our album.

Line 104 calls the Resize function on the current photo in $File::Find::fullname and passes the width in pixels in the $width variable.

Line 105 closes the loop.

106:         Massage_Data    (); 107:         Img_Copy        ($File::Find::fullname); 108:         Update_Database ($file);

Line 106 calls the Massage_Data subroutine to get the data into the format we want.

Line 107 calls the Img_Copy subroutine so that we can make a copy of the original image and place it in the album directory. We don’t want to move around or change the original photo, just in case something goes wrong.

Line 108 calls the Update_Database subroutine so that the photo information can be added to the database.

109:     print "\n"; 110:     } 111: } 

Line 109 prints a blank line.

Line 110 closes the if block of this subroutine.

Line 111 closes this subroutine.

And that is it for the generate.pl program. Once you have run this at least once to create an album, you can move to the next set of programs to view the photo or update the database data.

index.cgi is the CGI program that displays the photo to the user. It uses the BasicSession module we’ve used to store the user’s image-size preferences. Once we are done looking at this program, we’ll take a look at the admin.cgi program and cover the differences between the two.

Let’s take a look at what this program can do. Figure 16-1 shows you what the index.cgi program displays if no values are passed to it.

click to expand
Figure 16-1: index.cgi main album screen

In Figure 16-2, you see the page that lists the thumbnail images for a specific album. Notice in the URL of this page that the album ID is passed (album=1020740757). This tells the program what album information to get from the database.

click to expand
Figure 16-2: index.cgi album thumbnail listing

Notice the thumbnail images at the bottom of Figure 16-3. If we click the one to the left, we get the image in Figure 16-4.

click to expand
Figure 16-3: index.cgi photo display

click to expand
Figure 16-4: index.cgi photo display after the previous image has been clicked

So, now that we’ve seen what it can do, let’s see how it does it all!

index.cgi

start example
01: #!/usr/bin/perl -wT 02: # index.cgi  Photo Album Script
end example

Line 1 tells the system where to find Perl and turns on warnings and taint checking. We are using taint checking for this program, since it will be available through the Web.

Line 2 is simply a comment about this program.

03: use strict; 04: use DBI; 05: use CGI qw(:standard);

Line 3 loads the strict module.

Line 4 loads the DBI module for our database access.

Line 5 loads the CGI module and its :standard functions.

06: use lib qw(.); 07: use BasicSession; 08: our $sess; 09: my  %item; 10: $sess = Get_Session(); 11: tie %item, ‘BasicSession’;

Line 6 uses the lib module to push the directories passed to it into the @INC array. This line passes in the current directory so that Perl can find the BasicSession module when we load it in the next step.

Line 7 loads the BasicSession module.

Line 8 declares $sess as our variable. This allows it to be seen by all of the modules and functions in this application.

Line 9 declares the %item hash.

Line 10 calls the Get_Session function and stores the result of this call in the $sess variable. This creates a user session so that we can remember the user’s preferences.

Line 11 ties he %item array to the BasicSession module. This allows us to maintain user-session data between program calls.

12: my $album = param(‘album’); 13: my $photo = param(‘photo’); 14: my $size  = param(‘size’);

Lines 12–14 declare a scalar variable and read in some data from the calling HTML form, storing that data in the respective variables.

15: my $sizes = Make_Sizes(qw(640 800 orig)); 16: $size ? ($item{‘size’} = $size) : ($size = $item{‘size’});

Line 15 calls the Make_Sizes subroutine on the values passed. This simply creates the HTML needed to generate the links that allow the user to change image size.

Line 16 checks to see if the $size variable contains any data. If it does, the first item after ? is executed. This sets the $item{‘size’} hash element to the value in $size. If $size does not contain any data yet, the ternary operator sets the $size variable to the value that is currently in the $item{‘size’} hash element.

So, this code either gets or sets the value in $item{‘size’}. Since the %item hash is tied to the database, this also stores this data into the database. This allows us to remember what photo size our user prefers.

17: my $dbh = DBI->connect("DBI:mysql:photoalbum",       "bookuser","testpass") 18:     or die("Cannot connect: $DBI::errstr");

Line 17 creates a connection to the database and stores a handle to that database connection in the $dbh variable.

Line 18 causes the program to abort and display an error message if there is a problem connecting to the database.

19: if($photo) { 20:     Show_Photo($photo, $album); 21:     exit; 22: }

Line 19 checks to see if the $photo variable contains any data. If it does, this block of code is entered.

Line 20 calls the Show_Photo subroutine, which displays the photo, as in Figures 16-3 and 16-4.

Line 21 exits the program. We have done what we need to do, and this ensures that the program goes no farther.

Line 22 ends this code block.

23: elsif($album) { 24:     Show_Album($album); 25:     exit; 26: }

Line 23 checks to see if the $album variable contains any data. If it does, this code block is entered.

Line 24 calls the Show_Album subroutine. This subroutine shows the albums thumbnail gallery.

Line 25 exits the program so that no further execution can take place.

Line 26 closes this block of code.

27: else { 28:     List_Albums(); 29:     exit; 30: }

Line 27 is the default code for this if..elsif..else block. If nothing is in $photo or $album, we enter this block of code.

Line 28 calls the List_Albums subroutine. This subroutine is the main list of photo albums you get when this program is called without any arguments.

Line 29 exits the program. The rest of this program is all subroutines.

Line 30 closes this block of code.

31: sub Make_Sizes { 32:     my @sizes  = @_; 33:     my $string = ‘’;

Line 31 begins the Make_Sizes subroutine. This is subroutine generates the HTML links for the images passed to it.

Line 32 declares a new array named @sizes and populates it with the values passed to this subroutine when it is called.

Line 33 declares a variable named $string and sets it to an empty string.

34:     for my $size (@sizes) { 35:        $string .=  36:        qq(<a href="?photo=$photo&album=$album&size=$size">$size</a> ); 37:     }

Line 34 loops through the values in @sizes and stores the current value in $size each time through the loop.

Line 35 uses the appendination operator .= to append the string on line 36 to its current value.

Line 36 creates the HTML links for the different image sizes. This allows the user to choose a different image size.

Line 37 closes this loop.

38:     return($string); 39: }

Line 38 returns the value in $string.

Line 39 closes this subroutine.

40: sub List_Albums { 41:     my $sql = qq{SELECT * FROM album}; 42:     my $sth = $dbh->prepare($sql); 43:     $sth->execute();

Line 40 begins the List_Albums subroutine. This subroutine lists all of the albums that are in the database.

Line 41 creates the SQL statement needed to grab all of the items in the album table.

Line 42 prepares the SQL statement created on the preceding line and stores the result into the $sth statement handle.

Line 43 executes the SQL statement.

44:     Print_Page("./templates", "header.tmpl"); 45:     Print_Page("./templates", "list_top.tmpl");

Lines 44 and 45 call the Print_Page subroutine and pass it the appropriate directory and template file names to begin the HTML for this page.

46:     while(my $p = $sth->fetchrow_hashref){ 47:         Print_Page("./templates", "list_row.tmpl", $p); 48:     }

Line 46 begins a while loop that continues looping as long as the call to fetchrow_hashref is returning data. Each time the data is fetched, the $p variable is loaded with a reference to a hash containing the returned data.

Line 47 calls the Print_Page subroutine to print a row of data.

Line 48 closes this loop.

49:     Print_Page("./templates", "list_end.tmpl"); 50:     Print_Page("./templates", "footer.tmpl"); 51: }

Lines 49 and 50 print the footer HTML with some help from the Print_Page subroutine again.

Line 51 closes this subroutine.

52: sub Show_Album { 53:     my $cols = 3;

Line 52 begins the Show_Album subroutine. This subroutine shows the thumbnails of all photos in the album.

Line 53 declares a variable named $cols and sets it to 3. This is the number of columns we display the thumbnails in.

54:   my $sql_p = qq{SELECT * FROM photo WHERE album_id = ?}; 55:   my $sql_a = qq{SELECT * FROM album WHERE album_id = ?}; 56:   my $sth_p = $dbh->prepare($sql_p); 57:   my $sth_a = $dbh->prepare($sql_a); 58:   $sth_p->execute($album); 59:   $sth_a->execute($album);

Lines 54–59 almost make you think you are seeing double. These lines create the SQL to grab the data from the photo table and the album table.

Lines 58 and 59 execute the SQL statements that we’ve just created. Notice that even for the SQL statement that gets data from the photo table, we pass the $album variable. We do this to limit our matches to the items in that album.

60:     Print_Page("./templates", "header.tmpl");

Line 60 prints the header for this page by calling the Print_Page subroutine and passing the appropriate template file.

61:     while(my $p = $sth_a->fetchrow_hashref){ 62:         $p->{cols} = $cols; 63:         Print_Page("./templates", "album_top.tmpl", $p); 64:     }

Line 61 begins a while loop that continues looping as long as the calls to fetchrow_hashref keep returning data. Each time the data is retrieved, the $p variable stores a reference to a hash containing the data.

Line 62 sets the $p->{cols} hash element to the value in $cols.

Line 63 prints the page top.

Line 64 closes this loop.

65:     my $counter = 1;  66:     while(my $p = $sth_p->fetchrow_hashref){ 67:  $p->{img_location} = get_image($p->{img_location}, 100);

Line 65 declares a new variable named $counter and sets it to 1.

Line 66 begins a while loop that loops as long as fetchrow_hashref is returning data.

Line 67 sets the $p->{image_location} to the value returned by the get_image function call.

68:         print "<tr>"  if($counter == 1); 69:         Print_Page("./templates", "album_row.tmpl", $p); 70:         print "</tr>" if($counter == $cols);  

Line 68 prints the table-row tag if the value in $counter is equal to 1.

Line 69 prints an album row.

Line 70 prints the closing table-row tag if the value of $counter is equal to the value in $cols.

The print statements on lines 68 and 70 are used to print the different table rows. These values need to get printed only once for each row, so we use a counter to keep track of how many columns we have printed. Then we print a new row only when needed.

71:         $counter++; 72:         $counter = 1  if($counter > $cols); 73:     } 74:     print "</tr>" unless($counter == 1); 

Line 71 increments the value in $counter.

Line 72 sets $counter to 1 if the value of $counter is greater than that of $cols.

Line 73 closes this loop.

Line 74 prints a closing table tag unless the value in $counter is 1.

75:     Print_Page("./templates", "album_end.tmpl",           {‘cols’ => $cols}); 76:     Print_Page("./templates", "footer.tmpl"); 77: }

Line 75 uses the Print_Page function to print some closing HTML. We again have to pass the number of columns to the Print_Page function so that we can generate valid HTML.

Line 76 also uses the Print_Page function to print some more HTML.

Line 77 closes this subroutine.

78: sub Get_Photo_List { 79:     my ($photo, $album) = @_; 80:     my ($index, @photos); 81:     my $count = 0;

Line 78 begins the Get_Photo_List subroutine. This subroutine is used to get all of the photos for a particular album.

Line 79 declares two scalar variables and loads them with the data passed to the subroutine.

Line 80 declares two more variables this subroutine uses.

Line 81 declares the $count variable and sets it to 0.

82:     my $sql  = qq{SELECT img_id FROM photo                          WHERE album_id = ?}; 83:     my $sth  = $dbh->prepare($sql); 84:     my @all; 85:     $sth->execute($album);

Line 82 creates the SQL statement to get all of the img_id’s from the photo table for a particular album.

Line 83 uses the DBI’s prepare method to get the SQL ready for execution. The result of the prepare call is stored in $sth as a statement handle.

Line 84 declares a new array variable named @all.

Line 85 executes the SQL statement and passes the value in $album to the SQL statement to take the place of the placeholder.

86:     while(my @tmp = $sth->fetchrow_array){push @all,            $tmp[0];}

Line 86 is a while loop that loads up the @all array with the img_id values.

The condition for the while loop is the fetchrow_array call. As long as that is getting data, it is true, and the while loop continues. Each time fetchrow_array fetches a record from the database, the record is stored in @tmp.

Then, inside the (very small) code block, the push function is used to push the new value into the @all array. The value we are pushing into the @all array is in the $tmp[0] variable. Since we only fetch one record at a time with this SQL statement, the current value is always at element 0 of the array.

87:     for(@all) { 88:         $index = $count if(/$photo/); # Get index of photo 89:         $count++; 90:     }

Line 87 begins a for loop that traverses through the values in the @all array.

Line 88 stores the current value of $count into the $index variable if the current value matches the value in $photo. So, this line searches for the proper photo and sets the $index value appropriately.

Line 89 increments the $count variable.

Line 90 closes this for loop.

91:     unless($index) { 92:         @photos = @all[0, 1]; 93:         unshift @photos, ‘’; 94:     }

Line 91 checks to see if $index is 0, unless is the same as if not. If $index is 0, this is the first photo in the album. Since this is the first photo, we don’t have a "previous" photo to display and need to handle this case appropriately—so we enter this block of code.

Line 92 populates the @photos array with the array slice @all[0, 1]. This simply copies the first two elements of the @all array into the @photos array.

Line 93 adds an empty item to the beginning of the array with the unshift function. The unshift function does the opposite of the shift function. Instead of removing the first value from an array, it plops a value onto the beginning of an array.

Line 94 closes this code block.

95:     else { 96:         @photos = @all[($indx-1)..($indx+1)]; 97:     }

Line 95 is the else condition for this if..else block. We get here if the photo we are looking for is not the first photo.

Line 96 gets an array slice that includes the photo and the next and previous photos. The ($indx-1) is the previous photo, since $indx is the array index of the photo we want. Then ($indx+1) is the array index of the next photo.

What happens with ($indx+1) if $indx is the last photo? Well, simple: that element is not defined, so it contains no data. Perl simply instantiates the new array element and returns what it contains (nothing). If you had been programming in C or something similar, your program would have died right here; the array bounds would have been broken. But this doesn’t happen in Perl!

Line 97 ends this if..else block.

98:     return (@photos); 99: }

Line 98 returns the @photos array.

Line 99 closes the Get_Photo_List subroutine.

100: sub Show_Photo { 101:     my ($photo, $album) = @_;

Line 100 begins the Show_Photo subroutine.

Line 101 declares the $photo and $album variables and loads them with the values passed to the subroutine.

102:     my @photos = Get_Photo_List($photo, $album); 103:     my $ptr = Get_Details(@photos);

Line 102 declares an array named @photos and calls the Get_Photo_List function. The result of this function call populates the @photos array.

Line 103 calls the Get_Details function and passes it the @photos array. The value returned by this function call is stored in the $ptr variable.

104:     $ptr->{img_location} =             get_image($ptr->{img_location}, $size); 105:     $ptr->{image_sizes}  = $sizes;

Line 104 calls the get_image function and stores the result back into the value pointed to at $ptr->{img_location}. This handles getting the appropriate-sized image.

Line 105 sets the value at $ptr->{image_sizes} to the values in $sizes.

106:    Print_Page("./templates", "header.tmpl"); 107:    Print_Page("./templates", "photo_main.tmpl", $ptr); 108:    Print_Page("./templates", "photo_footer.tmpl", $ptr); 109: }

Lines 106–108 call the Print_Page function with different template files. These simply print the HTML needed to generate the page we are displaying on the user’s browser.

Line 109 closes this subroutine.

110: sub Get_Details { 111:     my @photos = @_; 112:     my ($p, %tmp); 113:     my %hsh = (‘prev’ => 0, ‘curr’ => 1, ‘next’ => 2);

Line 110 begins the Get_Details subroutine. This subroutine handles getting image details needed for the Web page.

Line 111 declares a hash named @photos and loads it with the values passed to the subroutine when it is called.

Line 112 declares a scalar variable and a hash that we’ll need in this subroutine.

Line 113 declares a hash named %hsh and loads it with some data.

114:     my $sql1 = qq{SELECT img_id, img_location FROM photo             WHERE img_id = ?}; 115:     my $sql2 = qq{SELECT * FROM photo WHERE img_id = ?};

Line 114 creates our first SQL statement that gets the image id and location from the photo table.

Line 115 creates our second SQL statement. This one gets all of the data that matches the photo we are looking for.

We have two SQL statements because for the previous and next images, we need only their id and location. For the photo we are displaying, however, we need all of the data.

116:     my $sth1 = $dbh->prepare($sql1); 117:     my $sth2 = $dbh->prepare($sql2);

Lines 116–117 prepare the SQL statements and store them in their respective statement handles.

118:     for(keys %hsh){ 119:         if(/curr/) { 120:             $sth2->execute($photos[$hsh{$_}]); 121:             $p = $sth2->fetchrow_hashref; 122:         }

Line 118 begins a for loop that iterates through all of the keys in the %hsh array. These keys we set previously and are prev, curr, and next.

Line 119 checks to see if the current key matches curr. If so, we enter this block of code.

Line 120 executes the statement pointed to by $sth2. Notice that we use the %hsh value as the index of the array for the placeholder value. That is why we set these values in the hash array. The current item in the array is index 1—so $photos[1] is sent to the SQL statement.

Line 121 calls the fetchrow_hashref function to get the data returned by the database and stores it in $p.

Line 122 closes this part of the if..else block.

123:         else { 124:             $sth1->execute($photos[$hsh{$_}])                     or die("Error!\n");

Line 123 begins the else part of this if..else block.

Line 124 executes the first SQL statement and passes the appropriate value to the placeholder.

125:             my $data = $sth1->fetch; 126:             $tmp{$_ . "_thumb_id"} = $data->[0]; 127:             $tmp{$_ . "_thumb_location"} = get_image($data->[1], 100); 128:         } 129:     } 

Line 125 calls the fetch function and stores the result in $data.

Line 126 sets the value at $tmp{$_ . "_thumb_id"} to the value in $data->[0].

Line 127 sets the value at $tmp{$_ . "_thumb_location"} to the value returned by the call to the get_image function with a witdh of 100 (our thumbnail).

Line 128 closes the if..else block.

Line 129 closes the for loop.

130:     while(my($key,$val) = each %tmp) { $p->{$key} = $val; } 131:     return($p); 132: }

Line 130 begins a while loop that iterates through each item in the %tmp hash and stores it in the hash pointed to by $p.

Line 131 returns the pointer to the hash, $p.

Line 132 ends this subroutine.

133: sub get_image { 134:     my ($photo, $size) = @_;

Line 133 begins the get_image subroutine. This subroutine takes the photo name and size and returns the appropriate filename.

Line 134 declares two scalars and loads them with the values passed from the function call.

135:     if(($size ne "orig") and (!$size eq "")){ 136:         $photo =~ s/\.jpg$/\.$size\.jpg/; 137:     }

Line 135 checks to see if $size is not equal to orig and that $size is also not equal to an empty string.

Line 136 is where we get if this is not the original photo size. This line replaces the .jpg extension with the $size.jpg extension.

Line 137 closes this if block.

138:     return($photo); 139: }

Line 138 returns $photo.

Line 139 closes this subroutine.

That is all there is to the index.cgi program. In these 139 lines of code, we handle the listing of all albums, the listing of each album’s thumbnail gallery, and the display of the images. One of the reasons we can do so much with so little code is that we have removed most of the HTML from the code and placed it into template files.

There is one other piece to this whole Web-based, photo-album puzzle: the admin.cgi program, which handles adding titles and descriptions to the albums and photos.

We need some way to edit the titles and comments for the photos and the albums. This functionality could have been added to the index.cgi program—but you don’t want just anyone editing these items. So, admin.cgi is a copy of the index.cgi program with a little extra added. That way, you can put admin.cgi in a safer location on your server so you don’t have to worry about people changing your photo descriptions.

We’ll cover the differences between these programs—but we won’t cover the differences where just the template files have changed (only places where functionality is different).

The line numbers shown with this code correspond to the line numbers from this program, admin.cgi. The complete program listings are at the end of the chapter, so you can compare line numbers between index.cgi and admin.cgi if you desire.

Figure 16-5 is what the admin album display looks like. The only differences are the form fields at the top and bottom of the page and the button to submit the changes.

click to expand
Figure 16-5: admin.cgi admin album display

In Figure 16-6, an image is displayed along with the form fields to allow changes in the image title and comments. The code following the figure shows the changes needed to do this.

click to expand
Figure 16-6: admin.cgi admin photo display

admin.cgi

start example
 52: sub Update_Data { 53:     my $type = shift; 54:     my ($title, $id, $sql);
end example

Line 52 begins the Update_Data subroutine. This subroutine is the biggest change in this program. It is used to add the data to the database.

Line 53 declares a variable named $type and sets it to the value passed into the function call. This value determines whether this is a photo or an album table change.

Line 54 declares a few more variables that we’ll use in this subroutine.

55:     if($type eq "photo") { 56:        $title = param(‘img_title’); 57:        $id    = $photo; 58:        $sql = qq{UPDATE photo SET img_title=?, comments=?               WHERE img_id = ?}; 59:     }

Line 55 begins an if..elsif..else block. This section checks to see if the $type is photo. If so, this block is entered.

Line 56 sets the $title variable to the value passed in from the img_title form field.

Line 57 sets the $id variable to the value in $photo.

Line 58 creates the SQL statement to update the photo title and comments.

Line 59 closes this part of the if..elsif..else block.

60:     elsif($type eq "album") { 61:         $title = param(‘title’); 62:         $id    = $album; 63:         $sql = qq{UPDATE album SET name=?, comments=?                WHERE album_id = ?}; 64:     }

Line 60 checks to see if $type is equal to album. If so, this block is entered.

Line 61 sets the $title variable to the value returned by the title field from the HTML form.

Line 62 sets the variable $id to the value in $album.

Line 63 creates the SQL statement to update the album-table data.

Line 64 closes this block.

65:     else { 66:         return 0; 67:     }

Line 65 begins the else block.

Line 66 returns 0. If $type doesn’t match album or photo, something must be wrong, and we can trap the error if we need to.

Line 67 closes the if..elsif..else block.

68:     my $comments = param(‘comments’); 69:     return unless($title or $comments);

Line 68 sets the $comments variable to the value passed from the comments section of the calling HTML page.

Line 69 returns unless $title or $comments contains data. We don’t want to bother updating the database if there is nothing to update.

70:     my $sth = $dbh->prepare($sql); 71:     $sth->execute($title, $comments, $id); 72: }

Line 70 prepares the SQL statement for execution.

Line 71 executes the SQL statement and passes the $title, $comments, and $id variables to replace the placeholders in the SQL statements.

Line 72 ends this subroutine.

73: sub Show_Album { 74:     Update_Data("album");

Line 73 is actually no different from the index.cgi program, but it helps to see it because the next line is different.

Line 74 is a call to the Update_Data subroutine and is passed album because this is for an album update.

83:  my $alb = $sth_a->fetchrow_hashref; 84:  $alb->{cols} = $cols;

Line 83 declares a variable named $alb and sets it to the hash reference returned by the call to fetchrow_hashref.

Line 84 gets the number of columns from $cols and stores it in $alb->{cols}.

That is all that needs to be changed in the Show_Album subroutine to update the data.

121: sub Show_Photo { 122:     my ($photo, $album) = @_; 123:     Update_Data("photo");

Line 123 is the only that needs to be changed for the Show_Photo subroutine!



Perl Database Programming
Perl Database Programming
ISBN: 0764549561
EAN: 2147483647
Year: 2001
Pages: 175

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net