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