To give you just a taste of PHP image manipulation, Im going to show you how to generate a smaller copy of an uploaded image.. In this chapter, youll create two classes: one to generate
Trang 1USING PHP TO MANAGE FILES
211
2 Remove any default code created by your script editor, and insert the following code:
<?php
// define error page
$error = 'http://localhost/phpsols/error.php';
// define the path to the download folder
$filepath = 'C:/xampp/htdocs/phpsols/images/';
$getfile = NULL;
// block any attempt to explore the filesystem
if (isset($_GET['file']) && basename($_GET['file']) == $_GET['file']) { $getfile = $_GET['file'];
} else {
header("Location: $error");
exit;
}
if ($getfile) {
$path = $filepath $getfile;
// check that it exists and is readable
if (file_exists($path) && is_readable($path)) {
// get the file's size and send the appropriate headers
$size = filesize($path);
header('Content-Type: application/octet-stream');
header('Content-Length: ' $size);
header('Content-Disposition: attachment; filename=' $getfile);
header('Content-Transfer-Encoding: binary');
// open the file in read-only mode
// suppress error messages if the file can't be opened
$file = @fopen($path, 'r');
if ($file) {
// stream the file and exit the script when complete
fpassthru($file);
exit;
} else {
header("Location: $error");
}
} else {
header("Location: $error");
}
The only two lines that you need to change in this script are highlighted in bold type The first defines $error, a variable that contains the URL of your error page The second line that
needs to be changed defines the path to the folder where the download file is stored
The script works by taking the name of the file to be downloaded from a query string appended
to the URL and saving it as $getfile Because query strings can be easily tampered with,
Trang 2CHAPTER 7
212
$getfile is initially set to NULL This is an important security measure If you fail to do this, you could give a malicious user access to any file on your server
The opening conditional statement uses basename() to make sure that an attacker cannot request a file, such as one that stores passwords, from another part of your file structure As explained in Chapter 4, basename() extracts the filename component of a path, so if
basename($_GET['file']) is different from $_GET['file'], you know theres an attempt to probe your server, and you can stop the script from going any further by using the header() function to redirect the user to the error page
After checking that the requested file exists and is readable, the script gets the files size, sends the appropriate HTTP headers, and opens the file in read-only mode using fopen() Finally, fpassthru() dumps the file to the output buffer But if the file cant be opened or doesnt exist, the user is redirected to the error page
3. Test the script by creating another page and add a couple of links to download.php Add a query string at the end of each link with file= followed by the name a file to be downloaded Youll find a page called getdownloads.php in the ch07 folder, which contains the following two links:
<p><a href="download.php?file=maiko.jpg">Download image 1</a></p>
<p><a href="download.php?file=basin.jpg">Download image 2</a></p>
4 Click one of the links, and the browser should present you with a dialog box prompting you to
download the file or choose a program to open it, as shown in Figure 7-6
Figure 7-6 The browser prompts the user to download the image, rather than opening it directly
Trang 3USING PHP TO MANAGE FILES
213
5 Select Save File, and click OK, and the file should be saved rather than displayed Click
Cancel to abandon the download Whichever button you click, the original page remains in the
browser window The only time download.php should load into the browser is if the file cannot
be opened Thats why its important to send the user to an error page if theres a problem Ive demonstrated download.php with image files, but it can be used for any type of file because the headers send the file as a binary stream
This script relies on header() to send the appropriate HTTP headers to the browser It is vital to ensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removed all whitespace and still get an error message saying “headers already sent,” your editor may have inserted invisible control characters at the beginning of the file Some editing programs insert the byte order mark (BOM), which is known to cause problems with the ability to use the header() function Check your program preferences to make sure the option to insert the BOM is deselected
Chapter review
The file system functions arent particularly difficult to use, but there are many subtleties that can turn a seemingly simple task into a complicated one Its important to check that you have the right permissions Even when handling files in your own website, PHP needs permission to access any folder where you want
to read files or write to them
The SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine the contents of folders Used in combination with the SplFileInfo methods and the RegexIterator, you can quickly find files of a specific type within a folder or folder hierarchy
When dealing with remote data sources, you need to check that allow_url_fopen hasnt been disabled One of the most common uses of remote data sources is extracting information from RSS news feeds or XML documents, a task that takes only a few lines of code thanks to SimpleXML
In the next two chapters, well put some of the PHP solutions from this chapter to further practical use when working with images and building a simple user authentication system
Trang 4CHAPTER 7
214
Trang 5215
Chapter 8
Generating Thumbnail Images
PHP has an extensive range of functions designed to work with images Youve already met one of them, getimagesize(), in Chapter 4 As well as providing useful information about an images dimensions, PHP can manipulate images by resizing or rotating them It can also add text dynamically without affecting the original; it can even create images on the fly
To give you just a taste of PHP image manipulation, Im going to show you how to generate a smaller copy
of an uploaded image Most of the time, youll want to use a dedicated graphics program, such as Photoshop or Fireworks, to generate thumbnail images because it gives you much better quality control However, automatic thumbnail generation with PHP can be very useful if you want to allow registered users to upload images, but make sure they conform to a maximum size You can save just the resized copy, or the copy along with the original
In Chapter 6, you built a PHP class to handle file uploads In this chapter, youll create two classes: one to generate thumbnail images, the other to upload and resize images in a single operation Rather than build the second class from scratch, youll base it on the Ps2_Upload class from Chapter 6 A great advantage
of using classes is that theyre extensible—a class based on another can inherit the functionality of its parent class Building the classes to upload images and generate thumbnails from them involves a lot of code But once you have defined the classes, using them involves only a few lines of script If youre in a rush or writing a lot of code makes you break out in a cold sweat, you can just use the finished classes Come back later to learn how the code works It uses many basic PHP functions that youll find useful in other situations
In this chapter youll learn about the following:
• Scaling an image
• Saving a rescaled image
• Automatically resizing and renaming uploaded images
• Creating a subclass by extending an existing one
Trang 6CHAPTER 8
216
Checking your servers capabilities
Working with images in PHP relies on the GD extension Originally GD stood for GIF Draw, but problems with the GIF patent led to support for GIF files being dropped in 1999, but the name GD stuck The problematic patent expired in 2004, and GIF is once again supported The all-in-one PHP packages recommended in Chapter 2 support GD by default, but you need to make sure the GD extension has also been enabled on your remote web server
As in previous chapters, run phpinfo() on your website to check the servers configuration Scroll down until you reach the section shown in the following screenshot (it should be about halfway down the page)
If you cant find this section, the GD extension isnt enabled, so you wont be able to use any of the scripts
in this chapter on your website Ask for it to be enabled or move to a different host
Strictly for abbreviation/acronym freaks: GIF stands for Graphics Interchange Format, JPEG is the standard created by the Joint Photographic Experts Group, and PNG is short for Portable Network Graphics Although JPEG is the correct name for the standard, the “E” is frequently dropped, particularly when used as a filename extension
Manipulating images dynamically
The GD extension allows you to generate images entirely from scratch or work with existing images Either way, the underlying process always follows four basic steps:
1 Create a resource for the image in the servers memory while its being processed
Trang 7GENERATING THUMBNAIL IMAGES
217
2 Process the image
3 Display and/or save the image
4 Remove the image resource from the servers memory
This process means that you are always working on an image in memory only and not on the original Unless you save the image to disk before the script terminates, any changes are discarded Working with images requires a lot of memory, so its vital to destroy the image resource as soon as its no longer needed If a script runs very slowly or crashes, it probably indicates that the original image is too large
Making a smaller copy of an image
The aim of this chapter is to show you how to resize images automatically on upload This involves extending the Ps2_Upload class from Chapter 6 However, to make it easier to understand how to work with PHPs image manipulation functions, I propose to start by using images already on the server, and create a separate class to generate the thumbnail images
Getting ready
The starting point is the following simple form, which uses PHP Solution 7-3 to create a drop-down menu of the photos in the images folder You can find the code in create_thumb_win01.php and create_thumb_mac01.php in the ch08 folder Copy it to a new folder called gd in the phpsols site root, and rename it create_thumb.php
<form id="form1" name="form1" method="post" action="">
<p>
<select name="pix" id="pix">
<option value="">Select an image</option>
<?php
$files = new DirectoryIterator(' /images');
$images = new RegexIterator($files, '/\.(?:jpg|png|gif)$/i');
foreach ($images as $image) {
?>
<option value="C:/xampp/htdocs/phpsols/images/<?php echo $image; ?>">
<?php echo $image; ?></option>
<?php } ?>
</select>
</p>
<p>
<input type="submit" name="create" id="create" value="Create Thumbnail">
</p>
</form>
The Win and Mac versions contain the fully qualified path to the images folder in default installations of XAMPP and MAMP If necessary, change the path (highlighted in bold) to match your setup When loaded into a browser, the drop-down menu should display the names of the photos in the images folder This makes it easier to pick images quickly for testing
Trang 8CHAPTER 8
218
Inside the upload_test folder that you created in Chapter 6, create a new folder called thumbs, and make sure it has the necessary permissions for PHP to write to it Refer to “Establishing an upload directory” in Chapter 6 if you need to refresh your memory
Building the Ps2_Thumbnail class
To generate a thumbnail image, the class needs to execute the following steps:
1 Get the dimensions of the original image
2 Get the images MIME type
3 Calculate the scaling ratio
4 Create an image resource of the correct MIME type for the original image
5 Create an image resource for the thumbnail
6 Create the resized copy
7 Save the resized copy to the destination folder using the correct MIME type
8 Destroy the image resources to free memory
In addition to generating a thumbnail image, the class automatically inserts _thb before the filename extension, but a public method allows you to alter this value The class also needs public methods to set the destination folder and the maximum size of the thumbnail, and to retrieve messages generated by the class To keep the calculations simple, the maximum size controls only the larger of the thumbnails dimensions
Theres a lot to do, so Ill break up the code into sections Theyre all part of the same class definition, but presenting the script this way should make it easier to understand, particularly if you want to use some of the code in a different context
PHP Solution 8-1: Getting the image details
This PHP Solution describes how to get the dimensions and MIME type of the original image
1 Create a new page called Thumbnail.php in the classes/Ps2 folder The file will contain only
PHP, so strip out any HTML code inserted by your editing program
2 The class needs to keep track of quite a few properties Begin the class definition by listing
them like this:
<?php
class Ps2_Thumbnail {
protected $_original;
protected $_originalwidth;
protected $_originalheight;
protected $_thumbwidth;
protected $_thumbheight;
protected $_maxSize = 120;
protected $_canProcess = false;
protected $_imageType;
Trang 9GENERATING THUMBNAIL IMAGES
219
protected $_destination;
protected $_name;
protected $_suffix = '_thb';
protected $_messages = array();
}
As in the Ps2_Upload class, all the properties have been declared as protected, which means they cant be changed accidentally outside the class definition Again, I have followed the convention of beginning protected property names with an underscore The names are
descriptive, so they need little explanation The $_maxSize property has been given a default value of 120 (pixels) This determines the maximum size of the thumbnails longer dimension The $_canProcess Boolean is initially set to false This is to prevent the script from
attempting to process a file that isnt an image The value will be reset to true if the MIME type matches that of an image You can also use it to prevent the generation of a thumbnail if
another error occurs
3 The constructor takes one argument, the path to an image Add the constructor definition after
the list of protected properties, but inside the the closing curly brace:
protected $_messages = array();
public function construct($image) {
if (is_file($image) && is_readable($image)) {
$details = getimagesize($image);
} else {
$details = null;
$this->_messages[] = "Cannot open $image.";
}
// if getimagesize() returns an array, it looks like an image
if (is_array($details)) {
$this->_original = $image;
$this->_originalwidth = $details[0];
$this->_originalheight = $details[1];
// check the MIME type
$this->checkType($details['mime']);
} else {
$this->_messages[] = "$image doesn't appear to be an image.";
}
}
}
The constructor begins with a conditional statement that checks that $image is a file and is readable If it is, its passed to getimagesize() and the result is stored in $details
Otherwise, $details is set to null, and an error message is added to the $_messages
property
When you pass an image to getimagesize(), it returns an array containing the following elements:
Trang 10CHAPTER 8
220
• 0: width (in pixels)
• 1: height
• 2: an integer indicating the type of image
• 3: a string containing the correct width and height attributes ready for insertion
in an <img> tag
• mime: the images MIME type
• channels: 3 for RGB, and 4 for CMYK images
• bits: the number of bits for each color
If the value passed as an argument to getimagesize() isnt an image, it returns false Consequently, if $details is an array, you know youre dealing with an image The images path is stored in the $_original property, and its width and height are stored in
$_originalWidth and $_originalHeight respectively
However, the image might not be a suitable type, so the final check is to pass its MIME type to
an internal method called checkType(), which youll define next
4 The checkType() method compares the MIME type with an array of acceptable image types If
it finds a match, it resets the $_canProcess property to true, and stores the type in the
$_imageType property The method is used internally, so it needs to be declared as protected Add the following code to the class definition:
protected function checkType($mime) {
$mimetypes = array('image/jpeg', 'image/png', 'image/gif');
if (in_array($mime, $mimetypes)) {
$this->_canProcess = true;
// extract the characters after 'image/'
$this->_imageType = substr($mime, 6);
}
}
There are many types of images, but only JPEG, PNG, and GIF are used in web pages, so the
$_canProcess property is set to true only if the images MIME type matches one of those listed in the $mimetypes array If the MIME type isnt in the list $_canProcess remains false, which later prevents the class from attempting to create a thumbnail
All image MIME types begin with image/ To make the value easier to use later, the substr() function extracts the characters after the slash and stores them in the $_imageType
property When used with two arguments, substr() starts at the position (counting from 0) specified in the second argument, and returns the rest of the string
5 Its a good idea to test your code as you build the class Catching errors early is much easier
than hunting for a problem in a long script To test the code, create a new public method called test() inside the class definition