PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/joomla/cache/cache.php

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