1: <?php
2: 3: 4: 5: 6: 7: 8:
9:
10:
11: namespace Rundiz\SimpleCache\Drivers;
12:
13: use Rundiz\SimpleCache\SimpleCacheInterface;
14:
15: 16: 17: 18: 19:
20: class FileSystem implements SimpleCacheInterface
21: {
22:
23:
24: 25: 26: 27:
28: protected $cache_path;
29:
30: 31: 32: 33:
34: protected $umask;
35:
36:
37: 38: 39: 40: 41: 42: 43:
44: public function __construct($cache_path = '', $umask = 0002)
45: {
46: if (!is_int($umask)) {
47: $umask = 0002;
48: }
49: $this->umask = $umask;
50:
51: if ($cache_path == null) {
52: $cache_path = dirname(dirname(dirname(__DIR__))).'/cache';
53: } else {
54: $cache_path = rtrim($cache_path, '/');
55: }
56: $this->cache_path = $cache_path;
57:
58:
59: if (!$this->createFolderIfNotExists($this->cache_path)) {
60: throw new \Exception(sprintf('The cache directory "%s" does not exists and could not be created.', $this->cache_path));
61: }
62:
63:
64: if (!is_writable($this->cache_path)) {
65: throw new \Exception(sprintf('The directory "%s" is not writable.', $this->cache_path));
66: }
67:
68: $this->cache_path = realpath($this->cache_path);
69: }
70:
71:
72: 73: 74:
75: public function clear()
76: {
77: $result = $this->deleteCacheSubfolderRecursively($this->cache_path);
78: if ($result === false) {
79: return false;
80: }
81: return true;
82: }
83:
84:
85: 86: 87: 88: 89: 90:
91: private function createFolderIfNotExists($cache_path)
92: {
93: if (!is_dir($cache_path)) {
94: if (false === @mkdir($cache_path, 0777 & (~$this->umask), true) && !is_dir($cache_path)) {
95: return false;
96: }
97: }
98: return true;
99: }
100:
101:
102: 103: 104:
105: public function delete($id)
106: {
107: $filename = $this->cache_path.DIRECTORY_SEPARATOR.$this->idToPathAndFileName($id);
108:
109: if (!is_file($filename) || (is_file($filename) && !is_writable($filename))) {
110:
111: unset($filename);
112: return false;
113: }
114:
115: $delete_cache_file_result = unlink($filename);
116: if ($delete_cache_file_result === false) {
117: unset($delete_cache_file_result, $filename);
118: return false;
119: }
120: unset($delete_cache_file_result);
121:
122: $filepath = pathinfo($filename, PATHINFO_DIRNAME);
123: if (is_dir($filepath)) {
124: $this->deleteCacheSubfoldersIfEmpty($filepath);
125: }
126: unset($filepath);
127:
128: return true;
129: }
130:
131:
132: 133: 134: 135: 136: 137:
138: private function deleteCacheSubfoldersIfEmpty($filepath)
139: {
140: if ($this->cache_path == $filepath) {
141:
142: return true;
143: }
144:
145: $filepath_exp = explode(DIRECTORY_SEPARATOR, $filepath);
146: if (is_array($filepath_exp)) {
147: for ($i = count($filepath_exp)-1; $i >= 0 ; $i--) {
148: $dir = implode(DIRECTORY_SEPARATOR, $filepath_exp);
149: $iterator = new \FilesystemIterator($dir);
150: $isDirEmpty = !$iterator->valid();
151: unset($iterator);
152:
153: if ($this->cache_path == $dir) {
154:
155: return true;
156: } elseif (is_dir($dir) && !is_writable($dir)) {
157:
158: return false;
159: } elseif (is_dir($dir) && $isDirEmpty !== true) {
160:
161: return true;
162: } elseif (is_dir($dir) && $isDirEmpty === true && is_writable($dir) && $this->cache_path != $dir) {
163: rmdir($dir);
164: unset($filepath_exp[$i]);
165: } else {
166: return true;
167: }
168: }
169: unset($i);
170: }
171: unset($filepath_exp);
172:
173: return true;
174: }
175:
176:
177: 178: 179: 180: 181: 182:
183: private function deleteCacheSubfolderRecursively($dir)
184: {
185: if (is_dir($dir)) {
186: $objects = scandir($dir);
187: foreach ($objects as $object) {
188: if ($dir . DIRECTORY_SEPARATOR . $object == $this->cache_path) {
189: return false;
190: } elseif ($object != '.' && $object != '..') {
191: if (is_writable($dir . DIRECTORY_SEPARATOR . $object)) {
192: if (is_dir($dir . DIRECTORY_SEPARATOR . $object)) {
193: $this->deleteCacheSubfolderRecursively($dir . DIRECTORY_SEPARATOR . $object);
194: } else {
195: unlink($dir . DIRECTORY_SEPARATOR . $object);
196: }
197: } else {
198: return false;
199: }
200: }
201: }
202:
203: if ($dir !== $this->cache_path) {
204: rmdir($dir);
205: }
206: }
207: }
208:
209:
210: 211: 212:
213: public function get($id)
214: {
215: $filename = $this->cache_path.DIRECTORY_SEPARATOR.$this->idToPathAndFileName($id);
216:
217: if (!is_file($filename)) {
218: return false;
219: }
220:
221: $fp = fopen($filename, 'r');
222: if ($fp === false) {
223:
224: unset($filename, $fp);
225: return false;
226: }
227:
228: $content = fread($fp, filesize($filename));
229: fclose($fp);
230: unset($filename, $fp);
231:
232:
233: $lifetime = 0;
234: $data_type = null;
235: $data = null;
236:
237: $content_exp = explode("\n", $content);
238: preg_match('/data: (.+?):enddata/ius', $content, $match_data);
239: unset($content);
240: if (is_array($content_exp)) {
241: foreach ($content_exp as $content_line) {
242: if (strpos($content_line, 'expire: ') !== false) {
243: preg_match('/^expire: (.+?)$/iu', $content_line, $match_lifetime);
244: } elseif (strpos($content_line, 'data_type: ') !== false) {
245: preg_match('/^data_type: (.+?)$/iu', $content_line, $match_data_type);
246: }
247: }
248: unset($content_line);
249:
250: if (isset($match_lifetime[1])) {
251: $lifetime = $match_lifetime[1];
252: }
253: if (isset($match_data_type[1])) {
254: $data_type = $match_data_type[1];
255: }
256: if (isset($match_data[1])) {
257: $data = unserialize($match_data[1]);
258: }
259: unset($match_data, $match_data_type, $match_lifetime);
260: }
261: unset($content_exp);
262:
263: if ($lifetime < time()) {
264: unset($data, $data_type, $lifetime);
265: return false;
266: }
267:
268: return $data;
269: }
270:
271:
272: 273: 274: 275: 276: 277: 278:
279: private function idToPathAndFileName($id)
280: {
281: if (strpos($id, '.') !== false) {
282:
283: $id_exp = explode('.', $id);
284: $path_array = array();
285: if (is_array($id_exp)) {
286: foreach ($id_exp as $id_path) {
287: $path_array[] = mb_substr($this->sanitizeFileName($id_path), 0, 255);
288: }
289: unset($id_path);
290: }
291: $id_to_path = implode(DIRECTORY_SEPARATOR, $path_array).DIRECTORY_SEPARATOR;
292: $id_to_path .= mb_substr(md5($id), 0, 255);
293: unset($id_exp, $path_array);
294: } else {
295: $id_to_path = mb_substr(md5($id), 0, 255);
296: }
297:
298: return $id_to_path.'.php';
299: }
300:
301:
302: 303: 304: 305: 306: 307: 308: 309:
310: private function sanitizeFileName($string, $force_lowercase = false)
311: {
312: $strip = array(
313: "~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "=", "+", "[", "{", "]",
314: "}", "\\", "|", ";", ":", "\"", "'", "‘", "’", "“", "”", "–", "—",
315: "—", "–", ",", "<", ".", ">", "/", "?"
316: );
317: $clean = trim(str_replace($strip, "", strip_tags($string)));
318: $clean = preg_replace('/\s+/', "-", $clean);
319: $clean = preg_replace("/[^a-zA-Z0-9\-_]/", "", $clean);
320:
321: if ($force_lowercase === true) {
322: if (function_exists('mb_strtolower')) {
323: $output = mb_strtolower($clean);
324: } else {
325: $output = strtolower($clean);
326: }
327: } else {
328: $output = $clean;
329: }
330:
331: unset($clean, $strip);
332: return $output;
333: }
334:
335:
336: 337: 338:
339: public function save($id, $data, $lifetime = 60)
340: {
341: if (!is_int($lifetime) || is_int($lifetime) && $lifetime <= 0) {
342: $lifetime = 60;
343: }
344:
345: $filepath = pathinfo($this->cache_path.DIRECTORY_SEPARATOR.$this->idToPathAndFileName($id), PATHINFO_DIRNAME);
346:
347: if (!$this->createFolderIfNotExists($filepath)) {
348: return false;
349: }
350:
351: if (!is_writable($filepath)) {
352: return false;
353: }
354:
355: $tmpFile = tempnam($filepath, 'tmp');
356: @chmod($tmpFile, 0666 & (~$this->umask));
357:
358:
359: $cache_content = '<?php'."\n";
360: $cache_content .= '/**'."\n\n\n";
361: $cache_content .= 'expire: '.(time()+$lifetime)."\n";
362: $cache_content .= 'data_type: '.gettype($data)."\n";
363: $cache_content .= 'data: '.serialize($data)."\n".':enddata'."\n";
364: $cache_content .= "\n\n\n".'*/';
365:
366: if (file_put_contents($tmpFile, $cache_content) !== false) {
367: if (@rename($tmpFile, $this->cache_path.DIRECTORY_SEPARATOR.$this->idToPathAndFileName($id))) {
368: $output = true;
369: } else {
370: $output = false;
371: }
372: @unlink($tmpFile);
373: } else {
374: $output = false;
375: }
376:
377: unset($cache_content, $filepath, $tmpFile);
378: return $output;
379: }
380:
381:
382: }
383: