Hands-on How-to is a service mark of Brass Cannon LLC
 

PHP102

A Hands-on How-toSM

from Brass Cannon Consulting

A little vague handwaving can often save hours of tedious explanation.

"A Simple Sample - PHP Picture Popper"

There's nothing like a bunch of working examples when you're trying to learn a new language. If it's something you've been looking for anyway, that's a bonus. One of the things that most people ask about is a picture gallery or slideshow. Here's a simple one, suitable for someone who's starting out. It's not state-of-the-art; the 2006 version would use Cascading Style Sheets to position everything and to hide and swap pictures as if by magic. I'm taking an old-fashioned approach; positioning things with tables and using frames. This is not something I'm holding up as an example of good HTML coding; it's just a convenient way to present some examples of passing parameters with PHP.

The setup

I've been fiddling around with a couple of different webcams. Each has software to upload snapshots on a timer, and in theory they will rename any old snapshots so you can build up an album over time. Unfortunately, the rename code seems to be broken, and there is also a little issue -- depending on which camera I use, the pictures have different sizes.

Worse, when my Significant Other checked the album while dialed up through AOL, she kept seeing the same picture every time. "What's that penguin doing there? That picture is from a year ago!" Part of the issue is that AOL is extremely aggressive about "caching" a copy of large files like photos, and it's hard to convince them that you've updated a file if you keep the same file name.

Long story short: Here's a simple webcam display app that uses just a little bit of PHP to defeat unwanted caching, among other things.

The Support Script

We need some pictures to display, don't we? Let's set that up before we go on to "left.php" and "right.php".

Here's a little shell script, rotate.sh, that I run every hour or so. If I have uploaded a new picture, it will add it to the top of the list and push all the old pictures down.


#!/bin/sh
cd /tmp/webcam
set lastrot=`cat /tmp/webcam/lastrot.inc`
if [ -f home.jpg ]
then
 set xnow = `date +%s`
 for i in 1 2 3 4 5 6
 do
 /usr/bin/touch t_home$i-$xnow.jpg x_home$i-$xnow.jpg
 done;
 mv t_home5-$lastrot.jpg t_home6-$xnow.jpg
 mv t_home4-$lastrot.jpg t_home5-$xnow.jpg
 mv t_home3-$lastrot.jpg t_home4-$xnow.jpg
 mv t_home2-$lastrot.jpg t_home3-$xnow.jpg
 mv t_home1-$lastrot.jpg t_home2-$xnow.jpg
 /usr/bin/convert -size 88x72 home.jpg \
   -resize 88x72 +profile "*" t_home1-$xnow.jpg
 mv x_home5-$lastrot.jpg x_home6-$xnow.jpg
 mv x_home4-$lastrot.jpg x_home5-$xnow.jpg
 mv x_home3-$lastrot.jpg x_home4-$xnow.jpg
 mv x_home2-$lastrot.jpg x_home3-$xnow.jpg
 mv x_home1-$lastrot.jpg x_home2-$xnow.jpg
 /usr/bin/convert -size 640x480 home.jpg \
   -resize 640x480 +profile "*" x_home1-$xnow.jpg
 /bin/sleep 1
 rm home.jpg
 echo $xnow > /tmp/webcam/lastrot.inc
fi

Remember I mentioned that I had a problem with the webcam software not renaming its pictures automatically? This script takes over that duty and also makes nice thumbnail pictures to use as links. You could simply use the full-size picture and tell the browser to resize it, yes, but it takes a lot of time to download the big image and it will make your web page seem very slow to load. Better to have small images for thumbnails and load the big pictures "in the background" -- a trick we'll see in just a moment.

First up, we're using a little trick that is worth its weight in gold. We're running this from the shell, don't forget. The backticks as quote characters tell the shell to evalute the command that is quoted and return its output. This is the magic that allows you to save the output of a command into a variable. In our case, that variable will get the current "Unix time," which is the number of seconds since 1970. It's a nice big number now, around 1.15 billion. We're going to make that a central part of our "don't cache my content" plan.

We need to know the last time we ran this script -- we get that by reading a file with the "cat" command, and saving the value into a variable called "lastrot". So far, so good. Obviously, I had to set that up by hand to get things started. We then grab the current time using a GNU version of the "date" command. date +%s is a GNU extension or modification to the date command that tells it to return the Unix time rather than something like "Apr 01 2009" which would not be as useful for our project.

