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