1 |
dpavlin |
1.1 |
<? |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
/* |
5 |
|
|
Class cacheFastTemplate |
6 |
|
|
|
7 |
|
|
PHP extension for managing cached templates |
8 |
|
|
|
9 |
|
|
This program is free software; you can redistribute it and/or modify it under |
10 |
|
|
the GNU Library General Public License, with the following stipulations; |
11 |
|
|
|
12 |
|
|
Changes or modifications must retain these Copyright statements. |
13 |
|
|
Changes or modifications must be submitted to both AUTHORS. |
14 |
|
|
|
15 |
|
|
This program is released under the GNU Library General Public License. |
16 |
|
|
( http://www.gnu.org/copyleft/lgpl.html ) |
17 |
|
|
|
18 |
|
|
This program is distributed in the hope that it will be useful, |
19 |
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
20 |
|
|
FOR A PARTICULAR PURPOSE. See the Artistic License for more details. |
21 |
|
|
This software is distributed AS-IS. |
22 |
|
|
|
23 |
|
|
Some caching functions by Benjamin Kahn <xkahn@cybersites.com> |
24 |
|
|
http://www.zoned.net/~xkahn/php/fasttemplate |
25 |
|
|
Portions written by Benjamin Kahn Copyright (c) 2000 CyberSites, Inc; xkahn@cybersites.com, All Rights Reserved |
26 |
|
|
|
27 |
|
|
originally by: "JP" <jprins@dds.nl> |
28 |
|
|
http://www.phpbuilder.com/columns/jprins20000201.php3 |
29 |
|
|
|
30 |
|
|
with code by: Spencer D. Mindlin <smindlin@beaconeast.com> |
31 |
|
|
http://www.phpbuilder.com/columns/spencer20000208.php3 |
32 |
|
|
|
33 |
|
|
Portions of this code are based on code from FastTemplate for PHP |
34 |
|
|
Copyright (c) 1999 CDI, cdi@thewebmasters.net, All Rights Reserved. |
35 |
|
|
|
36 |
|
|
*/ |
37 |
|
|
|
38 |
|
|
class cachedFastTemplate extends FastTemplate { |
39 |
|
|
|
40 |
|
|
|
41 |
|
|
var $CACHE = array(); // An array of arrays which set |
42 |
|
|
// caching on a block |
43 |
|
|
|
44 |
|
|
// ************************************************************ |
45 |
|
|
|
46 |
|
|
function cachedFastTemplate ($pathToTemplates = "") { |
47 |
|
|
$this->FastTemplate ($pathToTemplates); |
48 |
|
|
|
49 |
|
|
} // end (new) FastTemplate () |
50 |
|
|
|
51 |
|
|
|
52 |
|
|
// ********************* |
53 |
|
|
// Function: is_cached |
54 |
|
|
// Input: RETURN, FileHandle (FIXME: Plural FileHandles are not supported.) |
55 |
|
|
// Return: true or false depending on whether a cache file is available. |
56 |
|
|
// Side Effects: assigns variables as if a parse call had been made |
57 |
|
|
// |
58 |
|
|
// Notes: |
59 |
|
|
// Find the cache name, and see if the file exists |
60 |
|
|
// If it doesn't, return FALSE |
61 |
|
|
// If it does, check is the file is current (see ~/src/php-cache.php) |
62 |
|
|
// If is isn't, return FALSE |
63 |
|
|
// check if the section is dynamic. If so, use clear_dynamic to insert contents of file |
64 |
|
|
// If not, assign file contents to ReturnVar |
65 |
|
|
// return TRUE |
66 |
|
|
function is_cached ( $ReturnVar, $FileTags ) { |
67 |
|
|
|
68 |
|
|
// Fixme: We should do something smart if $FileTags is an array |
69 |
|
|
|
70 |
|
|
$str_cache_file = $this->cache_file_name( $ReturnVar, $FileTags ); |
71 |
|
|
|
72 |
|
|
// This function will wait until a cache has been created. (Lock file cleared.) |
73 |
|
|
clearstatcache($str_cache_file); |
74 |
|
|
// Here we perform some error correction. If a lock file |
75 |
|
|
// is left over from a previous request, this ensures that |
76 |
|
|
// it is deleted. |
77 |
|
|
// FIXME: If this takes too long, we should turn caching off for this file, and return FALSE; |
78 |
|
|
while (file_exists($str_cache_file . '.lock')) { |
79 |
|
|
sleep(2); |
80 |
|
|
clearstatcache(); |
81 |
|
|
continue; |
82 |
|
|
} |
83 |
|
|
clearstatcache(); |
84 |
|
|
|
85 |
|
|
// If the cache file doesn't exist, it isn't cached. QED. |
86 |
|
|
if (!file_exists($str_cache_file)) { |
87 |
|
|
return FALSE; |
88 |
|
|
} |
89 |
|
|
|
90 |
|
|
// Now we need to find out if we are caching. |
91 |
|
|
$refresh = $this->CACHE[$FileTags]["refresh"]; |
92 |
|
|
$expires = $this->CACHE[$FileTags]["expire"]; |
93 |
|
|
$cache_read = 0; // Default to no read |
94 |
|
|
if (!refresh && !expires) // Do we have any set time? |
95 |
|
|
$expires = 600; |
96 |
|
|
|
97 |
|
|
if (isset ($expires)) { |
98 |
|
|
if ((filectime ($str_cache_file) + $expires) > date ( "U")) { |
99 |
|
|
$cache_read = 1; |
100 |
|
|
} |
101 |
|
|
} |
102 |
|
|
|
103 |
|
|
if (isset ($refresh)) { |
104 |
|
|
switch (strtoupper($refresh)) { |
105 |
|
|
case 'MINUTE': |
106 |
|
|
if (checkRefreshMinute(mktime(), filemtime($str_cache_file))) { |
107 |
|
|
$cache_read = 1; |
108 |
|
|
} |
109 |
|
|
break; |
110 |
|
|
case 'QUARTERHOUR': |
111 |
|
|
if (checkRefreshQuarterHour(mktime(), filemtime($str_cache_file))) { |
112 |
|
|
$cache_read = 1; |
113 |
|
|
} |
114 |
|
|
break; |
115 |
|
|
case 'HALFHOUR': |
116 |
|
|
if (checkRefreshHalfHour(mktime(), filemtime($str_cache_file))) { |
117 |
|
|
$cache_read = 1; |
118 |
|
|
} |
119 |
|
|
break; |
120 |
|
|
case 'HOUR': |
121 |
|
|
if (checkRefreshHour(mktime(), filemtime($str_cache_file))) { |
122 |
|
|
$cache_read = 1; |
123 |
|
|
} |
124 |
|
|
break; |
125 |
|
|
case 'HALFDAY': |
126 |
|
|
if (checkExpiredHalfDay(mktime(), filemtime($str_cache_file))) { |
127 |
|
|
$cache_read = 1; |
128 |
|
|
} |
129 |
|
|
break; |
130 |
|
|
case 'DAY': |
131 |
|
|
if (checkRefreshDay(mktime(), filemtime($str_cache_file))) { |
132 |
|
|
$cache_read = 1; |
133 |
|
|
} |
134 |
|
|
break; |
135 |
|
|
case 'MONTH': |
136 |
|
|
if (checkRefreshMonth(mktime(), filemtime($str_cache_file))) { |
137 |
|
|
$cache_read = 1; |
138 |
|
|
} |
139 |
|
|
} |
140 |
|
|
} |
141 |
|
|
|
142 |
|
|
if ($cache_read == 1) { |
143 |
|
|
|
144 |
|
|
// We have a cache file and we should read from it! |
145 |
|
|
if ($f = fopen ($str_cache_file, "r")) { |
146 |
|
|
$buf = ""; |
147 |
|
|
while ($str = fgets ($f, 4096)) { |
148 |
|
|
$buf .= $str; |
149 |
|
|
} |
150 |
|
|
fclose ($f); |
151 |
|
|
|
152 |
|
|
$this->LAST = $ReturnVar; |
153 |
|
|
|
154 |
|
|
// This is the part that actually does all the work. |
155 |
|
|
// The template and the parsed version are the same when caching |
156 |
|
|
$this->assign( array( $ReturnVar => $buf ) ); |
157 |
|
|
$this->$ReturnVar = $buf; |
158 |
|
|
$this->$FileTags = $buf; |
159 |
|
|
$this->LOADED[$FileTags] = "1"; // Make sure the template doesn't get loaded. |
160 |
|
|
|
161 |
|
|
// If we were supposed to be appending, maybe we should assume a dynamic block |
162 |
|
|
if ( (substr ($FileTags, 0, 1)) == '.' ) { |
163 |
|
|
$this->add_cache_dynamic ($ReturnVar, $buf); |
164 |
|
|
} |
165 |
|
|
|
166 |
|
|
return TRUE; |
167 |
|
|
} |
168 |
|
|
$this->ROOT = $root; |
169 |
|
|
} |
170 |
|
|
|
171 |
|
|
} // End set_root() |
172 |
|
|
|
173 |
|
|
// ********************* |
174 |
|
|
// Function: write_cache |
175 |
|
|
// Input: RETURN, FileHandle (FIXME: Plural FileHandles are not supported.) |
176 |
|
|
// Return: true or false depending on whether a cache file could be created |
177 |
|
|
// Side Effects: None |
178 |
|
|
// |
179 |
|
|
// Notes: |
180 |
|
|
// Find the cache name. |
181 |
|
|
// Write out the ReturnVar contents to the file |
182 |
|
|
// Return |
183 |
|
|
function write_cache ( $ReturnVar, $FileTags ) { |
184 |
|
|
|
185 |
|
|
// FIXME: I should do something clever if $FileTags is an array. |
186 |
|
|
|
187 |
|
|
$str_cache_file = $this->cache_file_name ( $ReturnVar, $FileTags ); |
188 |
|
|
|
189 |
|
|
$new = $this->parse_template_messy ($this->$FileTags, $this->PARSEVARS); |
190 |
|
|
|
191 |
|
|
// Time to write the cache -- but we need to look out for lock files |
192 |
|
|
// FIXME: I'm SURE there is a race condition here. |
193 |
|
|
while (file_exists($str_cache_file . '.lock')) { |
194 |
|
|
sleep(2); |
195 |
|
|
clearstatcache(); |
196 |
|
|
continue; |
197 |
|
|
} |
198 |
|
|
clearstatcache(); |
199 |
|
|
|
200 |
|
|
// We pray that nothing bad happens after this point. |
201 |
|
|
touch ($str_cache_file . '.lock'); |
202 |
|
|
|
203 |
|
|
$f = fopen ($str_cache_file, "w"); |
204 |
|
|
if (!$f) |
205 |
|
|
return FALSE; |
206 |
|
|
|
207 |
|
|
fputs ($f, $new); |
208 |
|
|
fclose ($f); |
209 |
|
|
|
210 |
|
|
// And remove the lock file. |
211 |
|
|
$this->remove_cache_lock ($str_cache_file); |
212 |
|
|
|
213 |
|
|
return TRUE; |
214 |
|
|
|
215 |
|
|
} |
216 |
|
|
|
217 |
|
|
// ********************* |
218 |
|
|
// Function: cache_expire |
219 |
|
|
// Input: RETURN, number_of_seconds |
220 |
|
|
// Return: None |
221 |
|
|
// Side Effects: Stores time to expire |
222 |
|
|
// |
223 |
|
|
// Notes: |
224 |
|
|
// tells in how many seconds the cached data will expire |
225 |
|
|
|
226 |
|
|
function cache_expire ( $ReturnVar, $seconds = 3000) { |
227 |
|
|
$this->CACHE[$ReturnVar]["expire"] = $seconds; |
228 |
|
|
} |
229 |
|
|
|
230 |
|
|
// ********************* |
231 |
|
|
// Function: cache_refresh |
232 |
|
|
// Input: RETURN, frequency |
233 |
|
|
// Return: None |
234 |
|
|
// Side Effects: Stores time to refresh |
235 |
|
|
// |
236 |
|
|
// Notes: |
237 |
|
|
// sets an event (turn of day) at which cache will expire |
238 |
|
|
function cache_refresh ( $ReturnVar, $freq = 'HOUR') { |
239 |
|
|
$this->CACHE[$ReturnVar]["refresh"] = $freq; |
240 |
|
|
} |
241 |
|
|
|
242 |
|
|
// ************************************************************ |
243 |
|
|
// All templates will be loaded from this "root" directory |
244 |
|
|
// Can be changed in mid-process by re-calling with a new |
245 |
|
|
// value. |
246 |
|
|
|
247 |
|
|
function set_root ($root) { |
248 |
|
|
|
249 |
|
|
$trailer = substr($root,-1); |
250 |
|
|
|
251 |
|
|
if(!$this->WIN32) { |
252 |
|
|
|
253 |
|
|
if( (ord($trailer)) != 47 ) { |
254 |
|
|
$root = "$root". chr(47); |
255 |
|
|
} |
256 |
|
|
|
257 |
|
|
if(is_dir($root)) { |
258 |
|
|
$this->ROOT = $root; |
259 |
|
|
} else { |
260 |
|
|
$this->ROOT = ""; |
261 |
|
|
$this->error("Specified ROOT dir [$root] is not a directory"); |
262 |
|
|
} |
263 |
|
|
} else { |
264 |
|
|
// WIN32 box - no testing |
265 |
|
|
if( (ord($trailer)) != 92 ) { |
266 |
|
|
$root = "$root" . chr(92); |
267 |
|
|
} |
268 |
|
|
$this->ROOT = $root; |
269 |
|
|
} |
270 |
|
|
|
271 |
|
|
} // End set_root() |
272 |
|
|
|
273 |
|
|
|
274 |
|
|
function parse_template_messy ($template, $tpl_array) { |
275 |
|
|
$a = strtok($template,"{"); |
276 |
|
|
$oldtemp = $template; |
277 |
|
|
while($a || $t) { |
278 |
|
|
$t = strtok("}"); |
279 |
|
|
|
280 |
|
|
if($t){ |
281 |
|
|
settype($tpl_array[$t],"string"); |
282 |
|
|
|
283 |
|
|
if(empty($tpl_array[$t])){ |
284 |
|
|
$toprint = $toprint . $a . "{". $t . "}"; // Recreate the substitution. |
285 |
|
|
} else { |
286 |
|
|
$toprint = $toprint . $a . $tpl_array[$t]; |
287 |
|
|
} |
288 |
|
|
|
289 |
|
|
} else { |
290 |
|
|
$toprint = $toprint . $a; |
291 |
|
|
} |
292 |
|
|
//print('T-' . $t . '<BR>'); |
293 |
|
|
//print('value' . htmlentities($tpl_array[$t]) . '<BR>'); |
294 |
|
|
//print('A-' . htmlentities($a) . '<BR>'); |
295 |
|
|
$a = strtok("{"); |
296 |
|
|
|
297 |
|
|
} |
298 |
|
|
|
299 |
|
|
$template = $toprint; |
300 |
|
|
|
301 |
|
|
return $template; |
302 |
|
|
|
303 |
|
|
} // end parse_template_messy(); |
304 |
|
|
|
305 |
|
|
// ************************************************************ |
306 |
|
|
// Adds cached data to a DYNAMIC BLOCK from a template. |
307 |
|
|
|
308 |
|
|
function add_cache_dynamic ($Macro="", $cache_data="" ) { |
309 |
|
|
if(empty($Macro)) { return false; } |
310 |
|
|
|
311 |
|
|
// The file must already be in memory. |
312 |
|
|
|
313 |
|
|
$ParentTag = $this->DYNAMIC["$Macro"]; |
314 |
|
|
|
315 |
|
|
if( (!$this->$ParentTag) or (empty($this->$ParentTag)) ) { |
316 |
|
|
$fileName = $this->FILELIST[$ParentTag]; |
317 |
|
|
$this->$ParentTag = $this->get_template($fileName); |
318 |
|
|
$this->LOADED[$ParentTag] = 1; |
319 |
|
|
} |
320 |
|
|
|
321 |
|
|
if($this->$ParentTag) { |
322 |
|
|
$template = $this->$ParentTag; |
323 |
|
|
$DataArray = split("\n",$template); |
324 |
|
|
$newParent = ""; |
325 |
|
|
$outside = true; |
326 |
|
|
$start = false; |
327 |
|
|
$end = false; |
328 |
|
|
while ( list ($lineNum,$lineData) = each ($DataArray) ) { |
329 |
|
|
$lineTest = trim($lineData); |
330 |
|
|
if("<!-- BEGIN DYNAMIC BLOCK: $Macro -->" == "$lineTest" ) { |
331 |
|
|
$start = true; |
332 |
|
|
$end = false; |
333 |
|
|
$outside = false; |
334 |
|
|
} |
335 |
|
|
if("<!-- END DYNAMIC BLOCK: $Macro -->" == "$lineTest" ) { |
336 |
|
|
$start = false; |
337 |
|
|
$end = true; |
338 |
|
|
$outside = true; |
339 |
|
|
} |
340 |
|
|
if( ($outside) and (!$start) and (!$end) ) { |
341 |
|
|
$newParent .= "$lineData\n"; // Restore linebreaks |
342 |
|
|
} |
343 |
|
|
if ($end) { |
344 |
|
|
$newParent .= $cache_data; |
345 |
|
|
} |
346 |
|
|
// Next line please |
347 |
|
|
if($end) { $end = false; } |
348 |
|
|
if($start) { $start = false; } |
349 |
|
|
} // end While |
350 |
|
|
|
351 |
|
|
$this->$ParentTag = $newParent; |
352 |
|
|
return true; |
353 |
|
|
|
354 |
|
|
} else {// $ParentTag NOT loaded - MAJOR oopsie |
355 |
|
|
@error_log("ParentTag: [$ParentTag] not loaded!",0); |
356 |
|
|
$this->error("ParentTag: [$ParentTag] not loaded!",0); |
357 |
|
|
} |
358 |
|
|
return false; |
359 |
|
|
} |
360 |
|
|
|
361 |
|
|
// ************************************************************ |
362 |
|
|
// Caching system |
363 |
|
|
|
364 |
|
|
//*********************************/ |
365 |
|
|
// Some other functions |
366 |
|
|
function remove_cache_lock($str_cache_file) { |
367 |
|
|
clearstatcache(); |
368 |
|
|
if (file_exists($str_cache_file . '.lock')) { |
369 |
|
|
@unlink($str_cache_file . '.lock'); |
370 |
|
|
} |
371 |
|
|
} |
372 |
|
|
|
373 |
|
|
// Create the cache file name |
374 |
|
|
function cache_file_name ( $ReturnVar, $FileTags ) { |
375 |
|
|
|
376 |
|
|
// What are we going to do? |
377 |
|
|
$str_cache_root = "/tmp/"; |
378 |
|
|
$str_cache_dir = strtolower(getenv ("HTTP_HOST")) . getenv ("SCRIPT_URL"); |
379 |
|
|
if ( (substr ($FileTags, 0, 1)) == '.' ) { |
380 |
|
|
$str_cache_file = "/" . substr($FileTags,1); |
381 |
|
|
} else { |
382 |
|
|
$str_cache_file = "/" . $FileTags; |
383 |
|
|
} |
384 |
|
|
|
385 |
|
|
// First we need to make the directory the cache file is in if it doesn't already exist. |
386 |
|
|
if ($this->rmkdir ($str_cache_root . $str_cache_dir)) |
387 |
|
|
return $str_cache_root . $str_cache_dir . $str_cache_file; |
388 |
|
|
|
389 |
|
|
return; |
390 |
|
|
} |
391 |
|
|
|
392 |
|
|
// Create a directory tree |
393 |
|
|
function rmkdir ( $directory ) { |
394 |
|
|
|
395 |
|
|
// If we encounter this as a file, we can't make it a directory |
396 |
|
|
$type = filetype ( $directory ); |
397 |
|
|
if ($type != 'dir' && $type != 'FALSE' && !empty($type)) { |
398 |
|
|
return FALSE; |
399 |
|
|
} |
400 |
|
|
if ($type == 'dir') |
401 |
|
|
return TRUE; |
402 |
|
|
|
403 |
|
|
$pieces = explode ("/", $directory); |
404 |
|
|
|
405 |
|
|
for ($num = 0; $num < count($pieces); $num += 1) { |
406 |
|
|
$so_far = $so_far . "/" . $pieces[$num]; |
407 |
|
|
|
408 |
|
|
$type = filetype ( $so_far ); |
409 |
|
|
if ($type != 'dir' && $type != 'FALSE' && !empty($type)) { |
410 |
|
|
return FALSE; |
411 |
|
|
} |
412 |
|
|
|
413 |
|
|
if ($type == 'dir') { |
414 |
|
|
continue; |
415 |
|
|
} |
416 |
|
|
|
417 |
|
|
if (!mkdir ($so_far, 0700)) { |
418 |
|
|
return FALSE; |
419 |
|
|
} |
420 |
|
|
} |
421 |
|
|
|
422 |
|
|
return TRUE; |
423 |
|
|
|
424 |
|
|
} |
425 |
|
|
|
426 |
|
|
//*********************************/ |
427 |
|
|
// CACHE REFRESH CHECKING FUNCTIONS |
428 |
|
|
// Each function checks the next interval higher for verification |
429 |
|
|
|
430 |
|
|
function checkRefreshYear($systime, $filetime) { |
431 |
|
|
$sysYear = date( "y", $systime); |
432 |
|
|
$fileYear = date( "y", $filetime); |
433 |
|
|
if ($sysYear != $fileYear) { return false; } |
434 |
|
|
return true; |
435 |
|
|
} |
436 |
|
|
|
437 |
|
|
function checkRefreshMonth($systime, $filetime) { |
438 |
|
|
$sysMonth = date( "M", $systime); |
439 |
|
|
$fileMonth = date( "M", $filetime); |
440 |
|
|
if ($sysMonth != $fileMonth) { return false; } |
441 |
|
|
if (!(checkRefreshYear($systime, $filetime))) { return false; } |
442 |
|
|
return true; |
443 |
|
|
} |
444 |
|
|
|
445 |
|
|
function checkRefreshDay($systime, $filetime) { |
446 |
|
|
$sysDay = date( "j", $systime); |
447 |
|
|
$fileDay = date( "j", $filetime); |
448 |
|
|
if ($sysDay != $fileDay) { return false; } |
449 |
|
|
if (!(checkRefreshMonth($systime, $filetime))) { return false; } |
450 |
|
|
return true; |
451 |
|
|
} |
452 |
|
|
|
453 |
|
|
function checkRefreshDayHalf($systime, $filetime) { |
454 |
|
|
$sysHour = date( "H", $systime); |
455 |
|
|
$fileHour = date( "H", $filetime); |
456 |
|
|
if (($sysHour % 12) < ($fileHour % 12)) { return false; } |
457 |
|
|
if (!(checkRefreshDay($systime, $filetime))) { return false; } |
458 |
|
|
return true; |
459 |
|
|
} |
460 |
|
|
|
461 |
|
|
function checkRefreshHour($systime, $filetime) { |
462 |
|
|
$sysHour = date( "H", $systime); |
463 |
|
|
$fileHour = date( "H", $filetime); |
464 |
|
|
if ($sysHour != $fileHour) { return false; } |
465 |
|
|
if (!(checkRefreshDayHalf($systime, $filetime))) { return false; } |
466 |
|
|
return true; |
467 |
|
|
} |
468 |
|
|
|
469 |
|
|
function checkRefreshHalfHour($systime, $filetime) { |
470 |
|
|
$sysMin = date( "i", $systime); |
471 |
|
|
$fileMin = date( "i", $filetime); |
472 |
|
|
if (($sysMin % 30) < ($fileMin % 30)) { return false; } |
473 |
|
|
if (!(checkRefreshHour($systime, $filetime))) { return false; } |
474 |
|
|
return true; |
475 |
|
|
} |
476 |
|
|
|
477 |
|
|
function checkRefreshQuarterHour($systime, $filetime) { |
478 |
|
|
$sysMin = date( "i", $systime); |
479 |
|
|
$fileMin = date( "i", $filetime); |
480 |
|
|
if (($sysMin % 15) < ($fileMin % 15)) { return false; } |
481 |
|
|
if (!(checkRefreshHalfHour($systime, $filetime))) { return false; } |
482 |
|
|
return true; |
483 |
|
|
} |
484 |
|
|
|
485 |
|
|
function checkRefreshMinute($systime, $filetime) { |
486 |
|
|
$sysMin = date( "i", $systime); |
487 |
|
|
$fileMin = date( "i", $filetime); |
488 |
|
|
if ($sysMin != $fileMin) { return false; } |
489 |
|
|
if (!(checkRefreshQuarterHour($systime, $filetime))) { return false; } |
490 |
|
|
return true; |
491 |
|
|
} |
492 |
|
|
|
493 |
|
|
|
494 |
|
|
|
495 |
|
|
} // End class.cacheFastTemplate |
496 |
|
|
|
497 |
|
|
?> |