Passed
Push — release-2.1 ( 493a4c...4017f8 )
by Mathias
09:59 queued 10s
created

FileBased::writeFile()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 17
nop 2
dl 0
loc 27
rs 9.3888
c 3
b 0
f 0
nc 5
1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines https://www.simplemachines.org
8
 * @copyright 2020 Simple Machines and individual contributors
9
 * @license https://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 RC3
12
 */
13
14
namespace SMF\Cache\APIs;
15
16
use SMF\Cache\CacheApi;
17
use SMF\Cache\CacheApiInterface;
18
19
if (!defined('SMF'))
20
	die('No direct access...');
21
22
/**
23
 * Our Cache API class
24
 *
25
 * @package CacheAPI
26
 */
27
class FileBased extends CacheApi implements CacheApiInterface
28
{
29
	/**
30
	 * @var string The path to the current $cachedir directory.
31
	 */
32
	private $cachedir = null;
33
34
	/**
35
	 * {@inheritDoc}
36
	 */
37
	public function __construct()
38
	{
39
		parent::__construct();
40
41
		// Set our default cachedir.
42
		$this->setCachedir();
43
	}
44
45
	/**
46
	 * {@inheritDoc}
47
	 */
48
	public function isSupported($test = false)
49
	{
50
		$supported = is_writable($this->cachedir);
51
52
		if ($test)
53
			return $supported;
54
55
		return parent::isSupported() && $supported;
56
	}
57
58
	private function readFile($file)
59
	{
60
		if (($fp = fopen($file, 'rb')) !== false)
61
		{
62
			if (!flock($fp, LOCK_SH))
63
			{
64
				fclose($fp);
65
				return false;
66
			}
67
			$string = '';
68
			while (!feof($fp))
69
				$string .= fread($fp, 8192);
70
71
			flock($fp, LOCK_UN);
72
			fclose($fp);
73
74
			return $string;
75
		}
76
77
		return false;
78
	}
79
80
	private function writeFile($file, $string)
81
	{
82
		if (($fp = fopen($file, 'cb')) !== false)
83
		{
84
			if (!flock($fp, LOCK_EX))
85
			{
86
				fclose($fp);
87
				return false;
88
			}
89
			ftruncate($fp, 0);
90
			$bytes = 0;
91
			$pieces = str_split($string, 8192);
92
			foreach ($pieces as $piece)
93
			{
94
				if (($val = fwrite($fp, $piece, 8192)) !== false)
95
					$bytes += $val;
96
				else
97
					return false;
98
			}
99
			fflush($fp);
100
			flock($fp, LOCK_UN);
101
			fclose($fp);
102
103
			return $bytes;
104
		}
105
106
		return false;
107
	}
108
109
	/**
110
	 * {@inheritDoc}
111
	 */
112
	public function connect()
113
	{
114
		return true;
115
	}
116
117
	/**
118
	 * {@inheritDoc}
119
	 */
120
	public function getData($key, $ttl = null)
121
	{
122
		$file = sprintf('%s/data_%s.cache',
123
			$this->cachedir,
124
			$this->prefix . strtr($key, ':/', '-_')
125
		);
126
127
		// SMF Data returns $value and $expired.  $expired has a unix timestamp of when this expires.
128
		if (file_exists($file) && ($raw = $this->readFile($file)) !== false)
129
		{
130
			if (($value = smf_json_decode($raw, true, false)) !== array() && $value['expiration'] >= time())
131
				return $value['value'];
132
			else
133
				@unlink($file);
134
		}
135
136
		return null;
137
	}
138
139
	/**
140
	 * {@inheritDoc}
141
	 */
142
	public function putData($key, $value, $ttl = null)
143
	{
144
		$file = sprintf('%s/data_%s.cache',
145
			$this->cachedir,
146
			$this->prefix . strtr($key, ':/', '-_')
147
		);
148
		$ttl = $ttl !== null ? $ttl : $this->ttl;
149
150
		if ($value === null)
151
			@unlink($file);
152
		else
153
		{
154
			$cache_data = json_encode(
155
				array(
156
					'expiration' => time() + $ttl,
157
					'value' => $value
158
				),
159
				JSON_NUMERIC_CHECK
160
			);
161
162
			// Write out the cache file, check that the cache write was successful; all the data must be written
163
			// If it fails due to low diskspace, or other, remove the cache file
164
			if ($this->writeFile($file, $cache_data) !== strlen($cache_data))
165
			{
166
				@unlink($file);
167
				return false;
168
			}
169
			else
170
				return true;
171
		}
172
	}
173
174
	/**
175
	 * {@inheritDoc}
176
	 */
177
	public function cleanCache($type = '')
178
	{
179
		// No directory = no game.
180
		if (!is_dir($this->cachedir))
181
			return;
182
183
		// Remove the files in SMF's own disk cache, if any
184
		$files = new GlobIterator($this->cachedir . '/' . $type . '*.cache', FilesystemIterator::NEW_CURRENT_AND_KEY);
0 ignored issues
show
Bug introduced by
The type SMF\Cache\APIs\FilesystemIterator was not found. Did you mean FilesystemIterator? If so, make sure to prefix the type with \.
Loading history...
Bug introduced by
The type SMF\Cache\APIs\GlobIterator was not found. Did you mean GlobIterator? If so, make sure to prefix the type with \.
Loading history...
185
186
		foreach ($files as $file => $info)
187
			unlink($this->cachedir . '/' . $file);
188
189
		// Make this invalid.
190
		$this->invalidateCache();
191
192
		return true;
193
	}
194
195
	/**
196
	 * {@inheritDoc}
197
	 */
198
	public function invalidateCache()
199
	{
200
		// We don't worry about $cachedir here, since the key is based on the real $cachedir.
201
		parent::invalidateCache();
202
203
		// Since SMF is file based, be sure to clear the statcache.
204
		clearstatcache();
205
206
		return true;
207
	}
208
209
	/**
210
	 * {@inheritDoc}
211
	 */
212
	public function cacheSettings(array &$config_vars)
213
	{
214
		global $context, $txt;
215
216
		$class_name = $this->getImplementationClassKeyName();
217
		$class_name_txt_key = strtolower($class_name);
218
219
		$config_vars[] = $txt['cache_'. $class_name_txt_key .'_settings'];
220
		$config_vars[] = array('cachedir', $txt['cachedir'], 'file', 'text', 36, 'cache_cachedir');
221
222
		if (!isset($context['settings_post_javascript']))
223
			$context['settings_post_javascript'] = '';
224
225
		$context['settings_post_javascript'] .= '
226
			$("#cache_accelerator").change(function (e) {
227
				var cache_type = e.currentTarget.value;
228
				$("#cachedir").prop("disabled", cache_type != "'. $class_name .'");
229
			});';
230
	}
231
232
	/**
233
	 * Sets the $cachedir or uses the SMF default $cachedir..
234
	 *
235
	 * @access public
236
	 * @param string $dir A valid path
237
	 * @return boolean If this was successful or not.
238
	 */
239
	public function setCachedir($dir = null)
240
	{
241
		global $cachedir;
242
243
		// If its invalid, use SMF's.
244
		if (is_null($dir) || !is_writable($dir))
245
			$this->cachedir = $cachedir;
246
247
		else
248
			$this->cachedir = $dir;
249
	}
250
251
	/**
252
	 * Gets the current $cachedir.
253
	 *
254
	 * @access public
255
	 * @return string the value of $ttl.
256
	 */
257
	public function getCachedir()
258
	{
259
		return $this->cachedir;
260
	}
261
262
	/**
263
	 * {@inheritDoc}
264
	 */
265
	public function getVersion()
266
	{
267
		return SMF_VERSION;
268
	}
269
}
270
271
?>