Next, we check to see if there is any reason to run the script. If there is no new "home.jpg" file, we simply skip everything else.

Some webcam packages will generate thumbnails for you (like Evocam for the Apple Macintosh). If I were dealing only with Evocam I wouldn't have put nearly as much work into this page... but I also have a PC webcam. Instead of trying to deal with the lowest common denominator I moved all the image manipulation into this script.

/usr/bin/convert is part of the ImageMagick package for Linux. The parameters tell it that I want a thumbnail of 88x72 pixels and a main image of 640x480. If one of my webcams makes a larger or smaller picture, ImageMagick doesn't care -- the output will be consistent. I can even use my nice digital camera that takes a really big picture, upload that to the webcam directory as "home.jpg," and ImageMagick will happily reformat it to be consistent with the smaller webcam shots.

The "touch filename" lines are a lazy way to keep the script working even if I'm starting from scratch. Yes, I could go through and test to see that all six thumbnails and all six big pictures exist, but even if they don't, this script will just create empty files and keep on going. As new versions of "home.jpg" are uploaded, eventually all six spots will be filled by real images.

As for running this script, it's just a matter of adding it to the cron table, "crontab". I set it to run automatically every hour or so. It doesn't matter if I run it more often, or if I don't upload anything for a while, as the script just quits when it sees there is nothing for it to do.

So, let's watch it work: I upload a new "home.jpg" file. (Remember, if there is no home.jpg file, nothing happens.) Ignore the "lastrot" and "xnow" variables for a moment. The script renames the existing images so generation 5 (that's t_home5.jpg and x_home5.jpg) replaces generation 6, 4 replaces 5, and so forth. After t_home1.jpg becomes t_home2.jpg ImageMagick then makes a new thumbnail called t_home1.jpg. Similarly, we get a VGA-size x_home1.jpg. That's x as in "x-tra large," not x-rated. :-) The script then removes the home.jpg file so the process stops until there is a new upload -- otherwise all six pictures would be replaced by the same image.

The lastrot and xnow variables are an add-on so that we get to have our cake and eat it too -- we have a nice simple numbering scheme, 1 through 6, that is easy to understand, but we also have a tag based on time that tells all the computers involved that "this generation of image names is completely unique, so the old image I called 'home1-1234567890.jpg' is not the same as 'home1-1234567999.jpg' -- no, not at all." We save the new value, xrot, in place of the old file, /tmp/webcam/lastrot, by redirecting the output of an echo command, so next time we run we will know what the old value of xnow was.

You can read more about these Unix shell tricks at our UNIX 101 page.

The index page



<? header("Cache-Control: proxy-revalidate");
$myFile = "/tmp/webcam/lastrot.inc";
$fh = fopen($myFile, 'r');
$lr = rtrim(fgets($fh));
fclose($fh);
echo "
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\"
 \"http://www.w3.org/TR/html4/frameset.dtd\">
<html>
<head><title>Homecam</TITLE></head>
<FRAMESET cols=\"20%, 80%\" border=0>
 <FRAME name=\"left\" src=\"left.php?v=$lr\" noresize scrolling=\"no\">
 <FRAME name=\"right\" src=\"right.php?pic=1&v=$lr\">
</FRAMESET>
</body></html>";
?>

First we have the PHP counterpart of the shell script logic that we used to read our "lastrot" file. It's not as simple as `cat filename` but the shell isn't a good web scripting language in part because it makes things a little too simple for the bad guys. So, we explictly open the file, read the file, close the file.

If you have used Perl, we are using "rtrim()" for exactly the same reason you would use "chomp()" to get rid of an unwanted line break; otherwise we would have a newline which would make this bit of generated HTML:

 <FRAME name="right" src="right.php?pic=1&v=1146440457">

...look like this, instead:

 <FRAME name="right" src="right.php?pic=1&v=1146440457
">

Next comes a nice, formal HTML header. Yes, you can get away with just "<HTML>" but it isn't hard to do it right. This formal declaration also sets us up to do something fancy; we're going to have two "frames" inside our index page. Why frames? It's like having two pages in one, so we can update one without reloading the whole thing. One frame is a column of thumbnail images, each of which is a link. Clicking a thumbnail will load the corresponding full-size image in the other frame.

(I did cheat a little here -- my server is set up to interpret PHP even though this file is called "webcam.html". To make it work elsewhere, you might have to save it as "webcam.php" instead.)

