Pages

Banner 468

Tuesday 24 May 2011

Born Again... Virtually

0 comments
 
This week’s post is all about Second Life, an online virtual world created by a company called Linden Lab. Good or bad, I had never heard about Second Life before starting this course. Considering that as of 2011 Second Life has more than 20 million registered user accounts (Wikipedia) I had to ask myself how I could have missed it. I was never a fan of Massively Multiplayer Online Role-Playing Games (MMORPG) so that could explain why, but that’s beside the point. More importantly Second Life is more than just an MMORPG, firstly it’s not really a game and secondly it’s much more massive… from all angles. Its name says it all, Second Life allows you to lead a second, virtual life online.
I must admit that I found the prospect quite scary (for lack of a better word), and this was before I downloaded the client software or created an account. I mean scary in the sense that there is so much one can do and see in Second Life that it’s quite frankly overwhelming. But it only really hits you once you get in.

Creating An Account

I started off by creating an account, which is a task in itself. With more than 20 million registered users, trying to find a good user id is like looking for a particular pixel on the Second Life Grid (a needle in a haystack is easy in comparison). I have an obsession with my user/character names, I want them to be different from my real name, sound cool (to me at least) and make some sort of sense. Putting these constraints to the already limited possibilities was not helping. Each user name I tried was taken and what I found rather annoying was the fact that the website did not suggest alternatives. Those of you who follow my blog will know that I’m a stickler when it comes to User Experience (UX) and will understand my annoyance at such things. I finally settled for the name “Wyder” which stands for “Wayne” (my real name) “derivative” and which was available. Next I downloaded and installed the Second Life Viewer and I was good to go.

Virtually There

After logging in I found my virtual self (Avatar) on Welcome Island, a region on the Grid aimed at getting newbies like me up and running in Second Life within a claimed 10 minutes. Welcome Island lived up to its promises and I got a hang of the basic controls well within those 10 minutes, including walking/flying around, chatting, basic camera controls and object interaction. With the pleasantries out of the way it was time to take the plunge into the Grid proper. I popped out the destination guide and I was surprised at the sheer number of locations available. I spent the good part of an hour teleporting between locations, spending a few minutes in each just to get a feel for the interface and to assess performance across locations. It was immediately apparent that a good internet connection and even better hardware is required to experience this virtual world at its best.

Customising the Avatar

I also attempted to change the look of my avatar and was impressed with the level of customization that is available to you. It did take some trial and error to decipher what each setting did (I still cannot figure out how to change the eye colour) and to realize that your hair can be changed only by acquiring new hair styles (at least that’s what I concluded). I also found some preset ‘looks’ in the inventory but to my horror, the presets re-set the facial features I had painstakingly tweaked to my liking just before. Saying I was frustrated is an understatement, so I set everything back to default and let the whole customization thing be for a while. I’ll try again another time.

Conclusion

I have to say that I did feel quite a bit disorientated, out of place even, while logged into Second Life. There’s so much to take in that it will take me several other sessions to feel comfortable enough. What does Second Life tell me? Well, I don’t want to pass judgment just yet as I still have to get over my baby blues J so I’ll leave all philosophical considerations for another post. I can say however that I am looking forward to building stuff in this virtual world and take a look at Linden Scripting Language to see what I can accomplish. In the meantime, I’ll concentrate on getting a better hang of the interface, the places, the people and the life!
Readmore...
Thursday 5 May 2011

Building a Web Space Management System - 4

0 comments
 
As promised, in this fourth (and final) post of the series I will explain how I went about building the Web Space Manager itself.

I must admit that when I first read the requirements for this assignment I thought that it would be quite challenging to do. I came to realise however that the real challenge was learning how to structure a PHP application in such a way as to achieve as much sepraration as possible between the presentation and application layers while keeping the code easy to follow (without overloading pages with PHP includes). Once I got the hang of it though, the rest was pretty easy, including the Space Manager page.

The Web Space Manager

As stated in my last post, I wanted the user to be able to access all the application's functionality from one 'main' page. This includes the ability to browse through the uploaded files, create folders, upload new files and delete existing files. I also wanted users to be able to see at a glance how much free space they had left. Here's the end result:

Preventing unauthorised access
The first thing the page does is verify that the user is authenticated by checking the appropriate session variables set were by the login page. If they are not, the user is re-directed to the login page. There are 2 such variables, a flag that signals if the user is authenticated and the serialised user class itself. For this project I opted to check just the flag and ignore the user class. For added security I could if need be, re-validate the username and password (using the class) against the database but that would mean a query being executed on the database each time the page was loaded which could affect performance. I figured checking the flag would suffice for this project.

The SpaceManager Class
The SpaceManager class is the 'core' of this whole project and is responsible for all operations performed on the user's web space. Whenever the class is instantiated, it sets the path to the user's home directory, the quota and calculates the used space. If the user has logged-in for the first time, it creates the user's space (home folder) beforehand. I'll be explaining how this class works in more detail while going through each piece of functionality.

The File Browser

The file browser initially displays the files and folders in the user's home directory. These 'Home' folders are created under a folder called 'vault' located in the website's root. To prevent users from browsing to the contents of this folder (through the browser's address bar) I created a '.htaccess' file within the 'vault' folder that contains a 'deny from all' rule. Simple, but effective.
The list shows the file/folder name, the size (in the case of files) and last modification date. Folder names can be clicked to display the files within that folder. While browsing, breadcrumb-style links are shown in the toolbar, from the home folder to the current directory, to serve as useful shortcuts. Different icons are used for the different file types and to distinguish between folders that are empty, compressed and those that contain files. The file browser also allows users to delete selected files and create new folders. Let's look at each of these bits of functionality individually.
The file browser

Listing Files & Folders
The 'listFiles()' function of the SpaceManager class iterates through the contents of the current directory (initially the user's home directory) and returns a multidimensional array describing the files and folders contained within. Here's the code for the function:

function listFiles(){
   $fileList = array();   
   $files = array();
 
   $path = $this->_home.DS;
   if ($_SESSION['currentdir'] !== "") $path .= $_SESSION['currentdir'].DS;  
  
      if (is_dir($path)) {
         if ($dirref = opendir($path)) {
            while (($file = readdir($dirref)) !== false) {
               if ($file !== '.' && $file !== '..') {     
                  $files['name'] = $file;
                  $files['type'] = filetype ($path.$file);
                  $files['size'] = filesize ($path.$file);
                  $files['time'] = date("F d Y H:i:s.", filemtime($path.$file));
     
                  if ($files['type'] == 'dir') {    
                     $files['count'] = count(glob($path.$file.DS."*")); 
                     $files['ext'] = "";
                     $files['icon'] = 'dir';
                     
                     if ($files['count'] == 0) $files['icon'] .= "-empty";
                  } else {      
                     $files['count'] = 1;
                     $files['ext']  = pathinfo ($path.$file, PATHINFO_EXTENSION);
                     $files['icon'] = $this->getIconForExtension($files['ext']);
                  }
                  array_push($fileList, $files);
               }         
            }
            closedir($dirref);
         }
      }
      // Sort the list by type (folders followed by files) and name  
      if (sizeof($fileList) > 0 ) {  
         foreach ($fileList as $key => $row) {
            $name[$key] = $row['name'];
            $type[$key] = $row['type'];
            $size[$key] = $row['size'];
         }  
         array_multisort($type, SORT_ASC, $name, SORT_ASC, $fileList);
      }
      return $fileList;
   }

The currently selected directory is stored as a session variable. The 'opendir' PHP function is used to open this directory and a while loop is used to iterate through the contents. For each file/folder found, the '$files' array is populated with its properties including the name, type (file or folder) size, last modification date etc. PHP functions are used to get this information, including 'filetype', 'filesize', 'filemtime' and 'pathinfo' which in this case is used to determine a file's extension. Certain properties (such as the icon) are set depending on whether the file is in fact a file or a folder. The SpaceManager class' getIconForExtension function returns the icon to use (basically a CSS class name) based on a simple 'switch' statement.
Although it might sound complex, it's actually quite straight forward. What's interesting however is how to sort the resulting multidimensional array '$fileList'. I wanted to sort the files by type - folders first - and name. To do this, I iterate through each file, extracting the name and type into separate arrays. I then pass these arrays to the 'array_multisort' PHP function in the order I need to sort my '$fileList' array which then is returned back to the main page. At this point it's just a question of building an HTML table while iterating through this array to display the files and related information:

...
// Get list of files from SpaceManager Class ($Space)
$files = $Space->listFiles();         
$index = 0;
$class = "";
$path = "";
   
if ($_SESSION['currentdir'] !== "") $path = $_SESSION['currentdir'].DS;       
     
foreach ($files as $file) {
 
   if ($file['type'] == 'dir'){
      $text = "".$file['name']." (" .$file['count']." files)";
   } else {
      // create download link 
      $text = "".$file['name']."";
   }      
        
   if ($index == 1) {
      $class = " class = 'alt' ";
   } else {
      $class = "";
   }
           
   echo "<tr".$class.">";
   echo "<td><input type='checkbox' name='chk_".$file['type']."[]' value='".$file['name']."'/>";        
   echo "$lt;td class='icon16 icon16-".$file['icon']."'></td>";
   echo "<td>".$text."</td>";
   echo "<td>".$file['type']."</td>";
   echo "<td class='text-right'>".$Space->formatBytes($file['size'],1)."</td>";
   echo "<td>".$file['time']."</td>";
   echo "</tr>";
        
   $index = 1-$index;       
}

As you might have noticed from the code, folder names and file names are created as links (HTML anchors) such that users are able to 'open' or browse the former and download the latter. Folder links point to the 'goto.php' script while file names point to the 'download.php' script. I will be explaining these two scripts in detail in the next sections.


Browsing Through Folders
As explained above, folder names are rendered as HTML anchors that link to the goto.php script, passing the location as a query string. The script simply sets the current directory session variable to the location in the query string and re-directs the browser to the main.php page which shows the content of the selected folder, as explained above. While browsing through the folders, a "breadcrumb trail"-style navigator is built to allow users to see where they are in the folder structure and 'hop' directly to any folder in the trail. These links work exactly like the folder links in the file browser, using the goto.php script. The 'getBreadCrumbs()' function of the SpaceManager function is responsible for building this trail of links. It uses the 'explode' PHP function to split the current directory path (by the directory delimiter) into an array of folder names. It then iterates through this array creating a string of links which it returns to the calling script.
Breadcrumbs in the file browser toolbar

function getBreadCrumbs() {

   $homeFolder = $_SESSION['user']->getUserName();
   $path = ''; 
   $delimiter = " ";
   $out = "";
   $index = 1;

   // Set the current directory. 
   if (!isset($_SESSION['currentdir']) || $_SESSION['currentdir'] == '') {  
      $_SESSION['currentdir'] = '';
      $this->_uploadPath = VAULT.DS.$this->_home.DS;   
   } else {
      $this->_uploadPath = VAULT.DS.$this->_home.DS.$_SESSION['currentdir'].DS;
      
      // Parse the current directory to obtain the trail
      $folders = explode (DS, $_SESSION['currentdir']);
      // Iterate through the folder names and create breadcrumbs for all except the home and current folders.  
      foreach ($folders as $folder) {
         if ($folder !== $homeFolder) {
            if ($index !== sizeof($folders)) {
               $index ++;
               if ($path == '') {
                  $path = $folder;
               } else {
                  $path .= DS.$folder;     
               }
               $out .= $delimiter." ".$folder."";
            } else { // Last folder name in the trail should not be a link
               $out .= $delimiter." ".$folder;
            }
         }
      } 
   }
   return $out;
} 

Downloading Files
Similarly to folders, file names are rendered as links to allow the user to download the files:

// create download link
$text = "<a href='download.php?x=".$path.$file['name']."'>".$file['name']."</a>";

The link points to the 'download.php' script that expects the path to the file as an argument in the query string. The script makes sure the user is logged in, checks that the file exists and initiates the download:

<?php
   include_once("./inc/shared.php");
   include_once ('.'.DS.'inc'.DS.'checklogin.php');
 
   if (isset($_GET['x']) && $_GET['x'] !== "") {
  
      $path = VAULT.DS.$_SESSION['user']->getUserName().DS.$_GET['x'];
  
      if (file_exists($path)) {
         header('Content-type: application/force-download');
         header('Content-Disposition: attachment; filename="'.basename ($path).'"');

         readfile($path);
        
      } else {
         header ("Location: ".HOME."main.php");
         die();
      }
  
   } else {
      header ("Location: ".HOME."main.php");
      die(); 
   } 


Users can only download files from their own 'space' since the path received by the script is relative to the user's home directory. At this point I would like to mention that setting the names of home folders to the registered usernames is not such a good idea. In a real project, I would probably generate some sort of GUID or a hash based on the username to set the name of the various home folders.

Creating Folders
To create a folder, a user enters the new folder name and clicks the "New Folder" button. The text-box and button are actually part of an HTML form that is set to post to the 'newfolder.php' script. This script gets the name of the folder from the '$_POST' asscociative array and creates an instance of the SpaceManager class. It then appends the new folder name to the current directory path and calls the 'createFolder()' function of the spaceManager class which in turn simply calls the mkdir PHP fucntion to create the folder. The script then re-directs the browser to the 'main.php' page which displays the new folder in the file browser.

Deleting Files and Folders
You may have noticed that there is a check-box next to each file and folder being listed (see File Browser further up this post). This check-box is used to mark the files and folders the user wishes to delete. Here's the code that's generating these check-boxes (taken from the file browser listing above):

echo "";      


The file list is contained within an HTML form whose action is set to 'delete.php'. This form is submitted when the user clicks the delete button in the file browser toolbar. As can be seen from the code-snipped above, the name of a check-box is set to either 'chk_file' or 'chk_dir' depending on whether the associated item is a file or a folder. Furthermore, the check-box value is set to the file/folder name. By using identical names for the check-boxes (per type) we can obtain an array of folder/file names when posting to the 'delete.php' script as shown below:

<?php
 
   include_once("./lib/shared.php");
 
   $SpaceMan = new SpaceManager($_SESSION['user']->getUserName());  

   // Loop through the list of files (if any) and delete. 
   if (isset($_POST['chk_file'])) {
      $files = $_POST['chk_file'];
      for ($i = 0; $i< count($files); $i++) {
         $SpaceMan->deleteFile($files[$i]);    
      }
   }
   // Loop through the list of folders (if any) and delete.
   if (isset($_POST['chk_dir'])){
      $folders = $_POST['chk_dir'];
         for ($i = 0; $i<count($folders); $i++) {
            //Build the full folder path
            $folder = VAULT.DS.$_SESSION['user']->getUserName().DS.$_SESSION['currentdir'].$folders[$i];
     $SpaceMan->deleteFolder($folder);  
         }  
   }
   // Redirect to the main page when finished. 
   header ("Location: ".HOME."main.php");
   die();

The 'delete.php' script is quite straight forward. It simply loops through the lists of check-boxes and calls the 'deleteFile' and 'deleteFolder' functions of the SpaceManager class.

The 'deleteFile' function simply checks that the file exists and calls the PHP 'unlink()' function to delete the file. 'deleteFolder' on the other hand is slightly more complex. A folder can only be deleted if it is empty, so if it's not we first need to delete its contents which might also be folders...recursion anyone? Here's the code:

function deleteFolder($folderPath) {
   
   if(!file_exists($folderPath) || !is_dir($folderPath)) { 
      return false; 
   } elseif(!is_readable($folderPath)) { 
      return false; 
   } else { 
      $directoryHandle = opendir($folderPath); 
        
      while ($contents = readdir($directoryHandle)) { 
         if($contents != '.' && $contents != '..') { 
            $path= $folderPath . DS . $contents; 
            if(is_dir($path)) { 
               $this->deleteFolder($path); 
            } else { 
               unlink($path); 
            } 
         } 
      } 
        
      closedir($directoryHandle); 
  
      if(!rmdir($folderPath)) {           
         return false; 
      } 
        
      return true;
   } 
 }  
 

The function recursively calls itself until all the contents of a directory (and any sub directories) are deleted.

And that's about it, all the main bits of functionality explained (understandably I hope). There are other bits an pieces which I have not mentioned, such as handling the quota and how the 'Space Utilisation Meter' was rendered, but since these bits are rather simple, I didn't want to bloat an already lengthy post. Having said that, if anyone is interested in learning more, drop a comment and I will be more than happy to explain further in another post.

Conclusion

I have to say that overall, this project was qiute enjoyable. As with any first encounter with a language, the start was quite frustrating especially until I settled on a way to structure the code which made sense to me and with which I was comfortable. Once past that hurdle it's just a matter of learning the new syntax. This does not mean however that the code is perfect (it never is) or that it is what you might call best-practise. There are lots of things to improve especially with respect to error/exception handling and security, which when pressed for time, are always the first things to drop off the list (unfortunately). Having said that, I plan to improve on these aspects (especially security) in the coming days. Things like implementing stricter validation of POSTed values and query string encryption are good learning opportunities. The project also implements features to make it as platform independent as possible such as using the directory separator global variable when building paths and URLs. Another nice feature to add would be to make the project database-independent. This could be achieved by encapsulating all database specific calls (such as mysql functions) into a separate classes (per database) each inheriting from a common abstract class that serves as an interface. The list goes on.

That's it for this series, I hope you enjoyed reading it as much as I did learning what's written in it!
Readmore...
Tuesday 3 May 2011

Building a Web Space Management System - 3

0 comments
 
Last week I mentioned that I wasn't to happy with the way the code was structured so far. It was untidy to say the least and hard to maintain, even at this early stage. I also said that I was looking into MVC and OOP to instill some sense of structure and order to the project. We have a saying in Maltese that translates to: "the sauce will cost you more than the fish it's meant to garnish". Writing even a simple MVC framework for this project would have been massive overkill and would have taken too much time and effort for something that is not meant for production. Of course there are PHP frameworks out there that support MVC but using existing frameworks is beyond the scope of this blog.

However, I wasn't giving up. I still wanted to improve the way the code was written so I turned to good old Object Oriented Principles (OOP) and some other nifty tricks I picked up on the net while doing my research to help me turn my spaghetti code into neatly layered lasagne!

I wanted to spend just a little bit of time on how I re-structured the code I had so far before going further, as all the new stuff I added is obviously based on these changes. You will also notice changes in the look and feel of the app (being a UX buff I couldn't resist :) and the name (I've changed everything else so why not!).

1. Introducing Objects

Up to now we have functionality that allows users to register and log-into our system, and this sentence alone screams "UserAccount Class". My UserAccount class is structured as follows:

Properties

  • username
  • password
  • email
  • errors()  - an array that stores any errors thrown by the class
  • salt         - a random set of characters that are appended to the user's password before hashing it to improve security
Methods

  • login - accepts a username and password as arguments and handles authentication
  • register - attempts to register the user
  • isUsernameAvailable - private method used during registration to check whether the chosen username is unique or not.
The code in the UserAccount class methods was extracted from the login and registration pages.  This code remained largely intact, with only minor modifications required to suit the class.

I also added a small FormValidator class to handle common validations such as comparing strings (used in the registration form to confirm the user's password choice).

2. Re-structuring the Pages

With the authentication and registration code safely encapsulated in the UserAccount class, the login and registration pages became simpler. So much so that I decided to merge them into one page. The login page now contains two forms: the login form and the registration form. These forms post to the "processlogin.php" and "processreg.php" scripts respectively. These scripts contain all the calls to the UserAccount class that handle authentication and registration. Placing this code in these script files separates server-side code from the HTML which is exactly what I want. I also placed php code that is common to all pages (such as session management and database connection) in another script called "shared.php". Here's the code for the "shared.php" and "processlogin.php" scripts:

<?php
   define ('DS', DIRECTORY_SEPARATOR);
   define ('ROOT', dirname(dirname(__FILE__)));
   define ('MaxFiles', 3);

   include_once (ROOT.DS."lib".DS."dbconfig.php");

   function __autoload($className) {
      if (file_exists(ROOT . DS . 'lib' . DS . strtolower($className) . '.php')) {
         require_once(ROOT . DS . 'lib' . DS . strtolower($className) . '.php');
      } else {
         /* Error Generation Code Here */
      }
   }

   mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);      
   mysql_select_db("db_webspace");

   session_start();


The shared.php script defines constants for the root folder of our website and the directory separator. The latter is useful because of the fact that Windows and Linux have different directory separators (backslash and forwardslash) so it makes our code slightly more platform independent which is nice. The scripts also makes use of the __autoload function in PHP to make sure that class files (such as our UserAccount.php) are "automagically" loaded whenever they are needed, which is pretty handy. It also connects to the database and starts the session. I've stripped all the error handling code from the script for simplicity's sake, but obviously there is some to handle those annoying exceptions.

The "processlogin.php" script below is called when posting the login form. In a nutshell, it reads the values for the username and password from the $_POST associative array and after making sure the values are not blank, it creates an instance of the UserAccount class and attempts authentication. If all is successful the script sets session variables to indicate that the user has been authenticated and re-directs to the main application page. If not, the login page is included so that the error messages can be displayed to the user.

<?php // processlogin.php
   include_once ('./lib/shared.php');
   $errMsg = "";
   
   if ($_POST['txtUname'] != "" && $_POST['txtPassword'] != "") {
      $User = new UserAccount ();
  
      if (!$User->login($_POST['txtUname'], $_POST['txtPassword'])) {
         $errMsg = 'Incorrect Username or password';
      }
   } else {
      $errMsg = "Please enter a username and password";  
   }

   if ($errMsg != "") {
      include 'login.php';
   } else {
      $_SESSION['loggedin'] = 1;
      $_SESSION['user'] = $User;
      header('Location: .' . DS . 'space' . DS . 'main.php');
      die();
   }


You may notice that in both these scripts, since they solely contain PHP code, the closing PHP tag "?>" was left out. This is purposely done to prevent any whitespace from being unintentionally sent into the response.

Here's what the combined login and registration page looks like:


3. The Web Space Manager


Past the login and into the 'private' area of the site which I refer to as the 'Web Space Manager'. I wanted a single interface through which users could browse through the files and folders of their web space, upload new files and see how much free space they had left. I also wanted to allow users to create folders in their space and delete unwanted files.  Quite a lot to do, so I want to dedicate the next post to describe how I went about it, in the meantime here's a little screenshot of the space manager page (still needs polishing up).


4. Final Thoughts

Although going back and restructuring the pages took some time and effort, the end result was definitely worth it.  The entire exercise helped me understand more how PHP works and how to get more out of it which after all is the whole point of this project.  I also picked up some nifty tips & tricks while going through countless articles on the web, which is always good.
Readmore...