PluginProbe ʕ •ᴥ•ʔ
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization / 1.5.2
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization v1.5.2
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 / NitroPack.php
nitropack / nitropack-sdk / NitroPack / SDK Last commit date
Api 5 years ago Integrations 6 years ago StorageDriver 5 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 5 years ago HealthStatus.php 5 years ago IntegrationUrl.php 6 years ago NitroPack.php 5 years ago NoConfigException.php 5 years ago Pagecache.php 5 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
NitroPack.php
792 lines
1 <?php
2 namespace NitroPack\SDK;
3
4 class NitroPack {
5 const VERSION = '0.20.0';
6 const PAGECACHE_LOCK_EXPIRATION_TIME = 300; // in seconds
7 private $dataDir;
8 private $cachePath = array('data', 'pagecache');
9 private $configFile = array('data', 'config.json');
10 private $healthStatusFile = array('data', 'service-health');
11 private $pageCacheLockFile = array('data', 'get_cache.lock');
12 private $cachePathSuffix = NULL;
13 private $configTTL; // In seconds
14
15 private $siteId;
16 private $siteSecret;
17 private $userAgent; // Defaults to desktop Chrome
18
19 private $url;
20 private $config;
21 private $device;
22 private $api;
23
24 public $backlog;
25 public $healthStatus;
26 public $pageCache; // TODO: consider better ways of protecting/providing this outside the class
27
28 private static $cachePrefixes = array();
29 private static $cookieFilters = array();
30
31 public static function getRemoteAddr() {
32 // IP check order is: CloudFlare, Proxy, Client IP
33 $ipKeys = ["HTTP_X_FORWARDED_FOR", "HTTP_CF_CONNECTING_IP", "REMOTE_ADDR"];
34 foreach ($ipKeys as $key) {
35 if (!empty($_SERVER[$key])) {
36 return $_SERVER[$key];
37 }
38 }
39
40 return NULL;
41 }
42
43 public static function getCookies() {
44 $cookies = array();
45
46 foreach ($_COOKIE as $name=>$value) {
47 if (is_array($value)) {
48 foreach ($value as $k=>$v) {
49 $key = $name . "[$k]";
50 $cookies[$key] = $v;
51 }
52 } else {
53 $cookies[$name] = $value;
54 }
55 }
56
57 foreach (self::$cookieFilters as $cookieFilter) {
58 call_user_func_array($cookieFilter, array(&$cookies));
59 }
60
61 return $cookies;
62 }
63
64 public static function addCookieFilter($callback) {
65 if (is_callable($callback)) {
66 if (!in_array($callback, self::$cookieFilters, true)) {
67 self::$cookieFilters[] = $callback;
68 }
69 } else {
70 throw new \RuntimeException("Non-callable callback passed to " . __FUNCTION__);
71 }
72 }
73
74 public static function addCustomCachePrefix($prefix = "") {
75 self::$cachePrefixes[] = $prefix;
76 }
77
78 public static function getCustomCachePrefix() {
79 return implode("-", self::$cachePrefixes);
80 }
81
82 public static function wildcardToRegex($str, $delim = "/") {
83 return implode(".*?", array_map(function($input) use ($delim) { return preg_quote($input, $delim); }, explode("*", $str)));
84 }
85
86 public function __construct($siteId, $siteSecret, $userAgent = NULL, $url = NULL, $dataDir = __DIR__) {
87 $this->configTTL = 3600;
88 $this->siteId = $siteId;
89 $this->siteSecret = $siteSecret;
90 $this->dataDir = $dataDir;
91 $this->backlog = new Backlog($dataDir, $this);
92 $this->healthStatus = HealthStatus::HEALTHY;
93 $this->loadHealthStatus();
94
95 if (empty($userAgent)) {
96 $this->userAgent = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36';
97 } else {
98 $this->userAgent = $userAgent;
99 }
100
101 $this->loadConfig($siteId, $siteSecret);
102 $this->device = new Device($this->userAgent);
103
104 if(empty($url)) {
105 $host = !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : "example.com";
106 $uri = !empty($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : "/";
107 $url = $this->getScheme() . $host . $uri;
108 }
109
110 $queryStr = parse_url($url, PHP_URL_QUERY);
111
112 if ($queryStr) {
113 parse_str($queryStr, $queryParams);
114
115 if ($queryParams) {
116 if ($this->config->IgnoredParams) {
117 foreach ($this->config->IgnoredParams as $ignorePattern) {
118 $regex = "/^" . self::wildcardToRegex($ignorePattern) . "$/";
119 foreach($queryParams as $paramName => $paramValue) {
120 if (preg_match($regex, $paramName)) {
121 unset($queryParams[$paramName]);
122 }
123 }
124 }
125 }
126
127 ksort($queryParams);
128 $url = str_replace($queryStr, http_build_query($queryParams), $url);
129 }
130 }
131
132 $urlInfo = new \NitroPack\Url($url);
133 $this->url = $urlInfo->getNormalized();
134
135 $this->pageCache = new Pagecache($this->url, $this->userAgent, $this->supportedCookiesFilter(self::getCookies()), $this->config->PageCache->SupportedCookies, $this->isAJAXRequest());
136 if ($this->isAJAXRequest() && $this->isAllowedAJAXUrl($this->url) && !empty($_SERVER["HTTP_REFERER"])) {
137 $refererInfo = new \NitroPack\Url($_SERVER["HTTP_REFERER"]);
138 $this->pageCache->setReferer($refererInfo->getNormalized());
139 }
140
141 $this->api = new Api($this->siteId, $siteSecret);
142 $this->api->setBacklog($this->backlog);
143 $this->api->setNitroPack($this);
144
145 $this->pageCache->setDataDir($this->getCacheDir());
146
147 $this->useCompression = false;
148 }
149
150 public function supportedCookiesFilter($cookies) {
151 $supportedCookies = array();
152 foreach ($cookies as $cookieName=>$cookieValue) {
153 foreach ($this->config->PageCache->SupportedCookies as $cookie) {
154 if (preg_match('/^' . self::wildcardToRegex($cookie) . '$/', $cookieName)) {
155 $supportedCookies[$cookieName] = $cookieValue;
156 }
157 }
158 }
159 return $supportedCookies;
160 }
161
162 public function tagUrl($url, $tag) {
163 if ($this->isAllowedUrl($url)) {
164 return $this->api->tagUrl($url, $tag);
165 } else {
166 return false;
167 }
168 }
169
170 public function setCachePathSuffix($suffix) {
171 $this->cachePathSuffix = $suffix;
172 $this->pageCache->setDataDir($this->getCacheDir());
173 }
174
175 public function enableCompression() {
176 $this->pageCache->enableCompression();
177 }
178
179 public function disableCompression() {
180 $this->pageCache->disableCompression();
181 }
182
183 public function getUrl() {
184 return $this->url;
185 }
186
187 public function getApi() {
188 return $this->api;
189 }
190
191 public function getSiteId() {
192 return $this->siteId;
193 }
194
195 public function getConfig() {
196 return $this->config;
197 }
198
199 public function getCacheDir() {
200 $cachePath = $this->cachePath;
201 array_unshift($cachePath, $this->dataDir);
202 if ($this->cachePathSuffix) {
203 $cachePath[] = $this->cachePathSuffix;
204 }
205 return Filesystem::getOsPath($cachePath);
206 }
207
208 public function getHealthStatus() {
209 return $this->healthStatus;
210 }
211
212 public function getHealthStatusFile() {
213 $healthStatusFile = $this->healthStatusFile;
214 array_unshift($healthStatusFile, $this->dataDir);
215 return Filesystem::getOsPath($healthStatusFile);
216 }
217
218 public function setHealthStatus($status) {
219 $this->healthStatus = $status;
220 Filesystem::filePutContents($this->getHealthStatusFile(), $status);
221 }
222
223 public function loadHealthStatus() {
224 if (Filesystem::fileExists($this->getHealthStatusFile())) {
225 $this->healthStatus = Filesystem::fileGetContents($this->getHealthStatusFile());
226 } else {
227 $this->healthStatus = HealthStatus::HEALTHY;
228 }
229 }
230
231 public function checkHealthStatus() {
232 try {
233 // TODO: Potentially replace this with a dedicated method in the API
234 $this->fetchConfig(true);
235 $this->setHealthStatus(HealthStatus::HEALTHY);
236 return HealthStatus::HEALTHY;
237 } catch (\Exception $e) {
238 return $this->getHealthStatus();
239 }
240 }
241
242 public function hasCache($layout = 'default') {
243 if ($this->hasLocalCache()) {
244 return true;
245 } else {
246 return $this->hasRemoteCache($layout);
247 }
248 }
249
250 public function hasLocalCache($checkIfRequestIsAllowed = true) {
251 if ($this->backlog->exists()) return false;
252 if (!$this->isAllowedUrl($this->url) || ($checkIfRequestIsAllowed && !$this->isAllowedRequest())) return false;
253 $cacheRevision = !empty($this->config->RevisionHash) ? $this->config->RevisionHash : NULL;
254 if ($this->getHealthStatus() !== HealthStatus::HEALTHY) {
255 return false;
256 }
257
258 if (!$this->pageCache->getUseInvalidated()) {
259 $ttl = $this->config->PageCache->ExpireTime;
260 } else {
261 $ttl = $this->config->PageCache->StaleExpireTime;
262 }
263
264 return $this->pageCache->hasCache() && !$this->pageCache->hasExpired($ttl, $cacheRevision);
265 }
266
267 public function hasRemoteCache($layout, $checkIfRequestIsAllowed = true) {
268 if ($this->backlog->exists()) return false;
269 if (!$this->isAllowedUrl($this->url) || ($checkIfRequestIsAllowed && !$this->isAllowedRequest()) || $this->isPageCacheLocked()) return false;
270 $resp = $this->api->getCache($this->url, $this->userAgent, $this->supportedCookiesFilter(self::getCookies()), $this->isAJAXRequest(), $layout);
271
272 if ($resp->getStatus() == Api\ResponseStatus::OK) {// We have cache response
273
274 // Check for invalidated cache and delete it if such is found
275 $this->pageCache->useInvalidated(true);
276 if ($this->pageCache->hasCache()) {
277 $path = $this->pageCache->getCachefilePath();
278 Filesystem::deleteFile($path);
279 Filesystem::deleteFile($path . ".gz");
280 Filesystem::deleteFile($path . ".stale");
281 Filesystem::deleteFile($path . ".stale.gz");
282 if (Filesystem::isDirEmpty(dirname($path))) {
283 Filesystem::deleteDir(dirname($path));
284 }
285 }
286 $this->pageCache->useInvalidated(false);
287 // End of check
288
289 $this->pageCache->setContent($resp->getBody());
290 return true;
291 } else {
292 // The goal is to serve cache at all times even when it is slightly outdated. This approach should be ok because new cache has been requested and it should be ready soon
293 if ($this->pageCache->hasCache()) {
294 return true;
295 } else {
296 // Check for invalidated cache
297 $this->pageCache->useInvalidated(true);
298 if ($this->hasLocalCache(false)) {
299 return true;
300 } else {
301 $this->pageCache->useInvalidated(false);
302 }
303 }
304
305 return false;
306 }
307 }
308
309 public function invalidateCache($url = NULL, $tag = NULL, $reason = NULL) {
310 return $this->purgeCache($url, $tag, PurgeType::INVALIDATE | PurgeType::PAGECACHE_ONLY, $reason);
311 }
312
313 public function clearPageCache($reason = NULL) {
314 return $this->purgeCache(NULL, NULL, PurgeType::PAGECACHE_ONLY, $reason);
315 }
316
317 public function purgeCache($url = NULL, $tag = NULL, $purgeType = PurgeType::COMPLETE, $reason = NULL) {
318 @set_time_limit(0);
319 $this->lockPageCache(); // Set the page cache lock, expires after self::PAGECACHE_LOCK_EXPIRATION_TIME seconds
320
321 try {
322 $invalidate = !!($purgeType & PurgeType::INVALIDATE);
323 $pageCacheOnly = !!($purgeType & PurgeType::PAGECACHE_ONLY);
324
325 if ($url || $tag) {
326 $localResult = true;
327 $apiResult = true;
328 if ($url) {
329 if (is_array($url)) {
330 foreach ($url as &$urlLink) {
331 $urlLink = $this->normalizeUrl($urlLink);
332 if ($invalidate) {
333 $localResult &= $this->invalidateLocalUrlCache($urlLink);
334 } else {
335 $localResult &= $this->purgeLocalUrlCache($urlLink);
336 }
337 }
338 } else {
339 $url = $this->normalizeUrl($url);
340 if ($invalidate) {
341 $localResult &= $this->invalidateLocalUrlCache($url);
342 } else {
343 $localResult &= $this->purgeLocalUrlCache($url);
344 }
345 }
346
347 try {
348 $apiResult &= $this->api->purgeCache($url, false, $reason);
349 } catch (ServiceDownException $e) {
350 $apiResult = false;
351 // TODO: Potentially log this
352 }
353 }
354
355 if ($tag) {
356 $attemptsLeft = 10;
357 $purgedUrls = array();
358 do {
359 $hadError = false;
360
361 try {
362 $purgedUrls = $this->api->purgeCacheByTag($tag, $reason);
363
364 foreach ($purgedUrls as $url) {
365 if ($invalidate) {
366 $localResult &= $this->invalidateLocalUrlCache($url);
367 } else {
368 $localResult &= $this->purgeLocalUrlCache($url);
369 }
370 }
371 } catch (ServiceDownException $e) {
372 $this->purgeLocalCache(true); // TODO: This will leave stale cache files. Think of a way to delete them on systems that do not have a heartbeat (i.e custom integrations).
373 $apiResult = false;
374 // TODO: Log this
375 break;
376 } catch (\Exception $e) {
377 $hadError = true;
378 $attemptsLeft--;
379 sleep(3);
380 }
381 } while ($attemptsLeft > 0 && count($purgedUrls) > 0);
382 }
383 } else {
384 if ($invalidate) {
385 $localResult = $this->invalidateLocalCache();
386 $apiResult = $this->api->purgeCache(NULL, $pageCacheOnly, $reason); // delete only page cache
387 } else {
388 $staleCacheDir = $this->purgeLocalCache(true);
389
390 // Call the cache purge method
391 $apiResult = $this->api->purgeCache(NULL, $pageCacheOnly, $reason);
392
393 // Finally, delete the files of the stale directory
394 Filesystem::deleteDir($staleCacheDir);
395
396 $localResult = true; // We do not care if $staleCacheDir was not deleted successfully
397 }
398 }
399
400 $this->unlockPageCache(); // Purge cache is done, we can now unlock
401 } catch (\Exception $e) {
402 $this->unlockPageCache(); // Purge cache had an error, so just unlock
403
404 throw $e;
405 }
406
407 return $apiResult && $localResult;
408 }
409
410 public function purgeLocalCache($quick = false) {
411 $staleCacheDir = $this->getCacheDir() . '.stale.' . md5(microtime(true));
412 $this->purgeProxyCache();
413 $this->config->LastFetch = 0;
414 $this->setConfig($this->config);
415
416 // Rename cache files directory
417 if (Filesystem::fileExists($this->getCacheDir()) && !Filesystem::rename($this->getCacheDir(), $staleCacheDir)) {
418 throw new \Exception("No write permissions to rename the directory: " . $this->getCacheDir());
419 }
420
421 // Create a new empty directory
422 if (!Filesystem::createDir($this->getCacheDir())) {
423 throw new \Exception("No write permissions to create the directory: " . $this->getCacheDir());
424 }
425
426 if (!$quick) {
427 // Finally, delete the files of the stale directory
428 Filesystem::deleteDir($staleCacheDir);
429 }
430
431 return $staleCacheDir;
432 }
433
434 public function fetchConfig($ignoreHealthStatus = false) {
435 // TODO: Record failures and repeat with a delay
436 $fetcher = new Api\RemoteConfigFetcher($this->siteId, $this->siteSecret);
437
438 if (!$ignoreHealthStatus) {
439 $fetcher->setBacklog($this->backlog);
440 $fetcher->setNitroPack($this);
441 }
442
443 try {
444 $configContents = $fetcher->get(); // this can throw in case of http errors or validation failures
445 } catch (\Exception $e) {
446 // TODO: Record this failure, possibly in the backlog
447 throw $e;
448 }
449
450 $config = json_decode($configContents);
451 if ($config) {
452 $config->SDKVersion = NitroPack::VERSION;
453 $config->LastFetch = time();
454
455 $this->setConfig($config);
456 return true;
457 } else {
458 throw new EmptyConfigException("Config response was empty");
459 }
460 }
461
462 public function setConfig($config) {
463 $file = $this->getConfigFile();
464 if (Filesystem::createDir(dirname($file))) {
465 if (Filesystem::filePutContents($file, json_encode($config))) {
466 return true;
467 } else {
468 throw new StorageException(sprintf("Config file %s cannot be saved to disk", $file));
469 }
470 } else {
471 throw new StorageException(sprintf("Storage directory %s cannot be created", dirname($file)));
472 }
473 }
474
475 public function purgeProxyCache($url = NULL) {
476 if (!empty($this->config->CacheIntegrations)) {
477 if (!empty($this->config->CacheIntegrations->Varnish)) {
478 if ($url) {
479 $url = $this->normalizeUrl($url);
480 $varnish = new Integrations\Varnish($this->config->CacheIntegrations->Varnish->Servers, $this->config->CacheIntegrations->Varnish->PurgeSingleMethod);
481 $varnish->purge($url);
482 } else {
483 $varnish = new Integrations\Varnish($this->config->CacheIntegrations->Varnish->Servers, $this->config->CacheIntegrations->Varnish->PurgeAllMethod);
484 $varnish->purge($this->config->CacheIntegrations->Varnish->PurgeAllUrl);
485 }
486 }
487
488 //if (!empty($this->config->CacheIntegrations->LiteSpeed) && php_sapi_name() !== "cli") {
489 // if ($url) {
490 // $urlObj = new \NitroPack\Url($url);
491 // $liteSpeedPath = $urlObj->getPath();
492 // if ($urlObj->getQuery()) {
493 // $liteSpeedPath .= "?" . $urlObj->getQuery();
494 // }
495 // header("X-LiteSpeed-Purge: $liteSpeedPath", false);
496 // } else {
497 // header("X-LiteSpeed-Purge: *", false);
498 // }
499 //}
500 }
501 }
502
503 public function isAllowedUrl($url) {
504 if (strpos($url, 'sucurianticache=') !== false) return false;
505
506 if ($this->config->EnabledURLs->Status) {
507 if (!empty($this->config->EnabledURLs->URLs)) {
508 foreach ($this->config->EnabledURLs->URLs as $enabledUrl) {
509 $enabledUrlModified = preg_replace("/^(https?:)?\/\//", "*", $enabledUrl);
510
511 if (preg_match('/^' . self::wildcardToRegex($enabledUrlModified) . '$/', $url)) {
512 return true;
513 }
514 }
515
516 return false;
517 }
518 } else if ($this->config->DisabledURLs->Status) {
519 if (!empty($this->config->DisabledURLs->URLs)) {
520 foreach ($this->config->DisabledURLs->URLs as $disabledUrl) {
521 $disabledUrlModified = preg_replace("/^(https?:)?\/\//", "*", $disabledUrl);
522
523 if (preg_match('/^' . self::wildcardToRegex($disabledUrlModified) . '$/', $url)) {
524 return false; // don't cache disabled URLs
525 }
526 }
527 }
528 }
529
530 return true;
531 }
532
533 public function isAllowedRequest($allowServiceRequests = false) {
534 if (($this->isAJAXRequest() && !$this->isAllowedAJAX()) || !($this->isRequestMethod("GET") || $this->isRequestMethod("HEAD"))) {// TODO: Allow URLs which match a pattern in the AJAX URL whitelist
535 return false; // don't cache ajax or not GET requests
536 }
537
538 if (!$allowServiceRequests && isset($_SERVER["HTTP_X_NITROPACK_REQUEST"])) { // Skip requests coming from NitroPack
539 return false;
540 }
541
542 if (isset($_GET["nonitro"])) { // Skip requests having ?nonitro
543 return false;
544 }
545
546 if (!$this->isAllowedBrowser()) {
547 return false;
548 }
549
550 if (isset($this->config->ExcludedCookies) && $this->config->ExcludedCookies->Status) {
551 foreach ($this->config->ExcludedCookies->Cookies as $cookieExclude) {
552 foreach (self::getCookies() as $cookieName => $cookieValue) {
553 if (preg_match('/^' . self::wildcardToRegex($cookieExclude->name) . '$/', $cookieName)) {
554 if (count($cookieExclude->values) == 0) {
555 return false; // no excluded cookie values entered, reject all values
556 } else {
557 foreach ($cookieExclude->values as $val) {
558 if (preg_match('/^' . self::wildcardToRegex($val) . '$/', $cookieValue)) {
559 return false;
560 }
561 }
562 }
563 }
564 }
565 }
566 }
567
568 return true;
569 }
570
571 public function isAllowedBrowser() {
572 if (empty($_SERVER["HTTP_USER_AGENT"])) return true;
573
574 if (preg_match("~MSIE|Internet Explorer~i", $_SERVER["HTTP_USER_AGENT"]) || strpos($_SERVER["HTTP_USER_AGENT"], "Trident/7.0; rv:11.0") !== false) { // Skip IE
575 return false;
576 }
577
578 return true;
579 }
580
581 public function getScheme() {
582 return $this->isSecure() ? 'https://' : 'http://';
583 }
584
585 public function isSecure() {
586 return (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && in_array("https", array_map("strtolower", array_map("trim", explode(",", $_SERVER['HTTP_X_FORWARDED_PROTO']))))) ||
587 (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
588 (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) ||
589 (!empty($_SERVER['HTTP_SSL_FLAG']) && $_SERVER['HTTP_SSL_FLAG'] == 'SSL');
590 }
591
592 public function isAJAXRequest() {
593 return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
594 }
595
596 public function isRequestMethod($method) {
597 return empty($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] == $method;
598 }
599
600 public function isAllowedAJAX() {
601 if (!$this->pageCache->getParent()) return false;
602 if (!$this->pageCache->getParent()->hasCache() || $this->pageCache->getParent()->hasExpired($this->config->PageCache->ExpireTime)) return false;
603 return true;
604 }
605
606 public function isAllowedAJAXUrl($url) {
607 if ($this->config->AjaxURLs->Status) {
608 if (!empty($this->config->AjaxURLs->URLs)) {
609 foreach ($this->config->AjaxURLs->URLs as $ajaxUrl) {
610 $ajaxUrlModified = preg_replace("/^(https?:)?\/\//", "*", $ajaxUrl);
611 if (preg_match('/^' . self::wildcardToRegex($ajaxUrlModified) . '$/', $url)) {
612 return true;
613 }
614 }
615 return false;
616 }
617 }
618 return false;
619 }
620
621 public function isCacheAllowed() {
622 return $this->isAllowedRequest() && $this->isAllowedUrl($this->url);
623 }
624
625 public function purgeLocalUrlCache($url) {
626 $url = $this->normalizeUrl($url);
627 $this->purgeProxyCache($url);
628 $localResult = true;
629 $cacheDir = $this->getCacheDir();
630 $knownDeviceTypes = Device::getKnownTypes();
631 foreach ($knownDeviceTypes as $deviceType) {
632 $urlDir = PageCache::getUrlDir($cacheDir . "/" . $deviceType, $url);
633 $invalidatedUrlDir = PageCache::getUrlDir($cacheDir . "/" . $deviceType, $url, true);
634 $localResult &= Filesystem::deleteDir($urlDir);
635 $localResult &= Filesystem::deleteDir($invalidatedUrlDir);
636 }
637 return $localResult;
638 }
639
640 public function invalidateLocalUrlCache($url) {
641 $url = $this->normalizeUrl($url);
642 $this->purgeProxyCache($url);
643 $localResult = true;
644 $cacheDir = $this->getCacheDir();
645 $knownDeviceTypes = Device::getKnownTypes();
646 foreach ($knownDeviceTypes as $deviceType) {
647 $urlDir = PageCache::getUrlDir($cacheDir . "/" . $deviceType, $url);
648 $urlDirInvalid = PageCache::getUrlDir($cacheDir . "/" . $deviceType, $url, true);
649
650 $this->invalidateDir($urlDir, $urlDirInvalid);
651 }
652 return $localResult;
653 }
654
655 public function invalidateLocalCache() {
656 $this->purgeProxyCache();
657 $this->config->LastFetch = 0;
658 $this->setConfig($this->config);
659
660 $cacheDir = $this->getCacheDir();
661 $knownDeviceTypes = Device::getKnownTypes();
662 foreach ($knownDeviceTypes as $deviceType) {
663 $deviceTypeDir = $cacheDir . "/" . $deviceType;
664 Filesystem::dirForeach($deviceTypeDir, function($urlDir) {
665 if (substr($urlDir, -2) !== "_i") {
666 $this->invalidateDir($urlDir, $urlDir . "_i");
667 }
668 });
669 }
670 return true;
671 }
672
673 private function invalidateDir($urlDir, $urlDirInvalid) {
674 if (Filesystem::fileExists($urlDirInvalid)) {
675 Filesystem::dirForeach($urlDir, function($file) use ($urlDirInvalid) {
676 Filesystem::rename($file, $urlDirInvalid . "/" . basename($file));
677 });
678
679 Filesystem::deleteDir($urlDir);
680 } else {
681 Filesystem::rename($urlDir, $urlDirInvalid);
682 }
683 Filesystem::touch($urlDirInvalid);
684 }
685
686 public function integrationUrl($widget, $version = null) {
687 $integration = new IntegrationUrl($widget, $this->siteId, $this->siteSecret, $version);
688
689 return $integration->getUrl();
690 }
691
692 public function embedJsUrl() {
693 $embedjs = new Url\Embedjs();
694
695 return $embedjs->getUrl();
696 }
697
698 private function loadConfig() {
699 $file = $this->getConfigFile();
700
701 $config = array();
702 if (Filesystem::fileExists($file) || $this->fetchConfig()) {
703 $config = json_decode(Filesystem::fileGetContents($file));
704 if (empty($config->SDKVersion) || $config->SDKVersion !== NitroPack::VERSION || empty($config->LastFetch) || time() - $config->LastFetch >= $this->configTTL) {
705 if ($this->getHealthStatus() === HealthStatus::HEALTHY) {
706 if ($this->fetchConfig()) {
707 $config = json_decode(Filesystem::fileGetContents($file));
708 } else {
709 throw new NoConfigException("Can't load config file");
710 }
711 }
712 }
713 $this->config = $config;
714 } else {
715 throw new NoConfigException("Can't load config file");
716 }
717 }
718
719 private function getConfigFile() {
720 $configFile = $this->configFile;
721
722 $filename = array_pop($configFile);
723
724 $filename = $this->siteId . '-' . $filename;
725
726 array_push($configFile, $filename);
727 array_unshift($configFile, $this->dataDir);
728
729 return Filesystem::getOsPath($configFile);
730 }
731
732 private function lockPageCache() {
733 $filename = $this->getPageCacheLockFilename();
734
735 if (Filesystem::fileExists($filename)) {
736 $sem = 1 + (int)Filesystem::fileGetContents($filename);
737 } else {
738 $sem = 1;
739 }
740
741 return !!Filesystem::filePutContents($filename, $sem);
742 }
743
744 private function unlockPageCache() {
745 $filename = $this->getPageCacheLockFilename();
746
747 if (Filesystem::fileExists($filename)) {
748 $sem = (int)Filesystem::fileGetContents($filename);
749
750 $sem--;
751
752 if ($sem <= 0) {
753 return !!Filesystem::deleteFile($filename);
754 } else {
755 return !!Filesystem::filePutContents($filename, $sem);
756 }
757 }
758
759 return false;
760 }
761
762 private function isPageCacheLocked() {
763 $filename = $this->getPageCacheLockFilename();
764
765 if (!Filesystem::fileExists($filename)) {
766 return false;
767 } else {
768 if (time() - Filesystem::fileMTime($filename) <= self::PAGECACHE_LOCK_EXPIRATION_TIME) {
769 return true;
770 } else {
771 Filesystem::deleteFile($filename);
772
773 return false;
774 }
775 }
776
777 // We should never get here, so consider this a default return value in case of future changes
778 return false;
779 }
780
781 private function getPageCacheLockFilename() {
782 $pageCacheLockFile = $this->pageCacheLockFile;
783 array_unshift($pageCacheLockFile, $this->dataDir);
784 return Filesystem::getOsPath($pageCacheLockFile);
785 }
786
787 private function normalizeUrl($url) {
788 $urlObj = new \NitroPack\Url($url);
789 return $urlObj->getNormalized();
790 }
791 }
792