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 (http://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  $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  (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 int
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: