1: <?php
2: /**
3: * Cache engine For XOOPS
4: *
5: * You may not change or alter any portion of this comment or credits
6: * of supporting developers from this source code or any supporting source code
7: * which is considered copyrighted (c) material of the original comment or credit authors.
8: * This program is distributed in the hope that it will be useful,
9: * but WITHOUT ANY WARRANTY; without even the implied warranty of
10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11: *
12: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
13: * @license GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14: * @package class
15: * @subpackage cache
16: * @since 2.3.0
17: * @author Taiwen Jiang <phppp@users.sourceforge.net>
18: */
19: defined('XOOPS_ROOT_PATH') || exit('Restricted access');
20:
21: /**
22: * Caching for CakePHP.
23: *
24: * @package cake
25: * @subpackage cake.cake.libs
26: */
27: class XoopsCache
28: {
29: /**
30: * Cache engine to use
31: *
32: * @var object
33: * @access protected
34: */
35:
36: protected $engine;
37:
38: /**
39: * Cache configuration stack
40: *
41: * @var array
42: * @access private
43: */
44: private $configs = array();
45:
46: /**
47: * Holds name of the current configuration being used
48: *
49: * @var array
50: * @access private
51: */
52: private $name;
53:
54: /**
55: * XoopsCache::__construct()
56: */
57: public function __construct()
58: {
59: }
60:
61: /**
62: * Returns a singleton instance
63: *
64: * @return object
65: * @access public
66: */
67: public static function getInstance()
68: {
69: static $instance;
70: if (!isset($instance)) {
71: $class = __CLASS__;
72: $instance = new $class();
73: }
74:
75: return $instance;
76: }
77:
78: /**
79: * Tries to find and include a file for a cache engine and returns object instance
80: *
81: * @param string $name Name of the engine
82: * @return mixed $engine object or null
83: * @access private
84: */
85: private function loadEngine($name)
86: {
87: if (!class_exists('XoopsCache' . ucfirst($name))) {
88: if (file_exists($file = __DIR__ . '/' . strtolower($name) . '.php')) {
89: include $file;
90: } else {
91: trigger_error('File :' . $file . ' not found in file : ' . __FILE__ . ' at line: ' . __LINE__, E_USER_WARNING);
92:
93: return false;
94: }
95: }
96:
97: return true;
98: }
99:
100: /**
101: * Set the cache configuration to use
102: *
103: * @param string|array $name Name of the configuration
104: * @param array $settings Optional associative array of settings passed to the engine
105: * @return array|false (engine, settings) on success, false on failure
106: * @access public
107: */
108: public function config($name = 'default', $settings = array())
109: {
110: $_this = XoopsCache::getInstance();
111: if (is_array($name)) {
112: extract($name);
113: }
114:
115: if (isset($_this->configs[$name])) {
116: $settings = array_merge($_this->configs[$name], $settings);
117: } elseif (!empty($settings)) {
118: $_this->configs[$name] = $settings;
119: } elseif ($_this->configs !== null && isset($_this->configs[$_this->name])) {
120: $name = $_this->name;
121: $settings = $_this->configs[$_this->name];
122: } else {
123: $name = 'default';
124: if (!empty($_this->configs['default'])) {
125: $settings = $_this->configs['default'];
126: } else {
127: $settings = array(
128: 'engine' => 'file');
129: }
130: }
131: $engine = 'file';
132: if (!empty($settings['engine'])) {
133: $engine = $settings['engine'];
134: }
135:
136: if ($name !== $_this->name) {
137: if ($_this->engine($engine, $settings) === false) {
138: trigger_error("Cache Engine {$engine} is not set", E_USER_WARNING);
139:
140: return false;
141: }
142: $_this->name = $name;
143: $_this->configs[$name] = $_this->settings($engine);
144: }
145:
146: $settings = $_this->configs[$name];
147:
148: return compact('engine', 'settings');
149: }
150:
151: /**
152: * Set the cache engine to use or modify settings for one instance
153: *
154: * @param string $name Name of the engine (without 'Engine')
155: * @param array $settings Optional associative array of settings passed to the engine
156: * @return boolean True on success, false on failure
157: * @access public
158: */
159: public function engine($name = 'file', $settings = array())
160: {
161: if (!$name) {
162: return false;
163: }
164:
165: $cacheClass = 'XoopsCache' . ucfirst($name);
166: $_this = XoopsCache::getInstance();
167: if (!isset($_this->engine[$name])) {
168: if ($_this->loadEngine($name) === false) {
169: trigger_error("Cache Engine {$name} is not loaded", E_USER_WARNING);
170:
171: return false;
172: }
173: $_this->engine[$name] = new $cacheClass();
174: }
175:
176: if ($_this->engine[$name]->init($settings)) {
177: if (time() % $_this->engine[$name]->settings['probability'] == 0) {
178: $_this->engine[$name]->gc();
179: }
180:
181: return true;
182: }
183: $_this->engine[$name] = null;
184: trigger_error("Cache Engine {$name} is not initialized", E_USER_WARNING);
185:
186: return false;
187: }
188:
189: /**
190: * Garbage collection
191: *
192: * Permanently remove all expired and deleted data
193: *
194: * @access public
195: */
196: public function gc()
197: {
198: $_this = XoopsCache::getInstance();
199: $config = $_this->config();
200: extract($config);
201: $_this->engine[$engine]->gc();
202: }
203:
204: /**
205: * Write data for key into cache
206: *
207: * @param string $key Identifier for the data
208: * @param mixed $value Data to be cached - anything except a resource
209: * @param mixed $duration Optional - string configuration name OR how long to cache the data, either in seconds or a
210: * string that can be parsed by the strtotime() function OR array('config' => 'default', 'duration' => '3600')
211: * @return boolean True if the data was successfully cached, false on failure
212: * @access public
213: */
214: public static function write($key, $value, $duration = null)
215: {
216: $key = substr(md5(XOOPS_URL), 0, 8) . '_' . $key;
217: $_this = XoopsCache::getInstance();
218: $config = null;
219: if (is_array($duration)) {
220: extract($duration);
221: } elseif (isset($_this->configs[$duration])) {
222: $config = $duration;
223: $duration = null;
224: }
225: $config = $_this->config($config);
226:
227: if (!is_array($config)) {
228: return null;
229: }
230: extract($config);
231:
232: if (!$_this->isInitialized($engine)) {
233: trigger_error('Cache write not initialized: ' . $engine);
234:
235: return false;
236: }
237:
238: if (!$key = $_this->key($key)) {
239: return false;
240: }
241:
242: if (is_resource($value)) {
243: return false;
244: }
245:
246: if (!$duration) {
247: $duration = $settings['duration'];
248: }
249: $duration = is_numeric($duration) ? (int)$duration : strtotime($duration) - time();
250:
251: if ($duration < 1) {
252: return false;
253: }
254: $_this->engine[$engine]->init($settings);
255: $success = $_this->engine[$engine]->write($key, $value, $duration);
256:
257: return $success;
258: }
259:
260: /**
261: * Read a key from the cache
262: *
263: * @param string $key Identifier for the data
264: * @param string|array $config name of the configuration to use
265: * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
266: * @access public
267: */
268: public static function read($key, $config = null)
269: {
270: $key = substr(md5(XOOPS_URL), 0, 8) . '_' . $key;
271: $_this = XoopsCache::getInstance();
272: $config = $_this->config($config);
273:
274: if (!is_array($config)) {
275: return null;
276: }
277:
278: extract($config);
279:
280: if (!$_this->isInitialized($engine)) {
281: return false;
282: }
283: if (!$key = $_this->key($key)) {
284: return false;
285: }
286: $_this->engine[$engine]->init($settings);
287: $success = $_this->engine[$engine]->read($key);
288:
289: return $success;
290: }
291:
292: /**
293: * Delete a key from the cache
294: *
295: * @param string $key Identifier for the data
296: * @param string $config name of the configuration to use
297: * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
298: * @access public
299: */
300: public static function delete($key, $config = null)
301: {
302: $key = substr(md5(XOOPS_URL), 0, 8) . '_' . $key;
303: $_this = XoopsCache::getInstance();
304:
305: $config = $_this->config($config);
306: extract($config);
307:
308: if (!$_this->isInitialized($engine)) {
309: return false;
310: }
311:
312: if (!$key = $_this->key($key)) {
313: return false;
314: }
315:
316: $_this->engine[$engine]->init($settings);
317: $success = $_this->engine[$engine]->delete($key);
318:
319: return $success;
320: }
321:
322: /**
323: * Delete all keys from the cache
324: *
325: * @param boolean $check if true will check expiration, otherwise delete all
326: * @param string $config name of the configuration to use
327: * @return boolean True if the cache was successfully cleared, false otherwise
328: * @access public
329: */
330: public function clear($check = false, $config = null)
331: {
332: $_this = XoopsCache::getInstance();
333: $config = $_this->config($config);
334: extract($config);
335:
336: if (!$_this->isInitialized($engine)) {
337: return false;
338: }
339: $success = $_this->engine[$engine]->clear($check);
340: $_this->engine[$engine]->init($settings);
341:
342: return $success;
343: }
344:
345: /**
346: * Check if Cache has initialized a working storage engine
347: *
348: * @param string $engine Name of the engine
349: * @return bool
350: * @internal param string $configs Name of the configuration setting
351: * @access public
352: */
353: public function isInitialized($engine = null)
354: {
355: $_this = XoopsCache::getInstance();
356: if (!$engine && isset($_this->configs[$_this->name]['engine'])) {
357: $engine = $_this->configs[$_this->name]['engine'];
358: }
359:
360: return isset($_this->engine[$engine]);
361: }
362:
363: /**
364: * Return the settings for current cache engine
365: *
366: * @param string $engine Name of the engine
367: * @return array list of settings for this engine
368: * @access public
369: */
370: public function settings($engine = null)
371: {
372: $_this = XoopsCache::getInstance();
373: if (!$engine && isset($_this->configs[$_this->name]['engine'])) {
374: $engine = $_this->configs[$_this->name]['engine'];
375: }
376: if (isset($_this->engine[$engine]) && null !== $_this->engine[$engine]) {
377: return $_this->engine[$engine]->settings();
378: }
379:
380: return array();
381: }
382:
383: /**
384: * generates a safe key
385: *
386: * @param string $key the key passed over
387: * @return mixed string $key or false
388: * @access private
389: */
390: public function key($key)
391: {
392: if (empty($key)) {
393: return false;
394: }
395: $key = str_replace(array('/', '.'), '_', (string)$key);
396:
397: return $key;
398: }
399: }
400:
401: /**
402: * Abstract class for storage engine for caching
403: *
404: * @package core
405: * @subpackage cache
406: */
407: class XoopsCacheEngine
408: {
409: /**
410: * settings of current engine instance
411: *
412: * @var array
413: * @access public
414: */
415: public $settings;
416:
417: /**
418: * Iitialize the cache engine
419: *
420: * Called automatically by the cache frontend
421: *
422: * @param array $settings Associative array of parameters for the engine
423: * @return boolean True if the engine has been successfully initialized, false if not
424: * @access public
425: */
426: public function init($settings = array())
427: {
428: $this->settings = array_merge(array(
429: 'duration' => 31556926,
430: 'probability' => 100), $settings);
431:
432: return true;
433: }
434:
435: /**
436: * Garbage collection
437: *
438: * Permanently remove all expired and deleted data
439: *
440: * @access public
441: */
442: public function gc()
443: {
444: }
445:
446: /**
447: * Write value for a key into cache
448: *
449: * @param string $key Identifier for the data
450: * @param mixed $value Data to be cached
451: * @param mixed $duration How long to cache the data, in seconds
452: * @return boolean True if the data was successfully cached, false on failure
453: * @access public
454: */
455: public function write($key, $value, $duration = null)
456: {
457: trigger_error(sprintf(__('Method write() not implemented in %s', true), get_class($this)), E_USER_ERROR);
458: }
459:
460: /**
461: * Read a key from the cache
462: *
463: * @param string $key Identifier for the data
464: * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
465: * @access public
466: */
467: public function read($key)
468: {
469: trigger_error(sprintf(__('Method read() not implemented in %s', true), get_class($this)), E_USER_ERROR);
470: }
471:
472: /**
473: * Delete a key from the cache
474: *
475: * @param string $key Identifier for the data
476: * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
477: * @access public
478: */
479: public function delete($key)
480: {
481: }
482:
483: /**
484: * Delete all keys from the cache
485: *
486: * @param boolean $check if true will check expiration, otherwise delete all
487: * @return boolean True if the cache was successfully cleared, false otherwise
488: * @access public
489: */
490: public function clear($check)
491: {
492: }
493:
494: /**
495: * Cache Engine settings
496: *
497: * @return array settings
498: * @access public
499: */
500: public function settings()
501: {
502: return $this->settings;
503: }
504: }
505: