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