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