PHP 5 Social Networking: Integrating Media in Profile Posts

PHP 5 Social Networking


October 2010

$26.99

Create a powerful and dynamic Social Networking website in PHP by building a flexible framework

        Read more about this book      

(For more resources on PHP, see here.)

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.

PHP 5 Social Networking: Integrating Media in Profile Posts

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 about this book      

(For more resources on PHP, see here.)

Video (via YouTube)

To support video (via YouTube), we need one additional field on the form for the user to paste in the YouTube URL. From the URL, we can automatically generate code to play the video, and we can also look up the thumbnail image of the video from YouTube, from the data contained within the URL.

Database

As with our image's status type, we only require two fields in our new table:

Field

Type

Description

ID

Integer, Primary key

To relate to the main statuses table

Video_id

Varchar

The YouTube video ID

Model

The model needs to be very similar to our image's model. Firstly, the class extends the status class. Then, we have our variable for the video ID, after which we construct the object by calling the parent object's setTypeReference and __construct methods.

<?php
/**
* Video status object
* extends the base status object
*/
class Videostatus extends status {
private $video_id;

/**
* Constructor
* @param Registry $registry
* @param int $id
* @return void
*/
public function __construct( Registry $registry, $id = 0 )
{
$this->registry = $registry;
parent::__construct( $this->registry, $id );
parent::setTypeReference('video');
}

We then have a setter method to set the video ID (assuming we know what the video ID is).

public function setVideoId( $vid )
{
$this->video_id = $vid;
}

Then, we have a useful setter method that parses the YouTube URL, extracts the video ID from it, and sets the class variable accordingly. In this case, if no video ID is found in the URL, it uses a clip from the TV series "Dinosaurs" as a default video.

public function setVideoIdFromURL( $url )
{
$data = array();
parse_str( parse_url($url, PHP_URL_QUERY), $data );
$this->video_id = $this->registry->getObject('db')-
>sanitizeData(isset( $data['v'] ) ? $data['v']
: '7NzzzcOWPH0');
}

Finally we have our save method, which works in the same way as the image model.

/**
* Save the video 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 video status table, using the same ID
$extended = array();
$extended['id'] = $id;
$extended['video_id'] = $this->video_id;
$this->registry->getObject('db')->insertRecords(
'statuses_videos', $extended );
}
}
?>

Links

When sharing links with other users we need to at least store the URL itself. We could also store a brief description of the link or even an image from the site. If we wished, we could automatically populate this information from the link. However, for the moment, we will stick to just storing the link and a brief description of it.

Database

Our video statuses table requires three fields: an ID, the URL of the link, and the name or description of the link.

Field

Type

Description

ID

Integer, Primary key

To relate to the main statuses table

URL

Varchar

The link itself

Descriptionq

Varchar

Description of the link

Model

As with the video and image types, we require a simple model to extend our statuses model (models/videostatus.php).

<?php
/**
* Link status object
* extends the base status object
*/
class Linkstatus extends status {

private $url;
private $description;

Our constructor needs to set the registry, call the parent class' constructor, and then set the type of status by calling the parent's setTypeReference method.

/**
* Constructor
* @param Registry $registry
* @param int $id
* @return void
*/
public function __construct( Registry $registry, $id = 0 )
{
$this->registry = $registry;
parent::__construct( $this->registry, $id );
parent::setTypeReference('link');
}

We then have setters for the variables we are extending onto the class.

/**
* Set the URL
* @param String $url
* @return void
*/
public function setURL( $url )
{
$this->url = $url;
}

/**
* Set the description of the link
* @param String $description
* @return void
*/
public function setDescription( $description )
{
$this->description = $description;
}

Finally, our save method needs to call the parent object's save method, to save the status record. It then gets the ID of the status and creates a record in the statuses_links table, relating the core status with the custom link data.

/**
* Save the link 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 link status table, using the same ID
$extended = array();
$extended['id'] = $id;
$extended['URL'] = $this->url;
$extended['description'] = $this->description;
$this->registry->getObject('db')->insertRecords(
'statuses_links', $extended );
}
}
?>

Extending the profiles

With new database tables, status forms, and models in place for the new status types of Dino Space, we need to extend our profiles to save these new statuses, and also to include the information from these additional tables in the profile's statuses.

Processing the new status posts

In our profile statuses controller (controllers/profile/ profilestatusescontroller.php) in the addStatus method, in the two instances where we construct the status object, we instead need to check which type of status is being posted, and if it is a different status type, we must also include that file, and instead construct that object.

if( isset( $_POST['status_type'] ) && $_POST['status_type'] !=
'update' )
{
if( $_POST['status_type'] == 'image' )
{
require_once( FRAMEWORK_PATH . 'models/imagestatus.php' );
$status = new Imagestatus( $this->registry, 0 );
$status->processImage( 'image_file' );
}
elseif( $_POST['status_type'] == 'video' )
{
require_once( FRAMEWORK_PATH . 'models/videostatus.php' );
$status = new Videostatus( $this->registry, 0 );
$status->setVideoIdFromURL( $_POST['video_url'] );
}
elseif( $_POST['status_type'] == 'link' )
{
require_once( FRAMEWORK_PATH . 'models/linkstatus.php' );
$status = new Linkstatus( $this->registry, 0 );
$status->setURL( $this->registry->getObject('db')-
>sanitizeData( $_POST['link_url'] ) );
$status->setDescription( $this->registry->getObject('db')-
>sanitizeData( $_POST['link_description'] ) );
}
}
else
{
$status = new Status( $this->registry, 0 );
}

Altering our profile status' query

Our updates query (in the profile statuses controller) needs to be altered to left join onto the various extended statuses tables, pulling in additional information where appropriate, like the following code:

$sql = "SELECT t.type_reference, t.type_name, s.*, pa.name as
poster_name, i.image, v.video_id, l.URL, l.description FROM
status_types t, profile p, profile pa, statuses s LEFT JOIN
statuses_images i ON s.ID=i.id LEFT JOIN statuses_videos v ON
s.ID=v.id LEFT JOIN statuses_links l ON s.ID=l.id WHERE t.ID=s.type
AND p.user_id=s.profile AND pa.user_id=s.poster AND
p.user_id={$user} ORDER BY s.ID DESC LIMIT 20";

Status views

Next, we need to create the various template files to be included to display the relevant status information.

Images

It is saved as views/default/templates/profile/updates/image.tpl.php.

<p><strong>{poster_name}</strong>: posted an image "{update}"</p>
<img src="uploads/statusimages/{image}" alt="Image" />
<!-- START comments-{ID} -->
<p>&nbsp;Comments:</p>
<p>&nbsp;{comment} by {commenter}</p>
<!-- END comments-{ID} -->

Video

It is saved as views/default/templates/profile/updates/video.tpl.php.

<p><strong>{poster_name}</strong>: posted an video "{update}"</p>
<object width="200" height="164"><param name="movie"
value="http://www.youtube.com/v/{video_id}&amp;hl=en_GB&amp;fs=1?
rel=0&amp;border=1"></param><param name="allowFullScreen"
value="true"></param><param name="allowscriptaccess"
value="always"></param><embed
src="http://www.youtube.com/v/{video_id}&amp;hl=en_GB&amp;fs=1?rel=
0&amp;border=1" type="application/x-shockwave-flash"
allowscriptaccess="always" allowfullscreen="true" width="200"
height="164"></embed></object>
<!-- START comments-{ID} -->
<p>&nbsp;Comments:</p>
<p>&nbsp;{comment} by {commenter}</p>
<!-- END comments-{ID} -->

Links

It is saved as views/default/templates/profile/updates/link.tpl.php.

<p><strong>{poster_name}</strong>: posted an link "{update}"</p>
<a href="{URL}">{description}</a>
<!-- START comments-{ID} -->
<p>&nbsp;Comments:</p>
<p>&nbsp;{comment} by {commenter}</p>
<!-- END comments-{ID} -->

In action

Let's now take a look at our profile with these new status types on!

Images

After posting a status update with an image, the image is resized and displayed beneath the status.

PHP 5 Social Networking: Integrating Media in Profile Posts

Videos

A nice YouTube player is embedded with the video we selected when we posted a new video.

PHP 5 Social Networking: Integrating Media in Profile Posts

Links

If we post a status with a link attached, the link is shown beneath our status.

PHP 5 Social Networking: Integrating Media in Profile Posts

Repeat!

We also need to extend our status stream to work in the same way as our profiles, pulling in extended data from the extended status types. As we have discussed thoroughly how to update the profile view, you should be able to tackle the status stream on your own, applying the knowledge from this article.

Summary

In this article we have taken our simple status stream and user profiles and extended them to support statuses and messages that make use of other media including images, videos, and links.


Further resources on this subject:


Books to Consider

comments powered by Disqus