PluginProbe ʕ •ᴥ•ʔ
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization / 1.5.3
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization v1.5.3
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
803 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 (defined("NITROPACK_DISABLE_BACKLOG")) return;
225 if (Filesystem::fileExists($this->getHealthStatusFile())) {
226 $this->healthStatus = Filesystem::fileGetContents($this->getHealthStatusFile());
227 } else {
228 $this->healthStatus = HealthStatus::HEALTHY;
229 }
230 }
231
232 public function checkHealthStatus() {
233 try {
234 // TODO: Potentially replace this with a dedicated method in the API
235 $this->fetchConfig(true);
236 $this->setHealthStatus(HealthStatus::HEALTHY);
237 return HealthStatus::HEALTHY;
238 } catch (\Exception $e) {
239 return $this->getHealthStatus();
240 }
241 }
242
243 public function hasCache($layout = 'default') {
244 if ($this->hasLocalCache()) {
245 return true;
246 } else {
247 return $this->hasRemoteCache($layout);
248 }
249 }
250
251 public function hasLocalCache($checkIfRequestIsAllowed = true) {
252 if ($this->backlog->exists()) return false;
253 if (!$this->isAllowedUrl($this->url) || ($checkIfRequestIsAllowed && !$this->isAllowedRequest())) return false;
254 $cacheRevision = !empty($this->config->RevisionHash) ? $this->config->RevisionHash : NULL;
255 if ($this->getHealthStatus() !== HealthStatus::HEALTHY) {
256 return false;
257 }
258
259 if (!$this->pageCache->getUseInvalidated()) {
260 $ttl = $this->config->PageCache->ExpireTime;
261 } else {
262 $ttl = $this->config->PageCache->StaleExpireTime;
263 }
264
265 return $this->pageCache->hasCache() && !$this->pageCache->hasExpired($ttl, $cacheRevision);
266 }
267
268 public function hasRemoteCache($layout, $checkIfRequestIsAllowed = true) {
269 if ($this->backlog->exists()) return false;
270 if (!$this->isAllowedUrl($this->url) || ($checkIfRequestIsAllowed && !$this->isAllowedRequest()) || $this->isPageCacheLocked()) return false;
271 $resp = $this->api->getCache($this->url, $this->userAgent, $this->supportedCookiesFilter(self::getCookies()), $this->isAJAXRequest(), $layout);
272
273 if ($resp->getStatus() == Api\ResponseStatus::OK) {// We have cache response
274
275 // Check for invalidated cache and delete it if such is found
276 $this->pageCache->useInvalidated(true);
277 if ($this->pageCache->hasCache()) {
278 $path = $this->pageCache->getCachefilePath();
279 Filesystem::deleteFile($path);
280 Filesystem::deleteFile($path . ".gz");
281 Filesystem::deleteFile($path . ".stale");
282 Filesystem::deleteFile($path . ".stale.gz");
283 if (Filesystem::isDirEmpty(dirname($path))) {
284 Filesystem::deleteDir(dirname($path));
285 }
286 }
287 $this->pageCache->useInvalidated(false);
288 // End of check
289
290 $this->pageCache->setContent($resp->getBody());
291 return true;
292 } else {
293 // 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
294 if ($this->pageCache->hasCache()) {
295 return true;
296 } else {
297 // Check for invalidated cache
298 $this->pageCache->useInvalidated(true);
299 if ($this->hasLocalCache(false)) {
300 return true;
301 } else {
302 $this->pageCache->useInvalidated(false);
303 }
304 }
305
306 return false;
307 }
308 }
309
310 public function invalidateCache($url = NULL, $tag = NULL, $reason = NULL) {
311 return $this->purgeCache($url, $tag, PurgeType::INVALIDATE | PurgeType::PAGECACHE_ONLY, $reason);
312 }
313
314 public function clearPageCache($reason = NULL) {
315 return $this->purgeCache(NULL, NULL, PurgeType::PAGECACHE_ONLY, $reason);
316 }
317
318 public function purgeCache($url = NULL, $tag = NULL, $purgeType = PurgeType::COMPLETE, $reason = NULL) {
319 @set_time_limit(0);
320 $this->lockPageCache(); // Set the page cache lock, expires after self::PAGECACHE_LOCK_EXPIRATION_TIME seconds
321
322 try {
323 $invalidate = !!($purgeType & PurgeType::INVALIDATE);
324 $pageCacheOnly = !!($purgeType & PurgeType::PAGECACHE_ONLY);
325
326 if ($url || $tag) {
327 $localResult = true;
328 $apiResult = true;
329 if ($url) {
330 if (is_array($url)) {
331 foreach ($url as &$urlLink) {
332 $urlLink = $this->normalizeUrl($urlLink);
333 if ($invalidate) {
334 $localResult &= $this->invalidateLocalUrlCache($urlLink);
335 } else {
336 $localResult &= $this->purgeLocalUrlCache($urlLink);
337 }
338 }
339 } else {
340 $url = $this->normalizeUrl($url);
341 if ($invalidate) {
342 $localResult &= $this->invalidateLocalUrlCache($url);
343 } else {
344 $localResult &= $this->purgeLocalUrlCache($url);
345 }
346 }
347
348 try {
349 $apiResult &= $this->api->purgeCache($url, false, $reason);
350 } catch (ServiceDownException $e) {
351 $apiResult = false;
352 // TODO: Potentially log this
353 }
354 }
355
356 if ($tag) {
357 $attemptsLeft = 10;
358 $purgedUrls = array();
359 do {
360 $hadError = false;
361
362 try {
363 $purgedUrls = $this->api->purgeCacheByTag($tag, $reason);
364
365 foreach ($purgedUrls as $url) {
366 if ($invalidate) {
367 $localResult &= $this->invalidateLocalUrlCache($url);
368 } else {
369 $localResult &= $this->purgeLocalUrlCache($url);
370 }
371 }
372 } catch (ServiceDownException $e) {
373 $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).
374 $apiResult = false;
375 // TODO: Log this
376 break;
377 } catch (\Exception $e) {
378 $hadError = true;
379 $attemptsLeft--;
380 sleep(3);
381 }
382 } while ($attemptsLeft > 0 && count($purgedUrls) > 0);
383 }
384 } else {
385 if ($invalidate) {
386 $localResult = $this->invalidateLocalCache();
387 $apiResult = $this->api->purgeCache(NULL, $pageCacheOnly, $reason); // delete only page cache
388 } else {
389 $staleCacheDir = $this->purgeLocalCache(true);
390
391 // Call the cache purge method
392 $apiResult = $this->api->purgeCache(NULL, $pageCacheOnly, $reason);
393
394 // Finally, delete the files of the stale directory
395 Filesystem::deleteDir($staleCacheDir);
396
397 $localResult = true; // We do not care if $staleCacheDir was not deleted successfully
398 }
399 }
400
401 $this->unlockPageCache(); // Purge cache is done, we can now unlock
402 } catch (\Exception $e) {
403 $this->unlockPageCache(); // Purge cache had an error, so just unlock
404
405 throw $e;
406 }
407
408 return $apiResult && $localResult;
409 }
410
411 public function purgeLocalCache($quick = false) {
412 $staleCacheDir = $this->getCacheDir() . '.stale.' . md5(microtime(true));
413 $this->purgeProxyCache();
414 $this->config->LastFetch = 0;
415 $this->setConfig($this->config);
416
417 // Rename cache files directory
418 if (Filesystem::fileExists($this->getCacheDir()) && !Filesystem::rename($this->getCacheDir(), $staleCacheDir)) {
419 throw new \Exception("No write permissions to rename the directory: " . $this->getCacheDir());
420 }
421
422 // Create a new empty directory
423 if (!Filesystem::createDir($this->getCacheDir())) {
424 throw new \Exception("No write permissions to create the directory: " . $this->getCacheDir());
425 }
426
427 if (!$quick) {
428 // Finally, delete the files of the stale directory
429 Filesystem::deleteDir($staleCacheDir);
430 }
431
432 return $staleCacheDir;
433 }
434
435 public function fetchConfig($ignoreHealthStatus = false) {
436 // TODO: Record failures and repeat with a delay
437 $fetcher = new Api\RemoteConfigFetcher($this->siteId, $this->siteSecret);
438
439 if (!$ignoreHealthStatus) {
440 $fetcher->setBacklog($this->backlog);
441 $fetcher->setNitroPack($this);
442 }
443
444 try {
445 $configContents = $fetcher->get(); // this can throw in case of http errors or validation failures
446 } catch (\Exception $e) {
447 // TODO: Record this failure, possibly in the backlog
448 throw $e;
449 }
450
451 $config = json_decode($configContents);
452 if ($config) {
453 $config->SDKVersion = NitroPack::VERSION;
454 $config->LastFetch = time();
455
456 $this->setConfig($config);
457 return true;
458 } else {
459 throw new EmptyConfigException("Config response was empty");
460 }
461 }
462
463 public function setConfig($config) {
464 $file = $this->getConfigFile();
465 if (Filesystem::createDir(dirname($file))) {
466 if (Filesystem::filePutContents($file, json_encode($config))) {
467 return true;
468 } else {
469 throw new StorageException(sprintf("Config file %s cannot be saved to disk", $file));
470 }
471 } else {
472 throw new StorageException(sprintf("Storage directory %s cannot be created", dirname($file)));
473 }
474 }
475
476 public function purgeProxyCache($url = NULL) {
477 if (!empty($this->config->CacheIntegrations)) {
478 if (!empty($this->config->CacheIntegrations->Varnish)) {
479 if ($url) {
480 $url = $this->normalizeUrl($url);
481 $varnish = new Integrations\Varnish($this->config->CacheIntegrations->Varnish->Servers, $this->config->CacheIntegrations->Varnish->PurgeSingleMethod);
482 $varnish->purge($url);
483 } else {
484 $varnish = new Integrations\Varnish($this->config->CacheIntegrations->Varnish->Servers, $this->config->CacheIntegrations->Varnish->PurgeAllMethod);
485 $varnish->purge($this->config->CacheIntegrations->Varnish->PurgeAllUrl);
486 }
487 }
488
489 //if (!empty($this->config->CacheIntegrations->LiteSpeed) && php_sapi_name() !== "cli") {
490 // if ($url) {
491 // $urlObj = new \NitroPack\Url($url);
492 // $liteSpeedPath = $urlObj->getPath();
493 // if ($urlObj->getQuery()) {
494 // $liteSpeedPath .= "?" . $urlObj->getQuery();
495 // }
496 // header("X-LiteSpeed-Purge: $liteSpeedPath", false);
497 // } else {
498 // header("X-LiteSpeed-Purge: *", false);
499 // }
500 //}
501 }
502 }
503
504 public function isAllowedUrl($url) {
505 if (strpos($url, 'sucurianticache=') !== false) return false;
506
507 if ($this->config->EnabledURLs->Status) {
508 if (!empty($this->config->EnabledURLs->URLs)) {
509 foreach ($this->config->EnabledURLs->URLs as $enabledUrl) {
510 $enabledUrlModified = preg_replace("/^(https?:)?\/\//", "*", $enabledUrl);
511
512 if (preg_match('/^' . self::wildcardToRegex($enabledUrlModified) . '$/', $url)) {
513 return true;
514 }
515 }
516
517 return false;
518 }
519 } else if ($this->config->DisabledURLs->Status) {
520 if (!empty($this->config->DisabledURLs->URLs)) {
521 foreach ($this->config->DisabledURLs->URLs as $disabledUrl) {
522 $disabledUrlModified = preg_replace("/^(https?:)?\/\//", "*", $disabledUrl);
523
524 if (preg_match('/^' . self::wildcardToRegex($disabledUrlModified) . '$/', $url)) {
525 return false; // don't cache disabled URLs
526 }
527 }
528 }
529 }
530
531 return true;
532 }
533
534 public function isAllowedRequest($allowServiceRequests = false) {
535 if (($this->isAJAXRequest() && !$this->isAllowedAJAX()) || !($this->isRequestMethod("GET") || $this->isRequestMethod("HEAD"))) {// TODO: Allow URLs which match a pattern in the AJAX URL whitelist
536 return false; // don't cache ajax or not GET requests
537 }
538
539 if (!$allowServiceRequests && isset($_SERVER["HTTP_X_NITROPACK_REQUEST"])) { // Skip requests coming from NitroPack
540 return false;
541 }
542
543 if (isset($_GET["nonitro"])) { // Skip requests having ?nonitro
544 return false;
545 }
546
547 if (!$this->isAllowedBrowser()) {
548 return false;
549 }
550
551 if (isset($this->config->ExcludedCookies) && $this->config->ExcludedCookies->Status) {
552 foreach ($this->config->ExcludedCookies->Cookies as $cookieExclude) {
553 foreach (self::getCookies() as $cookieName => $cookieValue) {
554 if (preg_match('/^' . self::wildcardToRegex($cookieExclude->name) . '$/', $cookieName)) {
555 if (count($cookieExclude->values) == 0) {
556 return false; // no excluded cookie values entered, reject all values
557 } else {
558 foreach ($cookieExclude->values as $val) {
559 if (preg_match('/^' . self::wildcardToRegex($val) . '$/', $cookieValue)) {
560 return false;
561 }
562 }
563 }
564 }
565 }
566 }
567 }
568
569 return true;
570 }
571
572 public function isAllowedBrowser() {
573 if (empty($_SERVER["HTTP_USER_AGENT"])) return true;
574
575 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
576 return false;
577 }
578
579 return true;
580 }
581
582 public function getScheme() {
583 return $this->isSecure() ? 'https://' : 'http://';
584 }
585
586 public function isSecure() {
587 return (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && in_array("https", array_map("strtolower", array_map("trim", explode(",", $_SERVER['HTTP_X_FORWARDED_PROTO']))))) ||
588 (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
589 (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) ||
590 (!empty($_SERVER['HTTP_SSL_FLAG']) && $_SERVER['HTTP_SSL_FLAG'] == 'SSL');
591 }
592
593 public function isAJAXRequest() {
594 return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
595 }
596
597 public function isRequestMethod($method) {
598 return empty($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] == $method;
599 }
600
601 public function isAllowedAJAX() {
602 if (!$this->pageCache->getParent()) return false;
603 if (!$this->pageCache->getParent()->hasCache() || $this->pageCache->getParent()->hasExpired($this->config->PageCache->ExpireTime)) return false;
604 return true;
605 }
606
607 public function isAllowedAJAXUrl($url) {
608 if ($this->config->AjaxURLs->Status) {
609 if (!empty($this->config->AjaxURLs->URLs)) {
610 foreach ($this->config->AjaxURLs->URLs as $ajaxUrl) {
611 $ajaxUrlModified = preg_replace("/^(https?:)?\/\//", "*", $ajaxUrl);
612 if (preg_match('/^' . self::wildcardToRegex($ajaxUrlModified) . '$/', $url)) {
613 return true;
614 }
615 }
616 return false;
617 }
618 }
619 return false;
620 }
621
622 public function isCacheAllowed() {
623 return $this->isAllowedRequest() && $this->isAllowedUrl($this->url);
624 }
625
626 public function purgeLocalUrlCache($url) {
627 $url = $this->normalizeUrl($url);
628 $this->purgeProxyCache($url);
629 $localResult = true;
630 $cacheDir = $this->getCacheDir();
631 $knownDeviceTypes = Device::getKnownTypes();
632 foreach ($knownDeviceTypes as $deviceType) {
633 $urlDir = PageCache::getUrlDir($cacheDir . "/" . $deviceType, $url);
634 $invalidatedUrlDir = PageCache::getUrlDir($cacheDir . "/" . $deviceType, $url, true);
635 $localResult &= Filesystem::deleteDir($urlDir);
636 $localResult &= Filesystem::deleteDir($invalidatedUrlDir);
637 }
638 return $localResult;
639 }
640
641 public function invalidateLocalUrlCache($url) {
642 $url = $this->normalizeUrl($url);
643 $this->purgeProxyCache($url);
644 $localResult = true;
645 $cacheDir = $this->getCacheDir();
646 $knownDeviceTypes = Device::getKnownTypes();
647 foreach ($knownDeviceTypes as $deviceType) {
648 $urlDir = PageCache::getUrlDir($cacheDir . "/" . $deviceType, $url);
649 $urlDirInvalid = PageCache::getUrlDir($cacheDir . "/" . $deviceType, $url, true);
650
651 $this->invalidateDir($urlDir, $urlDirInvalid);
652 }
653 return $localResult;
654 }
655
656 public function invalidateLocalCache() {
657 $this->purgeProxyCache();
658 $this->config->LastFetch = 0;
659 $this->setConfig($this->config);
660
661 $cacheDir = $this->getCacheDir();
662 $knownDeviceTypes = Device::getKnownTypes();
663 foreach ($knownDeviceTypes as $deviceType) {
664 $deviceTypeDir = $cacheDir . "/" . $deviceType;
665 Filesystem::dirForeach($deviceTypeDir, function($urlDir) {
666 if (substr($urlDir, -2) !== "_i") {
667 $this->invalidateDir($urlDir, $urlDir . "_i");
668 }
669 });
670 }
671 return true;
672 }
673
674 private function invalidateDir($urlDir, $urlDirInvalid) {
675 if (Filesystem::fileExists($urlDirInvalid)) {
676 Filesystem::dirForeach($urlDir, function($file) use ($urlDirInvalid) {
677 Filesystem::rename($file, $urlDirInvalid . "/" . basename($file));
678 });
679
680 Filesystem::deleteDir($urlDir);
681 } else {
682 Filesystem::rename($urlDir, $urlDirInvalid);
683 }
684 Filesystem::touch($urlDirInvalid);
685 }
686
687 public function integrationUrl($widget, $version = null) {
688 $integration = new IntegrationUrl($widget, $this->siteId, $this->siteSecret, $version);
689
690 return $integration->getUrl();
691 }
692
693 public function embedJsUrl() {
694 $embedjs = new Url\Embedjs();
695
696 return $embedjs->getUrl();
697 }
698
699 public function enableSafeMode() {
700 $this->api->safe_mode->enable();
701 $this->fetchConfig();
702 }
703
704 public function disableSafeMode() {
705 $this->api->safe_mode->disable();
706 $this->fetchConfig();
707 }
708
709 private function loadConfig() {
710 $file = $this->getConfigFile();
711
712 $config = array();
713 if (Filesystem::fileExists($file) || $this->fetchConfig()) {
714 $config = json_decode(Filesystem::fileGetContents($file));
715 if (empty($config->SDKVersion) || $config->SDKVersion !== NitroPack::VERSION || empty($config->LastFetch) || time() - $config->LastFetch >= $this->configTTL) {
716 if ($this->getHealthStatus() === HealthStatus::HEALTHY) {
717 if ($this->fetchConfig()) {
718 $config = json_decode(Filesystem::fileGetContents($file));
719 } else {
720 throw new NoConfigException("Can't load config file");
721 }
722 }
723 }
724 $this->config = $config;
725 } else {
726 throw new NoConfigException("Can't load config file");
727 }
728 }
729
730 private function getConfigFile() {
731 $configFile = $this->configFile;
732
733 $filename = array_pop($configFile);
734
735 $filename = $this->siteId . '-' . $filename;
736
737 array_push($configFile, $filename);
738 array_unshift($configFile, $this->dataDir);
739
740 return Filesystem::getOsPath($configFile);
741 }
742
743 private function lockPageCache() {
744 $filename = $this->getPageCacheLockFilename();
745
746 if (Filesystem::fileExists($filename)) {
747 $sem = 1 + (int)Filesystem::fileGetContents($filename);
748 } else {
749 $sem = 1;
750 }
751
752 return !!Filesystem::filePutContents($filename, $sem);
753 }
754
755 private function unlockPageCache() {
756 $filename = $this->getPageCacheLockFilename();
757
758 if (Filesystem::fileExists($filename)) {
759 $sem = (int)Filesystem::fileGetContents($filename);
760
761 $sem--;
762
763 if ($sem <= 0) {
764 return !!Filesystem::deleteFile($filename);
765 } else {
766 return !!Filesystem::filePutContents($filename, $sem);
767 }
768 }
769
770 return false;
771 }
772
773 private function isPageCacheLocked() {
774 $filename = $this->getPageCacheLockFilename();
775
776 if (!Filesystem::fileExists($filename)) {
777 return false;
778 } else {
779 if (time() - Filesystem::fileMTime($filename) <= self::PAGECACHE_LOCK_EXPIRATION_TIME) {
780 return true;
781 } else {
782 Filesystem::deleteFile($filename);
783
784 return false;
785 }
786 }
787
788 // We should never get here, so consider this a default return value in case of future changes
789 return false;
790 }
791
792 private function getPageCacheLockFilename() {
793 $pageCacheLockFile = $this->pageCacheLockFile;
794 array_unshift($pageCacheLockFile, $this->dataDir);
795 return Filesystem::getOsPath($pageCacheLockFile);
796 }
797
798 private function normalizeUrl($url) {
799 $urlObj = new \NitroPack\Url($url);
800 return $urlObj->getNormalized();
801 }
802 }
803