]> git.mxchange.org Git - friendica.git/blob - library/Smarty/libs/sysplugins/smarty_cacheresource_keyvaluestore.php
networkheader: do css work the other supported themes
[friendica.git] / library / Smarty / libs / sysplugins / smarty_cacheresource_keyvaluestore.php
1 <?php
2 /**
3  * Smarty Internal Plugin
4  *
5  * @package    Smarty
6  * @subpackage Cacher
7  */
8
9 /**
10  * Smarty Cache Handler Base for Key/Value Storage Implementations
11  * This class implements the functionality required to use simple key/value stores
12  * for hierarchical cache groups. key/value stores like memcache or APC do not support
13  * wildcards in keys, therefore a cache group cannot be cleared like "a|*" - which
14  * is no problem to filesystem and RDBMS implementations.
15  * This implementation is based on the concept of invalidation. While one specific cache
16  * can be identified and cleared, any range of caches cannot be identified. For this reason
17  * each level of the cache group hierarchy can have its own value in the store. These values
18  * are nothing but microtimes, telling us when a particular cache group was cleared for the
19  * last time. These keys are evaluated for every cache read to determine if the cache has
20  * been invalidated since it was created and should hence be treated as inexistent.
21  * Although deep hierarchies are possible, they are not recommended. Try to keep your
22  * cache groups as shallow as possible. Anything up 3-5 parents should be ok. So
23  * »a|b|c« is a good depth where »a|b|c|d|e|f|g|h|i|j|k« isn't. Try to join correlating
24  * cache groups: if your cache groups look somewhat like »a|b|$page|$items|$whatever«
25  * consider using »a|b|c|$page-$items-$whatever« instead.
26  *
27  * @package    Smarty
28  * @subpackage Cacher
29  * @author     Rodney Rehm
30  */
31 abstract class Smarty_CacheResource_KeyValueStore extends Smarty_CacheResource
32 {
33     /**
34      * cache for contents
35      *
36      * @var array
37      */
38     protected $contents = array();
39     /**
40      * cache for timestamps
41      *
42      * @var array
43      */
44     protected $timestamps = array();
45
46     /**
47      * populate Cached Object with meta data from Resource
48      *
49      * @param  Smarty_Template_Cached   $cached    cached object
50      * @param  Smarty_Internal_Template $_template template object
51      *
52      * @return void
53      */
54     public function populate(Smarty_Template_Cached $cached, Smarty_Internal_Template $_template)
55     {
56         $cached->filepath = $_template->source->uid
57             . '#' . $this->sanitize($cached->source->resource)
58             . '#' . $this->sanitize($cached->cache_id)
59             . '#' . $this->sanitize($cached->compile_id);
60
61         $this->populateTimestamp($cached);
62     }
63
64     /**
65      * populate Cached Object with timestamp and exists from Resource
66      *
67      * @param  Smarty_Template_Cached $cached cached object
68      *
69      * @return void
70      */
71     public function populateTimestamp(Smarty_Template_Cached $cached)
72     {
73         if (!$this->fetch($cached->filepath, $cached->source->name, $cached->cache_id, $cached->compile_id, $content, $timestamp, $cached->source->uid)) {
74             return;
75         }
76         $cached->content = $content;
77         $cached->timestamp = (int) $timestamp;
78         $cached->exists = $cached->timestamp;
79     }
80
81     /**
82      * Read the cached template and process the header
83      *
84      * @param  Smarty_Internal_Template $_template template object
85      * @param  Smarty_Template_Cached   $cached    cached object
86      *
87      * @return boolean                 true or false if the cached content does not exist
88      */
89     public function process(Smarty_Internal_Template $_template, Smarty_Template_Cached $cached = null)
90     {
91         if (!$cached) {
92             $cached = $_template->cached;
93         }
94         $content = $cached->content ? $cached->content : null;
95         $timestamp = $cached->timestamp ? $cached->timestamp : null;
96         if ($content === null || !$timestamp) {
97             if (!$this->fetch($_template->cached->filepath, $_template->source->name, $_template->cache_id, $_template->compile_id, $content, $timestamp, $_template->source->uid)) {
98                 return false;
99             }
100         }
101         if (isset($content)) {
102             /** @var Smarty_Internal_Template $_smarty_tpl
103              * used in evaluated code
104              */
105             $_smarty_tpl = $_template;
106             eval("?>" . $content);
107
108             return true;
109         }
110
111         return false;
112     }
113
114     /**
115      * Write the rendered template output to cache
116      *
117      * @param  Smarty_Internal_Template $_template template object
118      * @param  string                   $content   content to cache
119      *
120      * @return boolean                  success
121      */
122     public function writeCachedContent(Smarty_Internal_Template $_template, $content)
123     {
124         $this->addMetaTimestamp($content);
125
126         return $this->write(array($_template->cached->filepath => $content), $_template->properties['cache_lifetime']);
127     }
128
129     /**
130      * Empty cache
131      * {@internal the $exp_time argument is ignored altogether }}
132      *
133      * @param  Smarty  $smarty   Smarty object
134      * @param  integer $exp_time expiration time [being ignored]
135      *
136      * @return integer number of cache files deleted [always -1]
137      * @uses purge() to clear the whole store
138      * @uses invalidate() to mark everything outdated if purge() is inapplicable
139      */
140     public function clearAll(Smarty $smarty, $exp_time = null)
141     {
142         if (!$this->purge()) {
143             $this->invalidate(null);
144         }
145
146         return - 1;
147     }
148
149     /**
150      * Empty cache for a specific template
151      * {@internal the $exp_time argument is ignored altogether}}
152      *
153      * @param  Smarty  $smarty        Smarty object
154      * @param  string  $resource_name template name
155      * @param  string  $cache_id      cache id
156      * @param  string  $compile_id    compile id
157      * @param  integer $exp_time      expiration time [being ignored]
158      *
159      * @return integer number of cache files deleted [always -1]
160      * @uses buildCachedFilepath() to generate the CacheID
161      * @uses invalidate() to mark CacheIDs parent chain as outdated
162      * @uses delete() to remove CacheID from cache
163      */
164     public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time)
165     {
166         $uid = $this->getTemplateUid($smarty, $resource_name, $cache_id, $compile_id);
167         $cid = $uid . '#' . $this->sanitize($resource_name) . '#' . $this->sanitize($cache_id) . '#' . $this->sanitize($compile_id);
168         $this->delete(array($cid));
169         $this->invalidate($cid, $resource_name, $cache_id, $compile_id, $uid);
170
171         return - 1;
172     }
173
174     /**
175      * Get template's unique ID
176      *
177      * @param  Smarty $smarty        Smarty object
178      * @param  string $resource_name template name
179      * @param  string $cache_id      cache id
180      * @param  string $compile_id    compile id
181      *
182      * @return string filepath of cache file
183      */
184     protected function getTemplateUid(Smarty $smarty, $resource_name, $cache_id, $compile_id)
185     {
186         $uid = '';
187         if (isset($resource_name)) {
188             $tpl = new $smarty->template_class($resource_name, $smarty);
189             if ($tpl->source->exists) {
190                 $uid = $tpl->source->uid;
191             }
192
193             // remove from template cache
194             if ($smarty->allow_ambiguous_resources) {
195                 $_templateId = $tpl->source->unique_resource . $tpl->cache_id . $tpl->compile_id;
196             } else {
197                 $_templateId = $smarty->joined_template_dir . '#' . $resource_name . $tpl->cache_id . $tpl->compile_id;
198             }
199             if (isset($_templateId[150])) {
200                 $_templateId = sha1($_templateId);
201             }
202             unset($smarty->template_objects[$_templateId]);
203         }
204
205         return $uid;
206     }
207
208     /**
209      * Sanitize CacheID components
210      *
211      * @param  string $string CacheID component to sanitize
212      *
213      * @return string sanitized CacheID component
214      */
215     protected function sanitize($string)
216     {
217         // some poeple smoke bad weed
218         $string = trim($string, '|');
219         if (!$string) {
220             return null;
221         }
222
223         return preg_replace('#[^\w\|]+#S', '_', $string);
224     }
225
226     /**
227      * Fetch and prepare a cache object.
228      *
229      * @param  string  $cid           CacheID to fetch
230      * @param  string  $resource_name template name
231      * @param  string  $cache_id      cache id
232      * @param  string  $compile_id    compile id
233      * @param  string  $content       cached content
234      * @param  integer &$timestamp    cached timestamp (epoch)
235      * @param  string  $resource_uid  resource's uid
236      *
237      * @return boolean success
238      */
239     protected function fetch($cid, $resource_name = null, $cache_id = null, $compile_id = null, &$content = null, &$timestamp = null, $resource_uid = null)
240     {
241         $t = $this->read(array($cid));
242         $content = !empty($t[$cid]) ? $t[$cid] : null;
243         $timestamp = null;
244
245         if ($content && ($timestamp = $this->getMetaTimestamp($content))) {
246             $invalidated = $this->getLatestInvalidationTimestamp($cid, $resource_name, $cache_id, $compile_id, $resource_uid);
247             if ($invalidated > $timestamp) {
248                 $timestamp = null;
249                 $content = null;
250             }
251         }
252
253         return !!$content;
254     }
255
256     /**
257      * Add current microtime to the beginning of $cache_content
258      * {@internal the header uses 8 Bytes, the first 4 Bytes are the seconds, the second 4 Bytes are the microseconds}}
259      *
260      * @param string &$content the content to be cached
261      */
262     protected function addMetaTimestamp(&$content)
263     {
264         $mt = explode(" ", microtime());
265         $ts = pack("NN", $mt[1], (int) ($mt[0] * 100000000));
266         $content = $ts . $content;
267     }
268
269     /**
270      * Extract the timestamp the $content was cached
271      *
272      * @param  string &$content the cached content
273      *
274      * @return float  the microtime the content was cached
275      */
276     protected function getMetaTimestamp(&$content)
277     {
278         $s = unpack("N", substr($content, 0, 4));
279         $m = unpack("N", substr($content, 4, 4));
280         $content = substr($content, 8);
281
282         return $s[1] + ($m[1] / 100000000);
283     }
284
285     /**
286      * Invalidate CacheID
287      *
288      * @param  string $cid           CacheID
289      * @param  string $resource_name template name
290      * @param  string $cache_id      cache id
291      * @param  string $compile_id    compile id
292      * @param  string $resource_uid  source's uid
293      *
294      * @return void
295      */
296     protected function invalidate($cid = null, $resource_name = null, $cache_id = null, $compile_id = null, $resource_uid = null)
297     {
298         $now = microtime(true);
299         $key = null;
300         // invalidate everything
301         if (!$resource_name && !$cache_id && !$compile_id) {
302             $key = 'IVK#ALL';
303         } // invalidate all caches by template
304         else {
305             if ($resource_name && !$cache_id && !$compile_id) {
306                 $key = 'IVK#TEMPLATE#' . $resource_uid . '#' . $this->sanitize($resource_name);
307             } // invalidate all caches by cache group
308             else {
309                 if (!$resource_name && $cache_id && !$compile_id) {
310                     $key = 'IVK#CACHE#' . $this->sanitize($cache_id);
311                 } // invalidate all caches by compile id
312                 else {
313                     if (!$resource_name && !$cache_id && $compile_id) {
314                         $key = 'IVK#COMPILE#' . $this->sanitize($compile_id);
315                     } // invalidate by combination
316                     else {
317                         $key = 'IVK#CID#' . $cid;
318                     }
319                 }
320             }
321         }
322         $this->write(array($key => $now));
323     }
324
325     /**
326      * Determine the latest timestamp known to the invalidation chain
327      *
328      * @param  string $cid           CacheID to determine latest invalidation timestamp of
329      * @param  string $resource_name template name
330      * @param  string $cache_id      cache id
331      * @param  string $compile_id    compile id
332      * @param  string $resource_uid  source's filepath
333      *
334      * @return float  the microtime the CacheID was invalidated
335      */
336     protected function getLatestInvalidationTimestamp($cid, $resource_name = null, $cache_id = null, $compile_id = null, $resource_uid = null)
337     {
338         // abort if there is no CacheID
339         if (false && !$cid) {
340             return 0;
341         }
342         // abort if there are no InvalidationKeys to check
343         if (!($_cid = $this->listInvalidationKeys($cid, $resource_name, $cache_id, $compile_id, $resource_uid))) {
344             return 0;
345         }
346
347         // there are no InValidationKeys
348         if (!($values = $this->read($_cid))) {
349             return 0;
350         }
351         // make sure we're dealing with floats
352         $values = array_map('floatval', $values);
353
354         return max($values);
355     }
356
357     /**
358      * Translate a CacheID into the list of applicable InvalidationKeys.
359      * Splits "some|chain|into|an|array" into array( '#clearAll#', 'some', 'some|chain', 'some|chain|into', ... )
360      *
361      * @param  string $cid           CacheID to translate
362      * @param  string $resource_name template name
363      * @param  string $cache_id      cache id
364      * @param  string $compile_id    compile id
365      * @param  string $resource_uid  source's filepath
366      *
367      * @return array  list of InvalidationKeys
368      * @uses $invalidationKeyPrefix to prepend to each InvalidationKey
369      */
370     protected function listInvalidationKeys($cid, $resource_name = null, $cache_id = null, $compile_id = null, $resource_uid = null)
371     {
372         $t = array('IVK#ALL');
373         $_name = $_compile = '#';
374         if ($resource_name) {
375             $_name .= $resource_uid . '#' . $this->sanitize($resource_name);
376             $t[] = 'IVK#TEMPLATE' . $_name;
377         }
378         if ($compile_id) {
379             $_compile .= $this->sanitize($compile_id);
380             $t[] = 'IVK#COMPILE' . $_compile;
381         }
382         $_name .= '#';
383         // some poeple smoke bad weed
384         $cid = trim($cache_id, '|');
385         if (!$cid) {
386             return $t;
387         }
388         $i = 0;
389         while (true) {
390             // determine next delimiter position
391             $i = strpos($cid, '|', $i);
392             // add complete CacheID if there are no more delimiters
393             if ($i === false) {
394                 $t[] = 'IVK#CACHE#' . $cid;
395                 $t[] = 'IVK#CID' . $_name . $cid . $_compile;
396                 $t[] = 'IVK#CID' . $_name . $_compile;
397                 break;
398             }
399             $part = substr($cid, 0, $i);
400             // add slice to list
401             $t[] = 'IVK#CACHE#' . $part;
402             $t[] = 'IVK#CID' . $_name . $part . $_compile;
403             // skip past delimiter position
404             $i ++;
405         }
406
407         return $t;
408     }
409
410     /**
411      * Check is cache is locked for this template
412      *
413      * @param  Smarty                 $smarty Smarty object
414      * @param  Smarty_Template_Cached $cached cached object
415      *
416      * @return boolean               true or false if cache is locked
417      */
418     public function hasLock(Smarty $smarty, Smarty_Template_Cached $cached)
419     {
420         $key = 'LOCK#' . $cached->filepath;
421         $data = $this->read(array($key));
422
423         return $data && time() - $data[$key] < $smarty->locking_timeout;
424     }
425
426     /**
427      * Lock cache for this template
428      *
429      * @param Smarty                 $smarty Smarty object
430      * @param Smarty_Template_Cached $cached cached object
431      *
432      * @return bool|void
433      */
434     public function acquireLock(Smarty $smarty, Smarty_Template_Cached $cached)
435     {
436         $cached->is_locked = true;
437         $key = 'LOCK#' . $cached->filepath;
438         $this->write(array($key => time()), $smarty->locking_timeout);
439     }
440
441     /**
442      * Unlock cache for this template
443      *
444      * @param Smarty                 $smarty Smarty object
445      * @param Smarty_Template_Cached $cached cached object
446      *
447      * @return bool|void
448      */
449     public function releaseLock(Smarty $smarty, Smarty_Template_Cached $cached)
450     {
451         $cached->is_locked = false;
452         $key = 'LOCK#' . $cached->filepath;
453         $this->delete(array($key));
454     }
455
456     /**
457      * Read values for a set of keys from cache
458      *
459      * @param  array $keys list of keys to fetch
460      *
461      * @return array list of values with the given keys used as indexes
462      */
463     abstract protected function read(array $keys);
464
465     /**
466      * Save values for a set of keys to cache
467      *
468      * @param  array $keys   list of values to save
469      * @param  int   $expire expiration time
470      *
471      * @return boolean true on success, false on failure
472      */
473     abstract protected function write(array $keys, $expire = null);
474
475     /**
476      * Remove values from cache
477      *
478      * @param  array $keys list of keys to delete
479      *
480      * @return boolean true on success, false on failure
481      */
482     abstract protected function delete(array $keys);
483
484     /**
485      * Remove *all* values from cache
486      *
487      * @return boolean true on success, false on failure
488      */
489     protected function purge()
490     {
491         return false;
492     }
493 }