Sunday, October 08, 2006

PHP copy folder from FTP site snippet

Have you ever wanted to copy an FTP folder with all its files and subfolders locally? Well, this is even better!

This PHP code snippet synchronizes the contents of a remote FTP folder with a local one, it checks file dates and refrains from copying files whose timestamps did not change! I would have encapsulated it in a class if I had time, but this will do :-)

<?php

// we'll assume our remote folder is located on this address... ftp://web.whatever.com/folder1/folder2

$hostname = 'web.whatever.com';      // ftp host
$directory_path = 'folder1/folder2';   // this is the ftp directory path to retrieve
$base_directory_path = 'local_folder';   // this is the local folder we want to synch to
$username = 'username';
$password = 'password';

// this code will create the base directory, local_folder/folder2
$directory = explode('/', $directory_path);
$directory = $directory[count($directory) - 1];

try
{
   if (!is_dir($base_directory_path.'/'.$directory))
      if (!mkdir($base_directory_path.'/'.$directory))
         throw new Exception('Could not create folder.', 0);   

   handle_url($hostname, $directory_path, $base_directory_path.'/'.$directory, $username, $password);
   print "done";
}
catch (Exception $e)
{
   die($e->getMessage());
}

function handle_url($hostname, $directory_path, $base_dir_path, $username, $password, $date_start_offset = FALSE)
{
   if ((@ $ftp = ftp_connect($hostname)) === FALSE)
      throw new Exception('Error establishing FTP connection. Do you have privileges on this server?', 1);
   if (@ !ftp_login($ftp, $username, $password))
      throw new Exception("Can't log in. Check username & password.", 2);
   if (@ !ftp_chdir($ftp, $directory_path))
      throw new Exception("Couldn't change FTP directory", 3);
   if ((@ $listing = ftp_rawlist($ftp, '.')) === FALSE)
      throw new Exception("Can't retrieve FTP directory listing.", 4);
   
   foreach ($listing AS $key=>$value)
   {
      if ($value[0] == 'd')   // directory
      {
         $folder_name = explode(' ', $value);
         $folder_name = $folder_name[count($folder_name) - 1];
         if (!is_dir($base_dir_path.'/'.$folder_name))
            if (!mkdir($base_dir_path.'/'.$folder_name))
               throw new Exception('Could not create folder.', 0);
         handle_url($hostname, $directory_path.'/'.$folder_name, $base_dir_path.'/'.$folder_name,
            $username, $password, $date_start_offset);
      }
      else   // file
      {
         $file_info = explode(' ', $value);

         // added this code because I found that for some reason the date sometimes starts at offset 12
         // other times at offset 13, I'll assume that it may even be at a different offset! We only check
         // the date offset if a previous call to handle_url had failed, and if it had succeeded, we check
         // for a new offset if the current one doesn't contain a date
         if ($date_start_offset === FALSE || strpos($file_info[$date_start_offset + 2], ':') === FALSE)
            for ($i = 0 ; $i < count($file_info) ; $i++)
            {
               if (strpos($file_info[$i], ':') !== FALSE)
               {
                  $date_start_offset = $i - 2;   // the location of the month
                  break;
               }
            }
            
         if ($date_start_offset === FALSE)
            throw new Exception("Can't find file date!", 6);
         
         $timestamp = $file_info[$date_start_offset].' '.$file_info[$date_start_offset + 1].' '.
            $file_info[$date_start_offset + 2].' '.date('Y');   // month day time year
         $timestamp = strtotime($timestamp);
         
         $file_name = $file_info[count($file_info) - 1];
         
         // does file exist and have same timestamp as the one on server? If so, just skip this file
         if (is_file($base_dir_path.'/'.$file_name))
            if (filemtime($base_dir_path.'/'.$file_name) == $timestamp) continue;
         
         // get file
         if (!ftp_get($ftp, $base_dir_path.'/tmp.file', $file_name, FTP_BINARY)) continue;
         if (!rename($base_dir_path.'/tmp.file', $base_dir_path.'/'.$file_name))
            throw new Exception("Couldn't rename file.");

         // change file's date to date from server file
         if (!touch($base_dir_path.'/'.$file_name, $timestamp))
            throw new Exception("Couldn't change file modification date.", 5);
      }
   }
}
?>