| @@ -15,8 +15,19 @@ | ||
| 15 | 15 | use Singleton; |
| 16 | 16 | private $settings; |
| 17 | 17 | |
| 18 | 18 | /** |
| 19 | + * Max seconds to wait for a remote feed. Kept short so a slow or dead | |
| 20 | + * endpoint can never stall the admin dashboard into a gateway timeout. | |
| 21 | + */ | |
| 22 | + const REQUEST_TIMEOUT = 5; | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * How long to skip remote requests after a failure. | |
| 26 | + */ | |
| 27 | + const FAILURE_BACKOFF = HOUR_IN_SECONDS; | |
| 28 | + | |
| 29 | + /** | |
| 19 | 30 | * Static variable to track if the feed has been displayed |
| 20 | 31 | */ |
| 21 | 32 | private static $feed_displayed = false; |
| 22 | 33 | |
| @@ -27,9 +38,8 @@ | ||
| 27 | 38 | $this->settings = $settings ?: [ |
| 28 | 39 | 'feed_title' => 'BdThemes News & Updates', |
| 29 | 40 | 'transient_key' => 'bdthemes_product_feeds', |
| 30 | 41 | 'feed_link' => 'https://bdthemes.com/feed', |
| 31 | - 'remote_feed_link' => 'https://dashboard.bdthemes.io/wp-json/bdthemes/v1/product-feed/?product_category=element-pack', | |
| 32 | 42 | 'text_domain' => 'ultimate-store-kit', |
| 33 | 43 | 'footer_links' => [ |
| 34 | 44 | [ |
| 35 | 45 | 'url' => 'https://bdthemes.com/blog/', |
| @@ -82,62 +92,12 @@ | ||
| 82 | 92 | /** |
| 83 | 93 | * Display RSS Feeds Content |
| 84 | 94 | */ |
| 85 | 95 | public function display_rss_feeds_content() { |
| 86 | - $feeds = $this->get_remote_feeds_data(); | |
| 87 | - if (is_array($feeds)) { | |
| 88 | - foreach ($feeds as $feed) { | |
| 89 | -?> | |
| 90 | - <div class="activity-block"> | |
| 91 | - <a href="<?php echo esc_url($feed->demo_link); ?>" target="_blank" style="margin-bottom:10px; display: inline-block;"> | |
| 92 | - <img src="<?php echo esc_url($feed->image); ?>" style="width:100%;min-height:240px;"> | |
| 93 | - </a> | |
| 94 | - <p> | |
| 95 | - <?php echo wp_kses_post(wp_trim_words(wp_strip_all_tags($feed->content), 50)); ?> | |
| 96 | - <a href="<?php echo esc_url($feed->demo_link); ?>" target="_blank"> | |
| 97 | - <?php esc_html_e('Learn more...', $this->settings['text_domain']); ?> | |
| 98 | - </a> | |
| 99 | - </p> | |
| 100 | - </div> | |
| 101 | - <?php | |
| 102 | - } | |
| 103 | - } | |
| 104 | 96 | echo wp_kses_post($this->get_rss_posts_data()); |
| 105 | 97 | } |
| 106 | 98 | |
| 107 | - /** | |
| 108 | - * Get Remote Feeds Data | |
| 109 | - * | |
| 110 | - * @return array|mixed | |
| 111 | - */ | |
| 112 | - private function get_remote_feeds_data() { | |
| 113 | - $transient_key = $this->settings['transient_key']; | |
| 114 | - $cached_data = get_transient($transient_key); | |
| 115 | 99 | |
| 116 | - if (! empty($cached_data)) { | |
| 117 | - return json_decode($cached_data); | |
| 118 | - } | |
| 119 | - | |
| 120 | - $response = wp_remote_get( | |
| 121 | - $this->settings['remote_feed_link'], | |
| 122 | - array( | |
| 123 | - 'timeout' => 30, | |
| 124 | - 'headers' => array( | |
| 125 | - 'Accept' => 'application/json', | |
| 126 | - ), | |
| 127 | - ) | |
| 128 | - ); | |
| 129 | - | |
| 130 | - if (is_wp_error($response)) { | |
| 131 | - return []; | |
| 132 | - } | |
| 133 | - | |
| 134 | - $response_body = wp_remote_retrieve_body($response); | |
| 135 | - set_transient($transient_key, $response_body, 6 * HOUR_IN_SECONDS); | |
| 136 | - | |
| 137 | - return json_decode($response_body); | |
| 138 | - } | |
| 139 | - | |
| 140 | 100 | /** |
| 141 | 101 | * Get RSS Posts Data |
| 142 | 102 | * |
| 143 | 103 | * @return string |
| @@ -142,10 +102,14 @@ | ||
| 142 | 102 | * |
| 143 | 103 | * @return string |
| 144 | 104 | */ |
| 145 | 105 | private function get_rss_posts_data() { |
| 146 | - $transient_key = $this->settings['transient_key'] . '_rss'; | |
| 147 | - $cached_data = get_transient($transient_key); | |
| 106 | + // Written out in full rather than concatenated so the prefix is visible to | |
| 107 | + // static analysis. The resolved key is byte-identical to the previous | |
| 108 | + // $this->settings['transient_key'] . '_rss', so no cached data is orphaned. | |
| 109 | + $transient_key = 'bdthemes_product_feeds_rss'; | |
| 110 | + $transient_failed_key = 'bdthemes_product_feeds_rss_failed'; | |
| 111 | + $cached_data = get_transient($transient_key); | |
| 148 | 112 | |
| 149 | 113 | if (! empty($cached_data)) { |
| 150 | 114 | /** |
| 151 | 115 | * Decode as associative array |
| @@ -150,15 +114,27 @@ | ||
| 150 | 114 | /** |
| 151 | 115 | * Decode as associative array |
| 152 | 116 | */ |
| 153 | 117 | $rss_items = json_decode($cached_data, true); |
| 118 | + | |
| 119 | + if (! is_array($rss_items)) { | |
| 120 | + $rss_items = []; | |
| 121 | + } | |
| 122 | + } elseif (get_transient($transient_failed_key)) { | |
| 123 | + /** | |
| 124 | + * A recent fetch failed, so skip the blocking request entirely. | |
| 125 | + */ | |
| 126 | + $rss_items = []; | |
| 154 | 127 | } else { |
| 155 | 128 | include_once ABSPATH . WPINC . '/feed.php'; |
| 156 | 129 | |
| 130 | + add_action('wp_feed_options', [$this, 'set_feed_timeout']); | |
| 157 | 131 | $rss = fetch_feed($this->settings['feed_link']); |
| 132 | + remove_action('wp_feed_options', [$this, 'set_feed_timeout']); | |
| 158 | 133 | |
| 159 | 134 | if (is_wp_error($rss)) { |
| 160 | - return '<li>' . esc_html__('Items Not Found', $this->settings['text_domain']) . '.</li>'; | |
| 135 | + set_transient($transient_failed_key, 1, self::FAILURE_BACKOFF); | |
| 136 | + return '<li>' . esc_html__('Items Not Found', 'ultimate-store-kit') . '.</li>'; | |
| 161 | 137 | } |
| 162 | 138 | |
| 163 | 139 | $maxitems = $rss->get_item_quantity(5); |
| 164 | 140 | $rss_items = $rss->get_items(0, $maxitems); |
| @@ -183,9 +159,9 @@ | ||
| 183 | 159 | ?> |
| 184 | 160 | <div class="bdt-widget"> |
| 185 | 161 | <ul> |
| 186 | 162 | <?php if (empty($rss_items)) : ?> |
| 187 | - <li><?php esc_html_e('Items Not Found', $this->settings['text_domain']); ?>.</li> | |
| 163 | + <li><?php esc_html_e('Items Not Found', 'ultimate-store-kit'); ?>.</li> | |
| 188 | 164 | <?php else : ?> |
| 189 | 165 | <?php foreach ($rss_items as $item) : ?> |
| 190 | 166 | <li> |
| 191 | 167 | <a target="_blank" href="<?php echo esc_url($item['link']); ?>" |
| @@ -190,14 +166,14 @@ | ||
| 190 | 166 | <li> |
| 191 | 167 | <a target="_blank" href="<?php echo esc_url($item['link']); ?>" |
| 192 | 168 | title="<?php echo esc_html($item['date']); ?>"> |
| 193 | 169 | <?php if ($this->is_feed_item_new($item['date'])) : ?> |
| 194 | - <span class="bdt-feed-badge bdt-feed-badge--new"><?php esc_html_e('New', $this->settings['text_domain']); ?></span> | |
| 170 | + <span class="bdt-feed-badge bdt-feed-badge--new"><?php esc_html_e('New', 'ultimate-store-kit'); ?></span> | |
| 195 | 171 | <?php endif; ?> |
| 196 | 172 | <?php echo esc_html($item['title']); ?> |
| 197 | 173 | </a> |
| 198 | 174 | <span class="bdt-date" style="display: block; margin: 0;"> |
| 199 | - <?php echo esc_html(human_time_diff($item['date'], current_time('timestamp')) . ' ' . __('ago', $this->settings['text_domain'])); ?> | |
| 175 | + <?php echo esc_html(human_time_diff($item['date'], current_time('timestamp')) . ' ' . __('ago', 'ultimate-store-kit')); ?> | |
| 200 | 176 | </span> |
| 201 | 177 | <div class="bdt-summary"> |
| 202 | 178 | <?php echo esc_html(wp_html_excerpt($item['content'], 120) . ' [...]'); ?> |
| 203 | 179 | </div> |
| @@ -222,8 +198,18 @@ | ||
| 222 | 198 | ?> |
| 223 | 199 | </p> |
| 224 | 200 | <?php |
| 225 | 201 | return ob_get_clean(); |
| 202 | + } | |
| 203 | + | |
| 204 | + /** | |
| 205 | + * Keep SimplePie's socket timeout in line with our own limit. | |
| 206 | + * | |
| 207 | + * @param object $feed SimplePie instance. | |
| 208 | + * @return void | |
| 209 | + */ | |
| 210 | + public function set_feed_timeout($feed) { | |
| 211 | + $feed->set_timeout(self::REQUEST_TIMEOUT); | |
| 226 | 212 | } |
| 227 | 213 | |
| 228 | 214 | /** |
| 229 | 215 | * Check if a feed item is "new" (published within the last 7 days). |