PHP is now going to "echo" (that is, write) a rather long string of text exactly as though it were part of a plain text file. The reason we're using PHP at this point is to carry our unique timestamp through into all our links. Note that anywhere we have a double-quote embedded inside our PHP string, we have to "escape" it by putting a back-quote in front of it: \" This is because the entire string is delimited or bounded by a matched set of the double-quote character.

Understanding about double-quotes is important. When a string is in "double-quotes" PHP replaces any variable names (such as "$xnow" or "$lr") with their values. You may say "Well, of course! That's the reason we are using PHP, after all." Yes, but if the same string is in 'single-quotes', PHP will NOT replace $xnow with its value, that long number. It will just display the string $xnow. There are situations where you need to have access to the name of the variable as well as its value, and using 'single-quotes' vs. "double-quotes" is the way that PHP chose to make that possible. It's one of those subtle things that takes some time for programmers to learn.

The next few lines set up our two frames, the column which holds the content of "left.php" and the large window which will contain the content of "right.php". We pass an image name to right.php so by default it opens with the first image in our list, "x_home1-1146440457.jpg".

One little detail; notice there is no <body> tag? That's not a mistake; our frameset page won't work if it sees a body tag. The framed pages will each have a body, but not the parent "frameset" page.

left.php

Okay, we have our six thumbnails and our six big pictures, thanks to our shell script. Let's display them.


<? header("Cache-Control: proxy-revalidate");
function sanitize_numeric_string($string, $min='', $max='')
{
  $string = preg_replace("/[^0-9]/", "", $string);
  $len = strlen($string);
  if((($min != '') && ($len < $min)) || (($max != '') && ($len > $max)))
    return FALSE;
  return $string;
}
$lr = sanitize_numeric_string($_GET[v], $min='10', $max='12');
?>

<html><head>
<meta name="ROBOTS" content="NONE">
<SCRIPT LANGUAGE = JAVASCRIPT>
if (document.images) 
{
   img1 = new Image();
   img2 = new Image();
   img3 = new Image();
   img4 = new Image();
   img5 = new Image();
   img6 = new Image();
<?
echo "
   img1.src = \"x_home1-$lr.jpg\"\;
   img2.src = \"x_home2-$lr.jpg\"\;
   img3.src = \"x_home3-$lr.jpg\"\;
   img4.src = \"x_home4-$lr.jpg\"\;
   img5.src = \"x_home5-$lr.jpg\"\;
   img6.src = \"x_home6-$lr.jpg\"\;
";
?>
}
</SCRIPT>
<title>HOMECAM</title></head>
<body bgcolor=black>
<center>
<table>

<?
echo "<tr><td><a href=\"right.php?pic=1&v=$lr\"
 target=right><img src=\"t_home1-$lr.jpg\"
 height=72 width=88 border=0></a></td></tr>";
echo "<tr><td><a href=\"right.php?pic=2&v=$lr\"
 target=right><img src=\"t_home2-$lr.jpg\"
 height=72 width=88 border=0></a></td></tr>";
echo "<tr><td><a href=\"right.php?pic=3&v=$lr\"
 target=right><img src=\"t_home3-$lr.jpg\"
 height=72 width=88 border=0></a></td></tr>";
echo "<tr><td><a href=\"right.php?pic=4&v=$lr\"
 target=right><img src=\"t_home4-$lr.jpg\"
 height=72 width=88 border=0></a></td></tr>";
echo "<tr><td><a href=\"right.php?pic=5&v=$lr\"
 target=right><img src=\"t_home5-$lr.jpg\"
 height=72 width=88 border=0></a></td></tr>";
echo "<tr><td><a href=\"right.php?pic=6&v=$lr\"
 target=right><img src=\"t_home6-$lr.jpg\"
 height=72 width=88 border=0></a></td></tr>";
?>
</center>
</body>
</html>

We're starting out with a PHP one-liner which should tell proxy servers (such as the ones at AOL) to check for fresh content before relying on their cached copy. This ability to generate custom headers for a single page (as opposed to reconfiguring your web server to apply them to ALL your pages) is a good reason to know PHP.

We're also including some bells and whistles here that aren't essential, but they are nice to have. For instance, when someone on a dialup clicks on a link, they don't want to wait for a picture that is perhaps 180KB to download. The javascript tells the browser to go ahead and download all the big images without trying to display them yet. The first image will be downloaded and displayed as soon as it can be in any case, but we can hope that it will keep the visitor occupied while the others load in the background. When they click on one of the other thumbnails, the corresponding big image should load up almost instantly, and we will look really clever.

