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