PluginProbe ʕ •ᴥ•ʔ
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization / 1.6.0
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization v1.6.0
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 3 years ago Integrations 3 years ago StorageDriver 4 years ago Url 3 years ago data 6 years ago Api.php 3 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 FileHandle.php 5 years ago Filesystem.php 4 years ago HealthStatus.php 5 years ago IntegrationUrl.php 6 years ago NitroPack.php 3 years ago NoConfigException.php 5 years ago Pagecache.php 3 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
355 lines
1 <?php
2 namespace NitroPack\SDK;
3
4 use \NitroPack\Url\Url;
5
6 class Pagecache {
7 protected $url;
8 protected $cookies;
9 protected $supportedCookies;
10 protected $dataDir;
11 protected $isAjax;
12 protected $referer;
13 protected $parent;
14 protected $Device;
15 protected $useCompression;
16 protected $useInvalidated;
17 private $urlPathVersion;
18 private $cookiesProvider;
19
20 public static function getUrlDir($dataDir, $url, $useInvalidated = false, $pathVersion = 1) {
21 $safeUrl = str_replace(array('/','?',':',';','=','&','.','--','%','~'),'-', $url);
22 if (defined('NITRO_DEBUG_MODE') && NITRO_DEBUG_MODE) {
23 $urlDir = $dataDir . "/" . $safeUrl;
24 } else {
25 switch ($pathVersion) {
26 case 2:
27 $urlDir = $dataDir . "/" . md5($url);
28 break;
29 default:
30 $urlDir = $dataDir . "/" . md5($safeUrl);
31 break;
32 }
33 }
34
35 if ($useInvalidated) {
36 return $urlDir . "_i";
37 } else {
38 return $urlDir;
39 }
40 }
41
42 public function __construct($url, $userAgent, $cookies = array(), $supportedCookies = array(), $isAjax = false, $referer = NULL) {
43 $this->url = new Url($url);
44 $this->cookies = $cookies;
45 $this->supportedCookies = $supportedCookies;
46 $this->dataDir = NULL;
47 $this->isAjax = $isAjax;
48 $this->parent = NULL;
49 $this->Device = new Device($userAgent);
50 $this->useCompression = false;
51 $this->useInvalidated = false;
52 $this->cookiesProvider = NULL;
53 $this->setUrlPathVersion(1);
54 $this->setReferer($referer);
55 }
56
57 public function enableCompression() {
58 $this->useCompression = true;
59 if ($this->parent) {
60 $this->parent->enableCompression();
61 }
62 }
63
64 public function disableCompression() {
65 $this->useCompression = false;
66 if ($this->parent) {
67 $this->parent->disableCompression();
68 }
69 }
70
71 public function useInvalidated($useInvalidated) {
72 $this->useInvalidated = $useInvalidated;
73 if ($this->parent) {
74 $this->parent->useInvalidated($useInvalidated);
75 }
76 }
77
78 public function getUseInvalidated() {
79 return $this->useInvalidated;
80 }
81
82 public function setReferer($referer) {
83 $this->referer = $referer ? new Url($referer) : NULL;
84 if ($this->referer) {
85 $this->parent = new Pagecache($this->referer->getNormalized(), $this->Device->getUserAgent(), $this->cookies, $this->supportedCookies);
86 $this->parent->setUrlPathVersion($this->urlPathVersion);
87 if ($this->dataDir) {
88 $this->parent->setDataDir(dirname($this->dataDir));
89 }
90 } else {
91 $this->parent = NULL;
92 }
93 }
94
95 public function setUrlPathVersion($version = 1) {
96 $this->urlPathVersion = $version;
97 if ($this->parent) {
98 $this->parent->setUrlPathVersion($version);
99 }
100 }
101
102 public function getReferer() {
103 return $this->referer;
104 }
105
106 public function getParent() {
107 return $this->parent;
108 }
109
110 public function setDataDir($dir) {
111 if ($dir === null) {
112 $this->dataDir = null;
113 } else {
114 $this->dataDir = $dir . "/" . $this->Device->getType();
115 }
116
117 if ($this->parent) {
118 $this->parent->setDataDir($dir);
119 }
120 }
121
122 public function setCookiesProvider($provider) {
123 $this->cookiesProvider = $provider;
124 }
125
126 public function hasCache() {
127 // 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
128 if ($this->parent && !$this->parent->hasCache()) return false;
129 if ($this->useInvalidated) {
130 $this->convertToStaleCache();
131 return Filesystem::fileExists($this->getCachefilePath("stale"));
132 } else {
133 return Filesystem::fileExists($this->getCachefilePath());
134 }
135 }
136
137 public function hasExpired($ttl = 86400, $cacheRevision = NULL) {
138 // 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
139 if ($this->parent && $this->parent->hasExpired($ttl, $cacheRevision)) return true;
140
141 if ($this->useInvalidated) {
142 $this->convertToStaleCache();
143 $cachefilePath = $this->getCachefilePath("stale");
144 $mtime = Filesystem::fileMtime(dirname($cachefilePath));
145 } else {
146 $cachefilePath = $this->getCachefilePath();
147 $mtime = Filesystem::fileMtime($cachefilePath);
148 }
149
150 $now = time();
151
152 if ($now - $mtime >= $ttl) {
153 return true;
154 } else {
155 try {
156 $headers = Filesystem::fileGetHeaders($cachefilePath);
157 if (!empty($headers["x-nitro-expires"]) && $now > (int)$headers["x-nitro-expires"]) {
158 return true;
159 }
160
161 if (
162 !$this->useInvalidated &&
163 !empty($headers["x-cache-ctime"]) &&
164 $now - (int)$headers["x-cache-ctime"] > $ttl
165 ) {
166 return true;
167 }
168
169 // Check if the revision has changed which makes cache file obsolete
170 if (!empty($headers["x-nitro-rev"]) && $cacheRevision && $cacheRevision != $headers["x-nitro-rev"]) {
171 return true;
172 }
173 } catch (\Exception $e) {
174 return true;
175 }
176 }
177 return false;
178 }
179
180 public function getRemainingTtl($ttl = 86400) {
181 // If there is a parent cache file return its remaining TTL
182 if ($this->parent) return $this->parent->getRemainingTtl();
183
184 if ($this->useInvalidated) {
185 return 0;
186 } else {
187 $cachefilePath = $this->getCachefilePath();
188 }
189
190 $now = time();
191 try {
192 $headers = Filesystem::fileGetHeaders($cachefilePath);
193 if (!empty($headers["x-nitro-expires"])) {
194 $expireTime = (int) $headers["x-nitro-expires"];
195 } else if (!empty($headers["x-cache-ctime"])) {
196 $expireTime = (int) $headers["x-cache-ctime"] + $ttl;
197 } else {
198 $mtime = Filesystem::fileMtime($cachefilePath);
199 $expireTime = $mtime + $ttl;
200 }
201
202 return max($expireTime - $now, 0);
203 } catch (\Exception $e) {
204 return 0;
205 }
206
207 return 0;
208 }
209
210 public function setContent($content, $headers = NULL) {
211 if (!$this->dataDir) return;
212
213 $filePath = $this->getCachefilePath();
214 if (Filesystem::createDir(dirname($filePath))) {
215 if (!Filesystem::filePutContents($filePath, $content, $this->headersFlatten($headers))) {
216 return false;
217 } else {
218 if ($headers && !empty($headers["x-cache-ctime"])) {
219 Filesystem::touch($filePath, (int)$headers["x-cache-ctime"]);
220 }
221 return $this->compress($filePath);
222 }
223 }
224 }
225
226 private function headersFlatten($headers) {
227 // The headers from fileGetAll come as associative array
228 // They must be converted to an array of strings before being passed to filePutContents, which is what this function does
229 $headersFlat = array();
230 foreach ($headers as $name => $value) {
231 if (is_array($value)) {
232 foreach ($value as $subValue) {
233 $headersFlat[] = $name . ":" . $subValue;
234 }
235 } else {
236 $headersFlat[] = $name . ":" . $value;
237 }
238 }
239
240 return $headersFlat;
241 }
242
243 private function compress($filePath) {
244 $fileInfo = Filesystem::fileGetAll($filePath);
245 $fileInfo->headers["Content-Encoding"] = "gzip";
246 $gzPath = $filePath . ".gz";
247 $res = Filesystem::filePutContents($gzPath, gzencode($fileInfo->content, 4), $this->headersFlatten($fileInfo->headers)); // save compressed version of the cache file
248
249 if ($res && $fileInfo->headers && !empty($fileInfo->headers["x-cache-ctime"])) {
250 Filesystem::touch($gzPath, (int)$fileInfo->headers["x-cache-ctime"]);
251 }
252
253 return $res;
254 }
255
256 public function readfile() {
257 if ($this->useInvalidated) {
258 $this->convertToStaleCache();
259 $filePath = $this->getCachefilePath("stale");
260 } else {
261 $filePath = $this->getCachefilePath();
262 }
263
264 if ($this->canUseCompression() && (ini_get("zlib.output_compression") === "0" || ini_set("zlib.output_compression", "0") !== false)) {
265 $filePath .= ".gz";
266 }
267 $file = Filesystem::fileGetAll($filePath);
268 foreach ($file->headers as $name => $value) {
269 if (is_array($value)) {
270 foreach ($value as $subVal) {
271 header($name . ": " . $subVal, false);
272 }
273 } else {
274 header("$name: $value", false);
275 }
276 }
277
278 echo $file->content;
279 }
280
281 public function getFileContents() {
282 if ($this->useInvalidated) {
283 $this->convertToStaleCache();
284 return Filesystem::fileGetContents($this->getCachefilePath("stale"));
285 }
286 return Filesystem::fileGetContents($this->getCachefilePath());
287 }
288
289 private function convertToStaleCache() {
290 $staleFile = $this->getCachefilePath("stale");
291 if (!Filesystem::fileExists($staleFile)) {
292 $freshFile = $this->getCachefilePath();
293 if (Filesystem::fileExists($freshFile)) {
294 $fileInfo = Filesystem::fileGetAll($freshFile);
295 $newContent = str_replace("NITROPACK_STATE='FRESH'", "NITROPACK_STATE='STALE'", $fileInfo->content);
296 Filesystem::filePutContents($freshFile, $newContent, $this->headersFlatten($fileInfo->headers));
297 Filesystem::rename($freshFile, $staleFile);
298 Filesystem::deleteFile($freshFile . ".gz");
299 $this->compress($staleFile);
300 }
301 }
302 }
303
304 private function cookiePrefix() {
305 $prefix = '';
306
307 $cookies = $this->cookiesProvider ? call_user_func($this->cookiesProvider) : $this->cookies;
308
309 ksort($cookies);
310
311 foreach ($cookies as $cookieName=>$cookieValue) {
312 foreach ($this->supportedCookies as $cookie) {
313 if (preg_match('/' . NitroPack::wildcardToRegex($cookie) . '/', $cookieName)) {
314 $prefix .= $cookieName.'='.$cookieValue.';';
315 }
316 }
317 }
318
319 return substr(md5($prefix), 0, 16);
320 }
321
322 private function sslPrefix() {
323 return $this->url->getScheme() == "https" ? "ssl-" : "";
324 }
325
326 private function ajaxPrefix() {
327 return $this->isAjax && $this->parent ? "ajax-" . md5($this->url->getNormalized()) . "-" : "";
328 }
329
330 private function customCachePrefix() {
331 $customCachePrefix = NitroPack::getCustomCachePrefix();
332 return $customCachePrefix ? $customCachePrefix . "-" : "";
333 }
334
335 private function isCompressionAllowed() {
336 return isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false;
337 }
338
339 private function canUseCompression() {
340 return $this->useCompression && $this->isCompressionAllowed() && !headers_sent();
341 }
342
343 public function nameOfCachefile() {
344 return $this->customCachePrefix() . $this->ajaxPrefix() . $this->sslPrefix() . $this->cookiePrefix() . ".html";
345 }
346
347 public function getCachefilePath($suffix = "") {
348 if ($suffix) $suffix = "." . $suffix;
349 if ($this->isAjax && $this->referer) {
350 return self::getUrlDir($this->dataDir, $this->referer->getNormalized(), $this->useInvalidated, $this->urlPathVersion) . "/" . $this->nameOfCachefile() . $suffix;
351 }
352 return self::getUrlDir($this->dataDir, $this->url->getNormalized(), $this->useInvalidated, $this->urlPathVersion) . "/" . $this->nameOfCachefile() . $suffix;
353 }
354 }
355