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