-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathObUtils.php
361 lines (332 loc) · 16.9 KB
/
ObUtils.php
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
<?php
namespace WebSharks\CometCache\Pro;
/*
* Calculated protocol; one of `http://` or `https://`.
*
* @since 150422 Rewrite.
*
* @type float One of `http://` or `https://`.
*/
$self->protocol = '';
/*
* Host token for this request.
*
* @since 150821 Improving multisite compat.
*
* @type string Host token for this request.
*/
$self->host_token = '';
/*
* Host base/dir tokens for this request.
*
* @since 150821 Improving multisite compat.
*
* @type string Host base/dir tokens for this request.
*/
$self->host_base_dir_tokens = '';
/*
* Calculated version salt; set by site configuration data.
*
* @since 150422 Rewrite.
*
* @type string|mixed Any scalar value does fine.
*/
$self->version_salt = '';
/*
* Relative cache path for the current request.
*
* @since 150422 Rewrite.
*
* @type string Cache path for the current request.
*/
$self->cache_path = '';
/*
* Absolute cache file path for the current request.
*
* @since 150422 Rewrite.
*
* @type string Absolute cache file path for the current request.
*/
$self->cache_file = '';
/*
* Relative 404 cache path for the current request.
*
* @since 150422 Rewrite.
*
* @type string 404 cache path for the current request.
*/
$self->cache_path_404 = '';
/*
* Absolute 404 cache file path for the current request.
*
* @since 150422 Rewrite.
*
* @type string Absolute 404 cache file path for the current request.
*/
$self->cache_file_404 = '';
/*
* Version salt followed by the current request location.
*
* @since 150422 Rewrite.
*
* @type string Version salt followed by the current request location.
*/
$self->salt_location = '';
/*
* Calculated max age; i.e., before expiration.
*
* @since 151002 Load average checks in pro version.
*
* @type integer Calculated max age; i.e., before expiration.
*/
$self->cache_max_age = 0;
/*
* Start output buffering (if applicable); or serve a cache file (if possible).
*
* @since 150422 Rewrite.
*
* @note This is a vital part of Comet Cache. This method serves existing (fresh) cache files.
* It is also responsible for beginning the process of collecting the output buffer.
*/
$self->maybeStartOutputBuffering = function () use ($self) {
if (strcasecmp(PHP_SAPI, 'cli') === 0) {
return $self->maybeSetDebugInfo(NC_DEBUG_PHP_SAPI_CLI);
}
if (empty($_SERVER['HTTP_HOST']) || !$self->hostToken()) {
return $self->maybeSetDebugInfo(NC_DEBUG_NO_SERVER_HTTP_HOST);
}
if (empty($_SERVER['REQUEST_URI'])) {
return $self->maybeSetDebugInfo(NC_DEBUG_NO_SERVER_REQUEST_URI);
}
if (defined('COMET_CACHE_ALLOWED') && !COMET_CACHE_ALLOWED) {
return $self->maybeSetDebugInfo(NC_DEBUG_COMET_CACHE_ALLOWED_CONSTANT);
}
if (isset($_SERVER['COMET_CACHE_ALLOWED']) && !$_SERVER['COMET_CACHE_ALLOWED']) {
return $self->maybeSetDebugInfo(NC_DEBUG_COMET_CACHE_ALLOWED_SERVER_VAR);
}
if (defined('DONOTCACHEPAGE')) {
return $self->maybeSetDebugInfo(NC_DEBUG_DONOTCACHEPAGE_CONSTANT);
}
if (isset($_SERVER['DONOTCACHEPAGE'])) {
return $self->maybeSetDebugInfo(NC_DEBUG_DONOTCACHEPAGE_SERVER_VAR);
}
if (isset($_GET[strtolower(SHORT_NAME).'AC']) && !filter_var($_GET[strtolower(SHORT_NAME).'AC'], FILTER_VALIDATE_BOOLEAN)) {
return $self->maybeSetDebugInfo(NC_DEBUG_AC_GET_VAR);
}
if ($self->isUncacheableRequestMethod()) {
return $self->maybeSetDebugInfo(NC_DEBUG_UNCACHEABLE_REQUEST);
}
if (isset($_SERVER['SERVER_ADDR']) && $self->currentIp() === $_SERVER['SERVER_ADDR']) {
if ((!IS_PRO || !$self->isAutoCacheEngine()) && !$self->isLocalhost()) {
return $self->maybeSetDebugInfo(NC_DEBUG_SELF_SERVE_REQUEST);
}
}
if (!COMET_CACHE_FEEDS_ENABLE && $self->isFeed()) {
return $self->maybeSetDebugInfo(NC_DEBUG_FEED_REQUEST);
}
if (preg_match('/\/(?:wp\-[^\/]+|xmlrpc)\.php(?:[?]|$)/i', $_SERVER['REQUEST_URI'])) {
return $self->maybeSetDebugInfo(NC_DEBUG_WP_SYSTEMATICS);
}
if (is_admin() || preg_match('/\/wp-admin(?:[\/?]|$)/i', $_SERVER['REQUEST_URI'])) {
return $self->maybeSetDebugInfo(NC_DEBUG_WP_ADMIN);
}
if (is_multisite() && preg_match('/\/files(?:[\/?]|$)/i', $_SERVER['REQUEST_URI'])) {
return $self->maybeSetDebugInfo(NC_DEBUG_MS_FILES);
}
if ((!IS_PRO || !COMET_CACHE_WHEN_LOGGED_IN) && $self->isLikeUserLoggedIn()) {
return $self->maybeSetDebugInfo(NC_DEBUG_IS_LIKE_LOGGED_IN_USER);
}
if (!COMET_CACHE_GET_REQUESTS && $self->requestContainsUncacheableQueryVars()) {
return $self->maybeSetDebugInfo(NC_DEBUG_GET_REQUEST_QUERIES);
}
if (!empty($_REQUEST['preview'])) {
return $self->maybeSetDebugInfo(NC_DEBUG_PREVIEW);
}
if (COMET_CACHE_EXCLUDE_URIS && preg_match(COMET_CACHE_EXCLUDE_URIS, $_SERVER['REQUEST_URI'])) {
return $self->maybeSetDebugInfo(NC_DEBUG_EXCLUDED_URIS);
}
if (COMET_CACHE_EXCLUDE_AGENTS && !empty($_SERVER['HTTP_USER_AGENT']) && (!IS_PRO || !$self->isAutoCacheEngine())) {
if (preg_match(COMET_CACHE_EXCLUDE_AGENTS, $_SERVER['HTTP_USER_AGENT'])) {
return $self->maybeSetDebugInfo(NC_DEBUG_EXCLUDED_AGENTS);
}
}
if (COMET_CACHE_EXCLUDE_REFS && !empty($_REQUEST['_wp_http_referer'])) {
if (preg_match(COMET_CACHE_EXCLUDE_REFS, stripslashes($_REQUEST['_wp_http_referer']))) {
return $self->maybeSetDebugInfo(NC_DEBUG_EXCLUDED_REFS);
}
}
if (COMET_CACHE_EXCLUDE_REFS && !empty($_SERVER['HTTP_REFERER'])) {
if (preg_match(COMET_CACHE_EXCLUDE_REFS, $_SERVER['HTTP_REFERER'])) {
return $self->maybeSetDebugInfo(NC_DEBUG_EXCLUDED_REFS);
}
}
$self->protocol = $self->isSsl() ? 'https://' : 'http://';
$self->host_token = $self->hostToken();
$self->host_base_dir_tokens = $self->hostBaseDirTokens();
$self->version_salt = ''; // Initialize the version salt.
/*[pro strip-from="lite"]*/ // Fill the version salt in pro version.
$self->version_salt = COMET_CACHE_VERSION_SALT; // Initialize the version salt. /*[/pro]*/
$self->version_salt = $self->applyFilters(get_class($self).'__version_salt', $self->version_salt);
$self->version_salt = $self->applyFilters(GLOBAL_NS.'_version_salt', $self->version_salt);
$self->cache_path = $self->buildCachePath($self->protocol.$self->host_token.$_SERVER['REQUEST_URI'], '', $self->version_salt);
$self->cache_file = COMET_CACHE_DIR.'/'.$self->cache_path; // Not considering a user cache. That's done in the postload phase.
$self->cache_path_404 = $self->buildCachePath($self->protocol.$self->host_token.rtrim($self->host_base_dir_tokens, '/').'/'.COMET_CACHE_404_CACHE_FILENAME);
$self->cache_file_404 = COMET_CACHE_DIR.'/'.$self->cache_path_404; // Not considering a user cache at all here--ever.
$self->salt_location = ltrim($self->version_salt.' '.$self->protocol.$self->host_token.$_SERVER['REQUEST_URI']);
$self->cache_max_age = strtotime('-'.COMET_CACHE_MAX_AGE);
/*[pro strip-from="lite"]*/ // Pro version allows for load average checks.
if (COMET_CACHE_MAX_AGE_DISABLE_IF_LOAD_AVERAGE_IS_GTE && ($load_averages = $self->sysLoadAverages())) {
if (max($load_averages) >= COMET_CACHE_MAX_AGE_DISABLE_IF_LOAD_AVERAGE_IS_GTE) {
$self->cache_max_age = 0; // No expiration time.
}
} /*[/pro]*/
if (IS_PRO && COMET_CACHE_WHEN_LOGGED_IN === 'postload' && $self->isLikeUserLoggedIn()) {
$self->postload['when_logged_in'] = true; // Enable postload check.
} elseif (is_file($self->cache_file) && (!$self->cache_max_age || filemtime($self->cache_file) >= $self->cache_max_age)) {
list($headers, $cache) = explode('<!--headers-->', file_get_contents($self->cache_file), 2);
$headers_list = $self->headersList();
foreach (unserialize($headers) as $_header) {
if (!in_array($_header, $headers_list, true) && stripos($_header, 'Last-Modified:') !== 0) {
header($_header); // Only cacheable/safe headers are stored in the cache.
}
}
unset($_header); // Just a little housekeeping.
if (COMET_CACHE_DEBUGGING_ENABLE && $self->isHtmlXmlDoc($cache)) {
$total_time = number_format(microtime(true) - $self->timer, 5, '.', '');
$cache .= "\n".'<!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->';
// translators: This string is actually NOT translatable because the `__()` function is not available at this point in the processing.
$cache .= "\n".'<!-- '.htmlspecialchars(sprintf(__('%1$s fully functional :-) Cache file served for (%2$s) in %3$s seconds, on: %4$s.', SLUG_TD), NAME, $self->salt_location, $total_time, date('M jS, Y @ g:i a T'))).' -->';
}
exit($cache); // Exit with cache contents.
} else {
ob_start(array($self, 'outputBufferCallbackHandler'));
}
return; // Return value not applicable.
};
/*
* Output buffer handler; i.e. the cache file generator.
*
* @note We CANNOT depend on any WP functionality here; it will cause problems.
* Anything we need from WP should be saved in the postload phase as a scalar value.
*
* @since 150422 Rewrite.
*
* @param string $buffer The buffer from {@link \ob_start()}.
* @param int $phase A set of bitmask flags.
*
* @throws \Exception If unable to handle output buffering for any reason.
*
* @return string|bool The output buffer, or `FALSE` to indicate no change.
*
* @attaches-to {@link \ob_start()}
*/
$self->outputBufferCallbackHandler = function ($buffer, $phase) use ($self) {
if (!($phase & PHP_OUTPUT_HANDLER_END)) {
throw new \Exception(sprintf(__('Unexpected OB phase: `%1$s`.', SLUG_TD), $phase));
}
AdvCacheBackCompat::zenCacheConstants();
$cache = trim((string) $buffer);
if (!isset($cache[0])) {
return false; // Don't cache an empty buffer.
}
if (!isset($GLOBALS[GLOBAL_NS.'_shutdown_flag'])) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_EARLY_BUFFER_TERMINATION);
}
if (defined('COMET_CACHE_ALLOWED') && !COMET_CACHE_ALLOWED) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_COMET_CACHE_ALLOWED_CONSTANT);
}
if (isset($_SERVER['COMET_CACHE_ALLOWED']) && !$_SERVER['COMET_CACHE_ALLOWED']) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_COMET_CACHE_ALLOWED_SERVER_VAR);
}
if (defined('DONOTCACHEPAGE')) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_DONOTCACHEPAGE_CONSTANT);
}
if (isset($_SERVER['DONOTCACHEPAGE'])) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_DONOTCACHEPAGE_SERVER_VAR);
}
if ((!IS_PRO || !COMET_CACHE_WHEN_LOGGED_IN) && $self->is_user_logged_in) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_IS_LOGGED_IN_USER);
}
if ((!IS_PRO || !COMET_CACHE_WHEN_LOGGED_IN) && $self->isLikeUserLoggedIn()) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_IS_LIKE_LOGGED_IN_USER);
}
if (!COMET_CACHE_CACHE_NONCE_VALUES && preg_match('/\b(?:_wpnonce|akismet_comment_nonce)\b/', $cache)) {
if (IS_PRO && COMET_CACHE_WHEN_LOGGED_IN && $self->isLikeUserLoggedIn()) {
if (!COMET_CACHE_CACHE_NONCE_VALUES_WHEN_LOGGED_IN) {
return (boolean)$self->maybeSetDebugInfo(NC_DEBUG_IS_LOGGED_IN_USER_NONCE);
}
} else { // Use the default debug notice for nonce conflicts.
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_PAGE_CONTAINS_NONCE);
} // An nonce makes the page dynamic; i.e., NOT cache compatible.
}
if ($self->is_404 && !COMET_CACHE_CACHE_404_REQUESTS) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_404_REQUEST);
}
if (stripos($cache, '<body id="error-page">') !== false) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_WP_ERROR_PAGE);
}
if (!$self->functionIsPossible('http_response_code')) {
if (stripos($cache, '<title>database error</title>') !== false) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_WP_ERROR_PAGE);
}
}
if (!$self->hasACacheableContentType()) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_UNCACHEABLE_CONTENT_TYPE);
}
if (!$self->hasACacheableStatus()) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_UNCACHEABLE_STATUS);
}
if ($self->is_maintenance) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_MAINTENANCE_PLUGIN);
}
if ($self->functionIsPossible('zlib_get_coding_type') && zlib_get_coding_type()
&& (!($zlib_oc = ini_get('zlib.output_compression')) || !filter_var($zlib_oc, FILTER_VALIDATE_BOOLEAN))) {
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_OB_ZLIB_CODING_TYPE);
}
# Lock the cache directory while writes take place here.
$cache_lock = $self->cacheLock(); // Lock cache directory.
# Construct a temp file for atomic cache writes.
$cache_file_tmp = $self->addTmpSuffix($self->cache_file);
# Cache directory checks. The cache file directory is created here if necessary.
if (!is_dir(COMET_CACHE_DIR) && mkdir(COMET_CACHE_DIR, 0775, true) && !is_file(COMET_CACHE_DIR.'/.htaccess')) {
file_put_contents(COMET_CACHE_DIR.'/.htaccess', $self->htaccess_deny);
}
if (!is_dir($cache_file_dir = dirname($self->cache_file))) {
$cache_file_dir_writable = mkdir($cache_file_dir, 0775, true);
}
if (empty($cache_file_dir_writable) && !is_writable($cache_file_dir)) {
throw new \Exception(sprintf(__('Cache directory not writable. %1$s needs this directory please: `%2$s`. Set permissions to `755` or higher; `777` might be needed in some cases.', SLUG_TD), NAME, $cache_file_dir));
}
# This is where a new 404 request might be detected for the first time.
if ($self->is_404 && is_file($self->cache_file_404)) {
if (!(symlink($self->cache_file_404, $cache_file_tmp) && rename($cache_file_tmp, $self->cache_file))) {
throw new \Exception(sprintf(__('Unable to create symlink: `%1$s` » `%2$s`. Possible permissions issue (or race condition), please check your cache directory: `%3$s`.', SLUG_TD), $self->cache_file, $self->cache_file_404, COMET_CACHE_DIR));
}
$self->cacheUnlock($cache_lock); // Release.
return (boolean) $self->maybeSetDebugInfo(NC_DEBUG_1ST_TIME_404_SYMLINK);
}
/* ------- Otherwise, we need to construct & store a new cache file. ----------------------------------------------- */
/*[pro strip-from="lite"]*/
$cache = $self->maybeCompressHtml($cache); // Possible HTML compression.
/*[/pro]*/
if (COMET_CACHE_DEBUGGING_ENABLE && $self->isHtmlXmlDoc($cache)) {
$total_time = number_format(microtime(true) - $self->timer, 5, '.', ''); // Based on the original timer.
$cache .= "\n".'<!-- '.htmlspecialchars(sprintf(__('%1$s file path: %2$s', SLUG_TD), NAME, str_replace(WP_CONTENT_DIR, '', $self->is_404 ? $self->cache_file_404 : $self->cache_file))).' -->';
$cache .= "\n".'<!-- '.htmlspecialchars(sprintf(__('%1$s file built for (%2$s%3$s) in %4$s seconds, on: %5$s.', SLUG_TD), NAME, $self->is_404 ? '404 [error document]' : $self->salt_location, (IS_PRO && COMET_CACHE_WHEN_LOGGED_IN && $self->user_token ? '; '.sprintf(__('user token: %1$s', SLUG_TD), $self->user_token) : ''), $total_time, date('M jS, Y @ g:i a T'))).' -->';
$cache .= "\n".'<!-- '.htmlspecialchars(sprintf(__('This %1$s file will auto-expire (and be rebuilt) on: %2$s (based on your configured expiration time).', SLUG_TD), NAME, date('M jS, Y @ g:i a T', strtotime('+'.COMET_CACHE_MAX_AGE)))).' -->';
}
if ($self->is_404) {
if (file_put_contents($cache_file_tmp, serialize($self->cacheableHeadersList()).'<!--headers-->'.$cache) && rename($cache_file_tmp, $self->cache_file_404)) {
if (!(symlink($self->cache_file_404, $cache_file_tmp) && rename($cache_file_tmp, $self->cache_file))) {
throw new \Exception(sprintf(__('Unable to create symlink: `%1$s` » `%2$s`. Possible permissions issue (or race condition), please check your cache directory: `%3$s`.', SLUG_TD), $self->cache_file, $self->cache_file_404, COMET_CACHE_DIR));
}
$self->cacheUnlock($cache_lock); // Release.
return $cache; // Return the newly built cache; with possible debug information also.
}
} elseif (file_put_contents($cache_file_tmp, serialize($self->cacheableHeadersList()).'<!--headers-->'.$cache) && rename($cache_file_tmp, $self->cache_file)) {
$self->cacheUnlock($cache_lock); // Release.
return $cache; // Return the newly built cache; with possible debug information also.
}
@unlink($cache_file_tmp); // Clean this up (if it exists); and throw an exception with information for the site owner.
throw new \Exception(sprintf(__('%1$s: failed to write cache file for: `%2$s`; possible permissions issue (or race condition), please check your cache directory: `%3$s`.', SLUG_TD), NAME, $_SERVER['REQUEST_URI'], COMET_CACHE_DIR));
};