Create a Portfolio Client Area Using PHP and MySQL: Part 4

Since Part 3, we've gotten through all the membership stuff — basically something you could find on any PHP/MySQL tutorial. Now, though, we will finally get into the part of this tutorial that makes it a "client area." In this tutorial, we'll cover the "shared documents" section of the client area.

Sharing documents back and forth between clients can be a real hassle via email, and other online tools to handle this can get expensive. Having your own client area manage documents is a great way to combat both issues.

To begin, let's brainstorm what we'll need this page to do:

  • It should allow the client to upload documents — text, images, Word docs, etc.
  • It should process the uploaded file to determine the size, type, and place it in the correct area. It should also be smart enough to avoid attacks or huge files that would hurt things on your end.
  • It should display any already uploaded documents, and let the client view/download them again.

With the bit of organization we have now, we jump right in and get started.

Read. up on the other parts of this tutorial below.

Create the "documents.php" File

Before we get into any real coding, let's create a new file for the shared documents area. I'll call it "documents.php" for this tutorial. Let's also remember to determine if the client is logged in, otherwise redirecting them to the log in page.

This was also done on the profile page, (previous tutorial) so we can just do a bit of copying and pasting for this page, and any page we want protected.

<?php
session_start();
if(!isset($_SESSION['username'])){
header( 'Location: loginform.html' );
}
require('db.php'); // Connect to the database since we'll be needing it later.

Notice I've also deleted the comments that we originally had on the profile page for this section of code. As we get more familiar with our code, it may not be necessary to keep all the comments. (Although it should always be decently documented.) This will save space in our file.

The Upload Script

We're going to want to keep all of the uploaded files in one folder, preferably called "uploads." Because of our unique goal for this script, though, we're going to need to find a way to associate a client with their own files.

To do this, let's create a new table in our database, and store the path to the uploaded file along with the same ID as our current client. Here's a breakdown of how it can work:

  • The client is logged in, with their session variables set. Although we never set the client's ID into the session's variables, we can still easily find it.
  • When the client uploads a file, our upload script will make a copy of the file and save it to a new location. We place both this URL and the same number as the client ID in a new table called "uploads."
  • Whenever a client uploads a new document, it will have that same ID number to reference. So, when the time comes to retrieve the information, we can use this ID number to display the files' information.
  • With a loop, we can easily find all of the client's uploads, and with their link stored, we can display them accordingly with a download link.

Let's begin this process now. We will need to add more basic security once we have a first working draft of our script ready.

The Upload HTML Form

For our upload form, I've used the basic form from the File Upload tutorial on Tizag. There is explanation below of our form, but feel free to check out that tutorial in it's entirety for more insight.

<form enctype="multipart/form-data" action="uploader.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
Choose a file to upload: <input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>

Here is a brief description of the important parts of the above code:

  • enctype="multipart/form-data" - Necessary for our to-be-created PHP file to function properly.
  • action="uploader.php" - The name of our PHP page that will be created, shortly.
  • method="POST" - Informs the browser that we want to send information to the server using POST.
  • input type="hidden" name="MA... - Sets the maximum allowable file size, in bytes, that can be uploaded. This safety mechanism is easily bypassed and we will show a solid backup solution in PHP. We have set the max file size to 100KB in this example.
  • input name="uploadedfile" - uploadedfile is how we will access the file in our PHP script.

Now, let's customize it a bit to fit our own needs. In the first line, we're going to do something we haven't done throughout the whole tutorial. We haven't done it yet to keep things simple, but doing it now can help you organize your code better for past scripts and for this one:

<form enctype='multipart/form-data' action='documents.php' method='POST'>

Notice we changed the form from "uploader.php" to call itself with "documents.php". This way, we can include our code on the same page. We will now have one page instead of our normal two. It can be a good practice to go through all our old files so far and change them to this format if you wish.

Create a Database Table to Hold Our URL's

