PluginProbe ʕ •ᴥ•ʔ
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization / 1.5.7
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization v1.5.7
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 5 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 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
290 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 setContent($content, $headers = NULL) {
148 if (!$this->dataDir) return;
149
150 $filePath = $this->getCachefilePath();
151 if (Filesystem::createDir(dirname($filePath))) {
152 if (!Filesystem::filePutContents($filePath, $content, $this->headersFlatten($headers))) {
153 return false;
154 } else {
155 if ($headers && !empty($headers["x-cache-ctime"])) {
156 Filesystem::touch($filePath, (int)$headers["x-cache-ctime"]);
157 }
158 return $this->compress($filePath);
159 }
160 }
161 }
162
163 private function headersFlatten($headers) {
164 // The headers from fileGetAll come as associative array
165 // They must be converted to an array of strings before being passed to filePutContents, which is what this function does
166 $headersFlat = array();
167 foreach ($headers as $name => $value) {
168 if (is_array($value)) {
169 foreach ($value as $subValue) {
170 $headersFlat[] = $name . ":" . $subValue;
171 }
172 } else {
173 $headersFlat[] = $name . ":" . $value;
174 }
175 }
176
177 return $headersFlat;
178 }
179
180 private function compress($filePath) {
181 $fileInfo = Filesystem::fileGetAll($filePath);
182 $fileInfo->headers["Content-Encoding"] = "gzip";
183 $gzPath = $filePath . ".gz";
184 $res = Filesystem::filePutContents($gzPath, gzencode($fileInfo->content, 4), $this->headersFlatten($fileInfo->headers)); // save compressed version of the cache file
185
186 if ($res && $fileInfo->headers && !empty($fileInfo->headers["x-cache-ctime"])) {
187 Filesystem::touch($gzPath, (int)$fileInfo->headers["x-cache-ctime"]);
188 }
189
190 return $res;
191 }
192
193 public function readfile() {
194 if ($this->useInvalidated) {
195 $this->convertToStaleCache();
196 $filePath = $this->getCachefilePath("stale");
197 } else {
198 $filePath = $this->getCachefilePath();
199 }
200
201 if ($this->canUseCompression() && (ini_get("zlib.output_compression") === "0" || ini_set("zlib.output_compression", "0") !== false)) {
202 $filePath .= ".gz";
203 }
204 $file = Filesystem::fileGetAll($filePath);
205 foreach ($file->headers as $name => $value) {
206 if (is_array($value)) {
207 foreach ($value as $subVal) {
208 header($name . ": " . $subVal, false);
209 }
210 } else {
211 header("$name: $value", false);
212 }
213 }
214
215 echo $file->content;
216 }
217
218 public function getFileContents() {
219 if ($this->useInvalidated) {
220 $this->convertToStaleCache();
221 return Filesystem::fileGetContents($this->getCachefilePath("stale"));
222 }
223 return Filesystem::fileGetContents($this->getCachefilePath());
224 }
225
226 private function convertToStaleCache() {
227 $staleFile = $this->getCachefilePath("stale");
228 if (!Filesystem::fileExists($staleFile)) {
229 $freshFile = $this->getCachefilePath();
230 if (Filesystem::fileExists($freshFile)) {
231 $fileInfo = Filesystem::fileGetAll($freshFile);
232 $newContent = str_replace("NITROPACK_STATE='FRESH'", "NITROPACK_STATE='STALE'", $fileInfo->content);
233 Filesystem::filePutContents($freshFile, $newContent, $this->headersFlatten($fileInfo->headers));
234 Filesystem::rename($freshFile, $staleFile);
235 Filesystem::deleteFile($freshFile . ".gz");
236 $this->compress($staleFile);
237 }
238 }
239 }
240
241 private function cookiePrefix() {
242 $prefix = '';
243
244 ksort($this->cookies);
245
246 foreach ($this->cookies as $cookieName=>$cookieValue) {
247 foreach ($this->supportedCookies as $cookie) {
248 if (preg_match('/' . NitroPack::wildcardToRegex($cookie) . '/', $cookieName)) {
249 $prefix .= $cookieName.'='.$cookieValue.';';
250 }
251 }
252 }
253
254 return substr(md5($prefix), 0, 16);
255 }
256
257 private function sslPrefix() {
258 return $this->url->getScheme() == "https" ? "ssl-" : "";
259 }
260
261 private function ajaxPrefix() {
262 return $this->isAjax && $this->parent ? "ajax-" . md5($this->url->getUrl()) . "-" : "";
263 }
264
265 private function customCachePrefix() {
266 $customCachePrefix = NitroPack::getCustomCachePrefix();
267 return $customCachePrefix ? $customCachePrefix . "-" : "";
268 }
269
270 private function isCompressionAllowed() {
271 return isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false;
272 }
273
274 private function canUseCompression() {
275 return $this->useCompression && $this->isCompressionAllowed() && !headers_sent();
276 }
277
278 public function nameOfCachefile() {
279 return $this->customCachePrefix() . $this->ajaxPrefix() . $this->sslPrefix() . $this->cookiePrefix() . ".html";
280 }
281
282 public function getCachefilePath($suffix = "") {
283 if ($suffix) $suffix = "." . $suffix;
284 if ($this->isAjax && $this->referer) {
285 return self::getUrlDir($this->dataDir, $this->referer->getUrl(), $this->useInvalidated) . "/" . $this->nameOfCachefile() . $suffix;
286 }
287 return self::getUrlDir($this->dataDir, $this->url->getUrl(), $this->useInvalidated) . "/" . $this->nameOfCachefile() . $suffix;
288 }
289 }
290