| @@ -28,17 +28,44 @@ | ||
| 28 | 28 | * @var array|false|null |
| 29 | 29 | */ |
| 30 | 30 | private static $best_sale; |
| 31 | 31 | |
| 32 | + /** | |
| 33 | + * Flag to prevent the $this->set_sales() call in the constructor. | |
| 34 | + * | |
| 35 | + * @since 6.34 | |
| 36 | + * | |
| 37 | + * @var bool | |
| 38 | + */ | |
| 39 | + private static $prevent_new_sales_request = false; | |
| 40 | + | |
| 32 | 41 | public function __construct() { |
| 33 | 42 | $this->set_cache_key(); |
| 34 | 43 | |
| 35 | - if ( false === self::$sales ) { | |
| 44 | + if ( ! self::$prevent_new_sales_request && false === self::$sales ) { | |
| 36 | 45 | $this->set_sales(); |
| 37 | 46 | } |
| 38 | 47 | } |
| 39 | 48 | |
| 40 | 49 | /** |
| 50 | + * @since 6.34 | |
| 51 | + * | |
| 52 | + * @return void | |
| 53 | + */ | |
| 54 | + public static function prevent_new_sales_requests() { | |
| 55 | + self::$prevent_new_sales_request = true; | |
| 56 | + } | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * @since 6.34 | |
| 60 | + * | |
| 61 | + * @return void | |
| 62 | + */ | |
| 63 | + public static function allow_new_sales_requests() { | |
| 64 | + self::$prevent_new_sales_request = false; | |
| 65 | + } | |
| 66 | + | |
| 67 | + /** | |
| 41 | 68 | * @since 6.17 |
| 42 | 69 | * |
| 43 | 70 | * @return void |
| 44 | 71 | */ |
| @@ -55,8 +82,25 @@ | ||
| 55 | 82 | return 'https://plapi.formidableforms.com/sales/'; |
| 56 | 83 | } |
| 57 | 84 | |
| 58 | 85 | /** |
| 86 | + * If the last check was a rate limit, we'll need to check again sooner. | |
| 87 | + * Other APIs use the FrmFormApi function which uses a 5 minute timeout. But for Sales, we can use 1 hour. | |
| 88 | + * | |
| 89 | + * @since 6.32 | |
| 90 | + * | |
| 91 | + * @param array $addons | |
| 92 | + * | |
| 93 | + * @return string | |
| 94 | + */ | |
| 95 | + protected function get_cache_timeout( $addons ) { | |
| 96 | + if ( isset( $addons['response_code'] ) && 429 === $addons['response_code'] ) { | |
| 97 | + return '+1 hour'; | |
| 98 | + } | |
| 99 | + return $this->cache_timeout; | |
| 100 | + } | |
| 101 | + | |
| 102 | + /** | |
| 59 | 103 | * @since 6.17 |
| 60 | 104 | * |
| 61 | 105 | * @return void |
| 62 | 106 | */ |
| @@ -63,9 +107,10 @@ | ||
| 63 | 107 | private function set_sales() { |
| 64 | 108 | self::$sales = array(); |
| 65 | 109 | |
| 66 | 110 | $api = $this->get_api_info(); |
| 67 | - if ( empty( $api ) ) { | |
| 111 | + | |
| 112 | + if ( ! $api ) { | |
| 68 | 113 | return; |
| 69 | 114 | } |
| 70 | 115 | |
| 71 | 116 | foreach ( $api as $sale ) { |
| @@ -85,10 +130,10 @@ | ||
| 85 | 130 | return; |
| 86 | 131 | } |
| 87 | 132 | |
| 88 | 133 | if ( ! is_array( $sale ) || ! isset( $sale['key'] ) ) { |
| 89 | - // if the API response is invalid, $sale may not be an array. | |
| 90 | - // if there are no sales from the API, it is returning a "No Entries Found" item with no key, so check for a key as well. | |
| 134 | + // If the API response is invalid, $sale may not be an array. | |
| 135 | + // If there are no sales from the API, it is returning a "No Entries Found" item with no key, so check for a key as well. | |
| 91 | 136 | return; |
| 92 | 137 | } |
| 93 | 138 | |
| 94 | 139 | if ( ! $this->sale_is_active( $sale ) ) { |
| @@ -99,8 +144,9 @@ | ||
| 99 | 144 | } |
| 100 | 145 | |
| 101 | 146 | /** |
| 102 | 147 | * @param array $sale |
| 148 | + * | |
| 103 | 149 | * @return array |
| 104 | 150 | */ |
| 105 | 151 | private function fill_sale( $sale ) { |
| 106 | 152 | $defaults = array( |
| @@ -143,8 +189,9 @@ | ||
| 143 | 189 | * |
| 144 | 190 | * @since 6.17 |
| 145 | 191 | * |
| 146 | 192 | * @param array $sale |
| 193 | + * | |
| 147 | 194 | * @return bool |
| 148 | 195 | */ |
| 149 | 196 | private function sale_is_active( $sale ) { |
| 150 | 197 | $starts = $sale['starts']; |
| @@ -168,8 +215,9 @@ | ||
| 168 | 215 | return self::$best_sale; |
| 169 | 216 | } |
| 170 | 217 | |
| 171 | 218 | $best_sale = false; |
| 219 | + | |
| 172 | 220 | foreach ( self::$sales as $sale ) { |
| 173 | 221 | if ( ! FrmApiHelper::is_for_user( $sale ) ) { |
| 174 | 222 | continue; |
| 175 | 223 | } |
| @@ -192,8 +240,9 @@ | ||
| 192 | 240 | * |
| 193 | 241 | * @since 6.17 |
| 194 | 242 | * |
| 195 | 243 | * @param string $key |
| 244 | + * | |
| 196 | 245 | * @return false|string False if no sale is active. |
| 197 | 246 | */ |
| 198 | 247 | public static function get_best_sale_value( $key ) { |
| 199 | 248 | if ( ! isset( self::$instance ) ) { |
| @@ -201,9 +250,19 @@ | ||
| 201 | 250 | } |
| 202 | 251 | |
| 203 | 252 | $sale = self::$instance->get_best_sale(); |
| 204 | 253 | |
| 205 | - return is_array( $sale ) && ! empty( $sale[ $key ] ) ? $sale[ $key ] : false; | |
| 254 | + if ( ! is_array( $sale ) || empty( $sale[ $key ] ) ) { | |
| 255 | + return false; | |
| 256 | + } | |
| 257 | + | |
| 258 | + $sale_value = $sale[ $key ]; | |
| 259 | + | |
| 260 | + if ( str_ends_with( $key, '_link' ) && ! str_starts_with( $sale_value, 'https://formidableforms.com' ) ) { | |
| 261 | + return false; | |
| 262 | + } | |
| 263 | + | |
| 264 | + return $sale_value; | |
| 206 | 265 | } |
| 207 | 266 | |
| 208 | 267 | /** |
| 209 | 268 | * @since 6.17 |
| @@ -208,8 +267,9 @@ | ||
| 208 | 267 | /** |
| 209 | 268 | * @since 6.17 |
| 210 | 269 | * |
| 211 | 270 | * @param array $sale |
| 271 | + * | |
| 212 | 272 | * @return bool True if the sale is a match for the applicable group (if one is defined). |
| 213 | 273 | */ |
| 214 | 274 | private function matches_ab_group( $sale ) { |
| 215 | 275 | if ( ! is_numeric( $sale['test_group'] ) ) { |
| @@ -227,13 +287,15 @@ | ||
| 227 | 287 | * @return int 1 or 0. |
| 228 | 288 | */ |
| 229 | 289 | private function get_ab_group_for_current_site() { |
| 230 | 290 | $option = get_option( 'frm_sale_ab_group' ); |
| 291 | + | |
| 231 | 292 | if ( ! is_numeric( $option ) ) { |
| 232 | 293 | // Generate either 0 or 1. |
| 233 | 294 | $option = mt_rand( 0, 1 ); |
| 234 | 295 | update_option( 'frm_sale_ab_group', $option, false ); |
| 235 | 296 | } |
| 297 | + | |
| 236 | 298 | return (int) $option; |
| 237 | 299 | } |
| 238 | 300 | |
| 239 | 301 | /** |
| @@ -252,8 +314,9 @@ | ||
| 252 | 314 | self::$instance = new FrmSalesApi(); |
| 253 | 315 | } |
| 254 | 316 | |
| 255 | 317 | $sale = self::$instance->get_best_sale(); |
| 318 | + | |
| 256 | 319 | if ( ! $sale || ! is_array( $sale ) ) { |
| 257 | 320 | return false; |
| 258 | 321 | } |
| 259 | 322 | |
| @@ -301,11 +364,13 @@ | ||
| 301 | 364 | $cta_attrs = array( |
| 302 | 365 | 'href' => '#', |
| 303 | 366 | 'style' => '', |
| 304 | 367 | ); |
| 368 | + | |
| 305 | 369 | if ( false !== $banner_cta_text_color ) { |
| 306 | 370 | $cta_attrs['style'] .= 'color: ' . esc_attr( $banner_cta_text_color ) . ';'; |
| 307 | 371 | } |
| 372 | + | |
| 308 | 373 | if ( false !== $banner_cta_bg_color ) { |
| 309 | 374 | $cta_attrs['style'] .= 'background-color: ' . esc_attr( $banner_cta_bg_color ) . ';'; |
| 310 | 375 | } |
| 311 | 376 | |
| @@ -320,8 +385,9 @@ | ||
| 320 | 385 | $content_attrs['style'] = 'color: ' . esc_attr( $banner_text_color ) . ';'; |
| 321 | 386 | $dismiss_attrs['style'] = 'color: ' . esc_attr( $banner_text_color ) . ';'; |
| 322 | 387 | } |
| 323 | 388 | |
| 389 | + // phpcs:disable Generic.WhiteSpace.ScopeIndent | |
| 324 | 390 | ?> |
| 325 | 391 | <div <?php FrmAppHelper::array_to_html_params( $banner_attrs, true ); ?>> |
| 326 | 392 | <div> |
| 327 | 393 | <img src="<?php echo esc_url( FrmAppHelper::plugin_url() . '/images/sales/' . $banner_icon . '.svg' ); ?>" alt="<?php echo esc_attr( $banner_title ); ?>" /> |
| @@ -326,9 +392,9 @@ | ||
| 326 | 392 | <div> |
| 327 | 393 | <img src="<?php echo esc_url( FrmAppHelper::plugin_url() . '/images/sales/' . $banner_icon . '.svg' ); ?>" alt="<?php echo esc_attr( $banner_title ); ?>" /> |
| 328 | 394 | </div> |
| 329 | 395 | <div <?php FrmAppHelper::array_to_html_params( $content_attrs, true ); ?>> |
| 330 | - <div> | |
| 396 | + <div class="frm-text-md frm-font-semibold"> | |
| 331 | 397 | <?php echo esc_html( $banner_title ); ?> |
| 332 | 398 | </div> |
| 333 | 399 | <div> |
| 334 | 400 | <?php echo esc_html( $banner_body ); ?> |
| @@ -338,11 +404,12 @@ | ||
| 338 | 404 | <a <?php FrmAppHelper::array_to_html_params( $cta_attrs, true ); ?>> |
| 339 | 405 | <?php echo esc_html( $banner_cta_text ); ?> |
| 340 | 406 | </a> |
| 341 | 407 | </div> |
| 342 | - <a <?php FrmAppHelper::array_to_html_params( $dismiss_attrs, true ); ?>><?php FrmAppHelper::icon_by_class( 'frm_icon_font frm_close_icon' ); ?></a> | |
| 408 | + <a <?php FrmAppHelper::array_to_html_params( $dismiss_attrs, true ); ?>><?php FrmAppHelper::icon_by_class( 'frmfont frm_close_icon' ); ?></a> | |
| 343 | 409 | </div> |
| 344 | 410 | <?php |
| 411 | + // phpcs:enable Generic.WhiteSpace.ScopeIndent | |
| 345 | 412 | |
| 346 | 413 | return true; |
| 347 | 414 | } |
| 348 | 415 | |
| @@ -359,13 +426,15 @@ | ||
| 359 | 426 | self::$instance = new FrmSalesApi(); |
| 360 | 427 | } |
| 361 | 428 | |
| 362 | 429 | $sale = self::$instance->get_best_sale(); |
| 430 | + | |
| 363 | 431 | if ( ! $sale || ! is_array( $sale ) ) { |
| 364 | 432 | wp_send_json_error(); |
| 365 | 433 | } |
| 366 | 434 | |
| 367 | 435 | $dismissed_sales = get_user_option( 'frm_dismissed_sales', get_current_user_id() ); |
| 436 | + | |
| 368 | 437 | if ( ! is_array( $dismissed_sales ) ) { |
| 369 | 438 | $dismissed_sales = array(); |
| 370 | 439 | } |
| 371 | 440 | |
| @@ -376,8 +445,9 @@ | ||
| 376 | 445 | } |
| 377 | 446 | |
| 378 | 447 | /** |
| 379 | 448 | * @param string $key |
| 449 | + * | |
| 380 | 450 | * @return bool |
| 381 | 451 | */ |
| 382 | 452 | private static function is_banner_dismissed( $key ) { |
| 383 | 453 | $dismissed_sales = get_user_option( 'frm_dismissed_sales', get_current_user_id() ); |