Before we go into the script, we'll need to create our database table that will hold two things: an ID number that matches the client's ID, and the URL to the saved file. After creating this, we can first store the information into this table, and then use it later to create the rest of our documents area.

  1. First log into phpMyAdmin, and go into the ClientArea database.
  2. Create a new table, and call it "uploads". Enter in "2" fields, and hit "Go."
  3. Create new table

  4. I filled in the following information below. Below the image are additional notes for specifying this to your own needs:
    Values

    Everything in the above image should be the same, or at least similar depending on what you want to name your values, except for the "Length/Values". By putting the length of ID at two, this gives room for 99 clients. If you feel you will be having more than that over the lifetime of this script, feel free to bump it up to three. This will give you potential for 999 clients.

    The URL length depends on where you're going to store the uploads, and what you'll name the folder. The character length will have to include your upload folder's name, the filename, the extension, and any punctuation throughout that. A sample file name would be "uploads/contentforwebsite.doc", which would be 29 characters. Give the client enough leeway for the file name length, but also not so long that it could be spam.

  5. It will say "No Index Defined!" once we go through and create this table, but that's fine for now.

The Upload Script

We continue to follow the tutorial over at Tizag:

When the documents.php file is executed, the uploaded file exists in a temporary storage area on the server. If the file is not moved to a different location it will be destroyed! To save our precious file we are going to need to make use of the $_FILES associative array.

The $_FILES array is where PHP stores all the information about files. There are two elements of this array that we will need to understand for this example.

  • uploadedfile - uploadedfile is the reference we assigned in our HTML form. We will need this to tell the $_FILES array which file we want to play around with.
  • $_FILES['uploadedfile']['name'] - name contains the original path of the user uploaded file.
  • $_FILES['uploadedfile']['tmp_name'] - tmp_name contains the path to the temporary file that resides on the server. The file should exist on the server in a temporary directory with a temporary name.

Now we can finally start to write a basic PHP upload manager script! Here is how we would get the temporary file name, choose a permanent name, and choose a place to store the file.

Create a new folder called "uploads" in the directory containing all of your other client area files. This script will not do it for you, and you must create if before the script is ever run.

Add the code below to the PHP portion of the page. This should still be in "documents.php" under where we checked if the user was logged in.

// Where the file is going to be placed
$target_path = "uploads/";
// Add the original filename to our target path.
// Result is "uploads/filename.extension"
$target_path = $target_path . basename( $_FILES['uploadedfile']['name']);

Basically, we've used a few lines of code to create just one variable: "$target_path". This will be the pathname to where we want to save our temporary uploaded file. It will be permanently saved here.

if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
echo "The file has been uploaded";
} else{
echo "There was an error uploading the file, please try again!";
}

The code above uses the "move_uploaded_file() function to do all the work for us. It takes two parameters: the current temporary path ($_FILES['uploadedfile']['tmp_name']) and our new path we'd like to save the file to ($target_path).

The entire thing is in an if/else statement. If the function works and the file is moved, it was a success and will tell us so. Otherwise, it tells us that there was an error.

We're going to want to change the happenings of the if/else statement to better meet our goal. The error message is fine, but we'll want to do more if the file was successfully moved. Below is what we still need to do:

  1. Insert the client's ID and path URL into our new table in our database.
  2. Return the client to the documents.php page. We will be displaying the files later in the tutorial, and this refresh should show the new file.

Let's first insert the data into our table using a MySQL query:

 $client_ID = mysql_query("SELECT 'client_ID'
FROM 'clients'
WHERE username='".$_SESSION['username']."'");

if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
mysql_query("INSERT INTO uploads(ID, URL) VALUES ('$client_ID', '$target_path')");
} else{
echo "There was an error uploading the file, please try again!";
}

Ok, so we created a variable called "client_ID" and selected our client's ID from our database using a MySQL query. We were able to retrieve the correct information by selecting the field client_ID where our username was equal to the username in our session variable (from when the user logs in). Also, note we put it outside of the if/else statement so we can use it later.

Finally, we used another MySQL query to insert the client ID and path into the database within the table "uploads." Now all we have to do is redirect the user to the same page.

