1<?php
2
3// Note that REAL cache should check for Last-Modified HTTP header at least;
4// As I don't like idea of implementing the full-scaled HTTP protocol library
5// and curl extension is very rare, this implementation of cache is very simple.
6// Cache is cleared after the script finishes it work!
7
8// Also, it can have problems with simultaneous access to the images.
9
10// The class responsible for downloading and caching images
11// as PHP does not support the static variables, we'll use a global variable
12// containing all cached objects; note that cache consumes memory!
13//
14$GLOBALS['g_image_cache'] = array();
15
16class Image {
17  var $_handle;
18  var $_filename;
19  var $_type;
20
21  function Image($handle, $filename, $type) {
22    $this->_handle = $handle;
23    $this->_filename = $filename;
24    $this->_type = $type;
25  }
26
27  function get_filename() {
28    return $this->_filename;
29  }
30
31  function get_handle() {
32    return $this->_handle;
33  }
34
35  function get_type() {
36    return $this->_type;
37  }
38
39  function sx() {
40    if (!$this->_handle) {
41      return 0;
42    };
43
44    return imagesx($this->_handle);
45  }
46
47  function sy() {
48    if (!$this->_handle) {
49      return 0;
50    };
51
52    return imagesy($this->_handle);
53  }
54}
55
56class ImageFactory {
57  // Static funcion; checks if given URL is already cached and either returns
58  // cached object or downloads the requested image
59  //
60  function get($url, &$pipeline) {
61    global $g_config;
62    if (!$g_config['renderimages']) { return null; };
63
64    global $g_image_cache;
65
66    // Check if this URL have been cached
67    //
68    if (isset($g_image_cache[$url])) {
69      //      return do_image_open($g_image_cache[$url]);
70      return $g_image_cache[$url];
71    };
72
73    // Download image; we should do it before we call do_image_open,
74    // as it tries to open image file twice: first to determine image type
75    // and second to actually create the image - PHP url wrappers do no caching
76    // at all
77    //
78    $filename = ImageFactory::make_cache_filename($url);
79
80    // REQUIRES: PHP 4.3.0+
81    // we suppress warning messages, as missing image files will cause 'copy' to print
82    // several warnings
83    //
84    // @TODO: change to fetcher class call
85    //
86
87    $data = $pipeline->fetch($url);
88
89    if (is_null($data)) {
90      error_log("Cannot fetch image: ".$url);
91      return null;
92    };
93
94    $file = fopen($filename, 'wb');
95    fwrite($file, $data->content);
96    fclose($file);
97    $pipeline->pop_base_url();
98
99    // register it in the cached objects array
100    //
101    $handle = do_image_open($filename, $type);
102    if ($handle) {
103      $g_image_cache[$url] =& new Image($handle,
104                                        $filename,
105                                        $type);
106    } else {
107      $g_image_cache[$url] = null;
108    };
109    // return image
110    //
111    // return do_image_open($filename);
112    return $g_image_cache[$url];
113  }
114
115  // Makes the filename to contain the cached version of URL
116  //
117  function make_cache_filename($url) {
118    // We cannot use the $url as an cache image name as it could be longer than
119    // allowed file name length (especially after escaping specialy symbols)
120    // thus, we generate long almost random 32-character name using the md5 hash function
121    //
122    return CACHE_DIR.md5(time() + $url + rand());
123  }
124
125  // Checks if cache directory is available
126  //
127  function check_cache_dir() {
128    // TODO: some cool easily understandable error message for the case
129    // image cache directory cannot be created or accessed
130
131    // Check if CACHE_DIR exists
132    //
133    if (!is_dir(CACHE_DIR)) {
134      // Cache directory does not exists; try to create it (with read/write rightss for the owner only)
135      //
136      if (!mkdir(CACHE_DIR, 0700)) { die("Cache directory cannot be created"); }
137    };
138
139    // Check if we can read and write to the CACHE_DIR
140    //
141    // Note that directory should have 'rwx' (7) permission, so the script will
142    // be able to list directory contents; under Windows is_executable always returns false
143    // for directories, so we need to drop this check in this case.
144    //
145    // A user's note for 'is_executable' function on PHP5:
146    // "The change doesn't appear to be documented, so I thought I would mention it.
147    // In php5, as opposed to php4, you can no longer rely on is_executable to check the executable bit
148    // on a directory in 'nix. You can still use the first note's method to check if a directory is traversable:
149    // @file_exists("adirectory/.");"
150    //
151    if (!is_readable(CACHE_DIR) ||
152        !is_writeable(CACHE_DIR) ||
153        (!@file_exists(CACHE_DIR.'.'))) {
154      // omg. Cache directory exists, but useless
155      //
156      die("Check cache directory permissions; cannot either read or write to directory cache");
157    };
158
159    return;
160  }
161
162  // Clears the image cache (as we're neither implemented checking of Last-Modified HTTP header nor
163  // provided the means of limiting the cache size
164  //
165  // TODO: Will cause problems with simultaneous access to the same images
166  //
167  function clear_cache() {
168    foreach ($GLOBALS['g_image_cache'] as $key => $value) {
169      if (!is_null($value)) {
170        unlink($value->get_filename());
171      };
172    };
173    $g_image_cache = array();
174  }
175}
176?>