Passed
Push — release-2.1 ( f28109...493a4c )
by Mathias
06:40
created

FileBased::setCachedir()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 10
rs 10
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
	/**
59
	 * {@inheritDoc}
60
	 */
61
	public function connect()
62
	{
63
		return true;
64
	}
65
66
	/**
67
	 * {@inheritDoc}
68
	 */
69
	public function getData($key, $ttl = null)
70
	{
71
		$key = $this->prefix . strtr($key, ':/', '-_');
72
		$cachedir = $this->cachedir;
73
74
		// SMF Data returns $value and $expired.  $expired has a unix timestamp of when this expires.
75
		if (file_exists($cachedir . '/data_' . $key . '.php') && filesize($cachedir . '/data_' . $key . '.php') > 10)
76
		{
77
			// Work around Zend's opcode caching (PHP 5.5+), they would cache older files for a couple of seconds
78
			// causing newer files to take effect a while later.
79
			if (function_exists('opcache_invalidate'))
80
				opcache_invalidate($cachedir . '/data_' . $key . '.php', true);
81
82
			if (function_exists('apc_delete_file'))
83
				@apc_delete_file($cachedir . '/data_' . $key . '.php');
84
85
			// php will cache file_exists et all, we can't 100% depend on its results so proceed with caution
86
			@include($cachedir . '/data_' . $key . '.php');
87
			if (!empty($expired) && isset($value))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $expired seems to never exist and therefore empty should always be true.
Loading history...
Comprehensibility Best Practice introduced by
The variable $value seems to never exist and therefore isset should always be false.
Loading history...
88
			{
89
				@unlink($cachedir . '/data_' . $key . '.php');
90
				unset($value);
91
			}
92
		}
93
94
		return !empty($value) ? $value : null;
95
	}
96
97
	/**
98
	 * {@inheritDoc}
99
	 */
100
	public function putData($key, $value, $ttl = null)
101
	{
102
		$key = $this->prefix . strtr($key, ':/', '-_');
103
		$cachedir = $this->cachedir;
104
		$ttl = $ttl !== null ? $ttl : $this->ttl;
105
106
		// Work around Zend's opcode caching (PHP 5.5+), they would cache older files for a couple of seconds
107
		// causing newer files to take effect a while later.
108
		if (function_exists('opcache_invalidate'))
109
			opcache_invalidate($cachedir . '/data_' . $key . '.php', true);
110
111
		if (function_exists('apc_delete_file'))
112
			@apc_delete_file($cachedir . '/data_' . $key . '.php');
113
114
		// Otherwise custom cache?
115
		if ($value === null)
116
			@unlink($cachedir . '/data_' . $key . '.php');
117
118
		else
119
		{
120
			$cache_data = '<' . '?' . 'php if (!defined(\'SMF\')) die; if (' . (time() + $ttl) . ' < time()) $expired = true; else{$expired = false; $value = \'' . addcslashes($value, "\0" . '\\\'') . '\';}' . '?' . '>';
121
122
			// Write out the cache file, check that the cache write was successful; all the data must be written
123
			// If it fails due to low diskspace, or other, remove the cache file
124
			$fileSize = file_put_contents($cachedir . '/data_' . $key . '.php', $cache_data, LOCK_EX);
125
			if ($fileSize !== strlen($cache_data))
126
			{
127
				@unlink($cachedir . '/data_' . $key . '.php');
128
129
				return false;
130
			}
131
132
			else
133
				return true;
134
		}
135
	}
136
137
	/**
138
	 * {@inheritDoc}
139
	 */
140
	public function cleanCache($type = '')
141
	{
142
		$cachedir = $this->cachedir;
143
144
		// No directory = no game.
145
		if (!is_dir($cachedir))
146
			return false;
147
148
		// Remove the files in SMF's own disk cache, if any
149
		$dh = opendir($cachedir);
150
		while ($file = readdir($dh))
0 ignored issues
show
Bug introduced by
It seems like $dh can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

150
		while ($file = readdir(/** @scrutinizer ignore-type */ $dh))
Loading history...
151
		{
152
			if ($file != '.' && $file != '..' && $file != 'index.php' && $file != '.htaccess' && (!$type || substr($file, 0, strlen($type)) == $type))
153
				@unlink($cachedir . '/' . $file);
154
		}
155
		closedir($dh);
0 ignored issues
show
Bug introduced by
It seems like $dh can also be of type false; however, parameter $dir_handle of closedir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

155
		closedir(/** @scrutinizer ignore-type */ $dh);
Loading history...
156
157
		// Make this invalid.
158
		$this->invalidateCache();
159
160
		return true;
161
	}
162
163
	/**
164
	 * {@inheritDoc}
165
	 */
166
	public function invalidateCache()
167
	{
168
		// We don't worry about $cachedir here, since the key is based on the real $cachedir.
169
		parent::invalidateCache();
170
171
		// Since SMF is file based, be sure to clear the statcache.
172
		clearstatcache();
173
174
		return true;
175
	}
176
177
	/**
178
	 * {@inheritDoc}
179
	 */
180
	public function cacheSettings(array &$config_vars)
181
	{
182
		global $context, $txt;
183
184
		$class_name = $this->getImplementationClassKeyName();
185
		$class_name_txt_key = strtolower($class_name);
186
187
		$config_vars[] = $txt['cache_'. $class_name_txt_key .'_settings'];
188
		$config_vars[] = array('cachedir', $txt['cachedir'], 'file', 'text', 36, 'cache_cachedir');
189
190
		if (!isset($context['settings_post_javascript']))
191
			$context['settings_post_javascript'] = '';
192
193
		$context['settings_post_javascript'] .= '
194
			$("#cache_accelerator").change(function (e) {
195
				var cache_type = e.currentTarget.value;
196
				$("#cachedir").prop("disabled", cache_type != "'. $class_name .'");
197
			});';
198
	}
199
200
	/**
201
	 * Sets the $cachedir or uses the SMF default $cachedir..
202
	 *
203
	 * @access public
204
	 * @param string $dir A valid path
205
	 * @return boolean If this was successful or not.
206
	 */
207
	public function setCachedir($dir = null)
208
	{
209
		global $cachedir;
210
211
		// If its invalid, use SMF's.
212
		if (is_null($dir) || !is_writable($dir))
213
			$this->cachedir = $cachedir;
214
215
		else
216
			$this->cachedir = $dir;
217
	}
218
219
	/**
220
	 * Gets the current $cachedir.
221
	 *
222
	 * @access public
223
	 * @return string the value of $ttl.
224
	 */
225
	public function getCachedir()
226
	{
227
		return $this->cachedir;
228
	}
229
230
	/**
231
	 * {@inheritDoc}
232
	 */
233
	public function getVersion()
234
	{
235
		return SMF_VERSION;
236
	}
237
}
238
239
?>