I know, you're thinking "Wait. We added all those lines saying 'don't even think of caching this!' Now you want things to be cached?" Well, yes. You see, we're talking about the window that is open right now, so oddly enough asking your browser to pre-load those images seems to work just fine, despite all the "Don't cache me!" commands. Those "no caching" commands are directed at caching servers or proxies that lie in between our server and the browser.

The "target=right" refers to the frameset we established in our index page, so the large image display will be located correctly inside the main window relative to our thumbnails, assuming the browser supports framed pages.

right.php

Now, you probably noted the line in red above. We're calling a .php page, right.php. We didn't really have to do that; we could in fact just display the .jpg file in our right-hand frame, like so:

<tr><td><a href="x_home1-$lr.jpg" target=right>
 <img src="t_home1$lr.jpg" height=72 width=88 border=0></a></td>

But if we do it that way, the image will come up without any border, on a default white screen, and positioned wherever it happens to land. If you don't mind a plain white screen, it's okay to do it that way....

... but our "right.php" page isn't all that complex, and it gives us a chance to show off more of the added value of PHP. Here's what it looks like:


<?
header("Cache-Control: proxy-revalidate"); 
// paranoid sanitization -- only let numbers through
function sanitize_numeric_string($string, $min='', $max='')
{
  $string = preg_replace("/[^0-9]/", "", $string);
  $len = strlen($string);
  if((($min != '') && ($len < $min)) || (($max != '') && ($len > $max)))
    return FALSE;
  return $string;
}
$myp0 = sanitize_numeric_string($_GET[pic], $min='1', $max='1');
$mylr = sanitize_numeric_string($_GET[v], $min='10', $max='12');
?>

<html>
<head>
</head>
<body bgcolor=black>
<center>
<? echo "<img src=\"x_home$myp0-$mylr.jpg\" lowsrc=\"pixel.gif\" width=640 height=480><br>"; ?>
</center>
</body>
</html>

We have, of course, disabled "global variables" as a security risk (see the previous page), so to get the value that was passed to us as "pic=[some picture]," we use a bit of cookbook code to access the "$_GET array". It's easy to protect this script, because we know exactly what values we expect: a string with the value "1" through "6". Exactly one numeric digit. Nothing else is valid.

A simpler version of this would be just $myp0 = $_GET[pic]; We're being a little more careful, though, so instead of just grabbing $_GET[pic] we are also running it through the "paranoid sanitize" function, which will discard anything that is not a plain digit. If someone tries to pass in a string like ../../../../etc/passwd they won't have any luck because one, we throw away all the dots and slashes, and two, we see that the string is not exactly one character long. Even so, we still take one more precaution, and add on the "x_home" prefix and ".jpg" suffix separately, something that remains outside the control of our user and his input. If someone tries to slip in a filename other than "home plus a digit", it can still only match a .jpg file, and the "x_home" prefix is something I can be fairly sure won't match anything else in the web document directory we are using. I can be really sure it won't match any of my sensitive system files.

We set a background consistent with the rest of our page (with <body bgcolor="black">), position our image with a nice margin around it using <center>, and finally, to keep things from jumping around as the browser figures out where to put everything, we define a low-resolution initial image by blowing up a 1-pixel gif to the exact dimensions of the final image, 640x480. This is a luxury we can afford, thanks to the use of ImageMagick to reformat all our images to the same size.

Notice how little PHP we needed to get the ability to pass filenames between the thumbnail bar and the main display, even after we added a really tough security filter? That's one of the things I like about PHP. It's interesting to compare PHP to the Perl CGI approach -- and we happen to have another picture-display page in our Introduction to CGI which does almost the same thing but using a Perl CGI. You might try to rewrite the logic of this one to look more like the other, and vice-versa.

Checking it out

If you want to see it in action, you can give it a try. One thing before you do; try hitting the third thumbnail from the top as soon as it shows up, as quickly as you can. Note how the large image loads a bit slowly? Wait just a couple of seconds and hit a different thumbnail, and see how much more quickly that one pops up. That's the preloading feature we described above. If you don't see the difference, try alternating between two thumbnails, and see how quickly a picture pops up the second time you view it. That's the effect that preloading should give us.


Google
 
Web handsonhowto.com



Hands-On
How-To Index