Since different status types will use different status tables, we should use a left join to connect the tables, so we can keep just a single query to look up the statuses. It also pulls in the extra information when it is required.
Let's get started with extending our profiles and the status stream!
Changes to the view
Since all of the media types we are going to support require at least one additional database field in a table that extends the statuses table, we are going to need to display any additional fields on the post status form. The standard type of status doesn't require additional fields, and new media types that we haven't discussed, which we may wish to support in the future, may require more than one additional field. To support a varying number of additional fields depending on the type, we could use some JavaScript (in this case, we will use the jQuery framework) to change the form depending on the context of the status. Beneath the main status box, we can add radio buttons for each of the status types, and depending on the one the user selects, the JavaScript can show or hide the additional fields, making the form more relevant.
Template
Our update status template needs a few changes:
We need to set the enctype on the form, so that we can upload files (for posting images)
We need radio buttons for the new types of statuses
We need additional fields for those statuses
The changes are highlighted in the following code segment:
<p>Tell your network what you are up to</p>
<form action="profile/statuses/{profile_user_id}" method="post"
enctype="multipart/form-data">
<textarea id="status" name="status"></textarea>
<br />
<input type="radio" name="status_type" id="status_checker_update"
class="status_checker" value="update" />Update
<input type="radio" name="status_type" id="status_checker_video"
class="status_checker" value="video" />Video
<input type="radio" name="status_type" id="status_checker_image"
class="status_checker" value="image" />Image
<input type="radio" name="status_type" id="status_checker_link"
class="status_checker" value="link" />Link
<br />
<div class="video_input extra_field">
<label for="video_url" class="">YouTube URL</label>
<input type="text" id="" name="video_url" class="" /><br />
</div>
<div class="image_input extra_field">
<label for="image_file" class="">Upload image</label>
<input type="file" id="" name="image_file" class="" /><br />
</div>
<div class="link_input extra_field">
<label for="link_url" class="">Link</label>
<input type="text" id="" name="link_url" class="" /><br />
<label for="link_description" class="">Description</label>
<input type="text" id="" name="link_description" class="" /><br />
</div>
<input type="submit" id="updatestatus" name="updatestatus"
value="Update" />
</form>
These changes also need to be made to the post template, for posting on another user's profile.
jQuery to enhance the user experience
For accessibility purposes, we need this form to function regardless of whether the user has JavaScript enabled on their browser. To that end, we should use JavaScript to hide the unused form elements. So, even if the user has JavaScript disabled, they can still use all aspects of the form. We can then use JavaScript to enhance the user experience, toggling which aspects of the form are hidden or shown.
<script type="text/javascript">
$(function() {
First, we hide all of the extended status fields.
$('.extra_field').hide();
$("input[name='status_type']").change(function(){
When the user changes the type of status, we hide all of the extended fields.
$('.extra_field').hide();
We then show the fields directly related to the status type they have chosen.
$('.'+ $("input[name='status_type']:checked").val() +
'_input').show();
});
});
</script>
View in action
If we now take a look at our status updates page for our profile, we have some radio buttons that we can use to toggle elements of the form.
Images
To process images as a new status type, we will need a new database table and a new model to extend from the main status model. We will also need some new views, and to change the profile and status stream controllers (though we will make those changes after adding the three new status types).
Database table
The database table for images simply needs two fields:
Field
Type
Description
ID
Integer, Primary key
To relate to the main statuses table
Image
Varchar
The image filename
These two fields will be connected to the statuses table via a left join, to bring in the image filename for statuses that are images.
Model
The model needs to extend our statuses model, providing setters for any new fields, call the parent constructor, call the parent setTypeReference method to inform that it is an image, call the parent save method to save the status, and then insert a new record into the image status table with the image information.
Class, variable, and constructor
Firstly, we define the class as an extension of the status class. We then define a variable for the image, and construct the object. The constructor calls the parent setTypeReference method to ensure it generates the correct type ID for an image, and then calls the parent constructor so it too has reference to the registry object. This file is saved as /models/imagestatus.php.
<?php
/**
* Image status object
* extends the base status object
*/
class Imagestatus extends status {
private $image;
/**
* Constructor
* @param Registry $registry
* @param int $id
* @return void
*/
public function __construct( Registry $registry, $id = 0 )
{
$this->registry = $registry;
parent::setTypeReference('image');
parent::__construct( $this->registry, $id );
}
To call a method from an object's parent class, we use the parent keyword, followed by the scope resolution operator, followed by the method we wish to call.
Processing the image upload
When dealing with image uploads, resizing, and saving, there are different PHP functions that should be used depending on the type of the image. To make this easier and to provide a centralized place for dealing with image uploads and other image-related tasks, we should create a library file (lib/images/imagemanager. class.php) to make this easier.
Let's discuss what an image manager library file should do to make our lives easier:
Process uploading of an image from $_POST data
Verify the type of file and the file extension
Process images from the file system so that we can modify them
Display an image to the browser
Resize an image
Rescale an image by resizing either the x or y co-ordinate, and scaling the other co-ordinate proportionally
Get image information such as size and name
Save the changes to the image
The following is the code required to perform the above-mentioned tasks:
<?php
/**
* Image manager class
* @author Michael Peacock
*/
class Imagemanager
{
/**
* Type of the image
*/
private $type = '';
/**
* Extensions that the user can upload
*/
private $uploadExtentions = array( 'png', 'jpg', 'jpeg', 'gif' );
/**
* Mime types of files the user can upload
*/
private $uploadTypes = array( 'image/gif', 'image/jpg',
'image/jpeg', 'image/pjpeg', 'image/png' );
/**
* The image itself
*/
private $image;
/**
* The image name
*/
private $name;
public function __construct(){}
We need a method to load a local image, so that we can work with images saved on the servers file system.
/**
* Load image from local file system
* @param String $filepath
* @return void
*/
public function loadFromFile( $filepath )
{
Based on the path to the image, we can get information on the image including the type of image (getimagesize gives us an array of information on the image; the second element in the array is the type).
$info = getimagesize( $filepath );
$this->type = $info[2];
We can then compare the image type to various PHP constants, and depending on the image type (JPEG, GIF, or PNG) we use the appropriate imagecreatefrom function.
if( $this->type == IMAGETYPE_JPEG )
{
$this->image = imagecreatefromjpeg($filepath);
}
elseif( $this->type == IMAGETYPE_GIF )
{
$this->image = imagecreatefromgif($filepath);
}
elseif( $this->type == IMAGETYPE_PNG )
{
$this->image = imagecreatefrompng($filepath);
}
}
We require a couple of getter methods to return the height or width of the image.
/**
* Get the image width
* @return int
*/
public function getWidth()
{
return imagesx($this->image);
}
/**
* Get the height of the image
* @return int
*/
public function getHeight()
{
return imagesy($this->image);
}
We use a simple resize method that resizes the image to the dimensions we request.
/**
* Resize the image
* @param int $x width
* @param int $y height
* @return void
*/
public function resize( $x, $y )
{
$new = imagecreatetruecolor($x, $y);
imagecopyresampled($new, $this->image, 0, 0, 0, 0, $x, $y,
$this->getWidth(), $this->getHeight());
$this->image = $new;
}
Here we use a scaling function that takes a height parameter to resize to and scales the width accordingly.
/**
* Resize the image, scaling the width, based on a new height
* @param int $height
* @return void
*/
public function resizeScaleWidth( $height )
{
$width = $this->getWidth() * ( $height / $this->getHeight() );
$this->resize( $width, $height );
}
Similar to the above method, this method takes a width parameter, resizes the width, and rescales the height based on the width.
/**
* Resize the image, scaling the height, based on a new width
* @param int $width
* @return void
*/
public function resizeScaleHeight( $width )
{
$height = $this->getHeight() * ( $width / $this->getWidth() );
$this->resize( $width, $height );
}
The following is another scaling function, this time to rescale the image to a percentage of its current size:
/**
* Scale an image
* @param int $percentage
* @return void
*/
public function scale( $percentage )
{
$width = $this->getWidth() * $percentage / 100;
$height = $this->getheight() * $percentage / 100;
$this->resize( $width, $height );
}
To output the image to the browser from PHP, we need to check the type of the image, set the appropriate header based off the type, and then use the appropriate image function to render the image. After calling this method, we need to call exit() to ensure the image is displayed correctly.
/**
* Display the image to the browser - called before output is sent,
exit() should be called straight after.
* @return void
*/
public function display()
{
if( $this->type == IMAGETYPE_JPEG )
{
$type = 'image/jpeg';
}
elseif( $this->type == IMAGETYPE_GIF )
{
$type = 'image/gif';
}
elseif( $this->type == IMAGETYPE_PNG )
{
$type = 'image/png';
}
header('Content-Type: ' . $type );
if( $this->type == IMAGETYPE_JPEG )
{
imagejpeg( $this->image );
}
elseif( $this->type == IMAGETYPE_GIF )
{
imagegif( $this->image );
}
elseif( $this->type == IMAGETYPE_PNG )
{
imagepng( $this->image );
}
}
To load an image from $_POST data, we need to know the post field the image is being sent through, the directory we wish to place the image in, and any additional prefix we may wish to add to the image's name (to prevent conflicts with images with the same name).
/**
* Load image from postdata
* @param String $postfield the field the image was uploaded via
* @param String $moveto the location for the upload
* @param String $name_prefix a prefix for the filename
* @return boolean
*/
public function loadFromPost( $postfield, $moveto,
$name_prefix='' )
{
Before doing anything, we should check that the file requested is actually a file that has been uploaded (and that this isn't a malicious user trying to access other files).
if( is_uploaded_file( $_FILES[ $postfield ]['tmp_name'] ) )
{
$i = strrpos( $_FILES[ $postfield ]['name'], '.');
if (! $i )
{
//'no extention';
return false;
}
else
{
We then check that the extension of the file is in our allowed extensions array.
$l = strlen( $_FILES[ $postfield ]['name'] ) - $i;
$ext = strtolower ( substr( $_FILES[ $postfield ]['name'],
$i+1, $l ) );
if( in_array( $ext, $this->uploadExtentions ) )
{
Next, we check if the file type is an allowed file type.
if( in_array( $_FILES[ $postfield ]['type'],
$this->uploadTypes ) )
{
Then, we move the file, as it has already been uploaded to our server's temp folder, to our own uploads directory and load it into our image manager class for any further processing we wish to make.
$name = str_replace( ' ', '', $_FILES[
$postfield ]['name'] );
$this->name = $name_prefix . $name;
$path = $moveto . $name_prefix.$name;
move_uploaded_file( $_FILES[ $postfield ]['tmp_name'] ,
$path );
$this->loadFromFile( $path );
return true;
}
else
{
// 'invalid type';
return false;
}
}
else
{
// 'invalid extention';
return false;
}
}
}
else
{
// 'not uploaded file';
return false;
}
}
The following getter method is used to return the name of the image we are working with:
/**
* Get the image name
* @return String
*/
public function getName()
{
return $this->name;
}
Finally, we have our save method, which again must detect the type of image, to work out which function to use.
/**
* Save changes to an image e.g. after resize
* @param String $location location of image
* @param String $type type of the image
* @param int $quality image quality /100
* @return void
*/
public function save( $location, $type='', $quality=100 )
{
$type = ( $type == '' ) ? $this->type : $type;
if( $type == IMAGETYPE_JPEG )
{
imagejpeg( $this->image, $location, $quality);
}
elseif( $type == IMAGETYPE_GIF )
{
imagegif( $this->image, $location );
}
elseif( $type == IMAGETYPE_PNG )
{
imagepng( $this->image, $location );
}
}
}
?>
Using the image manager library to process the file upload
Now that we have a simple, centralized way of processing file uploads and resizing them, we can process the image the user is trying to upload as their extended status.
/**
* Process an image upload and set the image
* @param String $postfield the $_POST field the image was uploaded
through
* @return boolean
*/
public function processImage( $postfield )
{
require_once( FRAMEWORK_PATH .
'lib/images/imagemanager.class.php' );
$im = new Imagemanager();
$prefix = time() . '_';
if( $im->loadFromPost( $postfield, $this->registry-
>getSetting('upload_path') . 'statusimages/', $prefix ) )
{
$im->resizeScaleWidth( 150 );
$im->save( $this->registry->getSetting('upload_path') .
'statusimages/' . $im->getName() );
$this->image = $im->getName();
return true;
}
else
{
return false;
}
}
Saving the status
This leaves us with the final method for saving the status. This calls the parent object's save method to create the record in the statuses table. Then it gets the ID, and inserts a new record into the images table with this ID as the ID.
/**
* Save the image status
* @return void
*/
public function save()
{
// save the parent object and thus the status table
parent::save();
// grab the newly inserted status ID
$id = $this->getID();
// insert into the images status table, using the same ID
$extended = array();
$extended['id'] = $id;
$extended['image'] = $this->image;
$this->registry->getObject('db')->insertRecords(
'statuses_images', $extended );
}
}
?>
Read more