Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
cache.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage Cache
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 /**
13  * Joomla! Cache base object
14  *
15  * @package Joomla.Platform
16  * @subpackage Cache
17  * @since 11.1
18  */
19 class JCache
20 {
21  /**
22  * @var object Storage handler
23  * @since 11.1
24  */
25  public static $_handler = array();
26 
27  /**
28  * @var array Options
29  * @since 11.1
30  */
31  public $_options;
32 
33  /**
34  * Constructor
35  *
36  * @param array $options options
37  *
38  * @since 11.1
39  */
40  public function __construct($options)
41  {
42  $conf = JFactory::getConfig();
43 
44  $this->_options = array(
45  'cachebase' => $conf->get('cache_path', JPATH_CACHE),
46  'lifetime' => (int) $conf->get('cachetime'),
47  'language' => $conf->get('language', 'en-GB'),
48  'storage' => $conf->get('cache_handler', ''),
49  'defaultgroup' => 'default',
50  'locking' => true,
51  'locktime' => 15,
52  'checkTime' => true,
53  'caching' => ($conf->get('caching') >= 1) ? true : false);
54 
55  // Overwrite default options with given options
56  foreach ($options as $option => $value)
57  {
58  if (isset($options[$option]) && $options[$option] !== '')
59  {
60  $this->_options[$option] = $options[$option];
61  }
62  }
63 
64  if (empty($this->_options['storage']))
65  {
66  $this->_options['caching'] = false;
67  }
68  }
69 
70  /**
71  * Returns a reference to a cache adapter object, always creating it
72  *
73  * @param string $type The cache object type to instantiate
74  * @param array $options The array of options
75  *
76  * @return JCache A JCache object
77  *
78  * @since 11.1
79  */
80  public static function getInstance($type = 'output', $options = array())
81  {
82  return JCacheController::getInstance($type, $options);
83  }
84 
85  /**
86  * Get the storage handlers
87  *
88  * @return array An array of available storage handlers
89  *
90  * @since 11.1
91  */
92  public static function getStores()
93  {
94  $handlers = array();
95 
96  // Get an iterator and loop trough the driver classes.
97  $iterator = new DirectoryIterator(__DIR__ . '/storage');
98 
99  foreach ($iterator as $file)
100  {
101  $fileName = $file->getFilename();
102 
103  // Only load for php files.
104  // Note: DirectoryIterator::getExtension only available PHP >= 5.3.6
105  if (!$file->isFile()
106  || substr($fileName, strrpos($fileName, '.') + 1) != 'php'
107  || $fileName == 'helper.php')
108  {
109  continue;
110  }
111 
112  // Derive the class name from the type.
113  $class = str_ireplace('.php', '', 'JCacheStorage' . ucfirst(trim($fileName)));
114 
115  // If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
116  if (!class_exists($class))
117  {
118  continue;
119  }
120 
121  // Sweet! Our class exists, so now we just need to know if it passes its test method.
122  if ($class::isSupported())
123  {
124  // Connector names should not have file extensions.
125  $handlers[] = str_ireplace('.php', '', $fileName);
126  }
127  }
128 
129  return $handlers;
130  }
131 
132  /**
133  * Set caching enabled state
134  *
135  * @param boolean $enabled True to enable caching
136  *
137  * @return void
138  *
139  * @since 11.1
140  */
141  public function setCaching($enabled)
142  {
143  $this->_options['caching'] = $enabled;
144  }
145 
146  /**
147  * Get caching state
148  *
149  * @return boolean Caching state
150  *
151  * @since 11.1
152  */
153  public function getCaching()
154  {
155  return $this->_options['caching'];
156  }
157 
158  /**
159  * Set cache lifetime
160  *
161  * @param integer $lt Cache lifetime
162  *
163  * @return void
164  *
165  * @since 11.1
166  */
167  public function setLifeTime($lt)
168  {
169  $this->_options['lifetime'] = $lt;
170  }
171 
172  /**
173  * Get cached data by id and group
174  *
175  * @param string $id The cache data id
176  * @param string $group The cache data group
177  *
178  * @return mixed boolean False on failure or a cached data string
179  *
180  * @since 11.1
181  */
182  public function get($id, $group = null)
183  {
184  // Get the default group
185  $group = ($group) ? $group : $this->_options['defaultgroup'];
186 
187  // Get the storage
188  $handler = $this->_getStorage();
189  if (!($handler instanceof Exception) && $this->_options['caching'])
190  {
191  return $handler->get($id, $group, $this->_options['checkTime']);
192  }
193  return false;
194  }
195 
196  /**
197  * Get a list of all cached data
198  *
199  * @return mixed Boolean false on failure or an object with a list of cache groups and data
200  *
201  * @since 11.1
202  */
203  public function getAll()
204  {
205  // Get the storage
206  $handler = $this->_getStorage();
207  if (!($handler instanceof Exception) && $this->_options['caching'])
208  {
209  return $handler->getAll();
210  }
211  return false;
212  }
213 
214  /**
215  * Store the cached data by id and group
216  *
217  * @param mixed $data The data to store
218  * @param string $id The cache data id
219  * @param string $group The cache data group
220  *
221  * @return boolean True if cache stored
222  *
223  * @since 11.1
224  */
225  public function store($data, $id, $group = null)
226  {
227  // Get the default group
228  $group = ($group) ? $group : $this->_options['defaultgroup'];
229 
230  // Get the storage and store the cached data
231  $handler = $this->_getStorage();
232  if (!($handler instanceof Exception) && $this->_options['caching'])
233  {
234  $handler->_lifetime = $this->_options['lifetime'];
235  return $handler->store($id, $group, $data);
236  }
237  return false;
238  }
239 
240  /**
241  * Remove a cached data entry by id and group
242  *
243  * @param string $id The cache data id
244  * @param string $group The cache data group
245  *
246  * @return boolean True on success, false otherwise
247  *
248  * @since 11.1
249  */
250  public function remove($id, $group = null)
251  {
252  // Get the default group
253  $group = ($group) ? $group : $this->_options['defaultgroup'];
254 
255  // Get the storage
256  $handler = $this->_getStorage();
257  if (!($handler instanceof Exception))
258  {
259  return $handler->remove($id, $group);
260  }
261  return false;
262  }
263 
264  /**
265  * Clean cache for a group given a mode.
266  *
267  * group mode : cleans all cache in the group
268  * notgroup mode : cleans all cache not in the group
269  *
270  * @param string $group The cache data group
271  * @param string $mode The mode for cleaning cache [group|notgroup]
272  *
273  * @return boolean True on success, false otherwise
274  *
275  * @since 11.1
276  */
277  public function clean($group = null, $mode = 'group')
278  {
279  // Get the default group
280  $group = ($group) ? $group : $this->_options['defaultgroup'];
281 
282  // Get the storage handler
283  $handler = $this->_getStorage();
284  if (!($handler instanceof Exception))
285  {
286  return $handler->clean($group, $mode);
287  }
288  return false;
289  }
290 
291  /**
292  * Garbage collect expired cache data
293  *
294  * @return boolean True on success, false otherwise.
295  *
296  * @since 11.1
297  */
298  public function gc()
299  {
300  // Get the storage handler
301  $handler = $this->_getStorage();
302  if (!($handler instanceof Exception))
303  {
304  return $handler->gc();
305  }
306  return false;
307  }
308 
309  /**
310  * Set lock flag on cached item
311  *
312  * @param string $id The cache data id
313  * @param string $group The cache data group
314  * @param string $locktime The default locktime for locking the cache.
315  *
316  * @return object Properties are lock and locklooped
317  *
318  * @since 11.1
319  */
320  public function lock($id, $group = null, $locktime = null)
321  {
322  $returning = new stdClass;
323  $returning->locklooped = false;
324 
325  // Get the default group
326  $group = ($group) ? $group : $this->_options['defaultgroup'];
327 
328  // Get the default locktime
329  $locktime = ($locktime) ? $locktime : $this->_options['locktime'];
330 
331  // Allow storage handlers to perform locking on their own
332  // NOTE drivers with lock need also unlock or unlocking will fail because of false $id
333  $handler = $this->_getStorage();
334  if (!($handler instanceof Exception) && $this->_options['locking'] == true && $this->_options['caching'] == true)
335  {
336  $locked = $handler->lock($id, $group, $locktime);
337  if ($locked !== false)
338  {
339  return $locked;
340  }
341  }
342 
343  // Fallback
344  $curentlifetime = $this->_options['lifetime'];
345 
346  // Set lifetime to locktime for storing in children
347  $this->_options['lifetime'] = $locktime;
348 
349  $looptime = $locktime * 10;
350  $id2 = $id . '_lock';
351 
352  if ($this->_options['locking'] == true && $this->_options['caching'] == true)
353  {
354  $data_lock = $this->get($id2, $group);
355 
356  }
357  else
358  {
359  $data_lock = false;
360  $returning->locked = false;
361  }
362 
363  if ($data_lock !== false)
364  {
365  $lock_counter = 0;
366 
367  // Loop until you find that the lock has been released.
368  // That implies that data get from other thread has finished
369  while ($data_lock !== false)
370  {
371 
372  if ($lock_counter > $looptime)
373  {
374  $returning->locked = false;
375  $returning->locklooped = true;
376  break;
377  }
378 
379  usleep(100);
380  $data_lock = $this->get($id2, $group);
381  $lock_counter++;
382  }
383  }
384 
385  if ($this->_options['locking'] == true && $this->_options['caching'] == true)
386  {
387  $returning->locked = $this->store(1, $id2, $group);
388  }
389 
390  // Revert lifetime to previous one
391  $this->_options['lifetime'] = $curentlifetime;
392 
393  return $returning;
394  }
395 
396  /**
397  * Unset lock flag on cached item
398  *
399  * @param string $id The cache data id
400  * @param string $group The cache data group
401  *
402  * @return boolean True on success, false otherwise.
403  *
404  * @since 11.1
405  */
406  public function unlock($id, $group = null)
407  {
408  $unlock = false;
409 
410  // Get the default group
411  $group = ($group) ? $group : $this->_options['defaultgroup'];
412 
413  // Allow handlers to perform unlocking on their own
414  $handler = $this->_getStorage();
415  if (!($handler instanceof Exception) && $this->_options['caching'])
416  {
417  $unlocked = $handler->unlock($id, $group);
418  if ($unlocked !== false)
419  {
420  return $unlocked;
421  }
422  }
423 
424  // Fallback
425  if ($this->_options['caching'])
426  {
427  $unlock = $this->remove($id . '_lock', $group);
428  }
429 
430  return $unlock;
431  }
432 
433  /**
434  * Get the cache storage handler
435  *
436  * @return JCacheStorage A JCacheStorage object
437  *
438  * @since 11.1
439  */
440  public function &_getStorage()
441  {
442  $hash = md5(serialize($this->_options));
443 
444  if (isset(self::$_handler[$hash]))
445  {
446  return self::$_handler[$hash];
447  }
448 
449  self::$_handler[$hash] = JCacheStorage::getInstance($this->_options['storage'], $this->_options);
450  return self::$_handler[$hash];
451  }
452 
453  /**
454  * Perform workarounds on retrieved cached data
455  *
456  * @param string $data Cached data
457  * @param array $options Array of options
458  *
459  * @return string Body of cached data
460  *
461  * @since 11.1
462  */
463  public static function getWorkarounds($data, $options = array())
464  {
465  $app = JFactory::getApplication();
466  $document = JFactory::getDocument();
467  $body = null;
468 
469  // Get the document head out of the cache.
470  if (isset($options['mergehead']) && $options['mergehead'] == 1 && isset($data['head']) && !empty($data['head']))
471  {
472  $document->mergeHeadData($data['head']);
473  }
474  elseif (isset($data['head']) && method_exists($document, 'setHeadData'))
475  {
476  $document->setHeadData($data['head']);
477  }
478 
479  // If the pathway buffer is set in the cache data, get it.
480  if (isset($data['pathway']) && is_array($data['pathway']))
481  {
482  // Push the pathway data into the pathway object.
483  $pathway = $app->getPathWay();
484  $pathway->setPathway($data['pathway']);
485  }
486 
487  // @todo check if the following is needed, seems like it should be in page cache
488  // If a module buffer is set in the cache data, get it.
489  if (isset($data['module']) && is_array($data['module']))
490  {
491  // Iterate through the module positions and push them into the document buffer.
492  foreach ($data['module'] as $name => $contents)
493  {
494  $document->setBuffer($contents, 'module', $name);
495  }
496  }
497 
498  // Set cached headers.
499  if (isset($data['headers']) && $data['headers'])
500  {
501  foreach($data['headers'] as $header)
502  {
503  $app->setHeader($header['name'], $header['value']);
504  }
505  }
506 
507  // The following code searches for a token in the cached page and replaces it with the
508  // proper token.
509  if (isset($data['body']))
510  {
511  $token = JSession::getFormToken();
512  $search = '#<input type="hidden" name="[0-9a-f]{32}" value="1" />#';
513  $replacement = '<input type="hidden" name="' . $token . '" value="1" />';
514 
515  $data['body'] = preg_replace($search, $replacement, $data['body']);
516  $body = $data['body'];
517  }
518 
519  // Get the document body out of the cache.
520  return $body;
521  }
522 
523  /**
524  * Create workarounded data to be cached
525  *
526  * @param string $data Cached data
527  * @param array $options Array of options
528  *
529  * @return string Data to be cached
530  *
531  * @since 11.1
532  */
533  public static function setWorkarounds($data, $options = array())
534  {
535  $loptions = array(
536  'nopathway' => 0,
537  'nohead' => 0,
538  'nomodules' => 0,
539  'modulemode' => 0,
540  );
541 
542  if (isset($options['nopathway']))
543  {
544  $loptions['nopathway'] = $options['nopathway'];
545  }
546 
547  if (isset($options['nohead']))
548  {
549  $loptions['nohead'] = $options['nohead'];
550  }
551 
552  if (isset($options['nomodules']))
553  {
554  $loptions['nomodules'] = $options['nomodules'];
555  }
556 
557  if (isset($options['modulemode']))
558  {
559  $loptions['modulemode'] = $options['modulemode'];
560  }
561 
562  $app = JFactory::getApplication();
563  $document = JFactory::getDocument();
564 
565  if ($loptions['nomodules'] != 1)
566  {
567  // Get the modules buffer before component execution.
568  $buffer1 = $document->getBuffer();
569  if (!is_array($buffer1))
570  {
571  $buffer1 = array();
572  }
573 
574  // Make sure the module buffer is an array.
575  if (!isset($buffer1['module']) || !is_array($buffer1['module']))
576  {
577  $buffer1['module'] = array();
578  }
579  }
580 
581  // View body data
582  $cached['body'] = $data;
583 
584  // Document head data
585  if ($loptions['nohead'] != 1 && method_exists($document, 'getHeadData'))
586  {
587 
588  if ($loptions['modulemode'] == 1)
589  {
590  $headnow = $document->getHeadData();
591  $unset = array('title', 'description', 'link', 'links', 'metaTags');
592 
593  foreach ($unset as $un)
594  {
595  unset($headnow[$un]);
596  unset($options['headerbefore'][$un]);
597  }
598 
599  $cached['head'] = array();
600 
601  // Only store what this module has added
602  foreach ($headnow as $now => $value)
603  {
604  if (isset($options['headerbefore'][$now]))
605  {
606  // We have to serialize the content of the arrays because the may contain other arrays which is a notice in PHP 5.4 and newer
607  $nowvalue = array_map('serialize', $headnow[$now]);
608  $beforevalue = array_map('serialize', $options['headerbefore'][$now]);
609  $newvalue = array_diff_assoc($nowvalue, $beforevalue);
610  $newvalue = array_map('unserialize', $newvalue);
611  }
612  else
613  {
614  $newvalue = $headnow[$now];
615  }
616 
617  if (!empty($newvalue))
618  {
619  $cached['head'][$now] = $newvalue;
620  }
621  }
622 
623  }
624  else
625  {
626  $cached['head'] = $document->getHeadData();
627  }
628  }
629 
630  // Pathway data
631  if ($app->isSite() && $loptions['nopathway'] != 1)
632  {
633  $pathway = $app->getPathWay();
634  $cached['pathway'] = isset($data['pathway']) ? $data['pathway'] : $pathway->getPathway();
635  }
636 
637  if ($loptions['nomodules'] != 1)
638  {
639  // @todo Check if the following is needed, seems like it should be in page cache
640  // Get the module buffer after component execution.
641  $buffer2 = $document->getBuffer();
642  if (!is_array($buffer2))
643  {
644  $buffer2 = array();
645  }
646 
647  // Make sure the module buffer is an array.
648  if (!isset($buffer2['module']) || !is_array($buffer2['module']))
649  {
650  $buffer2['module'] = array();
651  }
652 
653  // Compare the second module buffer against the first buffer.
654  $cached['module'] = array_diff_assoc($buffer2['module'], $buffer1['module']);
655  }
656 
657  // Headers data
658  if (isset($options['headers']) && $options['headers'])
659  {
660  $cached['headers'] = $app->getHeaders();
661  }
662 
663  return $cached;
664  }
665 
666  /**
667  * Create safe id for cached data from url parameters set by plugins and framework
668  *
669  * @return string md5 encoded cacheid
670  *
671  * @since 11.1
672  */
673  public static function makeId()
674  {
675  $app = JFactory::getApplication();
676 
677  // Get url parameters set by plugins
678  if (!empty($app->registeredurlparams))
679  {
680  $registeredurlparams = $app->registeredurlparams;
681  }
682 
683  // Platform defaults
684  $registeredurlparams->format = 'WORD';
685  $registeredurlparams->option = 'WORD';
686  $registeredurlparams->view = 'WORD';
687  $registeredurlparams->layout = 'WORD';
688  $registeredurlparams->tpl = 'CMD';
689  $registeredurlparams->id = 'INT';
690 
691  $safeuriaddon = new stdClass;
692 
693  foreach ($registeredurlparams as $key => $value)
694  {
695  $safeuriaddon->$key = $app->input->get($key, null, $value);
696  }
697 
698  return md5(serialize($safeuriaddon));
699  }
700 
701  /**
702  * Add a directory where JCache should search for handlers. You may
703  * either pass a string or an array of directories.
704  *
705  * @param string $path A path to search.
706  *
707  * @return array An array with directory elements
708  *
709  * @since 11.1
710  */
711  public static function addIncludePath($path = '')
712  {
713  static $paths;
714 
715  if (!isset($paths))
716  {
717  $paths = array();
718  }
719  if (!empty($path) && !in_array($path, $paths))
720  {
721  jimport('joomla.filesystem.path');
722  array_unshift($paths, JPath::clean($path));
723  }
724  return $paths;
725  }
726 }