if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
require('db.php'); // We havn't connected to the database yet, so we'll do it now.
$client_ID = mysql_query("SELECT client_ID
FROM clients
WHERE username='".$_SESSION['username']."'");
mysql_query("INSERT INTO uploads(ID, URL) VALUES ('$client_ID', '$target_path')");
header( 'Location: documents.php' ) ;
} else{
echo "There was an error uploading the file, please try again!";
}

Be sure to test it out by trying to upload a file. To check if it worked, viewing the database and then the uploads folder.

Securing the Code

To begin, let's think about our structure. For this portion, we're just going to check the file type and size. It may be necessary for other users of the tutorial to look into further methods of security.

After identifying what we need to check, we have to identify our problem: We have to check the file type and file size, but if there is something wrong with either one of them, we can't let the script input any data. To solve this problem, let's use another simple if/else statement:

if(The file is not of the right type or of the right size){
Then display an error message.
else{Or else we can go ahead with the rest of the script that we've already written.}

Simple enough, right? Now we just need to put it into action.

if((!$_FILES["file"]["type"] == "image/gif")
||(!$_FILES["uploadedfile"]["type"] == "image/png")
||(!$_FILES["uploadedfile"]["type"] == "image/jpeg") // "jpeg" for Firefox
||(!$_FILES["uploadedfile"]["type"] == "image/pjpeg") // "jpeg" for IE
||(!$_FILES["uploadedfile"]["type"] == "text/css")
||(!$_FILES["uploadedfile"]["type"] == "text/html")
||(!$_FILES["uploadedfile"]["type"] == "text/javascript")
||(!$_FILES["uploadedfile"]["type"] == "application/msword")
||(!$_FILES["uploadedfile"]["type"] == "application/pdf")
&&(!$_FILES["file"]["size"] < 100000)){
echo "The file is not of the right type or size. It should be a
.gif, .png, .jpeg/.jpg, .css, .html, .javascript, .doc, or .pdf and under 100kb.";
echo "/n If you need to send me a file different from these specification, feel free to
email it to me at you@domain.com. These specifications are for the website's safety.";
}else{}

Before doing the rest, let's take a closer look at what we did for the basic "if" part of the if/else statement. For ease of use, it is in orange. In our $_FILES associative array, "$_FILES["uploadedfile"]["type"]" gives us our MIME type for the file type we've uploaded. A MIME type is basically a way of telling a browser what kind of file we're dealing with. You can get a list of available Window's MIME types here. By checking to see if the file type of our given file ($_FILES["uploadedfile"]["type"]) is equal to (==) our wanted MIME type (eg. image/png) we can specify what files we want to include.

Note that there is an exclamation point before each comparison (!). This is because we are checking to see if it's NOT this MIME type. If it's not, then the client has uploaded the wrong type of file. In addition, "||" means "or" and "&&" means "and". So what we're saying in this conditional is: "If our file type is not equal to gif, or png, or jpg, or jpeg, or pjpeg, or css, or html, or javascript, or msword, or pdf, AND it is not under the file size we need, then they have uploaded a bad file and we will display that message. The message is in blue to differentiate the code.

Also note that this isn't the most efficient way to do this, considering how many file types we are allowing. A better way would be to store the allowed MIME types in an array, and go through the array when comparing the current file. I've done it this way above for simplicity since I realize most readers of this tutorial are fairly new to PHP. It would be smart (and good practice) to find ways to make this code more efficient. In other cases though, if we would only want to include a few file types, this way of coding would be completely fine.

Next, we'll include the "else" portion, which is just going to be our code that inserts our document's data into our new table.

if((!$_FILES["file"]["type"] == "image/gif")
||(!$_FILES["uploadedfile"]["type"] == "image/png")
||(!$_FILES["uploadedfile"]["type"] == "image/jpeg") // "jpeg" for Firefox
||(!$_FILES["uploadedfile"]["type"] == "image/pjpeg") // "jpeg" for IE
||(!$_FILES["uploadedfile"]["type"] == "text/css")
||(!$_FILES["uploadedfile"]["type"] == "text/html")
||(!$_FILES["uploadedfile"]["type"] == "text/javascript")
||(!$_FILES["uploadedfile"]["type"] == "application/msword")
||(!$_FILES["uploadedfile"]["type"] == "application/pdf")
&&(!$_FILES["file"]["size"] < 100000){
echo "The file is not of the right type or size. It should be a
.gif, .png, .jpeg/.jpg, .css, .html, .javascript, .doc, or .pdf and under 100kb.";
echo "/n If you need to send me a file different from these specification, feel free to
email it to me at you@domain.com. These specifications are for the website's safety.";
}else{
	if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
mysql_query("INSERT INTO uploads(ID, URL) VALUES ('$client_ID', '$target_path')");
} else{
echo "There was an error uploading the file, please try again!";
}
}

Check to Make Sure We've Uploaded a File

Since we're going to be loading this code every time the client accesses the page, it wouldn't be smart to run the code for uploading a document unless the client did indeed try to upload a document. So, around our upload code, we can add a simple if statement making sure our $_FILES associative array is not empty. If it is empty it will ignore the code, and move onto the rest of the page.

if(!empty($_FILES)){
// Add the original filename to our target path.
// Result is "uploads/filename.extension"
$target_path = $target_path . basename( $_FILES['uploadedfile']['name']);
if((!$_FILES["file"]["type"] == "image/gif")
||(!$_FILES["uploadedfile"]["type"] == "image/png")
||(!$_FILES["uploadedfile"]["type"] == "image/jpeg") // "jpeg" for Firefox
||(!$_FILES["uploadedfile"]["type"] == "image/pjpeg") // "jpeg" for IE
||(!$_FILES["uploadedfile"]["type"] == "text/css")
||(!$_FILES["uploadedfile"]["type"] == "text/html")
||(!$_FILES["uploadedfile"]["type"] == "text/javascript")
||(!$_FILES["uploadedfile"]["type"] == "application/msword")
||(!$_FILES["uploadedfile"]["type"] == "application/pdf")
&&(!$_FILES["file"]["size"] < 100000){
echo "The file is not of the right type or size. It should be a
.gif, .png, .jpeg/.jpg, .css, .html, .javascript, .doc, or .pdf and under 100kb.";
echo "/n If you need to send me a file different from these specification, feel free to
email it to me at you@domain.com. These specifications are for the website's safety."
}else{
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
mysql_query("INSERT INTO uploads(ID, URL) VALUES ('$client_ID', '$target_path')");
} else{
echo "There was an error uploading the file, please try again!";
}
}
}

Display the Client's Documents

Throughout the next few steps, we'll be actually displaying the client's uploaded documents, and allowing them to download them as needed.

Display the Files

In order to display the files, every time the documents.php page is loaded it should check to see if there are any files in our uploads table with the same ID as our logged in client's ID. If there is, we can use a loop to display all of them. In addition, we can use basic HTML to format this.

$getDocuments = mysql_query("SELECT * FROM uploads WHERE ID='".$client_ID."'");
while($row = mysql_fetch_array($getDocuments, MYSQL_ASSOC))
{
echo "File URL: " . $row['URL'];
}

What we did first was use a MySQL query to select all from the uploads folder where "ID" was equal to our $client_ID variable (defined earlier in the script). Then, we went through a while loop. Remember, the mysql_fetch_array creates an associative array we can work with from our $getDocuments variable. By assigning this function to the variable $row, it will put each row of the array into this variable as we go through that while loop. At each iteration through the loop, we are simply displaying the file's URL.

Download Files

To download the file, we just need to link it to it's original source. We have the original source as the path name, so with a mix of HTML and our PHP, we can create a successful download link.

<a href="document.doc">document.doc</a>

Above is the plain HTML way to create a download link. Let's see how we can integrate PHP to dynamically pull all the download links needed:

$getDocuments = mysql_query("SELECT * FROM uploads WHERE ID='$client_ID'");
while($row = mysql_fetch_array($getDocuments, MYSQL_ASSOC))
{
echo "Download: <a href='" . $row['URL'] . "'>" . $row['URL'] . "</a>";
}

Our Final Script

<?php
session_start();
if(!isset($_SESSION['username'])){
header( 'Location: loginform.html' );
}
require('db.php'); // Connect to the database since we'll be needing it later.
// Where the file is going to be placed
$target_path = "uploads/";
$client_ID = mysql_query("SELECT 'client_ID'
FROM 'clients'
WHERE username='".$_SESSION['username']."'");
if(!empty($_FILES)){
// Add the original filename to our target path.
// Result is "uploads/filename.extension"
$target_path = $target_path . basename( $_FILES['uploadedfile']['name']);
if((!$_FILES["uploadedfile"]["type"] == "image/gif")
||(!$_FILES["uploadedfile"]["type"] == "image/png")
||(!$_FILES["uploadedfile"]["type"] == "image/jpeg") // "jpeg" for Firefox
||(!$_FILES["uploadedfile"]["type"] == "image/pjpeg") // "jpeg" for IE
||(!$_FILES["uploadedfile"]["type"] == "text/css")
||(!$_FILES["uploadedfile"]["type"] == "text/html")
||(!$_FILES["uploadedfile"]["type"] == "text/javascript")
||(!$_FILES["uploadedfile"]["type"] == "application/msword")
||(!$_FILES["uploadedfile"]["type"] == "application/pdf")
&&(!$_FILES["file"]["size"] < 100000)){
echo "The file is not of the right type or size. It should be a
.gif, .png, .jpeg/.jpg, .css, .html, .javascript, .doc, or .pdf and under 100kb.";
echo "
If you need to send me a file different from these specification, feel free to
email it to me at you@domain.com. These specifications are for the website's safety.";
}else{
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
mysql_query("INSERT INTO uploads(ID, URL) VALUES ('$client_ID', '$target_path')");
} else{
echo "There was an error uploading the file, please try again!";
}
}
}
// Displays all current files
$getDocuments = mysql_query("SELECT * FROM uploads WHERE ID='".$client_ID."'");
while($row = mysql_fetch_array($getDocuments, MYSQL_ASSOC))
{
echo "Download: <a href='" . $row['URL'] . "'>" . $row['URL'] . "</a><br />";
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Client Documents</title>
</head>
<body>
<form enctype='multipart/form-data' action='documents.php' method='POST'>
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
Choose a file to upload: <input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
</body>
</html>

* This final script should be a correct version that works. I had some confusion when fixing minor errors throughout the script in the end, so if anything doesn't match from this version and any of the code above, please let me know in the comments!

Wrapping Up

We now have a basic uploading script and a way to manage documents on the client's end. This seems a bit unnecessary though — but don't worry — the admin area is coming soon, and then the admin (you) and the client will be able to seamlessly share documents back and forth.

Instead of making the admin area next, though, we'll be finishing everything up from the client's perspective, so we can better plan and create the admin area once we get around to it. Next time, we'll create a very simple message board structure, or "note board" if you will, where both you and the client can post notes to each other, communicating back and forth without the hassle of email.

20 Comments

  1. Dzinepress August 8, 2009
  2. Nick August 9, 2009
  3. Justin August 10, 2009
  4. John August 10, 2009
  5. unicendatte August 18, 2009
  6. jez August 21, 2009
  7. arriane September 30, 2009
  8. taufik May 30, 2010
  9. KK September 28, 2010
  10. Rob Macintosh January 6, 2011
  11. Dee February 6, 2011
  12. euan February 7, 2011
  13. euan February 8, 2011
  14. Eldiavolo March 8, 2011
  15. Vikx April 28, 2011
  16. kyriakos May 8, 2011
  17. Tuesday September 26, 2011
  18. Manoj February 15, 2012
  19. adammydesign May 31, 2012
  20. adammydesign May 31, 2012

Leave a Reply