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-2021 XOOPS Project (https://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: * File Storage engine for cache
23: *
24: *
25: * PHP versions 4 and 5
26: *
27: * CakePHP(tm) : Rapid Development Framework <https://www.cakephp.org/>
28: * Copyright 2005-2008, Cake Software Foundation, Inc.
29: * 1785 E. Sahara Avenue, Suite 490-204
30: * Las Vegas, Nevada 89104
31: *
32: * Licensed under The MIT License
33: * Redistributions of files must retain the above copyright notice.
34: *
35: * @filesource
36: * @copyright Copyright 2005-2008, Cake Software Foundation, Inc.
37: * @link https://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
38: * @package cake
39: * @subpackage cake.cake.libs.cache
40: * @since CakePHP(tm) v 1.2.0.4933
41: * @modifiedby $LastChangedBy$
42: * @lastmodified $Date$
43: * @license https://www.opensource.org/licenses/mit-license.php The MIT License
44: */
45:
46: /**
47: * File Storage engine for cache
48: *
49: * @todo use the File and Folder classes (if it's not a too big performance hit)
50: * @package cake
51: * @subpackage cake.cake.libs.cache
52: */
53: class XoopsCacheFile extends XoopsCacheEngine
54: {
55: /**
56: * Instance of File class
57: *
58: * @var object
59: * @access private
60: */
61: private $file;
62:
63: /**
64: * settings
65: * path = absolute path to cache directory, default => CACHE
66: * prefix = string prefix for filename, default => xoops_
67: * lock = enable file locking on write, default => false
68: * serialize = serialize the data, default => false
69: *
70: * @var array
71: * @see CacheEngine::__defaults
72: * @access public
73: */
74: public $settings = array();
75:
76: /**
77: * Set to true if FileEngine::init(); and FileEngine::active(); do not fail.
78: *
79: * @var boolean
80: * @access private
81: */
82: private $active = false;
83:
84: /**
85: * True unless FileEngine::active(); fails
86: *
87: * @var boolean
88: * @access private
89: */
90: private $init = true;
91:
92: /**
93: * Initialize the Cache Engine
94: *
95: * Called automatically by the cache frontend
96: * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array());
97: *
98: * @param array $settings array of setting for the engine
99: * @return boolean True if the engine has been successfully initialized, false if not
100: * @access public
101: */
102: public function init($settings = array())
103: {
104: parent::init($settings);
105: $defaults = array(
106: 'path' => XOOPS_VAR_PATH . '/caches/xoops_cache',
107: 'extension' => '.php',
108: 'prefix' => 'xoops_',
109: 'lock' => false,
110: 'serialize' => false,
111: 'duration' => 31556926);
112: $this->settings = array_merge($defaults, $this->settings);
113: if (!isset($this->file)) {
114: XoopsLoad::load('XoopsFile');
115: $this->file = XoopsFile::getHandler('file', $this->settings['path'] . '/index.php', true);
116: }
117: $this->settings['path'] = $this->file->folder->cd($this->settings['path']);
118: if (empty($this->settings['path'])) {
119: return false;
120: }
121:
122: return $this->active();
123: }
124:
125: /**
126: * Garbage collection. Permanently remove all expired and deleted data
127: *
128: * @return boolean True if garbage collection was successful, false on failure
129: * @access public
130: */
131: public function gc()
132: {
133: return $this->clear(true);
134: }
135:
136: /**
137: * Write data for key into cache
138: *
139: * @param string $key Identifier for the data
140: * @param mixed $data Data to be cached
141: * @param mixed $duration How long to cache the data, in seconds
142: * @return boolean True if the data was successfully cached, false on failure
143: * @access public
144: */
145: public function write($key, $data = null, $duration = null)
146: {
147: if (!isset($data) || !$this->init) {
148: return false;
149: }
150:
151: if ($this->setKey($key) === false) {
152: return false;
153: }
154:
155: if ($duration == null) {
156: $duration = $this->settings['duration'];
157: }
158: $windows = false;
159: $lineBreak = "\n";
160:
161: if (substr(PHP_OS, 0, 3) === 'WIN') {
162: $lineBreak = "\r\n";
163: $windows = true;
164: }
165: $expires = time() + $duration;
166: if (!empty($this->settings['serialize'])) {
167: if ($windows) {
168: $data = str_replace('\\', '\\\\\\\\', serialize($data));
169: } else {
170: $data = serialize($data);
171: }
172: $contents = $expires . $lineBreak . $data . $lineBreak;
173: } else {
174: $contents = $expires . $lineBreak . 'return ' . var_export($data, true) . ';' . $lineBreak;
175: }
176:
177: if ($this->settings['lock']) {
178: $this->file->lock = true;
179: }
180: $success = $this->file->write($contents);
181: $this->file->close();
182:
183: return $success;
184: }
185:
186: /**
187: * Read a key from the cache
188: *
189: * @param string $key Identifier for the data
190: * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
191: * @access public
192: */
193: public function read($key)
194: {
195: if ($this->setKey($key) === false || !$this->init) {
196: return false;
197: }
198: if ($this->settings['lock']) {
199: $this->file->lock = true;
200: }
201: $cachetime = $this->file->read(11);
202:
203: if ($cachetime !== false && (int)$cachetime < time()) {
204: $this->file->close();
205: $this->file->delete();
206:
207: return false;
208: }
209:
210: $data = $this->file->read(true);
211: if (!empty($data) && !empty($this->settings['serialize'])) {
212: $data = stripslashes($data);
213: // $data = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $data);
214: $data = preg_replace_callback('!s:(\d+):"(.*?)";!s', function ($m) { return 's:' . strlen($m[2]) . ':"' . $m[2] . '";'; }, $data);
215: $data = unserialize($data, array('allowed_classes' => false));
216: if (is_array($data)) {
217: XoopsLoad::load('XoopsUtility');
218: $data = XoopsUtility::recursive('stripslashes', $data);
219: }
220: } elseif ($data && empty($this->settings['serialize'])) {
221: $data = eval($data);
222: }
223: $this->file->close();
224:
225: return $data;
226: }
227:
228: /**
229: * Delete a key from the cache
230: *
231: * @param string $key Identifier for the data
232: * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
233: * @access public
234: */
235: public function delete($key)
236: {
237: if ($this->setKey($key) === false || !$this->init) {
238: return false;
239: }
240:
241: return $this->file->delete();
242: }
243:
244: /**
245: * Delete all values from the cache
246: *
247: * @param boolean $check Optional - only delete expired cache items
248: * @return boolean True if the cache was successfully cleared, false otherwise
249: * @access public
250: */
251: public function clear($check = true)
252: {
253: if (!$this->init) {
254: return false;
255: }
256: $dir = dir($this->settings['path']);
257: if ($check) {
258: $now = time();
259: $threshold = $now - $this->settings['duration'];
260: }
261: while (($entry = $dir->read()) !== false) {
262: if ($this->setKey(str_replace($this->settings['prefix'], '', $entry)) === false) {
263: continue;
264: }
265: if ($check) {
266: $mtime = $this->file->lastChange();
267:
268: if ($mtime === false || $mtime > $threshold) {
269: continue;
270: }
271:
272: $expires = $this->file->read(11);
273: $this->file->close();
274:
275: if ($expires > $now) {
276: continue;
277: }
278: }
279: $this->file->delete();
280: }
281: $dir->close();
282:
283: return true;
284: }
285:
286: /**
287: * Get absolute file for a given key
288: *
289: * @param string $key The key
290: * @return mixed Absolute cache file for the given key or false if erroneous
291: * @access private
292: */
293: private function setKey($key)
294: {
295: $this->file->folder->cd($this->settings['path']);
296: $this->file->name = $this->settings['prefix'] . $key . $this->settings['extension'];
297: $this->file->handle = null;
298: $this->file->info = null;
299: if (!$this->file->folder->inPath($this->file->pwd(), true)) {
300: return false;
301: }
302: return null;
303: }
304:
305: /**
306: * Determine is cache directory is writable
307: *
308: * @return boolean
309: * @access private
310: */
311: private function active()
312: {
313: if (!$this->active && $this->init && !is_writable($this->settings['path'])) {
314: $this->init = false;
315: trigger_error(sprintf('%s is not writable', $this->settings['path']), E_USER_WARNING);
316: } else {
317: $this->active = true;
318: }
319:
320: return true;
321: }
322: }
323: