1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
File: qa-include/Q2A/Storage/FileCache.php
Description: File-based driver for caching system.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
/**
* Caches data (typically from database queries) to the filesystem.
*/
class Q2A_Storage_FileCache
{
private $enabled = false;
private $error;
private $cacheDir;
/**
* Creates a new FileCache instance and checks we can write to the cache directory.
* @param array $config Configuration data, including cache storage directory.
*/
public function __construct($config)
{
if (isset($config['dir'])) {
$this->cacheDir = realpath($config['dir']);
if (!is_writable($this->cacheDir)) {
$this->error = qa_lang_html_sub('admin/caching_dir_error', $this->cacheDir);
} elseif (strpos($this->cacheDir, realpath($_SERVER['DOCUMENT_ROOT'])) === 0 || strpos($this->cacheDir, realpath(QA_BASE_DIR)) === 0) {
// check the folder is outside the public root - checks against server root and Q2A root, in order to handle symbolic links
$this->error = qa_lang_html_sub('admin/caching_dir_public', $this->cacheDir);
}
} else {
$this->error = qa_lang_html('admin/caching_dir_missing');
}
$this->enabled = empty($this->error);
}
/**
* Get the cached data for the supplied key.
* @param string $key The unique cache identifier.
* @return string The cached data, or null otherwise.
*/
public function get($key)
{
$file = $this->getFilename($key);
if (is_readable($file)) {
$lines = file($file, FILE_IGNORE_NEW_LINES);
$actualKey = array_shift($lines);
// double check this is the correct data
if ($key === $actualKey) {
$expiry = array_shift($lines);
if (is_numeric($expiry) && time() < $expiry) {
return implode("\n", $lines);
}
}
}
return null;
}
/**
* Store a string (usually serialized data) in the cache along with the key and expiry time.
* @param string $key The unique cache identifier.
* @param string $str The data to cache (usually a serialized string).
* @param int $ttl Number of minutes for which to cache the data.
* @return bool Whether the file was successfully cached.
*/
public function set($key, $str, $ttl)
{
$success = false;
$ttl = (int) $ttl;
if ($this->enabled && $ttl > 0) {
$file = $this->getFilename($key);
$dir = dirname($file);
$expiry = time() + ($ttl * 60);
$cache = $key . "\n" . $expiry . "\n" . $str;
if (is_dir($dir) || mkdir($dir, 0777, true)) {
$success = file_put_contents($file, $cache) !== false;
}
}
return $success;
}
/**
* Whether caching is available.
* @return bool
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* Get the last error.
* @return string
*/
public function getError()
{
return $this->error;
}
/**
* Generates filename for cache key, of the form `1/23/123abc`
* @param string $key The unique cache key.
* @return string
*/
private function getFilename($key)
{
$filename = sha1($key);
return $this->cacheDir . '/' . substr($filename, 0, 1) . '/' . substr($filename, 1, 2) . '/' . $filename;
}
}