#native_company# #native_desc#
#native_cta#

PHP FTP Client and Library

By Adam Sutton
on September 3, 2001

Version: 0.3

Type: Full Script

Category: File Management

License: GNU General Public License

Description: I have written a class to wrap most of the standard FTP functions supplied by PHP, as well as a client to use the libraries.

This is an ongoing project and is still a little rough.

The client is fairly functional and provides all of the basic FTP functions.

The library will eventually be capable of full filesystem maintenance via FTP.

If you want to check out the project then it can be found here, http://www.adamsutton.co.uk/projects/project.php?proj_id=phpftp . There is a link to the client if you wish to try it out.

I have only uploaded the library so if you want the client aswell see my site, sorry for any inconvenience.

<?php 
  
  /*
  
  Author        : Adam Sutton
  Created       : 31/08/2001
  Last-Modified : 31/08/2001

  Title         : FTP functions

  Most of the functions in this file are simple wrappers for the
  basic FTP functions (with extra message reporting) and some extra
  functions to ease use

  */
  
  /* The class used to represent an FTP file */
  class Ftp_file 
  {
    /* the path name */
    var $pathname;
    var $size;
    var $time;
    var $user;
    var $group;
    var $permission;

    /* Returns the standard name + ext */
    function name ()
    {
      $path_parts = pathinfo($this->pathname);
      return $path_parts['basename']; 
    }

    /* Returns the path of the file */
    function dir ()
    {
      $path_parts = pathinfo($this->pathname);
      return $path_parts['dirname'];
    }

    /* Returns the extension of the file */
    function ext ()
    {
      $path_parts = pathinfo($this->pathname);
      return $path_parts['extension'];
    }

    /* Returns the path to the icon to be used for this 
     * file type (this is of course dependant on having the
     * icons available 
     * 
     * Only returns a file or directory icon at the mo though
     */
    function icon ()
    {
      if ($this->size == -1)
        return "/icons/small/dir.gif";
  
      return "/icons/small/text.gif";
    }

    /* Returns true if this file is infact a directory */
    function is_dir ()
    {  
      return ($this->size == -1);
    }
    
    /* Returns the size of the file formatted (none yet applied) */
    function formatSize ()
    {
      if ($this->is_dir())
        return "";
      else
        return $this->size;
    }

    /* Returns the modification time in a formatted fashion */
    function formatTime ()
    {
      if ($this->is_dir())
        return "";
      else
        return date("d/m/y H:i", $this->time);
    }
  }

  /* Class represents a list of files which has various methods for sorting
   * listing etc..
   */
  class FTP_file_list
  {
    /* Split the files and directories, as 
     * we always want to have dirs first 
     */
    var $files;
    var $dirs;

    /* Constructor creates the arrays */
    function FTP_file_list ()
    {
      $this->files = array();
      $this->dirs  = array();
    }

    /* Add a file/dir to the list */
    function add ($file) 
    {
      if ($file->is_dir())
        array_push($this->dirs, $file);
      else
        array_push($this->files, $file);
    }
  
    /* Returns the list of files */
    function getFiles ()
    {
      return $this->files;
    }

    /* Returns the directories in the list */    
    function getDirs ()
    {
      return $this->dir;
    }

    /* Returns the files and directories */
    function getAll ()
    {  
      return array_merge($this->dirs, $this->files);  
    }

    /* A wrapper for the sort functions which takes the 
     * type of sort and the order to sort in 
     */
    function sort ($field, $order='asc')
    {
      switch ($field) {
        case 'name':
          $this->sortByName($order);
          break;
        case 'size':
          $this->sortBySize($order);
          break;
        case 'type':
          $this->sortByType($order);
          break;
      }
    }

    /* Sort files by name */
    function sortByName ($order='asc')
    {
      usort($this->dirs,  "nameCompare");        
      usort($this->files, "nameCompare");
  
      if ($order == "dsc") {
        $this->dirs  = array_reverse($this->dirs);
        $this->files = array_reverse($this->files);
      }
    }

    /* Sort files by size */
    function sortBySize ($order='asc')
    {
      usort($this->dirs,  "nameCompare");
      usort($this->files, "sizeCompare");
      if ($order == "dsc")
        $this->files = array_reverse($this->files);
    }
  
    /* Sort files by type */
    function sortByType ($order='asc')
    {
      usort($this->dirs,  "nameCompare");
      usort($this->files, "typeCompare");
      if ($order == "dsc")
        $this->files = array_reverse($this->files);
    }
  }
        
    /* Compare two files by name */
    function nameCompare ($file1, $file2)
    {
      if ($file1->name() == $file2->name())
        return 0;
      else if ($file1->name() < $file2->name())
        return -1 ;
      else
        return 1;
    }
  
    /* Compare two files by size */
    function sizeCompare ($file1, $file2)
    {
      if ($file1->size == $file2->size)
        return 0;
      else if ($file1->size < $file2->size)
        return -1 ;
      else
        return 1;
    }
  
    /* Compare two files by type */
    function typeCompare ($file1, $file2)
    {
      if ($file1->ext() == $file2->ext())
        return 0;
      else if ($file1->ext() < $file2->ext())
        return -1 ;
      else
        return 1;
    }

  /* This class is used to wrap up the ftp session
   * all commands should be issued to this class using the functions
   * available 
   */
  class FTP_connection
  {

    /* Connection information */
    var $myHost;
    var $myUser;
    var $myPassword;

    /* The ftp_stream */
    var $myConn;

    /* The System type that we are logged into */
    var $mySysType;
  
    /* The current working directory */
    var $myCwd;

    /* The message */
    var $myMessage;

    /* The transfer mode */
    var $myMode;

    /* Constructor that wraps the login function 
    function FTP_connection ($host, $user, $password, $dir=false)
    {  
      echo "HELLO PEOPLE<br/>";
      $this->login($host, $user, $password, $dir);
    }

    /* Empty Constructor for use when relogging in 
    function FTP_connection ()
    {
    }*/  

    /* Allows a message value to be set, which will contain 
     * info about whether a procedure has worked or not.
     * The main reason for implementing a function is that
     * it provides for greater flexibilty (such as a list
     * of messages) and means global variables are only accessed
     * here
     */
    function setMessage ($message)
    {
      $this->myMessage = $message;
    }

    /* Returns the message or message list */
    function getMessage ()
    {
      return $this->myMessage;
    }

    /* Returns the current directory */
    function pwd ()
    {
      return $this->myCwd;
    }

    /* Logs a user into the specified ftp account and returns
     * the ftp_session 
     * Takes the following variables
     * host       - machine to connect to
     * user       - User name 
     * password   - User password
     * cwd        - Change to the specified directory
     */
    function login ($host, $user, $password, $cwd=false)
    {
      /* Check if the ftp:// protocol specfier has been entered
       * into the host name and remove it if it has  
       */
      preg_replace("/ftp://(.*)/", "1", $host);
      trim($host);

      /* Check the host name is not empty */ 
      if (empty($host)) {
        $this->setMessage( 
          "<p class='error'>
           Error - You have not supplied a host to connect to.
           <br/>
           <i>Note Do not include the 'ftp://' in the address</i>
           </p>");
        return false;

      /* Try and open the initial connection to the host */
      } else if (!($this->myConn = @ftp_connect($host))) {
        $this->setMessage(
          "<p class='error'>
           Error - Could not connect to the specified machine 
           ($host). 
           <br/>
           Please try again, it may have been busy.
           </p>");

        return false;

      /* Attempt to login to the server */
      } else if (!(@ftp_login($this->myConn, $user, $password))) {
        $this->setMessage(
          "<p class='error'>
           Error - Could not login in as $user.
           <br/>
           Check the details and try again.
           </p>");

        return false;
      }

      /* Set the necessary values */
      $this->myHost     = $host;
      $this->myUser     = $user;
      $this->myPassword = $password;
      if ($cwd)
        $this->myCwd    = $this->cd($cwd);
      else  
        $this->myCwd    = @ftp_pwd($this->myConn);
      $this->mySysType  = @ftp_systype($this->myConn);

      return true;
    }

    /* Logs the user out, this includes unsetting the 
     * host, user and password details etc...
     */
    function logout ()
    {
      @ftp_quit($this->myConn);
      $this->myHost     = false;
      $this->myUser     = false;
      $this->myPassword = false;
      $this->myCwd      = false;
      $this->setMessage(
        "<p class='success'>
         Logout Succesful.
         </p>");
    }

    /* Will attempt to automatically login using the values
     * specified (this means that the object could be passed
     * as the only session variable and it would contain all the
     * necessary info to act like a permanent connection across
     * calls to the page
     *
     * Cannot be called externally
     */
    function autologin ()
    {
      /* If we can print working directory on current stream
       * then we are still logged in 
       */
      if (@ftp_pwd($this->myConn))
        return true;
      
      return $this->login($this->myHost, $this->myUser, 
                          $this->myPassword, $this->myCwd);
    }

    /* This command returns a list of files in the current or
     * specified directory, each file is represented as an object, 
     * with the member variables filename, name (without extension),
     * ext, icon, size, date, permission (as yet
     * unimplemented) and owner/group (not yet implemented)
     *
     * At the moment this function uses the ftp_nlist, ftp_size and
     * ftp_mdtm functions to get the appropriate data, though I may
     * use ftp_rawlist in the future
     *
     * For now you can only list files in the current directory
     * thus you must cd to the correct directory and then do ls
     * I hope to improve this in the future
     */
    function ls ($dir=false)
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;

      if (!$dir)
        $dir = $this->myCwd;

      $this->setMessage("<p>Systype : ".$this->mySysType."</p>");

      /* UNIX System use rawlist */
      if (preg_match("/UNIX/", $this->mySysType)) {

        /* Get the list of files */
        if (!($files_temp  = @ftp_rawlist($this->myConn, $dir))) {
          $this->setMessage(
            "<p class='error'>
            Error - Could not list the files in $dir
            </p>");
          return false;
        }
  
        /* Make array of Ftp_file objects */
        $files = new FTP_file_list();
        while (list($num, $entry) = each($files_temp)) {
          /* File object */
          $file = new Ftp_file();
  
          /* Split the row 
           * Which should be as follows:
           * permissions count owner group size month day time name
           */
          $entry_parts = preg_split("/[ ]+/", $entry);

          /* First check what kinda entry this is */
          $entry_type = substr($entry_parts[0], 0, 1);
      
          /* This is a directory */
          if ($entry_type == 'd')
            $entry_parts[4] = -1;

          /* This is a link */
          else if (substr($entry_parts[0], 0, 1) == 'l')  {
            /* Get the size using ftp_size, else
             * it will return the size of the link not the 
             * size of the file it points to 
             */
            $entry_parts[4] = @ftp_size($this->myConn, "$dir/".$entry_parts[8]);
          }

          /* Get file details */
          $file->pathname = "$dir/".$entry_parts[8];
          $file->size     = $entry_parts[4];
          $file->time     = $entry_parts[5] . " "
                          . $entry_parts[6] . " "
                          . $entry_parts[7];
          $files->add($file);
        }

      /* UNTIL I know about other systems I'm gonna use the slow method
       * for these systems 
       */
      } else {

        /* List the files */
        if (!($files_temp  = @ftp_nlist($this->myConn, $dir))) {
          $this->setMessage(
            "<p class='error'>
            Error - Could not list the files in $dir
            </p>");
          return false;
        }
  
        /* Make array of Ftp_file objects */
        $files = new FTP_file_list();
        while (list($num, $name) = each($files_temp)) {
          $file = new Ftp_file();
          $file->pathname = "$dir/$name";
          $file->size     = @ftp_size($this->myConn, $file->pathname);
          $file->time     = @ftp_mdtm($this->myConn, $file->pathname);
          $files->add($file);
        }
      }    

      return $files;
    }

    /* This command works much like the standard UNIX command
     * it can work with both absolute and relative paths
     * it will also remove any '..' and '.' from the path
     * and return the new version
     */
    function cd ($dir)
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;

      /* Get the current directory as we will need this is the   
       * specified directory is invalid 
       */
      $old_cwd = $this->pwd();

      /* Replace multiple instances of / or  with a single / */
      $dir = preg_replace(array("//+/", "/+/"), "/", trim($dir));

      /* Check to see whether this is an absolute path */
      if (substr($dir, 0, 1) != "/")
        $dir = $this->pwd()."/$dir";
  
      /* Correct for .'s and ..'s in the path 
       * The reason I am doing this rather than just using ftp_chdir
       * and ftp_pwd, is that this will change the directory name
       * to extend any symlinks, which I don't want
       */
      $path_parts = explode("/", $dir);
      $dir_parts  = array();
      for ($i = 0; $i < sizeof($path_parts); $i++)
        if ($path_parts[$i] == "..")
          array_pop($dir_parts);
        else if ($path_parts[$i] != "." and $path_parts[$i] != "")  
          array_push($dir_parts, $path_parts[$i]); 
      sizeof($dir_parts) <= 1 ? $this->myCwd = "/".$dir_parts[0]
                              : $this->myCwd = "/".implode("/", $dir_parts);

      /* Change the directory */
      if (!@ftp_chdir($this->myConn, $this->myCwd)) {
        $this->myCwd = $old_cwd;
        $this->setMessage
          ("<p class='error'>
            Error - Could not change the specified directory ($dir).
            <br/>
            Check the path is correct and then try again.
            </p>");
        return false;
      }

      /* Set success message */
      $this->setMessage(  
        "<p class='success'>
          Succesfully changed to ".$this->myCwd."
         </p>");

      return $this->myCwd;
    }

    /* Alias for mkdir() */
    function md ($dir)  
    {
      $this->mkdir($dir);
    }
  
    /* Creates the specified directory in the current working directory */
    function mkdir ($dir)
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;

      /* Create directory */
      if (!@ftp_mkdir($this->myConn, $dir)) {
        $this->setMessage(
          "<p class='error'>
           Error - could not create the specified directory ($dir).
           <br/>
           Please check that the current directory is valid and that
           a file/directory of the same name does not exist in the 
           current directory.
           </p>");
        return false;
      }

      /* Success message */
      $this->setMessage(
        "<p class='success'>
         Successfully created the directory $cwd/$dir
         </p>");

      return true;
    }
 
    /* Removes the specified directory, eventually this 
     * will be modified to implement a recursive feature
     * which will delete the directory and all of it's contents
     */
    function rmdir ($dir, $recursive=false)
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;

      /* Check that the specified directory is infact a directory */
      if (@ftp_size($this->myConn, $dir) != -1) {
        $this->setMessage(
          "<p class='error'> 
           Error - The directory you are trying to remove ($dir) is 
           not a valid directory.
           </p>");
        return false;
      }

      /* Delete the directory recursively */  
      if ($recursive)
        return asuk_ftp_rmdir_all($dir);
    
      /* Delete the specified directory */
      if (!ftp_rmdir($this->myConn, $dir)) {
        $this->setMessage(
          "<p class='error'>
          Error - Could not delete the specified directory ($dir).
          <br/>
          Check that the directory exists and is empty.
          </p>");
        return false;
      }
    
      $this->setMessage(
        "<p class='success'> 
         Succesfully deleted directory $dir.
         </p>");

      return true;
    }

    /* Deletes a directory recursively */
    function rmdir_all ($dir=false)
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;

      /* Get directory contents */
      $files = $this->ls($dir);

      /* Delete each file or diectory */
      while (list($num, $file) = each($files)) {
        if ($file->size == -1)
          $this->rmdir_all($file->name());
        else
          rmfile($file->name());
      }

      /* Report Success */
      $this->setMessage(      
        "<p class='success'>
         Succesfully deleted directory $cwd/$dir.
         </p>");
    
      return true;
    }
  
    /* Deletes the specified file */
    function rmfile ($file)
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;

      /* Delete the file */
      if (!@ftp_delete($this->myConn, $file)) {
        $this->setMessage(
          "<p class='error'>
           Error - Could not delete the file $cwd/$file.
           <br/>
           Check the file exists before trying again.
           </p>");
      
        return false;
      }
    
      /* Report Success */
      $this->setMessage(
        "<p class='success'>
         Succesfully deleted file $cwd/$file
         </p>");    

      return true;
    }

    /* Generic wrapper for all the delete functions */
    function rm ($file, $recursive=false)
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;

      /* This is a file */
      if (@ftp_size($this->myConn, $file) >= 0)
        $this->rmfile($file);  
  
      /* This is a directory */
      else
        $this->rmdir($file, $recursive);

      return true;
    } 

    /* Rename a file */
    function rename ($old, $new)
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;
      $cwd = $this->myCwd;

      /* Rename the file */
      if (!@ftp_rename($this->myConn, $old, $new)) {
        $this->setMessage(
          "<p class='error'>
           Error - Could not rename $cwd/$old to $cwd/$new.
           </p>");
        return false;
      }

      $this->setMessage(
        "<p class='success'>
         Succesfully renamed $cwd/$old to $cwd/$new.    
         </p>");
  
      return true;
    }
    
    /* Move a file */
    function move ($old, $new)
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;
      $cwd = $this->myCwd;

      /* We need to check whether the new location 
       * is an existing directory, or is a new name
       * for the file or directory. This is done
       * by seeing whether cd'ing to $new returns
       * an error (in which case it is a new name)
       */
      $cwd = $this->pwd();
      if ($this->cd($new))
        $new = "$new/$old";
      $this->cd($cwd);

      /* Rename the file or directory */
      if(!$this->rename($old, $new)) {
        $this->setMessage(
          "<p class='error'>
           Error - Could not move the file $old to $new.
           </p>");
        return false;
      }

      return true;
    }

    /* Alias to move */
    function mv ($old, $new)
    {
      return $this->move($old, $new);
    }

    /* Copy a file */

    /* Gets the specified file from the FTP server and then
     * returns an array containing it's contents, or false on
     * error.  
     */
    function get ($file, $mode=false)
    {
      /* Check the mode */
      if ($mode != FTP_BINARY and $mode != FTP_ASCII) {
        $mode         = FTP_BINARY;
        $this->myMode = $mode;
      }

      /* Make sure logged in */
      if (!$this->autologin())
        return false;

      /* Open the temporary file */
      $file_name = pathinfo($file);
      $file_name = $file_name['basename'];
      $temp_name = "/tmp/asuk_ftp_$file_name-".time();
      $temp_file = @fopen($temp_name, "w");

      /* Retrieve file from the FTP server */
      if (!@ftp_fget($this->myConn, $temp_file, $file, BINARY)) {
        $this->setMessage(
          "<p class='error'>
           Error - Could not retrieve the specified file ($file)
            </p>");
        return false;
      }

      /* Close the temp file */
      @fclose($temp_file);
      
      /* Get the contents of the file */
      $data = @file($temp_name);
      
      /* Delete temporary file */
      @unlink($temp_name);

      return $data;
    }
    
    /* Uploads the specified file to the server 
     * Optionaly a file name may be given for the uploaded 
     * file (this would be the same as uploading and then
     * renaming) 
     */
    function put ($path, $name=false, $mode=false) 
    {
      /* Make sure logged in */
      if (!$this->autologin())
        return false;

      /* Check we have a name */
      if (!$name) {
        $path_parts = pathinfo($path);
        $name = $path_parts['basename'];
      }

      /* Check the mode */
      if ($mode != FTP_BINARY and $mode != FTP_ASCII) {
        $mode         = FTP_BINARY;
        $this->myMode = $mode;
      }

      /* Check the specified file is infact an uploaded file */
      if (!is_uploaded_file($path)) {
        $this->setMessage(
          "<p class='error'>  
           Error - The file that was specified ($name) for uploading
           was invalid, please check the path and try again.
           </p>");
        return false;
      }

      /* Upload the file */
      if (!ftp_put($this->myConn, $name, $path, FTP_BINARY)) {  
        exit;
        $this->setMessage(
          "<p class='error'>    
           Error - Could not upload the file ($name), check the the 
           specified path was correct. Path - $path
           </p>");
        return false;
      }

      /* Delete the temporary file */
      unlink($path);

      $this->setMessage(
        "<p class='success'>
         Succesfully uploaded the file $name.
         </p>");

      return true;
    }

  }
?>