Introduction
I must admit that I have been seriously burning the midnight oil on this one ! It has been a very interesting and exciting experience. The challenge of creating this relatively complex application was not an easy one. As usual, learning a programming language does not only involve understanding the syntax but more a question of practice, practice and more practice !
At first progress is painfully slow and a lot of research is required practically to write every line of code. The learning curve starts to become less steep proportionally to the number of hours spent working with the language.
During the coding of this application I realized that to code in php one has to be tidy and organized. It is very easy to get away with messy code which might very well work but will eventually render the application very difficult to maintain. It became immediately apparent that coding has to be organised to be maintainable. The number of files can easily grow quickly and become more difficult to control.
I usually write this blog while I am coding but in this case I spent many hours coding and instead took notes on paper which I am now explaining in this blog. It would probably have been easier to blog while I was coding but I got so carried away coding and researching that I preferred doing it this way.
Application Overview
This application is by no means complete and given time it could possibly evolve into a useable application. Time constraints have limited the amount of work I could put into developing this application further.
The application uses an SQL database to store user credentials. I have also included a way to enroll new users but this is only for academic purposes. In a production environment the users would have to be verified by email, for example and not be created right away. This would typically involve moderation by an admin before they can actually start uploading files.
Database Structure
I used Navicat 8 for MySQL to design and manipulate the table that is used in the file repository. The image below shows the table structure :
Time permitting I would also have liked to create the script to check if the table exists and if it does not gives the admin the power to create the table. As this would also entail creating user hierarchy to be of any use I gave it little importance.
Besides the obvious Username and Password fields I also create a style field which would contain the preferred color selected by the user when he creates the account. Initially I populated these fields manually of course for testing purposes. Eventually when I created the "Join" script, the user is the allowed to select the desired color. I also though to creating the CSS on the fly using a php script. After some researching I discovered that this can be done, however it is a complex and time consuming task and outside the requirements of this project.
The CSS
I created a simple CSS script for the page which is basically a simple html page with a header, a footer and a main content div. I didn't use much graphics because I preferred to focus my efforts on the coding. Coding in php is new to me and I want to get as much practice I can get. Furthermore, not including a lot of images will keep the layout more fluid and allow for resizing.
I then created copies of the master css with different color settings that would serve to supply the choice of a color scheme for the users.
For example, here is the CSS for red colour scheme showing the header DIV only :
#header {
width: 700px;
height: 80px;
margin: 0 auto;
background-color:#FE5B67;
}
and here is the same CSS but from the bluecss file :
#header {
width: 700px;
height: 80px;
margin: 0 auto;
background-color:#5BC6FE;
}
As for the remainder of the CSS it is basically defining the DIV widths and colors to render the website. The image below shows the index.php rendered.
CSS on the fly ?
The difference between the CSS for Red, Green and Blue layouts is marginal. Basically, the background color and the button images are the only things changing. This is why I was seriously considering generating the CSS on the fly. As it is there is quite a lot of code repetition as the differences between the CSS files is marginal.
I looked into this and it can certainly be achieved however I decided to not pursue this for the time being.
PHP Includes and the HTML
Using "includes" can keep your PHP code relatively clean and it helps to separate the HTML scripts from the PHP scripts. I found using them very handy and easy to use. I split the HTML part of the website into two, namely the header and the footer part. These are then included using "include" within each page that needs to display a page from the website. The content simply goes in between.
as shown below "include" can help keep the scripts clean and easier to follow :
<?php
include("header.php"); ?>
<!-- main page content starts here -->
<?php include("footer.php"); ?>
Logging in a user
HTML form for login - login.php
login.php includes the HTML to create the login form which allows the user to type in his name and password to start using the system.
During the course of coding this application I got very familiar with using Sessions. After spending hours fiddling with code I realized that the session_start() function must be used every time anything is done with a session. I was using a lot of if(isset($_SESSION[''])) which was hiding the fact that the session was not being carried forward from page to page because I was not using session_start() every time. Intuitively I had erroneously expected that a session must only be started once but I was of course wrong!
login.php contains the form elements in an HTML table to display the text fields and labels in an orderly fashion and then calls checklogin.php to take action on the results.
Processing the form - checklogin.php
This script first gets the form variable from the form in login.php.It then compares the supplied credentials to those stored on the MySQL database.
I created a special function to connect to the database which I will be calling every time I need database access. This also has the added advantage that the MySQL login and password is only included in one location. Ideally this would be stored in a configuration file somewhere instead of in a script. This function is reproduced below :
function connect()
{
//Open connection to the local MySQL Server
$con = mysql_connect("localhost","root","root");
if (!$con)
{
die('Could not connect: ' . mysql_error());
} else {return $con;}
}
If the connection fails it will display the error that is generated by the MySQL connection and returns the connection as a result of the function. Once a good connection if returned, the script continues by retreiving the contents of the database using the following :
mysql_select_db("test", $con);
$result = mysql_query("SELECT * FROM usersTable");
The rows from the database and then places in an array and the script then iterates through the array to try and find a set of matching credentials to the ones supplied from the form using :
while($row = mysql_fetch_array($result))
{
if (($row['username'] == $user) and ($row['password'] == $pass)) {
$loginOk = true;
$loginOk is a flag that will be set should a matching pair be found. Once this happens the script sets a session variable to indicate that a user is logged on for this session. This variable is then checked in all the other script files whenever it needs to be known if a user is logged on and many times to disallow running of the script by people who are not logged on. This checking is done with the following line of code :
if(isset($_SESSION['userLogged'])){
Furthermore, in checklogin.php is a set of matching credentials is not supplied the script redirects execution to badlogin.php which informs the user that bad credentials where supplied. The script purposely does not specify if it was the login or password that was incorrect. Ideally, in a production environment, I would also include a "forgot password" script that would reset the user's password and email it to him. This would involve substantial work and changes, for example, the user would have to supply a valid email when he originally subscribes.
Security in checklogin.php - Handling SQL injection
I have taken steps to stop or at least minimize the possibility of SQL injection on the form text fields by using mysql_real_escape_string which "escapes" special characters from the text supplied in the form as follows :
$user = mysql_real_escape_string($user);
$pass = mysql_real_escape_string($pass);
The user's main "Dashboard" - main.php
main.php is the main script that brings together the functionality of this application. It is through this file that the user is given the opportunity to manipulate his files. Obviously a check on the userLogged session variable is made before anything can be done as a security measure.
The screen shot below shows a typical dashboard screen without any files having been uploaded so far. It also shows the "red" layout :
This script creates an array with the files that are contained in this user's folder and then displays this array in a list box as follows :
# Create array with the files in the current directory:
while ($file = readdir($handle))
{if (($file!='.') and ($file!='..')) {$files[] = $file;}}
closedir($handle);
It also takes care to omit the "." and ".." entries and not display them with the rest of the list elements.
The screen shot below shows the list box as above but now populated with some files and folders :

As shown in the image above I managed to display file and folders differently and even catered for displaying icons of some of the file types. The script will display an icon for .doc, .pdf, .jpg, .txt and I have an icon which shows that the extension is not displayable.
The following code takes care of changing the CSS class depending on the file type and depending on whether the item is a file or folder.
if (is_dir($folder."/".$file)){echo "<option value='".$file."' class='folder'>".$file."</option>";}
if (!is_dir($folder."/".$file)){
$path_info = pathinfo($folder."/".$file);
$ext=$path_info['extension'];
if (($ext=="txt") or ($ext=="pdf") or ($ext=="doc") or ($ext=="jpg")) {
if ($ext=="txt" ) {echo "<option value='".$file."' class='txt'>".$file."</option>";}
if ($ext=="pdf" ) {echo "<option value='".$file."' class='pdf'>".$file."</option>";}
if ($ext=="doc" ) {echo "<option value='".$file."' class='doc'>".$file."</option>";}
if ($ext=="jpg" ) {echo "<option value='".$file."' class='jpg'>".$file."</option>";}
} else {echo "<option value='".$file."' class='unknown'>".$file."</option>";}
}
}
The CSS class will be selected accordingly. Below are three of the classes I created for this purpose :
.jpg {
background: url('images/jpg.png') no-repeat left top;
padding-left: 20px;
}
.unknown {
background: url('images/unknown.png') no-repeat left top;
padding-left: 20px;
}
.folder{
background: url('images/folder.png') no-repeat left top;
padding-left: 20px;
}
Naturally, these could have been extended to cater for the most common file types but due mostly to time constraints I decided to stick to a few. The CSS is simply displaying an image on the left of the item name and using padding to move the text away from on top of the image.
The form itself which displays the buttons to allow the user to select the action on the selected file is an exercise in multi-button forms which I had to research:
<input type='hidden' name='MAX_FILE_SIZE' value='100000' />
<br /><br />Choose a file to upload: <br />
<input name='uploadedfile' type='file' /><br />
<input type='submit' name='upload' value='Upload File' /><br /><br />
<input type='submit' name='delete' value='Delete File' />
<input type='submit' name='rename' value='Rename File' />
<input type='submit' name='download' value='Download File' />
<input type='submit' name='copy' value='Copy File' /><br /><br />
<input type='submit' name='create_folder' value='Create Folder' />
<input type='submit' name='down_folder' value='Open Folder' />
<input type='submit' name='root_folder' value='Root Folder' />
</form>
I also created a special CSS class to display the buttons in different colors depending on the theme. The following CSS class changes the buttons accordingly.
input[type="submit"] {
background:url(images/bluebutton.png) no-repeat left top;
cursor:pointer;
width: 100px;
height: 31px;
border: 0px solid;
}
The class is the same for all three colors except where it specifies the button image. There is of course a different image button for each theme.
The form in main.php then calls actions.php to process the form entries.
Alternative ways of display
After spending many hours coding and researching I also realized that I could have tackled the file and folder display in a different way. Instead of using a list box I could have used a text list in a table and then used hyperlinks for some of the features.
Showing space used and number of files
It was interesting to add a script that shows the number of files and space used as well as number of folders for this user. I researched this and found several people on forums who wrote similar scripts and what is included here is borrowed from a collection of these scripts and adapted to this scenario. Here is the code :
$ar=getFolderSize($path);
echo "<br />You have used : ".sizeFormat($ar['size'])." of your allocated space";
echo " in ".$ar['count']." files<br>";
echo "which are in ".$ar['dircount']." folders<br>";
The code above is invoking the getFolderSize function which recursively displays the space being used. The function is being fed the $path variable which is created by the script depending on what the user was last viewing. The getFolderSize() function then calls the sizeFormat() function which prepares the result for outputting to the user and handles displaying Bytes, Mb and Gb accordingly.
The Upload Feature
This feature handles the uploading of files by the user. It also checks if a file name exists and if it does it prompts the user. If there is no other error the script uploads the file into the current folder and also handles the user sub folders properly.
The full script to perform the upload feature is shown below:
//UPLOADING
if(isset($_POST['upload'])){
$usedSpaceArray=getFolderSize($target_path);
$usedSpace=$usedSpaceArray['size'];
$target_path = $target_path . "/" . basename( $_FILES['uploadedfile']['name']);
//First lets check if the file exists already
if (file_exists($target_path)) { //checks if the new filename exists and prompt if it does
include("header.php");
echo "A file with the same name exists - please try again!";
echo "<br /><a href='main.php'>Dashboard</a>";
include("footer.php");}
//Check if with this file we will exceed the maximum
$upedFileSize=$_FILES['uploadedfile']['size'];
if (($usedSpace+$upedFileSize)>500000)
{include("header.php");
echo "Sorry but uploading this file will bring you over the limit of 500Kb<br /><a href='main.php'>Dashboard</a>";
include("footer.php");}
else {
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
header("Location: main.php"); //file uploades so go the the dashboard immediately
} else{
include("header.php");
echo "There was an error uploading the file, please try again! <br /><a href='main.php'>Dashboard</a>";
include("footer.php");}
}}
I had some difficulty understanding how file uploading works in Php and I spent a lot of time trying to find a way how I can really check if the uploaded file will bring the user over his space allocation limit. Apparently there is no way this can be done without resorting to Javascript. Each method in php required that the file be uploaded in the temportary folder first and then not moved when it is rejected due to quota limitations.
My script handles maximum file size limit as well as folder size limit which I have set to a default of 500,000 bytes. This could obviously be included in the database as a new field so that users can have individual folder size limits. It would also not be to difficult to include allowed file types for every user, again placing the allowed file type extensions in the database.
The uploading script shown above is included in the actions.php script which also includes all the other functions that can be performed such as :
The Delete Feature
The script also handles deleting of files and uses the "unlink" instruction to delete the underlying file. Due to time constrains I did not also include a script that prompts the user again before doing the delete action. The code also handles the possibility that no file is selected by the user. In this case it calls a function from funcs.php that displays a prompt as shown below in the full code listing of this feature :
// DELETING
if(isset($_POST['delete'])){
if(isset($_POST['listbox'])){
unlink($target_path."/".$selectedFile);
include("header.php");
echo "File :".$selectedFile." deleted";
echo "<br /><a href='main.php'>Dashboard</a>";
include("footer.php");
} else noFileSelected();
}
Allowing for file download
The code I have included here is mostly code that I have adapted that was originally borrowed from other coders on the internet as I did not have much knowledge about how this could be achieved. Ideally more file extension types could be added to the lines below to allow more file types to be opened automatically when downloaded by the user :
switch ($ext) {
case "pdf":
header("Content-type: application/pdf"); // add here more headers for diff. extensions
header("Content-Disposition: attachment; filename=\"".$path_parts["basename"]."\""); // use 'attachment' to force a download
The Rename File Feature
This feature, as its name implies, allows the user to specify a new name for the file through a new form which is displayed. The script also handles cases where no file is selected by the user before clicking the rename button. The script then calls rename_action.php to process the contents of the form :
if (file_exists($target_path."/".$newfilename)) { //checks if the new filename exists and prompt if it does
include("header.php");
echo "A file with the same name exists - please try again!";
echo "<br /><a href='main.php'>Dashboard</a>";
include("footer.php");
}
else {
rename($target_path."/".$oldfilename,$target_path."/".$newfilename);
header("Location: main.php");}}
As can be seen above, the script then invokes the rename() php function to do the actual renaming.
The Copy File Feature
The repository script allows users to create copies of files that have already been uploaded. The script creates a copy with a new name by adding a prefix of "copy of" to the original file. It also checks if a file with the new name already exists and prompts the user if it does as an be seen in the following code :
if (file_exists($target_path."/".$newfilename)) { //checks if the new filename exists and prompt if it does
include("header.php");
echo "A file with the same name exists - please rename it!";
echo "<br /><a href='main.php'>Dashboard</a>";
include("footer.php");}
else {copy($target_path."/".$_POST['listbox'],$target_path."/".$newfilename);
header("Location: main.php");
The Create Folder Feature
The application also allows the user to create folder which would be sub-folders of the user's root folder which in this case is the same as the username.
The script displays a new form to accept the new folder name and then invokes the newfolder_action.php script to process the request. This script the checks if a folder with the same name and if that is the case will inform the user and if not it will create the folder as follows :
if (is_dir($target_path."/".$new_folder_name)) { //checks if the new folder exists and prompt if it does
include("header.php");
echo "A folder with the same name exists - please try again!";
echo "<br /><a href='main.php'>Dashboard</a>";
include("footer.php");
}
else {
mkdir($target_path."/".$new_folder_name);
header("Location: main.php");}
Allowing the user to traverse folders
Using the following script, the application allows the user to view the contents of a sub folder :
//DOWN FOLDER
if(isset($_POST['down_folder'])){
if(isset($_POST['listbox'])){
chdir($target_path."/".$selectedFile);
$_SESSION['currentFolder']=$target_path."/".$selectedFile;
header("Location: main.php");
}
else nofolderselected();
}
I have also included a feature to allow the user to return to the root folder. Ideally it would have been "go up one folder" but due to time limitations I decided to let this go !
The code below handles this feature :
//ROOT FOLDER
if(isset($_POST['root_folder'])){
chdir("uploads/".$_SESSION['userLogged']);
$_SESSION['currentFolder']="uploads/".$_SESSION['userLogged'];
header("Location: main.php");
}
User Creation - join.php
I decided that this project would not be complete without including an experiment in allowing users to be created through this script. join.php produces a form that asks the user for the desired username and password as well as the preferred color which will influence the CSS that displays the site. The screen shot below shows how this looks in action :
Once the user clicks the "join" button the join.pho script invokes the joinAction.php script to process the selections and create the user entry. Again mysql_real_escape_string is used to add security and stop SQL injection scripting. The script then opens a connection to the SQL server using the connect() function that was explained previously in this text. It then uses the code below to query the database :
$result = mysql_query("INSERT INTO userstable (username, password, style) VALUES('$user', '$pass' , '$style') ");
Finally it informs the user of a successful creation or an error if one occurs. It then redirects the user to login with the freshly created credentials.
Styling the site according to user choice
The style selected by the user when the account is created is then used to select the appropriate CSS file to use. In checklogin.php (explained previously) I included the following line that sets a session variable according to the style :
session_start(); $_SESSION['colorscheme'] = $row['style'];
Logging Out
the logout.php script simply uses "unset" to kill the session and consequently the "userLogged" session variable.
funcs.php
During the coding of this application I had decided to place all function in a separate file. In this way they would be grouped together and easier to manage. I then used "include" to include the functions in the funcs.php file whenever needed.
Cross Browser Checks
The scripts were tested and showed successfully on Google Chrome, Mozilla Firefox and Microsoft Internet Explorer. The html behind this page is very simple and straightforward and I did not expect to have cross browser issues.
Security considerations
I have given attention to security using various methods some of which have already been explained in the text above. Primarily my security considerations are based around :
- Preventing SQL Injection by "escaping" rogue characters
- Encapsulating scripts in "check session" blocks to prevent running of a script by users who are not logged in
- Using .htaccess to prevent directory listing
For the last point re using .htaccess files I did some research and discovered that this would be the ideal way to prevent directory listing but will work on an Apache Server. As I was working with XAMPP which includes Apache, my .htaccess tests worked fine. I tested this by putting an .htaccess file in the "uploads" folder and using :
IndexIgnore *
which basically prevents listing of any type of file and produces a blank list. I also tried using :
Options –Indexes
but although this did prevent directory listing it also produced a server error.
Checking on Mobile phone platforms
A Google search revealed that there are many mobile phone emulators available on line which can be used to test this application and see how it looks on a mobile phone. I had to set my router to route port 80 to my PC so that the site can be viewed externally. It worked well immediately.
The screen shot below shows the testing done through www.ipadseek.com which revealed that the site appears well on the ipad.
I also tested the application on the iphone using another site and it also seemed to work, however as the site is set to use fixed widths, there is a lot of left to right scrolling. Ideally the site would be made with a fluid layout so that it would also display correctly on the iphone.
I also tested the system on an old style Nokia platform but in this case, although it did work, the results were much less usable as can be seen in the screenshot below :
Limitations
This is by no means a "ready for production" system. I would expect to allocate at least double the time I have allocated so far to get this ready for production.
I would also have to revert to including Javascript to enhance the use of this system, namely to provide for field validation before the fields are actually submitted and to provide with on-click functionality which intuitively users have come to expect.
A production system would also have to include limitations on the total amount of space that is allowed per user and a system for enrolling users that would have to be moderated by the administrator.
Also, testing had been limited due to time constraints and to go live much more testing would need to be performed.
Blogger failures and cloud computing
I have been working on this particular blog entry for quite some time, continously refining and adding material. A couple of days before the deadline Blogger went down. We were prevented from accessing our blog and at a point in time Blogger was even reporting that the blogs did not exist. In my case, I had a backup of the blog on MS Word and also had the XML file which I had exported so it was not a catastrophe but this incident made me think.
I am very excited about the prospects of cloud computing and I have always though that it would be the way forward, however if even site like Blogger had problems and have had to rollback then are we every going to be assured total safety ? It is already difficult to convince the businessman that it is safe to host applications and data on remote servers and occurrences like these are eye openers. In a world where business applications are already being hosted on clouds incidents like these could cost serious money and have serious consequences.