PluginProbe ʕ •ᴥ•ʔ
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization / 1.10.4
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization v1.10.4
1.19.8 1.19.7 1.19.6 1.19.5 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.11.0 1.12.0 1.13.0 1.14.0 1.15.0 1.15.1 1.15.2 1.15.3 1.16.0 1.16.1 1.16.2 1.16.3 1.16.4 1.16.5 1.16.6 1.16.7 1.16.8 1.17.0 1.17.6 1.17.7 1.17.8 1.17.9 1.18.0 1.18.1 1.18.2 1.18.3 1.18.4 1.18.5 1.18.6 1.18.7 1.18.8 1.18.9 1.19.0 1.19.1 1.19.2 1.19.3 1.19.4 1.3.19 1.3.20 1.4.0 1.4.1 1.5.0 1.5.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.16 1.5.17 1.5.18 1.5.19 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.7.0 1.7.1 1.8.0 1.8.1 1.8.3 1.9.0 1.9.1 1.9.2
nitropack / nitropack-sdk / NitroPack / SDK / Pagecache.php
nitropack / nitropack-sdk / NitroPack / SDK Last commit date
Api 2 years ago Integrations 3 years ago StorageDriver 4 years ago Url 2 years ago data 6 years ago Api.php 2 years ago Backlog.php 4 years ago BacklogReplayTimeoutException.php 4 years ago ChallengeProcessingException.php 4 years ago ChallengeVerificationException.php 4 years ago ConfigFetcherException.php 4 years ago Crypto.php 3 years ago Device.php 6 years ago DeviceType.php 6 years ago ElementRevision.php 3 years ago EmptyConfigException.php 6 years ago ExcludeEntry.php 2 years ago ExcludeOperationType.php 2 years ago FileHandle.php 5 years ago Filesystem.php 4 years ago HealthStatus.php 5 years ago IntegrationUrl.php 2 years ago NitroPack.php 2 years ago NoConfigException.php 5 years ago Pagecache.php 2 years ago PurgeType.php 3 years ago ServiceDownException.php 5 years ago StorageException.php 6 years ago VariationCookieException.php 5 years ago WebhookException.php 5 years ago Website.php 6 years ago
Pagecache.php
405 lines
1 <?php
2
3 namespace NitroPack\SDK;
4
5 use \NitroPack\Url\Url;
6
7 class Pagecache
8 {
9 protected $url;
10 protected $cookies;
11 protected $supportedCookies;
12 protected $dataDir;
13 protected $isAjax;
14 protected $referer;
15 protected $parent;
16 protected $Device;
17 protected $useCompression;
18 protected $useInvalidated;
19 private $urlPathVersion;
20 private $cookiesProvider;
21
22 public static function getUrlDir($dataDir, $url, $useInvalidated = false, $pathVersion = 1)
23 {
24 $safeUrl = str_replace(array('/', '?', ':', ';', '=', '&', '.', '--', '%', '~'), '-', $url);
25 if (defined('NITRO_DEBUG_MODE') && NITRO_DEBUG_MODE) {
26 $urlDir = $dataDir . "/" . $safeUrl;
27 } else {
28 switch ($pathVersion) {
29 case 2:
30 $urlDir = $dataDir . "/" . md5($url);
31 break;
32 default:
33 $urlDir = $dataDir . "/" . md5($safeUrl);
34 break;
35 }
36 }
37
38 if ($useInvalidated) {
39 return $urlDir . "_i";
40 } else {
41 return $urlDir;
42 }
43 }
44
45 public function __construct($url, $userAgent, $cookies = array(), $supportedCookies = array(), $isAjax = false, $referer = NULL)
46 {
47 $this->url = new Url($url);
48 $this->cookies = $cookies;
49 $this->supportedCookies = $supportedCookies;
50 $this->dataDir = NULL;
51 $this->isAjax = $isAjax;
52 $this->parent = NULL;
53 $this->Device = new Device($userAgent);
54 $this->useCompression = false;
55 $this->useInvalidated = false;
56 $this->cookiesProvider = NULL;
57 $this->setUrlPathVersion(1);
58 $this->setReferer($referer);
59 }
60
61 public function enableCompression()
62 {
63 $this->useCompression = true;
64 if ($this->parent) {
65 $this->parent->enableCompression();
66 }
67 }
68
69 public function disableCompression()
70 {
71 $this->useCompression = false;
72 if ($this->parent) {
73 $this->parent->disableCompression();
74 }
75 }
76
77 public function useInvalidated($useInvalidated)
78 {
79 $this->useInvalidated = $useInvalidated;
80 if ($this->parent) {
81 $this->parent->useInvalidated($useInvalidated);
82 }
83 }
84
85 public function getUseInvalidated()
86 {
87 return $this->useInvalidated;
88 }
89
90 public function setReferer($referer)
91 {
92 $this->referer = $referer ? new Url($referer) : NULL;
93 if ($this->referer) {
94 $this->parent = new Pagecache($this->referer->getNormalized(), $this->Device->getUserAgent(), $this->cookies, $this->supportedCookies);
95 $this->parent->setUrlPathVersion($this->urlPathVersion);
96 if ($this->dataDir) {
97 $this->parent->setDataDir(dirname($this->dataDir));
98 }
99 } else {
100 $this->parent = NULL;
101 }
102 }
103
104 public function setUrlPathVersion($version = 1)
105 {
106 $this->urlPathVersion = $version;
107 if ($this->parent) {
108 $this->parent->setUrlPathVersion($version);
109 }
110 }
111
112 public function getReferer()
113 {
114 return $this->referer;
115 }
116
117 public function getParent()
118 {
119 return $this->parent;
120 }
121
122 public function setDataDir($dir)
123 {
124 if ($dir === null) {
125 $this->dataDir = null;
126 } else {
127 $this->dataDir = $dir . "/" . $this->Device->getType();
128 }
129
130 if ($this->parent) {
131 $this->parent->setDataDir($dir);
132 }
133 }
134
135 public function setCookiesProvider($provider)
136 {
137 $this->cookiesProvider = $provider;
138 }
139
140 public function hasCache()
141 {
142 // If there is no cache for the parent we do not need to check cache existance for the current object, because we only serve AJAX cache for pages already cached by NitroPack
143 if ($this->parent && !$this->parent->hasCache()) return false;
144 if ($this->useInvalidated) {
145 $this->convertToStaleCache();
146 return Filesystem::fileExists($this->getCachefilePath("stale"));
147 } else {
148 return Filesystem::fileExists($this->getCachefilePath());
149 }
150 }
151
152 public function hasExpired($ttl = 86400, $cacheRevision = NULL)
153 {
154 // If the cache for the parent is expired we do not need to check cache for the current object, because we only serve AJAX cache for pages already cached by NitroPack
155 if ($this->parent && $this->parent->hasExpired($ttl, $cacheRevision)) return true;
156
157 if ($this->useInvalidated) {
158 $this->convertToStaleCache();
159 $cachefilePath = $this->getCachefilePath("stale");
160 $mtime = Filesystem::fileMtime(dirname($cachefilePath));
161 } else {
162 $cachefilePath = $this->getCachefilePath();
163 $mtime = Filesystem::fileMtime($cachefilePath);
164 }
165
166 $now = time();
167
168 if ($now - $mtime >= $ttl) {
169 return true;
170 } else {
171 try {
172 $headers = Filesystem::fileGetHeaders($cachefilePath);
173 if (!empty($headers["x-nitro-expires"]) && $now > (int)$headers["x-nitro-expires"]) {
174 return true;
175 }
176
177 if (
178 !$this->useInvalidated &&
179 !empty($headers["x-cache-ctime"]) &&
180 $now - (int)$headers["x-cache-ctime"] > $ttl
181 ) {
182 return true;
183 }
184
185 // Check if the revision has changed which makes cache file obsolete
186 if (!empty($headers["x-nitro-rev"]) && $cacheRevision && $cacheRevision != $headers["x-nitro-rev"]) {
187 return true;
188 }
189 } catch (\Exception $e) {
190 return true;
191 }
192 }
193 return false;
194 }
195
196 public function getRemainingTtl($ttl = 86400)
197 {
198 // If there is a parent cache file return its remaining TTL
199 if ($this->parent) return $this->parent->getRemainingTtl();
200
201 if ($this->useInvalidated) {
202 return 0;
203 } else {
204 $cachefilePath = $this->getCachefilePath();
205 }
206
207 $now = time();
208 try {
209 $headers = Filesystem::fileGetHeaders($cachefilePath);
210 if (!empty($headers["x-nitro-expires"])) {
211 $expireTime = (int)$headers["x-nitro-expires"];
212 } else if (!empty($headers["x-cache-ctime"])) {
213 $expireTime = (int)$headers["x-cache-ctime"] + $ttl;
214 } else {
215 $mtime = Filesystem::fileMtime($cachefilePath);
216 $expireTime = $mtime + $ttl;
217 }
218
219 return max($expireTime - $now, 0);
220 } catch (\Exception $e) {
221 return 0;
222 }
223
224 return 0;
225 }
226
227 public function setContent($content, $headers = NULL)
228 {
229 if (!$this->dataDir) return;
230
231 $filePath = $this->getCachefilePath();
232 if (Filesystem::createDir(dirname($filePath))) {
233 if (!Filesystem::filePutContents($filePath, $content, $this->headersFlatten($headers))) {
234 return false;
235 } else {
236 if ($headers && !empty($headers["x-cache-ctime"])) {
237 Filesystem::touch($filePath, (int)$headers["x-cache-ctime"]);
238 }
239 return $this->compress($filePath);
240 }
241 }
242 }
243
244 private function headersFlatten($headers)
245 {
246 // The headers from fileGetAll come as associative array
247 // They must be converted to an array of strings before being passed to filePutContents, which is what this function does
248 $headersFlat = array();
249 foreach ($headers as $name => $value) {
250 if (is_array($value)) {
251 foreach ($value as $subValue) {
252 $headersFlat[] = $name . ":" . $subValue;
253 }
254 } else {
255 $headersFlat[] = $name . ":" . $value;
256 }
257 }
258
259 return $headersFlat;
260 }
261
262 private function compress($filePath)
263 {
264 $fileInfo = Filesystem::fileGetAll($filePath);
265 $fileInfo->headers["Content-Encoding"] = "gzip";
266 $gzPath = $filePath . ".gz";
267 $res = Filesystem::filePutContents($gzPath, gzencode($fileInfo->content, 4), $this->headersFlatten($fileInfo->headers)); // save compressed version of the cache file
268
269 if ($res && $fileInfo->headers && !empty($fileInfo->headers["x-cache-ctime"])) {
270 Filesystem::touch($gzPath, (int)$fileInfo->headers["x-cache-ctime"]);
271 }
272
273 return $res;
274 }
275
276 public function readfile()
277 {
278 $cacheFileContent = $this->returnCacheFileContent();
279 $headers = $cacheFileContent[0];
280 $contents = $cacheFileContent[1];
281
282 foreach ($headers as $header) {
283 header($header['name'] . ': ' . $header['value'], false);
284 }
285
286 echo $contents;
287 }
288
289 public function returnCacheFileContent()
290 {
291 if ($this->useInvalidated) {
292 $this->convertToStaleCache();
293 $filePath = $this->getCachefilePath("stale");
294 } else {
295 $filePath = $this->getCachefilePath();
296 }
297 if ($this->canUseCompression() && (ini_get("zlib.output_compression") === "0" || ini_set("zlib.output_compression", "0") !== false)) {
298 $filePath .= ".gz";
299 }
300 $file = Filesystem::fileGetAll($filePath);
301 $returnHeaders = [];
302
303 foreach ($file->headers as $name => $value) {
304 if (is_array($value)) {
305 foreach ($value as $subVal) {
306 $returnHeaders[] = [
307 'name' => $name,
308 'value' => $subVal
309 ];
310 }
311 } else {
312 $returnHeaders[] = [
313 'name' => $name,
314 'value' => $value
315 ];
316 }
317 }
318
319 return [ $returnHeaders, $file->content ];
320 }
321
322 public function getFileContents()
323 {
324 if ($this->useInvalidated) {
325 $this->convertToStaleCache();
326 return Filesystem::fileGetContents($this->getCachefilePath("stale"));
327 }
328 return Filesystem::fileGetContents($this->getCachefilePath());
329 }
330
331 private function convertToStaleCache()
332 {
333 $staleFile = $this->getCachefilePath("stale");
334 if (!Filesystem::fileExists($staleFile)) {
335 $freshFile = $this->getCachefilePath();
336 if (Filesystem::fileExists($freshFile)) {
337 $fileInfo = Filesystem::fileGetAll($freshFile);
338 $newContent = str_replace("NITROPACK_STATE='FRESH'", "NITROPACK_STATE='STALE'", $fileInfo->content);
339 Filesystem::filePutContents($freshFile, $newContent, $this->headersFlatten($fileInfo->headers));
340 Filesystem::rename($freshFile, $staleFile);
341 Filesystem::deleteFile($freshFile . ".gz");
342 $this->compress($staleFile);
343 }
344 }
345 }
346
347 private function cookiePrefix()
348 {
349 $prefix = '';
350
351 $cookies = $this->cookiesProvider ? call_user_func($this->cookiesProvider) : $this->cookies;
352
353 ksort($cookies);
354
355 foreach ($cookies as $cookieName => $cookieValue) {
356 foreach ($this->supportedCookies as $cookie) {
357 if (preg_match('/' . NitroPack::wildcardToRegex($cookie) . '/', $cookieName)) {
358 $prefix .= $cookieName . '=' . $cookieValue . ';';
359 }
360 }
361 }
362
363 return substr(md5($prefix), 0, 16);
364 }
365
366 private function sslPrefix()
367 {
368 return $this->url->getScheme() == "https" ? "ssl-" : "";
369 }
370
371 private function ajaxPrefix()
372 {
373 return $this->isAjax && $this->parent ? "ajax-" . md5($this->url->getNormalized()) . "-" : "";
374 }
375
376 private function customCachePrefix()
377 {
378 $customCachePrefix = NitroPack::getCustomCachePrefix();
379 return $customCachePrefix ? $customCachePrefix . "-" : "";
380 }
381
382 private function isCompressionAllowed()
383 {
384 return isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false;
385 }
386
387 private function canUseCompression()
388 {
389 return $this->useCompression && $this->isCompressionAllowed() && !headers_sent();
390 }
391
392 public function nameOfCachefile()
393 {
394 return $this->customCachePrefix() . $this->ajaxPrefix() . $this->sslPrefix() . $this->cookiePrefix() . ".html";
395 }
396
397 public function getCachefilePath($suffix = "")
398 {
399 if ($suffix) $suffix = "." . $suffix;
400 if ($this->isAjax && $this->referer) {
401 return self::getUrlDir($this->dataDir, $this->referer->getNormalized(), $this->useInvalidated, $this->urlPathVersion) . "/" . $this->nameOfCachefile() . $suffix;
402 }
403 return self::getUrlDir($this->dataDir, $this->url->getNormalized(), $this->useInvalidated, $this->urlPathVersion) . "/" . $this->nameOfCachefile() . $suffix;
404 }
405 }