NitroPack - Version 1.5.0

Version Description

  • New Feature: Compatibility with Cloudflare APO
  • Improvement: Better resilience to network related issues
  • Improvement: Faster cache purge
  • Improvement: Overall stability improvements
  • Deprecation: Removed the Invalidate All Cache option. The invalidate action is much better suited for single page invalidations.
Download this release

Release Info

Developer nitropack
Plugin Icon 128x128 NitroPack
Version 1.5.0
Comparing to
See all releases

Code changes from version 1.4.1 to 1.5.0

cf-helper.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class NitroPack_CF_Helper extends \CF\WordPress\Hooks {
4
+ public function isApoEnabled() {
5
+ return $this->isAutomaticPlatformOptimizationEnabled();
6
+ }
7
+
8
+ public function purgeUrl($url) {
9
+ $wpDomainList = $this->integrationAPI->getDomainList();
10
+ if (!count($wpDomainList)) {
11
+ return;
12
+ }
13
+ $wpDomain = $wpDomainList[0];
14
+ $urls = [$url];
15
+
16
+ $zoneTag = $this->api->getZoneTag($wpDomain);
17
+
18
+ if (isset($zoneTag) && !empty($urls)) {
19
+ $chunks = array_chunk($urls, 30);
20
+
21
+ foreach ($chunks as $chunk) {
22
+ $this->api->zonePurgeFiles($zoneTag, $chunk);
23
+ }
24
+ }
25
+ }
26
+ }
constants.php CHANGED
@@ -6,11 +6,13 @@ function nitropack_trailingslashit($string) {
6
  return rtrim( $string, '/\\' ) . '/';
7
  }
8
 
9
- define( 'NITROPACK_VERSION', '1.4.1' );
10
  define( 'NITROPACK_OPTION_GROUP', 'nitropack' );
11
  define( 'NITROPACK_DATA_DIR', nitropack_trailingslashit(WP_CONTENT_DIR) . 'nitropack' );
12
  define( 'NITROPACK_CONFIG_FILE', nitropack_trailingslashit(NITROPACK_DATA_DIR) . 'config.json' );
13
  define( 'NITROPACK_PLUGIN_DIR', nitropack_trailingslashit(dirname(__FILE__)));
 
 
14
 
15
  if (!defined("NITROPACK_USE_REDIS")) define("NITROPACK_USE_REDIS", false); // Set this to true to enable storing cache in Redis
16
  if (!defined("NITROPACK_REDIS_HOST")) define("NITROPACK_REDIS_HOST", "127.0.0.1"); // Set this to the IP of your Redis server
6
  return rtrim( $string, '/\\' ) . '/';
7
  }
8
 
9
+ define( 'NITROPACK_VERSION', '1.5.0' );
10
  define( 'NITROPACK_OPTION_GROUP', 'nitropack' );
11
  define( 'NITROPACK_DATA_DIR', nitropack_trailingslashit(WP_CONTENT_DIR) . 'nitropack' );
12
  define( 'NITROPACK_CONFIG_FILE', nitropack_trailingslashit(NITROPACK_DATA_DIR) . 'config.json' );
13
  define( 'NITROPACK_PLUGIN_DIR', nitropack_trailingslashit(dirname(__FILE__)));
14
+ define( 'NITROPACK_INTEGRATIONS_ACTION', "nitropack_integrations_ready");
15
+ define( 'NITROPACK_HEARTBEAT_INTERVAL', 60*5); // 5min
16
 
17
  if (!defined("NITROPACK_USE_REDIS")) define("NITROPACK_USE_REDIS", false); // Set this to true to enable storing cache in Redis
18
  if (!defined("NITROPACK_REDIS_HOST")) define("NITROPACK_REDIS_HOST", "127.0.0.1"); // Set this to the IP of your Redis server
diagnostics.php CHANGED
@@ -20,6 +20,23 @@ function npdiag_helper_trailingslashit($string) {
20
  return rtrim( $string, '/\\' ) . '/';
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  function npdiag_get_general_info() {
24
  global $wp_version;
25
  if (null !== $nitro = get_nitropack_sdk()) {
@@ -32,17 +49,18 @@ function npdiag_get_general_info() {
32
  } else {
33
  $probe_result = 'Error: Cannot get SDK instance';
34
  }
35
-
36
  $info = array(
37
  'Nitro_WP_version' => !empty($wp_version) ? $wp_version : get_bloginfo('version'),
38
  'Nitro_Version' => defined('NITROPACK_VERSION') ? NITROPACK_VERSION : 'Undefined',
39
  'Nitro_API_Connection' => $probe_result,
40
  'Nitro_SDK_Version' => defined('NitroPack\SDK\Nitropack::VERSION') ? NitroPack\SDK\Nitropack::VERSION : 'Undefined',
 
41
  'Advanced_Cache_Version' => defined('NITROPACK_ADVANCED_CACHE_VERSION') ? NITROPACK_ADVANCED_CACHE_VERSION : 'Undefined',
42
  'Nitro_Absolute_Path' => defined('ABSPATH') ? ABSPATH : 'Undefined',
43
  'Nitro_Plugin_Direcotry' => defined('NITROPACK_PLUGIN_DIR') ? NITROPACK_PLUGIN_DIR : dirname(__FILE__),
44
  'Nitro_Data_Directory' => defined('NITROPACK_DATA_DIR') ? NITROPACK_DATA_DIR : 'Undefined',
45
- 'Nitro_Config_File' => defined('NITROPACK_CONFIG_FILE') ? NITROPACK_CONFIG_FILE : 'Undefined'
 
46
  );
47
 
48
  if (defined("NITROPACK_VERSION") && defined("NITROPACK_ADVANCED_CACHE_VERSION") && NITROPACK_VERSION == NITROPACK_ADVANCED_CACHE_VERSION && nitropack_is_dropin_cache_allowed()) {
20
  return rtrim( $string, '/\\' ) . '/';
21
  }
22
 
23
+ function npdiag_helper_compare_webhooks($nitro_sdk) {
24
+ try {
25
+ $siteConfig = nitropack_get_site_config();
26
+ if (!empty($siteConfig['siteId'])) {
27
+ $WHToken = nitropack_generate_webhook_token($siteConfig['siteId']);
28
+ $constructedWH = new \NitroPack\Url(strtolower(get_home_url())) . '?nitroWebhook=config&token=' . $WHToken;
29
+ $storedWH = $nitro_sdk->getApi()->getWebhook("config");
30
+ $matchResult = ($constructedWH == $storedWH) ? 'OK' : 'Warning: Webhooks do not match this site';
31
+ } else {
32
+ $matchResult = 'An empty SiteID was returned from site config.';
33
+ }
34
+ return $matchResult;
35
+ } catch (\Exception $e) {
36
+ return $e->getMessage();
37
+ }
38
+ }
39
+
40
  function npdiag_get_general_info() {
41
  global $wp_version;
42
  if (null !== $nitro = get_nitropack_sdk()) {
49
  } else {
50
  $probe_result = 'Error: Cannot get SDK instance';
51
  }
 
52
  $info = array(
53
  'Nitro_WP_version' => !empty($wp_version) ? $wp_version : get_bloginfo('version'),
54
  'Nitro_Version' => defined('NITROPACK_VERSION') ? NITROPACK_VERSION : 'Undefined',
55
  'Nitro_API_Connection' => $probe_result,
56
  'Nitro_SDK_Version' => defined('NitroPack\SDK\Nitropack::VERSION') ? NitroPack\SDK\Nitropack::VERSION : 'Undefined',
57
+ 'Nitro_WP_Cache' => defined('WP_CACHE') ? (WP_CACHE ? 'OK for drop-in' : 'Turned off') : 'Undefined',
58
  'Advanced_Cache_Version' => defined('NITROPACK_ADVANCED_CACHE_VERSION') ? NITROPACK_ADVANCED_CACHE_VERSION : 'Undefined',
59
  'Nitro_Absolute_Path' => defined('ABSPATH') ? ABSPATH : 'Undefined',
60
  'Nitro_Plugin_Direcotry' => defined('NITROPACK_PLUGIN_DIR') ? NITROPACK_PLUGIN_DIR : dirname(__FILE__),
61
  'Nitro_Data_Directory' => defined('NITROPACK_DATA_DIR') ? NITROPACK_DATA_DIR : 'Undefined',
62
+ 'Nitro_Config_File' => defined('NITROPACK_CONFIG_FILE') ? NITROPACK_CONFIG_FILE : 'Undefined',
63
+ 'Nitro_Webhooks' => $nitro ? npdiag_helper_compare_webhooks($nitro) : 'Error: Cannot get SDK instance'
64
  );
65
 
66
  if (defined("NITROPACK_VERSION") && defined("NITROPACK_ADVANCED_CACHE_VERSION") && NITROPACK_VERSION == NITROPACK_ADVANCED_CACHE_VERSION && nitropack_is_dropin_cache_allowed()) {
functions.php CHANGED
@@ -804,7 +804,7 @@ function nitropack_is_advanced_cache_allowed() {
804
 
805
  function nitropack_admin_notices() {
806
  if (!empty($_COOKIE["nitropack_after_activate_notice"])) {
807
- nitropack_print_notice("info", "<script>document.cookie = 'nitropack_after_activate_notice=1; expires=Thu, 01 Jan 1970 00:00:01 GMT;';</script>NitroPack has been successfully activated, but it is not connected yet. Please go to <a href='" . admin_url( 'options-general.php?page=nitropack' ) . "'>its settings</a> page to connect it in order to start opitmizing your site!");
808
  }
809
 
810
  nitropack_print_hosting_notice();
@@ -821,7 +821,7 @@ function nitropack_print_hosting_notice() {
821
  $documentedHostingSetups = array(
822
  "flywheel" => array(
823
  "name" => "Flywheel",
824
- "helpUrl" => "https://help.nitropack.io/en/articles/3326013-flywheel-hosting-configuration-for-nitropack"
825
  ),
826
  "wpengine" => array(
827
  "name" => "WP Engine",
@@ -1008,7 +1008,7 @@ function nitropack_sdk_invalidate($url = NULL, $tag = NULL, $reason = NULL) {
1008
  do_action('nitropack_integration_purge_all');
1009
  }
1010
  } catch (\Exception $e) {
1011
- // Exception while signaling our 3rd party integration addons to purge their cache
1012
  }
1013
  } catch (\Exception $e) {
1014
  return false;
@@ -1020,6 +1020,124 @@ function nitropack_sdk_invalidate($url = NULL, $tag = NULL, $reason = NULL) {
1020
  return false;
1021
  }
1022
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1023
  function nitropack_sdk_purge($url = NULL, $tag = NULL, $reason = NULL, $type = \NitroPack\SDK\PurgeType::COMPLETE) {
1024
  if (null !== $nitro = get_nitropack_sdk()) {
1025
  try {
@@ -1034,6 +1152,7 @@ function nitropack_sdk_purge($url = NULL, $tag = NULL, $reason = NULL, $type = \
1034
  }
1035
  }
1036
 
 
1037
  $nitro->purgeCache($url, $tag, $type, $reason);
1038
 
1039
  try {
@@ -1047,7 +1166,7 @@ function nitropack_sdk_purge($url = NULL, $tag = NULL, $reason = NULL, $type = \
1047
  do_action('nitropack_integration_purge_all');
1048
  }
1049
  } catch (\Exception $e) {
1050
- // Exception while signaling our 3rd party integration addons to purge their cache
1051
  }
1052
  } catch (\Exception $e) {
1053
  return false;
@@ -1151,6 +1270,7 @@ function nitropack_log_invalidate($url = NULL, $tag = NULL, $reason = NULL) {
1151
  foreach ($tag as $tagSingle) {
1152
  nitropack_log_invalidate($url, $tagSingle, $reason);
1153
  }
 
1154
  }
1155
 
1156
  $keyBase = "";
@@ -1946,6 +2066,10 @@ function nitropack_warmup_stats() {
1946
  try {
1947
  $stats = $nitro->getApi()->getWarmupStats();
1948
  } catch (\Exception $e) {
 
 
 
 
1949
  }
1950
 
1951
  nitropack_json_and_exit(array(
@@ -2042,7 +2166,7 @@ function nitropack_update_current_blog_config($siteId, $siteSecret, $blogId, $en
2042
  "hosting" => $hosting,
2043
  "alwaysBuffer" => $alwaysBuffer,
2044
  "isEzoicActive" => nitropack_is_ezoic_active(),
2045
- "isNginxHelperActive" => nitropack_is_nginx_helper_active(),
2046
  "isDlmActive" => nitropack_is_dlm_active(),
2047
  "dlm_downloading_url" => nitropack_is_dlm_active() ? nitropack_dlm_downloading_url() : NULL,
2048
  "dlm_download_endpoint" => nitropack_is_dlm_active() ? nitropack_dlm_download_endpoint() : NULL,
@@ -2173,69 +2297,73 @@ function nitropack_handle_request($servedFrom = "unknown") {
2173
  $siteConfig = nitropack_get_site_config();
2174
  if ( $siteConfig && null !== $nitro = get_nitropack_sdk($siteConfig["siteId"], $siteConfig["siteSecret"]) ) {
2175
  if (is_valid_nitropack_webhook()) {
2176
- if (did_action($np_integrationSetupEvent)) {
2177
  nitropack_handle_webhook();
2178
  } else {
2179
- add_action($np_integrationSetupEvent, 'nitropack_handle_webhook');
 
 
 
 
 
 
 
 
 
 
 
 
2180
  }
2181
  } else {
2182
- if (is_valid_nitropack_beacon()) {
2183
- if (did_action($np_integrationSetupEvent)) {
2184
- nitropack_handle_beacon();
2185
- } else {
2186
- add_action($np_integrationSetupEvent, 'nitropack_handle_beacon');
 
 
 
2187
  }
2188
- } else {
2189
- $GLOBALS["NitroPack.instance"] = $nitro;
2190
- if (nitropack_passes_cookie_requirements()) {
2191
- // Check whether the current URL is cacheable
2192
- // If this is an AJAX request, check whether the referer is cachable - this is needed for cases when NitroPack's "Enabled URLs" option is being used to whitelist certain URLs.
2193
- // If we are not checking the referer, the AJAX requests on these pages can fail.
2194
- $urlToCheck = nitropack_is_ajax() && !empty($_SERVER["HTTP_REFERER"]) ? $_SERVER["HTTP_REFERER"] : $nitro->getUrl();
2195
- if ($nitro->isAllowedUrl($urlToCheck)) {
2196
- add_filter( 'nonce_life', 'nitropack_extend_nonce_life' );
2197
  }
2198
 
2199
- if ($nitro->isCacheAllowed()) {
2200
- if (!empty($siteConfig["compression"])) {
2201
- $nitro->enableCompression();
 
 
 
 
2202
  }
2203
 
2204
- if ($nitro->hasLocalCache()) {
2205
- $cacheControlOverride = defined("NITROPACK_CACHE_CONTROL_OVERRIDE") ? NITROPACK_CACHE_CONTROL_OVERRIDE : NULL;
2206
- if (!$cacheControlOverride && !empty($siteConfig["hosting"]) && in_array($siteConfig["hosting"], array("pagely", "siteground")) ) {
2207
- $cacheControlOverride = "public,max-age=30";
2208
- if ($siteConfig["hosting"] == "siteground") {
2209
- header('X-Cache-Enabled: True');
2210
- }
2211
- }
2212
 
2213
- if ($cacheControlOverride) {
2214
- header('Cache-Control: ' . $cacheControlOverride);
2215
- }
 
 
 
 
 
 
 
 
 
2216
 
2217
- header('X-Nitro-Cache: HIT');
 
 
2218
  header('X-Nitro-Cache-From: ' . $servedFrom);
2219
  $nitro->pageCache->readfile();
2220
  exit;
2221
  } else {
2222
- // We need the following if..else block to handle bot requests which will not be firing our beacon
2223
- if (nitropack_is_warmup_request()) {
2224
- $nitro->hasRemoteCache("default"); // Only ping the API letting our service know that this page must be cached.
2225
- exit; // No need to continue handling this request. The response is not important.
2226
- } else if (nitropack_is_lighthouse_request() || nitropack_is_gtmetrix_request() || nitropack_is_pingdom_request()) {
2227
- $nitro->hasRemoteCache("default"); // Ping the API letting our service know that this page must be cached.
2228
- }
2229
-
2230
- $nitro->pageCache->useInvalidated(true);
2231
- if ($nitro->hasLocalCache()) {
2232
- header('X-Nitro-Cache: STALE');
2233
- header('X-Nitro-Cache-From: ' . $servedFrom);
2234
- $nitro->pageCache->readfile();
2235
- exit;
2236
- } else {
2237
- $nitro->pageCache->useInvalidated(false);
2238
- }
2239
  }
2240
  }
2241
  }
@@ -2255,7 +2383,7 @@ function nitropack_is_dropin_cache_allowed() {
2255
 
2256
  function nitropack_get_integration_setup_event() {
2257
  $siteConfig = nitropack_get_site_config();
2258
- if ($siteConfig && !empty($siteConfig["isNginxHelperActive"])) {
2259
  return "plugins_loaded";
2260
  }
2261
 
@@ -2356,7 +2484,7 @@ function nitropack_admin_bar_script($hook) {
2356
  wp_localize_script( 'nitropack_admin_bar_menu_script', 'frontendajax', array( 'ajaxurl' => admin_url( 'admin-ajax.php' )));
2357
  }
2358
 
2359
- function enqueue_load_fa() {
2360
  wp_enqueue_style( 'load-fa', plugin_dir_url(__FILE__) . 'view/stylesheet/fontawesome/font-awesome.min.css?np_v=' . NITROPACK_VERSION);
2361
  }
2362
 
@@ -2466,7 +2594,7 @@ function nitropack_plugin_notices() {
2466
  } else {
2467
  if (
2468
  (!array_key_exists("isEzoicActive", $siteConfig) || $siteConfig["isEzoicActive"] !== nitropack_is_ezoic_active()) ||
2469
- (!array_key_exists("isNginxHelperActive", $siteConfig) || $siteConfig["isNginxHelperActive"] !== nitropack_is_nginx_helper_active()) ||
2470
  (!array_key_exists("isDlmActive", $siteConfig) || $siteConfig["isDlmActive"] !== nitropack_is_dlm_active())
2471
  ) {
2472
  if (!nitropack_update_current_blog_config($siteId, $siteSecret, $blogId)) {
@@ -2511,6 +2639,10 @@ function nitropack_plugin_notices() {
2511
  return $npPluginNotices;
2512
  }
2513
 
 
 
 
 
2514
  function nitropack_display_admin_notices() {
2515
  $noticesArray = nitropack_plugin_notices();
2516
  foreach($noticesArray as $type => $notices){
804
 
805
  function nitropack_admin_notices() {
806
  if (!empty($_COOKIE["nitropack_after_activate_notice"])) {
807
+ nitropack_print_notice("info", "<script>document.cookie = 'nitropack_after_activate_notice=1; expires=Thu, 01 Jan 1970 00:00:01 GMT;';</script>NitroPack has been successfully activated, but it is not connected yet. Please go to <a href='" . admin_url( 'options-general.php?page=nitropack' ) . "'>its settings</a> page to connect it in order to start optimizing your site!");
808
  }
809
 
810
  nitropack_print_hosting_notice();
821
  $documentedHostingSetups = array(
822
  "flywheel" => array(
823
  "name" => "Flywheel",
824
+ "helpUrl" => "https://help.nitropack.io/en/articles/4280090-delayed-content-updates-only-for-flywheel-hosting-users"
825
  ),
826
  "wpengine" => array(
827
  "name" => "WP Engine",
1008
  do_action('nitropack_integration_purge_all');
1009
  }
1010
  } catch (\Exception $e) {
1011
+ // Exception while signaling 3rd party integration addons to purge their cache
1012
  }
1013
  } catch (\Exception $e) {
1014
  return false;
1020
  return false;
1021
  }
1022
 
1023
+ /* Start Heartbeat Related Functions */
1024
+ function nitropack_print_heartbeat_script() {
1025
+ if (!nitropack_is_optimizer_request() && !nitropack_is_heartbeat_running() && time() - nitropack_last_heartbeat() > NITROPACK_HEARTBEAT_INTERVAL) {
1026
+ if (defined("NITROPACK_HEARTBEAT_PRINTED")) return;
1027
+ define("NITROPACK_HEARTBEAT_PRINTED", true);
1028
+ echo nitropack_get_heartbeat_script();
1029
+ }
1030
+ }
1031
+
1032
+ function nitropack_get_heartbeat_script() {
1033
+ $siteConfig = nitropack_get_site_config();
1034
+ if ($siteConfig && !empty($siteConfig["siteId"]) && !empty($siteConfig["siteSecret"])) {
1035
+ if (null !== $nitro = get_nitropack_sdk($siteConfig["siteId"], $siteConfig["siteSecret"]) ) {
1036
+ if (is_admin()) {
1037
+ $credentials = "same-origin";
1038
+ } else {
1039
+ $credentials = "omit";
1040
+ }
1041
+
1042
+ return "
1043
+ <script nitro-exclude>
1044
+ var heartbeatData = new FormData(); heartbeatData.append('nitroHeartbeat', '1');
1045
+ fetch(location.href, {method: 'POST', body: heartbeatData, credentials: '$credentials'});
1046
+ </script>";
1047
+ }
1048
+ }
1049
+ }
1050
+
1051
+ function is_valid_nitropack_heartbeat() {
1052
+ return !empty($_POST['nitroHeartbeat']);
1053
+ }
1054
+
1055
+ function nitropack_get_heartbeat_file() {
1056
+ if (null !== $nitro = get_nitropack_sdk()) {
1057
+ return nitropack_trailingslashit($nitro->getCacheDir()) . "heartbeat";
1058
+ } else {
1059
+ return nitropack_trailingslashit(NITROPACK_DATA_DIR) . "heartbeat";
1060
+ }
1061
+ }
1062
+
1063
+ function nitropack_last_heartbeat() {
1064
+ if (null !== $nitro = get_nitropack_sdk()) {
1065
+ try {
1066
+ return \NitroPack\SDK\Filesystem::fileMTime(nitropack_get_heartbeat_file());
1067
+ } catch (\Exception $e) {
1068
+ return 0;
1069
+ }
1070
+ }
1071
+ }
1072
+
1073
+ function nitropack_is_heartbeat_running() {
1074
+ if (null !== $nitro = get_nitropack_sdk()) {
1075
+ try {
1076
+ $heartbeatContent = \NitroPack\SDK\Filesystem::fileGetContents(nitropack_get_heartbeat_file());
1077
+ if ($heartbeatContent == "1") {
1078
+ return time() - nitropack_last_heartbeat() < NITROPACK_HEARTBEAT_INTERVAL;
1079
+ }
1080
+ } catch (\Exception $e) {
1081
+ return false;
1082
+ }
1083
+ }
1084
+ }
1085
+
1086
+ function nitropack_handle_heartbeat() {
1087
+ session_write_close();
1088
+ if (null !== $nitro = get_nitropack_sdk()) {
1089
+ try {
1090
+ \NitroPack\SDK\Filesystem::filePutContents(nitropack_get_heartbeat_file(), 1);
1091
+ if (nitropack_healthcheck()) {
1092
+ nitropack_flush_backlog();
1093
+ }
1094
+ nitropack_cache_cleanup();
1095
+ \NitroPack\SDK\Filesystem::filePutContents(nitropack_get_heartbeat_file(), 0);
1096
+ } catch (\Exception $e) {
1097
+ return false;
1098
+ }
1099
+ }
1100
+ exit;
1101
+ }
1102
+
1103
+ function nitropack_healthcheck() {
1104
+ if (null !== $nitro = get_nitropack_sdk()) {
1105
+ return $nitro->getHealthStatus() == \NitroPack\SDK\HealthStatus::HEALTHY || $nitro->checkHealthStatus() == \NitroPack\SDK\HealthStatus::HEALTHY;
1106
+ }
1107
+ return true;
1108
+ }
1109
+
1110
+ function nitropack_flush_backlog() {
1111
+ if (null !== $nitro = get_nitropack_sdk()) {
1112
+ try {
1113
+ if ($nitro->backlog->exists()) {
1114
+ $nitro->backlog->replay(30);
1115
+ }
1116
+ } catch (\Exception $e) {
1117
+ return false;
1118
+ }
1119
+ }
1120
+ return true;
1121
+ }
1122
+
1123
+ function nitropack_cache_cleanup() {
1124
+ if (null !== $nitro = get_nitropack_sdk()) {
1125
+ $cacheDirParent = dirname($nitro->getCacheDir());
1126
+ $entries = scandir($cacheDirParent);
1127
+ foreach ($entries as $entry) {
1128
+ if (strpos($entry, ".stale.") !== false) {
1129
+ $cacheDir = nitropack_trailingslashit($cacheDirParent) . $entry;
1130
+ try {
1131
+ \NitroPack\SDK\Filesystem::deleteDir($cacheDir);
1132
+ } catch (\Exception $e) {
1133
+ // TODO: Log this
1134
+ }
1135
+ }
1136
+ }
1137
+ }
1138
+ }
1139
+ /* End Heartbeat Related Functions */
1140
+
1141
  function nitropack_sdk_purge($url = NULL, $tag = NULL, $reason = NULL, $type = \NitroPack\SDK\PurgeType::COMPLETE) {
1142
  if (null !== $nitro = get_nitropack_sdk()) {
1143
  try {
1152
  }
1153
  }
1154
 
1155
+ $nitro->purgeLocalCache(true);
1156
  $nitro->purgeCache($url, $tag, $type, $reason);
1157
 
1158
  try {
1166
  do_action('nitropack_integration_purge_all');
1167
  }
1168
  } catch (\Exception $e) {
1169
+ // Exception while signaling 3rd party integration addons to purge their cache
1170
  }
1171
  } catch (\Exception $e) {
1172
  return false;
1270
  foreach ($tag as $tagSingle) {
1271
  nitropack_log_invalidate($url, $tagSingle, $reason);
1272
  }
1273
+ return;
1274
  }
1275
 
1276
  $keyBase = "";
2066
  try {
2067
  $stats = $nitro->getApi()->getWarmupStats();
2068
  } catch (\Exception $e) {
2069
+ nitropack_json_and_exit(array(
2070
+ "type" => "error",
2071
+ "message" => "Error! There was an error while fetching warmup stats!"
2072
+ ));
2073
  }
2074
 
2075
  nitropack_json_and_exit(array(
2166
  "hosting" => $hosting,
2167
  "alwaysBuffer" => $alwaysBuffer,
2168
  "isEzoicActive" => nitropack_is_ezoic_active(),
2169
+ "isLateIntegrationInitRequired" => nitropack_is_late_integration_init_required(),
2170
  "isDlmActive" => nitropack_is_dlm_active(),
2171
  "dlm_downloading_url" => nitropack_is_dlm_active() ? nitropack_dlm_downloading_url() : NULL,
2172
  "dlm_download_endpoint" => nitropack_is_dlm_active() ? nitropack_dlm_download_endpoint() : NULL,
2297
  $siteConfig = nitropack_get_site_config();
2298
  if ( $siteConfig && null !== $nitro = get_nitropack_sdk($siteConfig["siteId"], $siteConfig["siteSecret"]) ) {
2299
  if (is_valid_nitropack_webhook()) {
2300
+ if (did_action(NITROPACK_INTEGRATIONS_ACTION)) {
2301
  nitropack_handle_webhook();
2302
  } else {
2303
+ add_action(NITROPACK_INTEGRATIONS_ACTION, 'nitropack_handle_webhook');
2304
+ }
2305
+ } else if (is_valid_nitropack_beacon()) {
2306
+ if (did_action(NITROPACK_INTEGRATIONS_ACTION)) {
2307
+ nitropack_handle_beacon();
2308
+ } else {
2309
+ add_action(NITROPACK_INTEGRATIONS_ACTION, 'nitropack_handle_beacon');
2310
+ }
2311
+ } else if (is_valid_nitropack_heartbeat()) {
2312
+ if (did_action(NITROPACK_INTEGRATIONS_ACTION)) {
2313
+ nitropack_handle_heartbeat();
2314
+ } else {
2315
+ add_action(NITROPACK_INTEGRATIONS_ACTION, 'nitropack_handle_heartbeat');
2316
  }
2317
  } else {
2318
+ $GLOBALS["NitroPack.instance"] = $nitro;
2319
+ if (nitropack_passes_cookie_requirements()) {
2320
+ // Check whether the current URL is cacheable
2321
+ // If this is an AJAX request, check whether the referer is cachable - this is needed for cases when NitroPack's "Enabled URLs" option is being used to whitelist certain URLs.
2322
+ // If we are not checking the referer, the AJAX requests on these pages can fail.
2323
+ $urlToCheck = nitropack_is_ajax() && !empty($_SERVER["HTTP_REFERER"]) ? $_SERVER["HTTP_REFERER"] : $nitro->getUrl();
2324
+ if ($nitro->isAllowedUrl($urlToCheck)) {
2325
+ add_filter( 'nonce_life', 'nitropack_extend_nonce_life' );
2326
  }
2327
+
2328
+ if ($nitro->isCacheAllowed()) {
2329
+ if (!empty($siteConfig["compression"])) {
2330
+ $nitro->enableCompression();
 
 
 
 
 
2331
  }
2332
 
2333
+ if ($nitro->hasLocalCache()) {
2334
+ $cacheControlOverride = defined("NITROPACK_CACHE_CONTROL_OVERRIDE") ? NITROPACK_CACHE_CONTROL_OVERRIDE : NULL;
2335
+ if (!$cacheControlOverride && !empty($siteConfig["hosting"]) && in_array($siteConfig["hosting"], array("pagely", "siteground")) ) {
2336
+ $cacheControlOverride = "public,max-age=30";
2337
+ if ($siteConfig["hosting"] == "siteground") {
2338
+ header('X-Cache-Enabled: True');
2339
+ }
2340
  }
2341
 
2342
+ if ($cacheControlOverride) {
2343
+ header('Cache-Control: ' . $cacheControlOverride);
2344
+ }
 
 
 
 
 
2345
 
2346
+ header('X-Nitro-Cache: HIT');
2347
+ header('X-Nitro-Cache-From: ' . $servedFrom);
2348
+ $nitro->pageCache->readfile();
2349
+ exit;
2350
+ } else {
2351
+ // We need the following if..else block to handle bot requests which will not be firing our beacon
2352
+ if (nitropack_is_warmup_request()) {
2353
+ $nitro->hasRemoteCache("default"); // Only ping the API letting our service know that this page must be cached.
2354
+ exit; // No need to continue handling this request. The response is not important.
2355
+ } else if (nitropack_is_lighthouse_request() || nitropack_is_gtmetrix_request() || nitropack_is_pingdom_request()) {
2356
+ $nitro->hasRemoteCache("default"); // Ping the API letting our service know that this page must be cached.
2357
+ }
2358
 
2359
+ $nitro->pageCache->useInvalidated(true);
2360
+ if ($nitro->hasLocalCache()) {
2361
+ header('X-Nitro-Cache: STALE');
2362
  header('X-Nitro-Cache-From: ' . $servedFrom);
2363
  $nitro->pageCache->readfile();
2364
  exit;
2365
  } else {
2366
+ $nitro->pageCache->useInvalidated(false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2367
  }
2368
  }
2369
  }
2383
 
2384
  function nitropack_get_integration_setup_event() {
2385
  $siteConfig = nitropack_get_site_config();
2386
+ if ($siteConfig && !empty($siteConfig["isLateIntegrationInitRequired"])) {
2387
  return "plugins_loaded";
2388
  }
2389
 
2484
  wp_localize_script( 'nitropack_admin_bar_menu_script', 'frontendajax', array( 'ajaxurl' => admin_url( 'admin-ajax.php' )));
2485
  }
2486
 
2487
+ function nitropack_enqueue_load_fa() {
2488
  wp_enqueue_style( 'load-fa', plugin_dir_url(__FILE__) . 'view/stylesheet/fontawesome/font-awesome.min.css?np_v=' . NITROPACK_VERSION);
2489
  }
2490
 
2594
  } else {
2595
  if (
2596
  (!array_key_exists("isEzoicActive", $siteConfig) || $siteConfig["isEzoicActive"] !== nitropack_is_ezoic_active()) ||
2597
+ (!array_key_exists("isLateIntegrationInitRequired", $siteConfig) || $siteConfig["isLateIntegrationInitRequired"] !== nitropack_is_late_integration_init_required()) ||
2598
  (!array_key_exists("isDlmActive", $siteConfig) || $siteConfig["isDlmActive"] !== nitropack_is_dlm_active())
2599
  ) {
2600
  if (!nitropack_update_current_blog_config($siteId, $siteSecret, $blogId)) {
2639
  return $npPluginNotices;
2640
  }
2641
 
2642
+ function nitropack_is_late_integration_init_required() {
2643
+ return nitropack_is_nginx_helper_active() || nitropack_is_apo_active();
2644
+ }
2645
+
2646
  function nitropack_display_admin_notices() {
2647
  $noticesArray = nitropack_plugin_notices();
2648
  foreach($noticesArray as $type => $notices){
integrations.php CHANGED
@@ -46,15 +46,34 @@ function nitropack_check_and_init_integrations() {
46
  break;
47
  }
48
 
49
- if (!empty($siteConfig["isNginxHelperActive"])) {
50
- add_action('nitropack_integration_purge_url', 'nitropack_nginx_helper_purge_url');
51
- add_action('nitropack_integration_purge_all', 'nitropack_nginx_helper_purge_all');
52
  }
53
 
54
- add_action('plugins_loaded', 'nitropack_init_late_integrations');
 
 
 
 
 
55
  }
56
 
57
  function nitropack_init_late_integrations() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  if (defined('SHORTPIXEL_AI_VERSION')) { // ShortPixel
59
  if (nitropack_is_ajax()) {
60
  if (version_compare(SHORTPIXEL_AI_VERSION, "2", ">=")) { // ShortPixel AI 2.x
@@ -73,6 +92,11 @@ function nitropack_init_late_integrations() {
73
  if (class_exists("WC_Cache_Helper")) {
74
  remove_action('template_redirect', array('WC_Cache_Helper', 'geolocation_ajax_redirect'));
75
  }
 
 
 
 
 
76
  }
77
 
78
  /** WP Engine **/
@@ -273,6 +297,37 @@ function nitropack_nginx_helper_purge_all() {
273
  return true;
274
  }
275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  /** Pagely **/
277
  function nitropack_pagely_purge_url($url) {
278
  try {
46
  break;
47
  }
48
 
49
+ if ($siteConfig && empty($siteConfig["isLateIntegrationInitRequired"])) {
50
+ do_action(NITROPACK_INTEGRATIONS_ACTION);
 
51
  }
52
 
53
+ // This is needed in order to load non-cache-related integrations like the one with ShortPixel and WooCommerce Geo Location.
54
+ if (did_action('plugins_loaded')) {
55
+ nitropack_init_late_integrations();
56
+ } else {
57
+ add_action('plugins_loaded', 'nitropack_init_late_integrations');
58
+ }
59
  }
60
 
61
  function nitropack_init_late_integrations() {
62
+ if (defined("NITROPACK_LATE_INTEGRATIONS")) return;
63
+ define("NITROPACK_LATE_INTEGRATIONS", true);
64
+
65
+ // Cache related integrations
66
+ if (nitropack_is_nginx_helper_active()) {
67
+ add_action('nitropack_integration_purge_url', 'nitropack_nginx_helper_purge_url');
68
+ add_action('nitropack_integration_purge_all', 'nitropack_nginx_helper_purge_all');
69
+ }
70
+
71
+ if (nitropack_is_apo_active()) {
72
+ add_action('nitropack_integration_purge_url', 'nitropack_apo_purge_url');
73
+ add_action('nitropack_integration_purge_all', 'nitropack_apo_purge_all');
74
+ }
75
+
76
+ // Non cache related integrations
77
  if (defined('SHORTPIXEL_AI_VERSION')) { // ShortPixel
78
  if (nitropack_is_ajax()) {
79
  if (version_compare(SHORTPIXEL_AI_VERSION, "2", ">=")) { // ShortPixel AI 2.x
92
  if (class_exists("WC_Cache_Helper")) {
93
  remove_action('template_redirect', array('WC_Cache_Helper', 'geolocation_ajax_redirect'));
94
  }
95
+
96
+ $siteConfig = nitropack_get_site_config();
97
+ if ($siteConfig && !empty($siteConfig["isLateIntegrationInitRequired"])) {
98
+ do_action(NITROPACK_INTEGRATIONS_ACTION);
99
+ }
100
  }
101
 
102
  /** WP Engine **/
297
  return true;
298
  }
299
 
300
+ // Cloudflare APO integration
301
+ function nitropack_is_apo_active() {
302
+ if (defined('CLOUDFLARE_PLUGIN_DIR')) {
303
+ require_once NITROPACK_PLUGIN_DIR . 'cf-helper.php';
304
+ $cfHelper = new NitroPack_CF_Helper();
305
+ return $cfHelper->isApoEnabled();
306
+ } else {
307
+ return false;
308
+ }
309
+ }
310
+
311
+ function nitropack_apo_purge_url($url) {
312
+ if (defined('CLOUDFLARE_PLUGIN_DIR')) {
313
+ require_once NITROPACK_PLUGIN_DIR . 'cf-helper.php';
314
+ $cfHelper = new NitroPack_CF_Helper();
315
+ return $cfHelper->purgeUrl($url);
316
+ } else {
317
+ return false;
318
+ }
319
+ }
320
+
321
+ function nitropack_apo_purge_all() {
322
+ if (defined('CLOUDFLARE_PLUGIN_DIR')) {
323
+ require_once NITROPACK_PLUGIN_DIR . 'cf-helper.php';
324
+ $cfHelper = new NitroPack_CF_Helper();
325
+ return $cfHelper->purgeCacheEverything();
326
+ } else {
327
+ return false;
328
+ }
329
+ }
330
+
331
  /** Pagely **/
332
  function nitropack_pagely_purge_url($url) {
333
  try {
main.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: NitroPack
4
  Plugin URI: https://nitropack.io/platform/wordpress
5
  Description: Everything you need for a fast website. Simple set up, easy to use, awesome support. Caching, Lazy Loading, Minification, Defer CSS/JS, CDN and more!
6
- Version: 1.4.1
7
  Author: NitroPack LLC
8
  Author URI: https://nitropack.io/
9
  License: GPL2
@@ -66,6 +66,10 @@ if (nitropack_has_advanced_cache()) {
66
  }
67
  }
68
 
 
 
 
 
69
  if ( is_admin() ) {
70
  add_action( 'admin_menu', 'nitropack_menu' );
71
  add_action( 'admin_init', 'register_nitropack_settings' );
@@ -119,8 +123,8 @@ function nitropack_action_links ( $links ) {
119
  add_action( 'init', function() {
120
  if (current_user_can( 'manage_options' )) {
121
  // Enqueue font awesome
122
- add_action( 'wp_enqueue_scripts', 'enqueue_load_fa');
123
- add_action( 'admin_enqueue_scripts', 'enqueue_load_fa');
124
 
125
  // Enqueue admin bar menu custom stylesheet
126
  add_action( 'wp_enqueue_scripts', 'enqueue_nitropack_admin_bar_menu_stylesheet');
3
  Plugin Name: NitroPack
4
  Plugin URI: https://nitropack.io/platform/wordpress
5
  Description: Everything you need for a fast website. Simple set up, easy to use, awesome support. Caching, Lazy Loading, Minification, Defer CSS/JS, CDN and more!
6
+ Version: 1.5.0
7
  Author: NitroPack LLC
8
  Author URI: https://nitropack.io/
9
  License: GPL2
66
  }
67
  }
68
 
69
+ add_action('wp_footer', 'nitropack_print_heartbeat_script');
70
+ add_action('admin_footer', 'nitropack_print_heartbeat_script');
71
+ add_action('get_footer', 'nitropack_print_heartbeat_script');
72
+
73
  if ( is_admin() ) {
74
  add_action( 'admin_menu', 'nitropack_menu' );
75
  add_action( 'admin_init', 'register_nitropack_settings' );
123
  add_action( 'init', function() {
124
  if (current_user_can( 'manage_options' )) {
125
  // Enqueue font awesome
126
+ add_action( 'wp_enqueue_scripts', 'nitropack_enqueue_load_fa');
127
+ add_action( 'admin_enqueue_scripts', 'nitropack_enqueue_load_fa');
128
 
129
  // Enqueue admin bar menu custom stylesheet
130
  add_action( 'wp_enqueue_scripts', 'enqueue_nitropack_admin_bar_menu_stylesheet');
nitropack-sdk/NitroPack/SDK/Api.php CHANGED
@@ -8,16 +8,36 @@ class Api {
8
  private $tagger;
9
  private $url;
10
  private $allowedWebhooks = array('config', 'cache_clear', 'cache_ready', 'sitemap');
 
11
 
12
  public function __construct($siteId, $siteSecret) {
13
- $this->cache = new Api\Cache($siteId, $siteSecret);
14
- $this->tagger = new Api\Tagger($siteId, $siteSecret);
15
- $this->url = new Api\Url($siteId, $siteSecret);
16
- $this->stats = new Api\Stats($siteId, $siteSecret);
17
- $this->webhook = new Api\Webhook($siteId, $siteSecret);
18
- $this->warmup = new Api\Warmup($siteId, $siteSecret);
19
- $this->integration = new Api\Integration($siteId, $siteSecret);
20
- $this->variation_cookie = new Api\VariationCookie($siteId, $siteSecret);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
 
23
  public function getCache($url, $userAgent, $cookies, $isAjax, $layout) {
8
  private $tagger;
9
  private $url;
10
  private $allowedWebhooks = array('config', 'cache_clear', 'cache_ready', 'sitemap');
11
+ private $children;
12
 
13
  public function __construct($siteId, $siteSecret) {
14
+ $this->children = [];
15
+ $this->children["cache"] = new Api\Cache($siteId, $siteSecret);
16
+ $this->children["tagger"] = new Api\Tagger($siteId, $siteSecret);
17
+ $this->children["url"] = new Api\Url($siteId, $siteSecret);
18
+ $this->children["stats"] = new Api\Stats($siteId, $siteSecret);
19
+ $this->children["webhook"] = new Api\Webhook($siteId, $siteSecret);
20
+ $this->children["warmup"] = new Api\Warmup($siteId, $siteSecret);
21
+ $this->children["integration"] = new Api\Integration($siteId, $siteSecret);
22
+ $this->children["variation_cookie"] = new Api\VariationCookie($siteId, $siteSecret);
23
+ $this->children["request_maker"] = new Api\RequestMaker($siteId);
24
+ $this->children["secure_request_maker"] = new Api\SecureRequestMaker($siteId, $siteSecret);
25
+
26
+ foreach ($this->children as $name=>$child) {
27
+ $this->{$name} = $child;
28
+ }
29
+ }
30
+
31
+ public function setBacklog($backlog) {
32
+ foreach ($this->children as $child) {
33
+ $child->setBacklog($backlog);
34
+ }
35
+ }
36
+
37
+ public function setNitroPack($nitropack) {
38
+ foreach ($this->children as $child) {
39
+ $child->setNitroPack($nitropack);
40
+ }
41
  }
42
 
43
  public function getCache($url, $userAgent, $cookies, $isAjax, $layout) {
nitropack-sdk/NitroPack/SDK/Api/Base.php CHANGED
@@ -1,20 +1,60 @@
1
  <?php
2
  namespace NitroPack\SDK\Api;
3
  use \NitroPack\HttpClient;
 
 
 
4
 
5
  class Base {
6
  protected $baseUrl = 'https://api.getnitropack.com/';
7
  protected $siteId;
 
 
 
8
 
9
  public function __construct($siteId) {
10
  $this->siteId = $siteId;
 
 
 
11
 
12
  if (defined('NITROPACK_API_BASE_URL')) {
13
  $this->baseUrl = NITROPACK_API_BASE_URL;
14
  }
15
  }
16
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  protected function makeRequest($path, $headers = array(), $cookies = array(), $type = 'GET', $bodyData=array(), $async = false, $verifySSL = false) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  $http = new HttpClient($this->baseUrl . $path); // HttpClient keeps a cache of the opened connections, so creating a new instance every time is not an issue
19
  $http->connect_timeout = 3; // in seconds
20
  $http->ssl_timeout = 3; // in seconds
@@ -34,22 +74,45 @@ class Base {
34
 
35
  $http->setVerifySSL($verifySSL);
36
 
 
 
 
 
37
  if ($async) {
38
  $http->fetch(true, $type, $async);
39
  } else {
40
  $retries = 1;
 
41
  while ($retries--) {
42
  try {
43
  $http->fetch(true, $type, $async);
44
- if ($http->getStatusCode() < 500) break;
 
 
 
45
  } catch (\Exception $e) {
46
- if ($retries == 0) throw $e;
 
 
 
 
 
 
 
 
47
  }
48
 
49
  if ($retries > 0) {
50
  usleep(500000);
51
  }
52
  }
 
 
 
 
 
 
 
53
  }
54
 
55
  return $http;
@@ -67,6 +130,8 @@ class Base {
67
  $errorMessage = 'Unknown';
68
  }
69
 
 
 
70
  if ($errorMessage == 'Unknown') { // Fallback to known HTTP errors
71
  $statusCode = $httpResponse->getStatusCode();
72
  switch ($statusCode) {
@@ -83,6 +148,7 @@ class Base {
83
  $errorMessage = "Runtime Error";
84
  break;
85
  case ResponseStatus::SERVICE_UNAVAILABLE:
 
86
  $errorMessage = "Service Unavailable";
87
  break;
88
  default:
@@ -91,6 +157,10 @@ class Base {
91
  }
92
  }
93
 
94
- throw new \RuntimeException(sprintf($template, $errorMessage));
 
 
 
 
95
  }
96
  }
1
  <?php
2
  namespace NitroPack\SDK\Api;
3
  use \NitroPack\HttpClient;
4
+ use \NitroPack\SDK\NitroPack;
5
+ use \NitroPack\SDK\HealthStatus;
6
+ use \NitroPack\SDK\ServiceDownException;
7
 
8
  class Base {
9
  protected $baseUrl = 'https://api.getnitropack.com/';
10
  protected $siteId;
11
+ protected $isBacklogEnabled;
12
+ protected $backlog;
13
+ protected $nitropack;
14
 
15
  public function __construct($siteId) {
16
  $this->siteId = $siteId;
17
+ $this->isBacklogEnabled = false;
18
+ $this->backlog = NULL;
19
+ $this->nitropack = NULL;
20
 
21
  if (defined('NITROPACK_API_BASE_URL')) {
22
  $this->baseUrl = NITROPACK_API_BASE_URL;
23
  }
24
  }
25
 
26
+ public function setBacklog($backlog) {
27
+ $this->backlog = $backlog;
28
+ }
29
+
30
+ public function setNitroPack($nitropack) {
31
+ $this->nitropack = $nitropack;
32
+ }
33
+
34
+ protected function addToBacklog($entry) {
35
+ $this->backlog->append($entry);
36
+ }
37
+
38
  protected function makeRequest($path, $headers = array(), $cookies = array(), $type = 'GET', $bodyData=array(), $async = false, $verifySSL = false) {
39
+ $backlogEntry = array(
40
+ "path" => $path,
41
+ "headers" => $headers,
42
+ "cookies" => $cookies,
43
+ "type" => $type,
44
+ "bodyData" => $bodyData,
45
+ "async" => $async,
46
+ "verifySSL" => $verifySSL
47
+ );
48
+
49
+ if ($this->nitropack && $this->nitropack->getHealthStatus() !== HealthStatus::HEALTHY) {
50
+ $unhealthyMsg = "Connection to NitroPack is not reliable at the moment.";
51
+ if ($this->isBacklogEnabled) {
52
+ $this->addToBacklog($backlogEntry);
53
+ $unhealthyMsg .= " Request has been added to the backlog for delayed processing.";
54
+ }
55
+ throw new ServiceDownException($unhealthyMsg);
56
+ }
57
+
58
  $http = new HttpClient($this->baseUrl . $path); // HttpClient keeps a cache of the opened connections, so creating a new instance every time is not an issue
59
  $http->connect_timeout = 3; // in seconds
60
  $http->ssl_timeout = 3; // in seconds
74
 
75
  $http->setVerifySSL($verifySSL);
76
 
77
+ if ($this->isBacklogEnabled) {
78
+ $http->backlogEntry = $backlogEntry;
79
+ }
80
+
81
  if ($async) {
82
  $http->fetch(true, $type, $async);
83
  } else {
84
  $retries = 1;
85
+ $isRequestProcessed = false;
86
  while ($retries--) {
87
  try {
88
  $http->fetch(true, $type, $async);
89
+ if ($http->getStatusCode() < 500) {
90
+ $isRequestProcessed = true;
91
+ break;
92
+ }
93
  } catch (\Exception $e) {
94
+ if ($retries == 0) {
95
+ if (!$isRequestProcessed) {
96
+ $this->nitropack && $this->nitropack->setHealthStatus(HealthStatus::SICK);
97
+ if ($this->isBacklogEnabled) {
98
+ $this->addToBacklog($backlogEntry);
99
+ }
100
+ }
101
+ throw $e;
102
+ }
103
  }
104
 
105
  if ($retries > 0) {
106
  usleep(500000);
107
  }
108
  }
109
+
110
+ if (!$isRequestProcessed) { // In case all response codes were 500+
111
+ $this->nitropack && $this->nitropack->setHealthStatus(HealthStatus::UNDER_THE_WEATHER);
112
+ if ($this->isBacklogEnabled) {
113
+ $this->addToBacklog($backlogEntry);
114
+ }
115
+ }
116
  }
117
 
118
  return $http;
130
  $errorMessage = 'Unknown';
131
  }
132
 
133
+ $isServiceUnavailable = false;
134
+
135
  if ($errorMessage == 'Unknown') { // Fallback to known HTTP errors
136
  $statusCode = $httpResponse->getStatusCode();
137
  switch ($statusCode) {
148
  $errorMessage = "Runtime Error";
149
  break;
150
  case ResponseStatus::SERVICE_UNAVAILABLE:
151
+ $isServiceUnavailable = true;
152
  $errorMessage = "Service Unavailable";
153
  break;
154
  default:
157
  }
158
  }
159
 
160
+ if ($isServiceUnavailable) {
161
+ throw new ServiceDownException(sprintf($template, $errorMessage));
162
+ } else {
163
+ throw new \RuntimeException(sprintf($template, $errorMessage));
164
+ }
165
  }
166
  }
nitropack-sdk/NitroPack/SDK/Api/Cache.php CHANGED
@@ -2,6 +2,7 @@
2
  namespace NitroPack\SDK\Api;
3
 
4
  use \NitroPack\SDK\NitroPack;
 
5
 
6
  class Cache extends SignedBase {
7
  protected $secret;
@@ -12,6 +13,7 @@ class Cache extends SignedBase {
12
  }
13
 
14
  public function get($url, $userAgent, $cookies, $isAjax, $layout = 'default', $remoteAddr = NULL) {
 
15
  $path = 'cache/get/' . $this->siteId . '/' . $layout;
16
  $remoteAddr = $remoteAddr ? $remoteAddr : NitroPack::getRemoteAddr();
17
  $headers = array(
@@ -41,6 +43,7 @@ class Cache extends SignedBase {
41
  }
42
 
43
  public function getLastPurge() {
 
44
  $path = 'cache/getlastpurge/' . $this->siteId;
45
 
46
  $httpResponse = $this->makeRequest($path);
@@ -54,6 +57,7 @@ class Cache extends SignedBase {
54
  }
55
 
56
  public function purge($url = NULL, $pagecacheOnly = false, $reason = NULL) {
 
57
  $path = 'cache/purge/' . $this->siteId;
58
 
59
  if (is_array($url)) {
@@ -64,6 +68,11 @@ class Cache extends SignedBase {
64
  $httpMulti = new \NitroPack\HttpClientMulti();
65
 
66
  $httpMulti->onSuccess(function($client) use ($path, &$url, &$requests, $httpMulti, $cache, $reason) {
 
 
 
 
 
67
  if ($url) {
68
  $_url = array_shift($url);
69
  $params = array();
@@ -81,6 +90,10 @@ class Cache extends SignedBase {
81
 
82
  $httpMulti->onError(function($client, $exception) use ($httpMulti, $retries) {
83
  if ($exception instanceof \NitroPack\SocketReadTimedOutException) {
 
 
 
 
84
  return;
85
  }
86
 
@@ -94,6 +107,10 @@ class Cache extends SignedBase {
94
  $retries->offsetSet($client, $clientRetries + 1);
95
  $client->replay();
96
  $httpMulti->push($client);
 
 
 
 
97
  }
98
  });
99
 
@@ -106,7 +123,12 @@ class Cache extends SignedBase {
106
  $params["reason"] = $reason;
107
  }
108
 
109
- $httpClient = $this->makeRequestAsync($path, array(), array(), 'POST', $params);
 
 
 
 
 
110
  $httpMulti->push($httpClient);
111
  $requests[] = $httpClient;
112
  }
@@ -164,6 +186,7 @@ class Cache extends SignedBase {
164
  }
165
 
166
  public function purgeByTag($tag, $reason = NULL) {
 
167
  $path = 'cache/purge/' . $this->siteId;
168
 
169
  $params = array();
2
  namespace NitroPack\SDK\Api;
3
 
4
  use \NitroPack\SDK\NitroPack;
5
+ use \NitroPack\SDK\ServiceDownException;
6
 
7
  class Cache extends SignedBase {
8
  protected $secret;
13
  }
14
 
15
  public function get($url, $userAgent, $cookies, $isAjax, $layout = 'default', $remoteAddr = NULL) {
16
+ $this->isBacklogEnabled = false;
17
  $path = 'cache/get/' . $this->siteId . '/' . $layout;
18
  $remoteAddr = $remoteAddr ? $remoteAddr : NitroPack::getRemoteAddr();
19
  $headers = array(
43
  }
44
 
45
  public function getLastPurge() {
46
+ $this->isBacklogEnabled = false;
47
  $path = 'cache/getlastpurge/' . $this->siteId;
48
 
49
  $httpResponse = $this->makeRequest($path);
57
  }
58
 
59
  public function purge($url = NULL, $pagecacheOnly = false, $reason = NULL) {
60
+ $this->isBacklogEnabled = true;
61
  $path = 'cache/purge/' . $this->siteId;
62
 
63
  if (is_array($url)) {
68
  $httpMulti = new \NitroPack\HttpClientMulti();
69
 
70
  $httpMulti->onSuccess(function($client) use ($path, &$url, &$requests, $httpMulti, $cache, $reason) {
71
+ if ($client->getStatusCode() >= 500 && !empty($client->backlogEntry)) {
72
+ $this->addToBacklog($client->backlogEntry);
73
+ $this->nitropack && $this->nitropack->setHealthStatus(HealthStatus::UNDER_THE_WEATHER);
74
+ }
75
+
76
  if ($url) {
77
  $_url = array_shift($url);
78
  $params = array();
90
 
91
  $httpMulti->onError(function($client, $exception) use ($httpMulti, $retries) {
92
  if ($exception instanceof \NitroPack\SocketReadTimedOutException) {
93
+ if (!empty($client->backlogEntry)) {
94
+ $this->addToBacklog($client->backlogEntry);
95
+ $this->nitropack && $this->nitropack->setHealthStatus(HealthStatus::SICK);
96
+ }
97
  return;
98
  }
99
 
107
  $retries->offsetSet($client, $clientRetries + 1);
108
  $client->replay();
109
  $httpMulti->push($client);
110
+ } else {
111
+ if (!empty($client->backlogEntry)) {
112
+ $this->addToBacklog($client->backlogEntry);
113
+ }
114
  }
115
  });
116
 
123
  $params["reason"] = $reason;
124
  }
125
 
126
+ try {
127
+ $httpClient = $this->makeRequestAsync($path, array(), array(), 'POST', $params);
128
+ } catch (ServiceDownException $e) {
129
+ continue;
130
+ }
131
+
132
  $httpMulti->push($httpClient);
133
  $requests[] = $httpClient;
134
  }
186
  }
187
 
188
  public function purgeByTag($tag, $reason = NULL) {
189
+ $this->isBacklogEnabled = true;
190
  $path = 'cache/purge/' . $this->siteId;
191
 
192
  $params = array();
nitropack-sdk/NitroPack/SDK/Api/RequestMaker.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace NitroPack\SDK\Api;
3
+
4
+ class RequestMaker extends Base {
5
+ public function __construct($siteId) {
6
+ parent::__construct($siteId);
7
+ }
8
+
9
+ public function makeRequest($path, $headers = array(), $cookies = array(), $type = 'GET', $bodyData=array(), $async = false, $verifySSL = false) {
10
+ return parent::makeRequest($path, $headers, $cookies, $type, $bodyData, $async, $verifySSL);
11
+ }
12
+ }
nitropack-sdk/NitroPack/SDK/Api/SecureRequestMaker.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace NitroPack\SDK\Api;
3
+
4
+ class SecureRequestMaker extends SignedBase {
5
+ public function __construct($siteId, $siteSecret) {
6
+ parent::__construct($siteId, $siteSecret);
7
+ }
8
+
9
+ public function makeRequest($path, $headers = array(), $cookies = array(), $type = 'GET', $bodyData=array(), $async = false, $verifySSL = false) {
10
+ return parent::makeRequest($path, $headers, $cookies, $type, $bodyData, $async, $verifySSL);
11
+ }
12
+ }
nitropack-sdk/NitroPack/SDK/Api/SignedBase.php CHANGED
@@ -28,6 +28,11 @@ class SignedBase extends Base {
28
  $this->secret = $siteSecret;
29
  }
30
 
 
 
 
 
 
31
  protected function makeRequest($path, $headers = array(), $cookies = array(), $type = 'GET', $bodyData=array(), $async = false, $verifySSL = false) {
32
  // calculate request signature
33
  $url = new Url($this->baseUrl . $path);
28
  $this->secret = $siteSecret;
29
  }
30
 
31
+ protected function addToBacklog($entry) {
32
+ $entry["siteSecret"] = $this->secret;
33
+ parent::addToBacklog($entry);
34
+ }
35
+
36
  protected function makeRequest($path, $headers = array(), $cookies = array(), $type = 'GET', $bodyData=array(), $async = false, $verifySSL = false) {
37
  // calculate request signature
38
  $url = new Url($this->baseUrl . $path);
nitropack-sdk/NitroPack/SDK/Backlog.php ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace NitroPack\SDK;
4
+ use NitroPack\SDK\Api\ResponseStatus;
5
+
6
+ class Backlog {
7
+ const TTL = 86400; // 1 day in seconds
8
+
9
+ private $dataDir;
10
+ private $nitropack;
11
+ private $communicators;
12
+ private $queue;
13
+ private $queuePath;
14
+ private $isAcquired;
15
+ private $fileHandle;
16
+ private $header;
17
+ private $backlogFile = array('data', 'backlog.queue');
18
+
19
+ public function __construct($dataDir, $nitropack) {
20
+ $this->dataDir = $dataDir;
21
+ $this->nitropack = $nitropack;
22
+ $this->communicators = array();
23
+ $this->queue = array();
24
+ $this->queuePath = $this->getQueuePath();
25
+ $this->isAcquired = false;
26
+ $this->fileHandle = NULL;
27
+ $this->header = new \stdClass();
28
+ $this->header->offset = 0;
29
+ $this->header->firstProcessingTimestamp = 0;
30
+ $this->header->lastProcessingTimestamp = 0;
31
+ }
32
+
33
+ public function __destruct() {
34
+ $this->closeHandle();
35
+ }
36
+
37
+ public function append($entry) {
38
+ $fh = $this->getHandle();
39
+ Filesystem::flock($fh, LOCK_EX);
40
+ Filesystem::fseek($fh, 0, SEEK_END);
41
+ $this->writeEntry($fh, $this->encodeEntry($entry));
42
+ Filesystem::flock($fh, LOCK_UN);
43
+ }
44
+
45
+ public function replay($timeLimit = 10) {
46
+ $fh = $this->getHandle();
47
+ Filesystem::flock($fh, LOCK_EX);
48
+ $lastProcessingTimestamp = $this->header->lastProcessingTimestamp;
49
+ if (time() - $lastProcessingTimestamp <= $timeLimit) {
50
+ return;
51
+ }
52
+ $this->acquireBacklog($fh);
53
+ Filesystem::flock($fh, LOCK_UN);
54
+
55
+ $initialProcesssTime = $this->header->firstProcessingTimestamp;
56
+ if (time() - $initialProcesssTime > self::TTL) {
57
+ // In case there have been previous attempts at clearing the backlog and these attempts started more than the specified TTL seconds ago
58
+ // Perform a full purge and clear the backlog
59
+ }
60
+
61
+ if ($this->header->offset > 0) {
62
+ Filesystem::fseek($fh, $this->header->offset, SEEK_SET);
63
+ }
64
+ $start = microtime(true);
65
+ while (!$this->isEndOfQueue($fh) && NULL != ($entry = $this->getentry($fh)) && microtime(true) - $start < $timeLimit) {
66
+ $elapsedTime = microtime(true) - $start;
67
+ try {
68
+ $this->replayEntry($entry, $timeLimit - $elapsedTime);
69
+ } catch (\Exception $e) {
70
+ break;
71
+ }
72
+ }
73
+
74
+ if ($this->isEndOfQueue($fh)) {
75
+ $this->closeHandle();
76
+ Filesystem::deleteFile($this->queuePath);
77
+ }
78
+ }
79
+
80
+ private function isEndOfQueue($fh) {
81
+ return Filesystem::feof($fh);
82
+ }
83
+
84
+ public function dumpEntries() {
85
+ $fh = $this->getHandle();
86
+ while (!$this->isEndOfQueue($fh) && NULL != ($entry = $this->getentry($fh))) {
87
+ try {
88
+ var_dump($entry);
89
+ } catch (\Exception $e) {
90
+ break;
91
+ }
92
+ }
93
+ }
94
+
95
+ public function dumpHeader() {
96
+ $this->getHandle();
97
+ var_dump($this->header);
98
+ }
99
+
100
+ public function resetOffset() {
101
+ $fh = $this->getHandle();
102
+ $this->header->offset = 0;
103
+ $this->writeHeader($fh);
104
+ }
105
+
106
+ public function exists() {
107
+ return Filesystem::fileExists($this->queuePath);
108
+ }
109
+
110
+ private function replayEntry($entry, $timeLimit) {
111
+ if (array_key_exists("siteSecret", $entry)) {
112
+ $requestMaker = $this->nitropack->getApi()->secure_request_maker;
113
+ } else {
114
+ $requestMaker = $this->nitropack->getApi()->request_maker;
115
+ }
116
+ $httpResponse = $requestMaker->makeRequest($entry["path"], $entry["headers"], $entry["cookies"], $entry["type"], $entry["bodyData"], false, $entry["verifySSL"]);
117
+ $status = ResponseStatus::getStatus($httpResponse->getStatusCode());
118
+ $headers = $httpResponse->getHeaders();
119
+
120
+ $start = microtime(true);
121
+ while ($status == ResponseStatus::OK && !empty($headers["x-nitro-repeat"]) && microtime(true) - $start < $timeLimit) {
122
+ $httpResponse->replay(); // In reality $httpResponse is an instance of HttpClient which has the replay method
123
+ $status = ResponseStatus::getStatus($httpResponse->getStatusCode());
124
+ $headers = $httpResponse->getHeaders();
125
+ if ($status != ResponseStatus::OK) {
126
+ throw \RuntimeException("Unable to replay backlogged entry");
127
+ }
128
+ }
129
+
130
+ if ($status == ResponseStatus::OK && empty($headers["x-nitro-repeat"])) {
131
+ $fh = $this->getHandle();
132
+ $this->header->offset = Filesystem::ftell($fh);
133
+ $this->writeHeader($fh);
134
+ }
135
+ }
136
+
137
+ private function getQueuePath() {
138
+ $backlogFile = $this->backlogFile;
139
+ array_unshift($backlogFile, $this->dataDir);
140
+ return Filesystem::getOsPath($backlogFile);
141
+ }
142
+
143
+ private function getEntry($fh = NULL) {
144
+ $closeFile = empty($fh);
145
+ $fh = !empty($fh) ? $fh : $this->getHandle();
146
+ $entry = @Filesystem::fgets($fh);
147
+ if ($closeFile) {
148
+ Filesystem::fclose($fh);
149
+ }
150
+ return $this->decodeEntry($entry);
151
+ }
152
+
153
+ private function writeEntry($fh, $entry) {
154
+ Filesystem::fwrite($fh, $entry . "\n");
155
+ Filesystem::fflush($fh);
156
+ }
157
+
158
+ private function encodeEntry($entry) {
159
+ return base64_encode(json_encode($entry));
160
+ }
161
+
162
+ private function decodeEntry($entry) {
163
+ return json_decode(base64_decode($entry), true);
164
+ }
165
+
166
+ private function acquireBacklog($fh) {
167
+ $this->header->lastProcessingTimestamp = time();
168
+ if (!$this->header->firstProcessingTimestamp) {
169
+ $this->header->firstProcessingTimestamp = $this->header->lastProcessingTimestamp;
170
+ }
171
+ $this->writeHeader($fh);
172
+ $this->isAcquired = true;
173
+ }
174
+
175
+ private function releaseBacklog($fh) {
176
+ $this->header->lastProcessingTimestamp = 0;
177
+ $this->writeHeader($fh);
178
+ $this->isAcquired = false;
179
+ }
180
+
181
+ private function readHeader($fh) {
182
+ $offsetBackup = Filesystem::ftell($fh);
183
+ Filesystem::fseek($fh, 0, SEEK_SET);
184
+ $header = Filesystem::fread($fh, 12);
185
+ $parts = unpack("Loffset/LfirstProcessingTimestamp/LlastProcessingTimestamp", $header);
186
+ $this->header->offset = $parts["offset"];
187
+ $this->header->firstProcessingTimestamp = $parts["firstProcessingTimestamp"];
188
+ $this->header->lastProcessingTimestamp = $parts["lastProcessingTimestamp"];
189
+ Filesystem::fseek($fh, $offsetBackup, SEEK_SET);
190
+ }
191
+
192
+ private function writeHeader($fh) {
193
+ $offsetBackup = Filesystem::ftell($fh);
194
+ Filesystem::fseek($fh, 0, SEEK_SET);
195
+ Filesystem::fwrite($fh, pack("L*", $this->header->offset, $this->header->firstProcessingTimestamp, $this->header->lastProcessingTimestamp), 12);
196
+ Filesystem::fflush($fh);
197
+ Filesystem::fseek($fh, $offsetBackup, SEEK_SET);
198
+ }
199
+
200
+ private function getHandle() {
201
+ if ($this->fileHandle) return $this->fileHandle;
202
+
203
+ $fh = Filesystem::fopen($this->queuePath, "c+");
204
+ Filesystem::flock($fh, LOCK_EX);
205
+ Filesystem::fseek($fh, 0, SEEK_END);
206
+ $pos = Filesystem::ftell($fh);
207
+ if ($pos > 11) { // The first 12 bytes are reserved for the header. If the last pos is further than the 12th byte then the log is considered initialized
208
+ $this->readHeader($fh);
209
+ } else {
210
+ $this->writeHeader($fh);
211
+ }
212
+ Filesystem::fseek($fh, 12, SEEK_SET);
213
+ Filesystem::flock($fh, LOCK_UN);
214
+
215
+ $this->fileHandle = $fh;
216
+
217
+ return $this->fileHandle;
218
+ }
219
+
220
+ private function closeHandle() {
221
+ if ($this->fileHandle) {
222
+ if ($this->isAcquired) {
223
+ $this->releaseBacklog($this->fileHandle);
224
+ }
225
+ Filesystem::fclose($this->fileHandle);
226
+ $this->fileHandle = NULL;
227
+ }
228
+ }
229
+ }
nitropack-sdk/NitroPack/SDK/FileHandle.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace NitroPack\SDK;
3
+
4
+ class FileHandle {
5
+ protected $handle;
6
+
7
+ public function __construct($handle) {
8
+ $this->handle = $handle;
9
+ }
10
+
11
+ public function getHandle() {
12
+ return $this->handle;
13
+ }
14
+
15
+ public function setHandle($handle) {
16
+ $this->handle = $handle;
17
+ }
18
+ }
nitropack-sdk/NitroPack/SDK/Filesystem.php CHANGED
@@ -118,6 +118,50 @@ class Filesystem {
118
  return false;
119
  }
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  private static function explodeByHeaders($content) {
122
  $headers = [];
123
  $pos = strpos($content, "\r\n\r\n");
118
  return false;
119
  }
120
 
121
+ public static function fopen($file, $mode) {
122
+ return Filesystem::getStorageDriver()->fopen($file, $mode);
123
+ }
124
+
125
+ public static function fclose($fh) {
126
+ return Filesystem::getStorageDriver()->fclose($fh);
127
+ }
128
+
129
+ public static function fflush($fh) {
130
+ return Filesystem::getStorageDriver()->fflush($fh);
131
+ }
132
+
133
+ public static function fseek($fh, $offset, $whence = SEEK_SET) {
134
+ return Filesystem::getStorageDriver()->fseek($fh, $offset, $whence);
135
+ }
136
+
137
+ public static function ftell($fh) {
138
+ return Filesystem::getStorageDriver()->ftell($fh);
139
+ }
140
+
141
+ public static function fwrite($fh, $string, $length = NULL) {
142
+ return Filesystem::getStorageDriver()->fwrite($fh, $string, $length);
143
+ }
144
+
145
+ public static function fread($fh, $length) {
146
+ return Filesystem::getStorageDriver()->fread($fh, $length);
147
+ }
148
+
149
+ public static function fgetc($fh) {
150
+ return Filesystem::getStorageDriver()->fgetc($fh);
151
+ }
152
+
153
+ public static function fgets($fh, $length = NULL) {
154
+ return Filesystem::getStorageDriver()->fgets($fh, $length);
155
+ }
156
+
157
+ public static function flock($fh, $operation, $wouldblock = NULL) {
158
+ return Filesystem::getStorageDriver()->flock($fh, $operation, $wouldblock);
159
+ }
160
+
161
+ public static function feof($fh) {
162
+ return Filesystem::getStorageDriver()->feof($fh);
163
+ }
164
+
165
  private static function explodeByHeaders($content) {
166
  $headers = [];
167
  $pos = strpos($content, "\r\n\r\n");
nitropack-sdk/NitroPack/SDK/HealthStatus.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace NitroPack\SDK;
3
+
4
+ class HealthStatus {
5
+ const UNDER_THE_WEATHER = "UNDER THE WEATHER";
6
+ const HEALTHY = "HEALTHY";
7
+ const SICK = "SICK";
8
+ }
nitropack-sdk/NitroPack/SDK/NitroPack.php CHANGED
@@ -2,11 +2,12 @@
2
  namespace NitroPack\SDK;
3
 
4
  class NitroPack {
5
- const VERSION = '0.19.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 $pageCacheLockFile = array('data', 'get_cache.lock');
11
  private $cachePathSuffix = NULL;
12
  private $configTTL; // In seconds
@@ -18,9 +19,12 @@ class NitroPack {
18
  private $url;
19
  private $config;
20
  private $device;
21
- public $pageCache; // TODO: consider better ways of protecting/providing this outside the class
22
  private $api;
23
 
 
 
 
 
24
  private static $cachePrefixes = array();
25
  private static $cookieFilters = array();
26
 
@@ -84,6 +88,10 @@ class NitroPack {
84
  $this->siteId = $siteId;
85
  $this->siteSecret = $siteSecret;
86
  $this->dataDir = $dataDir;
 
 
 
 
87
  if (empty($userAgent)) {
88
  $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';
89
  } else {
@@ -131,6 +139,8 @@ class NitroPack {
131
  }
132
 
133
  $this->api = new Api($this->siteId, $siteSecret);
 
 
134
 
135
  $this->pageCache->setDataDir($this->getCacheDir());
136
 
@@ -195,6 +205,40 @@ class NitroPack {
195
  return Filesystem::getOsPath($cachePath);
196
  }
197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  public function hasCache($layout = 'default') {
199
  if ($this->hasLocalCache()) {
200
  return true;
@@ -204,8 +248,12 @@ class NitroPack {
204
  }
205
 
206
  public function hasLocalCache($checkIfRequestIsAllowed = true) {
 
207
  if (!$this->isAllowedUrl($this->url) || ($checkIfRequestIsAllowed && !$this->isAllowedRequest())) return false;
208
  $cacheRevision = !empty($this->config->RevisionHash) ? $this->config->RevisionHash : NULL;
 
 
 
209
 
210
  if (!$this->pageCache->getUseInvalidated()) {
211
  $ttl = $this->config->PageCache->ExpireTime;
@@ -217,6 +265,7 @@ class NitroPack {
217
  }
218
 
219
  public function hasRemoteCache($layout, $checkIfRequestIsAllowed = true) {
 
220
  if (!$this->isAllowedUrl($this->url) || ($checkIfRequestIsAllowed && !$this->isAllowedRequest()) || $this->isPageCacheLocked()) return false;
221
  $resp = $this->api->getCache($this->url, $this->userAgent, $this->supportedCookiesFilter(self::getCookies()), $this->isAJAXRequest(), $layout);
222
 
@@ -278,21 +327,28 @@ class NitroPack {
278
  $apiResult = true;
279
  if ($url) {
280
  if (is_array($url)) {
281
- foreach ($url as $urlLink) {
 
282
  if ($invalidate) {
283
  $localResult &= $this->invalidateLocalUrlCache($urlLink);
284
  } else {
285
  $localResult &= $this->purgeLocalUrlCache($urlLink);
286
  }
287
  }
288
- $apiResult &= $this->api->purgeCache($url, false, $reason);
289
  } else {
 
290
  if ($invalidate) {
291
  $localResult &= $this->invalidateLocalUrlCache($url);
292
  } else {
293
  $localResult &= $this->purgeLocalUrlCache($url);
294
  }
 
 
 
295
  $apiResult &= $this->api->purgeCache($url, false, $reason);
 
 
 
296
  }
297
  }
298
 
@@ -312,12 +368,17 @@ class NitroPack {
312
  $localResult &= $this->purgeLocalUrlCache($url);
313
  }
314
  }
 
 
 
 
 
315
  } catch (\Exception $e) {
316
  $hadError = true;
317
  $attemptsLeft--;
318
  sleep(3);
319
  }
320
- } while (($hadError && $attemptsLeft > 0) || count($purgedUrls) > 0);
321
  }
322
  } else {
323
  if ($invalidate) {
@@ -370,9 +431,22 @@ class NitroPack {
370
  return $staleCacheDir;
371
  }
372
 
373
- public function fetchConfig() {
 
374
  $fetcher = new Api\RemoteConfigFetcher($this->siteId, $this->siteSecret);
375
- $configContents = $fetcher->get(); // this can throw in case of http errors or validation failures
 
 
 
 
 
 
 
 
 
 
 
 
376
  $config = json_decode($configContents);
377
  if ($config) {
378
  $config->SDKVersion = NitroPack::VERSION;
@@ -402,6 +476,7 @@ class NitroPack {
402
  if (!empty($this->config->CacheIntegrations)) {
403
  if (!empty($this->config->CacheIntegrations->Varnish)) {
404
  if ($url) {
 
405
  $varnish = new Integrations\Varnish($this->config->CacheIntegrations->Varnish->Servers, $this->config->CacheIntegrations->Varnish->PurgeSingleMethod);
406
  $varnish->purge($url);
407
  } else {
@@ -548,6 +623,7 @@ class NitroPack {
548
  }
549
 
550
  public function purgeLocalUrlCache($url) {
 
551
  $this->purgeProxyCache($url);
552
  $localResult = true;
553
  $cacheDir = $this->getCacheDir();
@@ -562,6 +638,7 @@ class NitroPack {
562
  }
563
 
564
  public function invalidateLocalUrlCache($url) {
 
565
  $this->purgeProxyCache($url);
566
  $localResult = true;
567
  $cacheDir = $this->getCacheDir();
@@ -625,10 +702,12 @@ class NitroPack {
625
  if (Filesystem::fileExists($file) || $this->fetchConfig()) {
626
  $config = json_decode(Filesystem::fileGetContents($file));
627
  if (empty($config->SDKVersion) || $config->SDKVersion !== NitroPack::VERSION || empty($config->LastFetch) || time() - $config->LastFetch >= $this->configTTL) {
628
- if ($this->fetchConfig()) {
629
- $config = json_decode(Filesystem::fileGetContents($file));
630
- } else {
631
- throw new NoConfigException("Can't load config file");
 
 
632
  }
633
  }
634
  $this->config = $config;
@@ -704,6 +783,11 @@ class NitroPack {
704
  array_unshift($pageCacheLockFile, $this->dataDir);
705
  return Filesystem::getOsPath($pageCacheLockFile);
706
  }
 
 
 
 
 
707
  }
708
 
709
  class NoConfigException extends \Exception {}
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
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
 
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 {
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
 
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;
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;
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
 
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
 
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) {
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;
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 {
623
  }
624
 
625
  public function purgeLocalUrlCache($url) {
626
+ $url = $this->normalizeUrl($url);
627
  $this->purgeProxyCache($url);
628
  $localResult = true;
629
  $cacheDir = $this->getCacheDir();
638
  }
639
 
640
  public function invalidateLocalUrlCache($url) {
641
+ $url = $this->normalizeUrl($url);
642
  $this->purgeProxyCache($url);
643
  $localResult = true;
644
  $cacheDir = $this->getCacheDir();
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;
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
 
793
  class NoConfigException extends \Exception {}
nitropack-sdk/NitroPack/SDK/ServiceDownException.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <?php
2
+ namespace NitroPack\SDK;
3
+
4
+ class ServiceDownException extends \RuntimeException {}
nitropack-sdk/NitroPack/SDK/StorageDriver/Disk.php CHANGED
@@ -1,6 +1,8 @@
1
  <?php
2
  namespace NitroPack\SDK\StorageDriver;
3
 
 
 
4
  class Disk {
5
  public function getOsPath($parts) {
6
  return implode(DIRECTORY_SEPARATOR, $parts);
@@ -100,4 +102,75 @@ class Disk {
100
  public function rename($oldName, $newName) {
101
  return @rename($oldName, $newName);
102
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
1
  <?php
2
  namespace NitroPack\SDK\StorageDriver;
3
 
4
+ use \NitroPack\SDK\FileHandle;
5
+
6
  class Disk {
7
  public function getOsPath($parts) {
8
  return implode(DIRECTORY_SEPARATOR, $parts);
102
  public function rename($oldName, $newName) {
103
  return @rename($oldName, $newName);
104
  }
105
+
106
+ public function fopen($file, $mode) {
107
+ $fh = @fopen($file, $mode);
108
+ if ($fh) {
109
+ return new DiskFileHandle($fh);
110
+ } else {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ public function fclose($fh) {
116
+ if (!($fh instanceof FileHandle)) return false;
117
+ return @fclose($fh->getHandle());
118
+ }
119
+
120
+ public function fflush($fh) {
121
+ if (!($fh instanceof FileHandle)) return false;
122
+ return @fflush($fh->getHandle());
123
+ }
124
+
125
+ public function fseek($fh, $offset, $whence = SEEK_SET) {
126
+ if (!($fh instanceof FileHandle)) return false;
127
+ return @fseek($fh->getHandle(), $offset, $whence);
128
+ }
129
+
130
+ public function ftell($fh) {
131
+ if (!($fh instanceof FileHandle)) return false;
132
+ return @ftell($fh->getHandle());
133
+ }
134
+
135
+ public function fwrite($fh, $string, $length = NULL) {
136
+ if (!($fh instanceof FileHandle)) return false;
137
+ if ($length !== NULL) {
138
+ return @fwrite($fh->getHandle(), $string, $length);
139
+ } else {
140
+ return @fwrite($fh->getHandle(), $string);
141
+ }
142
+ }
143
+
144
+ public function fread($fh, $length) {
145
+ if (!($fh instanceof FileHandle)) return false;
146
+ return @fread($fh->getHandle(), $length);
147
+ }
148
+
149
+ public function fgetc($fh) {
150
+ if (!($fh instanceof FileHandle)) return false;
151
+ return @fgetc($fh->getHandle());
152
+ }
153
+
154
+ public function fgets($fh, $length = NULL) {
155
+ if (!($fh instanceof FileHandle)) return false;
156
+ if ($length !== NULL) {
157
+ return @fgets($fh->getHandle(), $length);
158
+ } else {
159
+ return @fgets($fh->getHandle());
160
+ }
161
+ }
162
+
163
+ public function flock($fh, $operation, $wouldblock = NULL) {
164
+ if (!($fh instanceof FileHandle)) return false;
165
+ if ($wouldblock !== NULL) {
166
+ return @flock($fh->getHandle(), $operation, $wouldblock);
167
+ } else {
168
+ return @flock($fh->getHandle(), $operation);
169
+ }
170
+ }
171
+
172
+ public function feof($fh) {
173
+ if (!($fh instanceof FileHandle)) return false;
174
+ return @feof($fh->getHandle());
175
+ }
176
  }
nitropack-sdk/NitroPack/SDK/StorageDriver/DiskFileHandle.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+ namespace NitroPack\SDK\StorageDriver;
3
+
4
+ use \NitroPack\SDK\FileHandle;
5
+
6
+ class DiskFileHandle extends FileHandle {}
nitropack-sdk/NitroPack/SDK/StorageDriver/Redis.php CHANGED
@@ -3,7 +3,11 @@
3
  // This is also a bad design for emulating a file system performance wise. Only use this driver when you need shared storage between multiple servers. On a single server with an SSD using the Disk diver is a better idea.
4
  namespace NitroPack\SDK\StorageDriver;
5
 
 
 
6
  class Redis {
 
 
7
  private $redis;
8
 
9
  private function preparePathInput($path) {
@@ -120,6 +124,13 @@ class Redis {
120
  public function trunkDir($dir) {
121
  $dir = $this->preparePathInput($dir);
122
  if (!$this->isDir($dir)) return false;
 
 
 
 
 
 
 
123
  $success = false;
124
  try {
125
  $this->redis->eval('
@@ -132,7 +143,7 @@ repeat
132
  redis.call("UNLINK", list[i]);
133
  end;
134
  until cursor == "0";
135
- ', array($this->getOsPath(array($dir, "*"))), 0);
136
  $success = true;
137
  } catch (\Exception $e) {
138
  // TODO: Log an error
@@ -153,6 +164,7 @@ until cursor == "0";
153
  public function dirForeach($dir, $callback) {
154
  $dir = $this->preparePathInput($dir);
155
  if (!$this->isDir($dir)) return false;
 
156
  $it = NULL;
157
  $prevScanMode = $this->redis->getOption(\Redis::OPT_SCAN);
158
  $this->redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
@@ -166,11 +178,11 @@ until cursor == "0";
166
  }
167
  } catch (\Exception $e) {
168
  // TODO: Log an error
169
- return false;
170
  } finally {
171
  $this->redis->setOption(\Redis::OPT_SCAN, $prevScanMode);
172
  }
173
- return true;
174
  }
175
 
176
  public function mtime($path) {
@@ -227,7 +239,7 @@ repeat
227
  redis.call("RENAME", s, changed);
228
  end;
229
  until cursor == "0";
230
- ', array($this->getOsPath(array($oldKey, "*")), $oldKey . DIRECTORY_SEPARATOR, $newKey . DIRECTORY_SEPARATOR), 0);
231
  }
232
 
233
  $success = true;
@@ -236,4 +248,211 @@ until cursor == "0";
236
  }
237
  return $success;
238
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  }
3
  // This is also a bad design for emulating a file system performance wise. Only use this driver when you need shared storage between multiple servers. On a single server with an SSD using the Disk diver is a better idea.
4
  namespace NitroPack\SDK\StorageDriver;
5
 
6
+ use \NitroPack\SDK\FileHandle;
7
+
8
  class Redis {
9
+ const LOCK_TTL = 30000;
10
+ const LOCK_TIMEOUT = 30;
11
  private $redis;
12
 
13
  private function preparePathInput($path) {
124
  public function trunkDir($dir) {
125
  $dir = $this->preparePathInput($dir);
126
  if (!$this->isDir($dir)) return false;
127
+
128
+ if ($dir == DIRECTORY_SEPARATOR) {
129
+ $osPath = DIRECTORY_SEPARATOR . "*";
130
+ } else {
131
+ $osPath = $this->getOsPath(array($dir, "*"));
132
+ }
133
+
134
  $success = false;
135
  try {
136
  $this->redis->eval('
143
  redis.call("UNLINK", list[i]);
144
  end;
145
  until cursor == "0";
146
+ ', array($osPath), 0);
147
  $success = true;
148
  } catch (\Exception $e) {
149
  // TODO: Log an error
164
  public function dirForeach($dir, $callback) {
165
  $dir = $this->preparePathInput($dir);
166
  if (!$this->isDir($dir)) return false;
167
+ $result = true;
168
  $it = NULL;
169
  $prevScanMode = $this->redis->getOption(\Redis::OPT_SCAN);
170
  $this->redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
178
  }
179
  } catch (\Exception $e) {
180
  // TODO: Log an error
181
+ $result = false;
182
  } finally {
183
  $this->redis->setOption(\Redis::OPT_SCAN, $prevScanMode);
184
  }
185
+ return $result;
186
  }
187
 
188
  public function mtime($path) {
239
  redis.call("RENAME", s, changed);
240
  end;
241
  until cursor == "0";
242
+ ', array($this->getOsPath(array($oldKey, "*")), $this->prepareForLuaPattern($oldKey . DIRECTORY_SEPARATOR), $newKey . DIRECTORY_SEPARATOR), 0);
243
  }
244
 
245
  $success = true;
248
  }
249
  return $success;
250
  }
251
+
252
+ private function prepareForLuaPattern($pattern) {
253
+ $specialPatternChars = array("%", "(", ")", ".", "+", "-", "*", "?", "[", "^", "$");
254
+ $regex = "/(" . implode("|", array_map("preg_quote", $specialPatternChars)) . ")/";
255
+ return preg_replace($regex, "%$1", $pattern);
256
+ //return str_replace(array("%", "(", ")", ".", "+", "-", "*", "?", "[", "^", "$"), array("%%", "%(", "%)", "%.", "%+", "%-", "%*", "%?", "%[", "%^", "%$"), $pattern);
257
+ }
258
+
259
+ public function fopen($file, $mode) {
260
+ $fh = new \stdClass();
261
+ $fh->pos = 0;
262
+ $fh->content = "";
263
+ $fh->canRead = in_array($mode, array("r", "r+", "w+", "a+", "x+", "c+"));
264
+ $fh->canWrite = in_array($mode, array("r+", "w", "w+", "a", "a+", "x", "x+", "c", "c+"));
265
+ $fh->mode = $mode;
266
+ $fh->writeOccurred = false;
267
+ $fh->isOpen = true;
268
+ $fh->path = $file;
269
+
270
+ switch ($mode) {
271
+ case "r":
272
+ case "r+":
273
+ if (!$this->exists($file)) return false;
274
+ $fh->content = $this->getContent($file);
275
+ break;
276
+ case "w":
277
+ case "w+":
278
+ // Do nothing
279
+ break;
280
+ case "a":
281
+ case "a+":
282
+ if (!$this->exists($file)) return false;
283
+ $fh->content = $this->getContent($file);
284
+ break;
285
+ case "x":
286
+ case "x+":
287
+ if ($this->exists($file)) return false;
288
+ break;
289
+ case "c":
290
+ case "c+":
291
+ if ($this->exists($file)) {
292
+ $fh->content = $this->getContent($file);
293
+ }
294
+ break;
295
+ }
296
+
297
+ return new RedisFileHandle($fh);
298
+ }
299
+
300
+ public function fclose($handle) {
301
+ if (!($handle instanceof FileHandle)) return false;
302
+ $fh = $handle->getHandle();
303
+ if (!$fh->isOpen) return true;
304
+
305
+ if ($fh->canWrite && $fh->writeOccurred) {
306
+ $status = $this->fflush($handle);
307
+ if ($status) {
308
+ $fh->isOpen = false;
309
+ $fh->canRead = false;
310
+ $fh->canWrite = false;
311
+ }
312
+ return $status;
313
+ }
314
+
315
+ return true;
316
+ }
317
+
318
+ public function fflush($fh) {
319
+ if (!($fh instanceof FileHandle)) return false;
320
+ $fh = $fh->getHandle();
321
+ if ($fh->canWrite && $fh->writeOccurred && $fh->isOpen) {
322
+ if ($this->setContent($fh->path, $fh->content)) {
323
+ $fh->writeOccurred = false; // Reset the counter
324
+ return true;
325
+ } else {
326
+ return false;
327
+ }
328
+ }
329
+
330
+ return false;
331
+ }
332
+
333
+ public function fseek($fh, $offset, $whence = SEEK_SET) {
334
+ if (!($fh instanceof FileHandle)) return false;
335
+ $fh = $fh->getHandle();
336
+ switch ($whence) {
337
+ case SEEK_CUR:
338
+ $fh->pos += $offset;
339
+ break;
340
+ case SEEK_END:
341
+ $fh->pos = strlen($fh->content) + $offset;
342
+ break;
343
+ default:
344
+ $fh->pos = $offset;
345
+ break;
346
+ }
347
+
348
+ return 0;
349
+ }
350
+
351
+ public function ftell($fh) {
352
+ if (!($fh instanceof FileHandle)) return false;
353
+ $fh = $fh->getHandle();
354
+ return $fh->pos;
355
+ }
356
+
357
+ public function fwrite($fh, $string, $length = NULL) {
358
+ if (!($fh instanceof FileHandle)) return false;
359
+ $fh = $fh->getHandle();
360
+ if (!$fh->canWrite) return false;
361
+
362
+ if ($length === NULL || $length > strlen($string)) {
363
+ $length = strlen($string);
364
+ }
365
+
366
+ if ($fh->mode[0] == "a" || $fh->pos > strlen($fh->content)) {
367
+ $fh->content .= substr($string, 0, $length);
368
+ $fh->pos = strlen($fh->content);
369
+ } else {
370
+ $head = substr($fh->content, 0, $fh->pos);
371
+ $tail = substr($fh->content, $fh->pos + $length);
372
+ $fh->content = $head . substr($string, 0, $length) . $tail;
373
+ $fh->pos = strlen($head) + $length;
374
+ }
375
+
376
+ $fh->writeOccurred = true;
377
+
378
+ return $length;
379
+ }
380
+
381
+ public function fread($fh, $length) {
382
+ if (!($fh instanceof FileHandle)) return false;
383
+ $fh = $fh->getHandle();
384
+ if (!$fh->canRead) return false;
385
+
386
+ $result = substr($fh->content, $fh->pos, $length);
387
+ $fh->pos += strlen($result);//$length;
388
+
389
+ return $result;
390
+ }
391
+
392
+ public function fgetc($fh) {
393
+ if (!($fh instanceof FileHandle)) return false;
394
+ $fh = $fh->getHandle();
395
+ if (!$fh->canRead) return false;
396
+
397
+ return $fh->content[$fh->pos++];
398
+ }
399
+
400
+ public function fgets($fh, $length = NULL) {
401
+ if (!($fh instanceof FileHandle)) return false;
402
+ $fh = $fh->getHandle();
403
+ if (!$fh->canRead) return false;
404
+
405
+ if ($length === NULL) {
406
+ $length = strlen($fh->content);
407
+ }
408
+
409
+ $pos = strpos($fh->content, "\n", $fh->pos);
410
+ if ($pos === false) {
411
+ if ($fh->pos >= strlen($fh->content)) return false;
412
+ $result = substr($fh->content, $fh->pos, $length);
413
+ } else {
414
+ $result = substr($fh->content, $fh->pos, $pos - $fh->pos+1);
415
+ }
416
+ $fh->pos += strlen($result);
417
+ return $result;
418
+ }
419
+
420
+ public function flock($fh, $operation, $wouldblock = NULL) {
421
+ if (!($fh instanceof FileHandle)) return false;
422
+ $fh = $fh->getHandle();
423
+ if (!$fh->isOpen) return false;
424
+ switch ($operation) {
425
+ case LOCK_SH:
426
+ // In this case we acquire a lock in order to wait for any other process that has currently locked the file
427
+ // And then release the lock immediately.
428
+ // The sole purpose of this is to block until the writer process is complete.
429
+ return $this->acquireLock($fh->path, self::LOCK_TTL) && $this->releaseLock($fh->path);
430
+ case LOCK_EX:
431
+ return $this->acquireLock($fh->path, self::LOCK_TTL);
432
+ case LOCK_UN:
433
+ return $this->releaseLock($fh->path);
434
+ default:
435
+ return true;
436
+ }
437
+ return true;
438
+ }
439
+
440
+ public function feof($fh) {
441
+ if (!($fh instanceof FileHandle)) return false;
442
+ $fh = $fh->getHandle();
443
+ if (!$fh->isOpen) return false;
444
+ return $fh->pos >= strlen($fh->content);
445
+ }
446
+
447
+ private function acquireLock($path, $ttl) {
448
+ $startTime = microtime(true);
449
+ while (false === ($result = $this->redis->set('lock:' . $path, time(), ['nx', 'px' => self::LOCK_TTL])) && microtime(true) - $startTime < self::LOCK_TIMEOUT) {
450
+ usleep(50000);
451
+ }
452
+ return $result;
453
+ }
454
+
455
+ private function releaseLock($path) {
456
+ return $this->redis->del('lock:' . $path);
457
+ }
458
  }
nitropack-sdk/NitroPack/SDK/StorageDriver/RedisFileHandle.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+ namespace NitroPack\SDK\StorageDriver;
3
+
4
+ use \NitroPack\SDK\FileHandle;
5
+
6
+ class RedisFileHandle extends FileHandle {}
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: nitropack
3
  Tags: cache,perfomance,optimize,pagespeed,lazy load,cdn,critical css,compression,defer css javascript,minify css,minify,webp
4
  Requires at least: 4.7
5
- Tested up to: 5.5
6
  Requires PHP: 5.3
7
- Stable tag: 1.4.1
8
  License: GNU General Public License, version 2
9
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -169,6 +169,13 @@ No. We’ve designed NitroPack to be a very lightweight solution that adds no CP
169
 
170
  == Changelog ==
171
 
 
 
 
 
 
 
 
172
  = 1.4.1 =
173
  * Improvement: Performance improvements in content updates
174
  * Improvement: Better compatibility with Download Monitor
2
  Contributors: nitropack
3
  Tags: cache,perfomance,optimize,pagespeed,lazy load,cdn,critical css,compression,defer css javascript,minify css,minify,webp
4
  Requires at least: 4.7
5
+ Tested up to: 5.6
6
  Requires PHP: 5.3
7
+ Stable tag: 1.5.0
8
  License: GNU General Public License, version 2
9
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
10
 
169
 
170
  == Changelog ==
171
 
172
+ = 1.5.0 =
173
+ * New Feature: Compatibility with Cloudflare APO
174
+ * Improvement: Better resilience to network related issues
175
+ * Improvement: Faster cache purge
176
+ * Improvement: Overall stability improvements
177
+ * Deprecation: Removed the Invalidate All Cache option. The invalidate action is much better suited for single page invalidations.
178
+
179
  = 1.4.1 =
180
  * Improvement: Performance improvements in content updates
181
  * Improvement: Better compatibility with Download Monitor
view/dashboard.php CHANGED
@@ -21,7 +21,6 @@
21
  <div class="optimizations-subcount"><span data-optimized-pages-desktop>0</span> desktop pages</div>
22
  </div>
23
  <div class="row mt-5 justify-content-center">
24
- <button id="optimizations-invalidate-cache" class="btn btn-light btn-outline-secondary btn-widget-optimizations">Invalidate Cache</button>
25
  <button id="optimizations-purge-cache" class="btn btn-light btn-outline-secondary btn-widget-optimizations">Purge Cache</button>
26
  </div>
27
  </div>
@@ -97,7 +96,9 @@
97
  <h5 class="card-title">Settings</h5>
98
  <ul class="list-group list-group-flush">
99
  <li class="list-group-item px-0 d-flex justify-content-between align-items-center">
100
- <span>Cache Warmup</span>
 
 
101
  <span id="loading-warmup-status">
102
  Loading cache warmup status&nbsp;&nbsp;<i class="fa fa-refresh fa-spin" style="color: var(--blue);"></i>
103
  </span>
@@ -385,7 +386,7 @@
385
  },
386
  complete: function() {
387
  if (!getOptimizationsTimeout) {
388
- getOptimizationsTimeout = setTimeout(function() {getOptimizationsTimeout = null; getOptimizations();}, 10000);
389
  }
390
  }
391
  })
21
  <div class="optimizations-subcount"><span data-optimized-pages-desktop>0</span> desktop pages</div>
22
  </div>
23
  <div class="row mt-5 justify-content-center">
 
24
  <button id="optimizations-purge-cache" class="btn btn-light btn-outline-secondary btn-widget-optimizations">Purge Cache</button>
25
  </div>
26
  </div>
96
  <h5 class="card-title">Settings</h5>
97
  <ul class="list-group list-group-flush">
98
  <li class="list-group-item px-0 d-flex justify-content-between align-items-center">
99
+ <span>Cache Warmup</br>
100
+ <small>Learn more about this feature <a href="https://help.nitropack.io/en/articles/3534265-cache-warmup" target="_blank" rel="noreferrer noopener">here</a></small>
101
+ </span>
102
  <span id="loading-warmup-status">
103
  Loading cache warmup status&nbsp;&nbsp;<i class="fa fa-refresh fa-spin" style="color: var(--blue);"></i>
104
  </span>
386
  },
387
  complete: function() {
388
  if (!getOptimizationsTimeout) {
389
+ getOptimizationsTimeout = setTimeout(function() {getOptimizationsTimeout = null; getOptimizations();}, 60000);
390
  }
391
  }
392
  })
view/diag.php CHANGED
@@ -19,7 +19,7 @@
19
  </li>
20
  <li class="list-group-item px-0 d-flex justify-content-between align-items-center">
21
  <span id="loading-plugins-status">
22
- Include actve plugins list
23
  </span>
24
  <span id="active-plugins-toggle">
25
  <label id="active-plugins-slider" class="switch">
19
  </li>
20
  <li class="list-group-item px-0 d-flex justify-content-between align-items-center">
21
  <span id="loading-plugins-status">
22
+ Include active plugins list
23
  </span>
24
  <span id="active-plugins-toggle">
25
  <label id="active-plugins-slider" class="switch">