<?php

// Note that REAL cache should check for Last-Modified HTTP header at least;
// As I don't like idea of implementing the full-scaled HTTP protocol library
// and curl extension is very rare, this implementation of cache is very simple.
// Cache is cleared after the script finishes it work!

// Also, it can have problems with simultaneous access to the images.

// The class responsible for downloading and caching images
// as PHP does not support the static variables, we'll use a global variable
// containing all cached objects; note that cache consumes memory!
//
$GLOBALS['g_image_cache'] = array();

class Image {
  var $_handle;
  var $_filename;
  var $_type;

  function Image($handle, $filename, $type) {
    $this->_handle = $handle;
    $this->_filename = $filename;
    $this->_type = $type;
  }

  function get_filename() {
    return $this->_filename;
  }

  function get_handle() {
    return $this->_handle;
  }

  function get_type() {
    return $this->_type;
  }

  function sx() {
    if (!$this->_handle) {
      return 0;
    };

    return imagesx($this->_handle);
  }

  function sy() {
    if (!$this->_handle) {
      return 0;
    };

    return imagesy($this->_handle);
  }
}

class ImageFactory {
  // Static funcion; checks if given URL is already cached and either returns 
  // cached object or downloads the requested image
  //
  function get($url, &$pipeline) {
    global $g_config;
    if (!$g_config['renderimages']) { return null; };

    global $g_image_cache;

    // Check if this URL have been cached
    //
    if (isset($g_image_cache[$url])) {
      //      return do_image_open($g_image_cache[$url]);
      return $g_image_cache[$url];
    };

    // Download image; we should do it before we call do_image_open,
    // as it tries to open image file twice: first to determine image type 
    // and second to actually create the image - PHP url wrappers do no caching 
    // at all
    //
    $filename = ImageFactory::make_cache_filename($url);

    // REQUIRES: PHP 4.3.0+
    // we suppress warning messages, as missing image files will cause 'copy' to print 
    // several warnings
    // 
    // @TODO: change to fetcher class call
    //

    $data = $pipeline->fetch($url);

    if (is_null($data)) {
      error_log("Cannot fetch image: ".$url);
      return null; 
    };

    $file = fopen($filename, 'wb');
    fwrite($file, $data->content);
    fclose($file);
    $pipeline->pop_base_url();
    
    // register it in the cached objects array
    //
    $handle = do_image_open($filename, $type);
    if ($handle) {
      $g_image_cache[$url] =& new Image($handle,
                                        $filename,
                                        $type);
    } else {
      $g_image_cache[$url] = null;
    };
    // return image
    //
    // return do_image_open($filename);
    return $g_image_cache[$url];
  }

  // Makes the filename to contain the cached version of URL
  // 
  function make_cache_filename($url) {
    // We cannot use the $url as an cache image name as it could be longer than 
    // allowed file name length (especially after escaping specialy symbols)
    // thus, we generate long almost random 32-character name using the md5 hash function
    //
    return CACHE_DIR.md5(time() + $url + rand());
  }

  // Checks if cache directory is available
  //
  function check_cache_dir() {
    // TODO: some cool easily understandable error message for the case 
    // image cache directory cannot be created or accessed
    
    // Check if CACHE_DIR exists
    //
    if (!is_dir(CACHE_DIR)) { 
      // Cache directory does not exists; try to create it (with read/write rightss for the owner only)
      //
      if (!mkdir(CACHE_DIR, 0700)) { die("Cache directory cannot be created"); }
    };

    // Check if we can read and write to the CACHE_DIR
    //
    // Note that directory should have 'rwx' (7) permission, so the script will
    // be able to list directory contents; under Windows is_executable always returns false
    // for directories, so we need to drop this check in this case.
    //
    // A user's note for 'is_executable' function on PHP5:
    // "The change doesn't appear to be documented, so I thought I would mention it. 
    // In php5, as opposed to php4, you can no longer rely on is_executable to check the executable bit 
    // on a directory in 'nix. You can still use the first note's method to check if a directory is traversable:
    // @file_exists("adirectory/.");"
    // 
    if (!is_readable(CACHE_DIR) || 
        !is_writeable(CACHE_DIR) || 
        (!@file_exists(CACHE_DIR.'.'))) {
      // omg. Cache directory exists, but useless
      //
      die("Check cache directory permissions; cannot either read or write to directory cache");
    };
    
    return;
  }

  // Clears the image cache (as we're neither implemented checking of Last-Modified HTTP header nor 
  // provided the means of limiting the cache size
  //
  // TODO: Will cause problems with simultaneous access to the same images  
  //
  function clear_cache() {
    foreach ($GLOBALS['g_image_cache'] as $key => $value) {
      if (!is_null($value)) {
        unlink($value->get_filename());
      };
    };
    $g_image_cache = array();
  }
}
?>