PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.32.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.32.1
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / models / FrmSalesApi.php

FrmSalesApi.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.32.1, at classes/models/FrmSalesApi.php

430 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 /**
7 * @since 6.17
8 */
9 class FrmSalesApi extends FrmFormApi {
10
11 /**
12 * @var FrmSalesApi|null
13 */
14 private static $instance;
15
16 protected $cache_key;
17
18 /**
19 * All sales from API data.
20 *
21 * @var array|false
22 */
23 private static $sales = false;
24
25 /**
26 * Best sale from API data.
27 *
28 * @var array|false|null
29 */
30 private static $best_sale;
31
32 public function __construct() {
33 $this->set_cache_key();
34
35 if ( false === self::$sales ) {
36 $this->set_sales();
37 }
38 }
39
40 /**
41 * @since 6.17
42 *
43 * @return void
44 */
45 protected function set_cache_key() {
46 $this->cache_key = 'frm_sales_cache';
47 }
48
49 /**
50 * @since 6.17
51 *
52 * @return string
53 */
54 protected function api_url() {
55 return 'https://plapi.formidableforms.com/sales/';
56 }
57
58 /**
59 * If the last check was a rate limit, we'll need to check again sooner.
60 * Other APIs use the FrmFormApi function which uses a 5 minute timeout. But for Sales, we can use 1 hour.
61 *
62 * @since 6.32
63 *
64 * @param array $addons
65 *
66 * @return string
67 */
68 protected function get_cache_timeout( $addons ) {
69 if ( isset( $addons['response_code'] ) && 429 === $addons['response_code'] ) {
70 return '+1 hour';
71 }
72 return $this->cache_timeout;
73 }
74
75 /**
76 * @since 6.17
77 *
78 * @return void
79 */
80 private function set_sales() {
81 self::$sales = array();
82
83 $api = $this->get_api_info();
84
85 if ( ! $api ) {
86 return;
87 }
88
89 foreach ( $api as $sale ) {
90 $this->add_sale( $sale );
91 }
92 }
93
94 /**
95 * @param array|string $sale
96 *
97 * @return void
98 */
99 private function add_sale( $sale ) {
100 if ( ! is_array( self::$sales ) ) {
101 // This gets set in the constructor.
102 // This check is just here for Psalm analysis.
103 return;
104 }
105
106 if ( ! is_array( $sale ) || ! isset( $sale['key'] ) ) {
107 // If the API response is invalid, $sale may not be an array.
108 // 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.
109 return;
110 }
111
112 if ( ! $this->sale_is_active( $sale ) ) {
113 return;
114 }
115
116 self::$sales[ $sale['key'] ] = $this->fill_sale( $sale );
117 }
118
119 /**
120 * @param array $sale
121 *
122 * @return array
123 */
124 private function fill_sale( $sale ) {
125 $defaults = array(
126 'key' => '',
127 'starts' => '',
128 'expires' => '',
129 // Use 'free', 'personal', 'business', 'elite', 'grandfathered'.
130 'who' => 'all',
131 'discount_percent' => 0,
132 'test_group' => '',
133 'lite_banner_cta_link' => '',
134 'lite_banner_cta_text' => '',
135 'menu_cta_link' => '',
136 'menu_cta_text' => '',
137 'dashboard_license_cta_link' => '',
138 'dashboard_license_cta_text' => '',
139 'global_settings_license_cta_link' => '',
140 'global_settings_license_cta_text' => '',
141 'global_settings_unlock_more_cta_link' => '',
142 'global_settings_unlock_more_cta_text' => '',
143 'global_settings_upgrade_cta_link' => '',
144 'builder_sidebar_cta_link' => '',
145 'builder_sidebar_cta_text' => '',
146 'banner_title' => '',
147 'banner_body' => '',
148 'banner_icon' => '',
149 'banner_text_color' => '',
150 'banner_bg_color' => '',
151 'banner_cta_link' => '',
152 'banner_cta_text' => '',
153 'banner_cta_text_color' => '',
154 'banner_cta_bg_color' => '',
155 );
156
157 return array_merge( $defaults, $sale );
158 }
159
160 /**
161 * Check if a sale is within the active period.
162 *
163 * @since 6.17
164 *
165 * @param array $sale
166 *
167 * @return bool
168 */
169 private function sale_is_active( $sale ) {
170 $starts = $sale['starts'];
171 $expires = $sale['expires'] + DAY_IN_SECONDS;
172 $date = new DateTime( 'now', new DateTimeZone( 'America/New_York' ) );
173 $today = $date->getTimestamp();
174 return $today >= $starts && $today <= $expires;
175 }
176
177 /**
178 * @since 6.17
179 *
180 * @return array|false
181 */
182 public function get_best_sale() {
183 if ( ! self::$sales ) {
184 return false;
185 }
186
187 if ( isset( self::$best_sale ) ) {
188 return self::$best_sale;
189 }
190
191 $best_sale = false;
192
193 foreach ( self::$sales as $sale ) {
194 if ( ! FrmApiHelper::is_for_user( $sale ) ) {
195 continue;
196 }
197
198 if ( ! $this->matches_ab_group( $sale ) ) {
199 continue;
200 }
201
202 if ( ! $best_sale || $sale['discount_percent'] > $best_sale['discount_percent'] ) {
203 $best_sale = $sale;
204 }
205 }
206
207 self::$best_sale = $best_sale;
208 return self::$best_sale;
209 }
210
211 /**
212 * Get text for best sale if applicable.
213 *
214 * @since 6.17
215 *
216 * @param string $key
217 *
218 * @return false|string False if no sale is active.
219 */
220 public static function get_best_sale_value( $key ) {
221 if ( ! isset( self::$instance ) ) {
222 self::$instance = new FrmSalesApi();
223 }
224
225 $sale = self::$instance->get_best_sale();
226
227 if ( ! is_array( $sale ) || empty( $sale[ $key ] ) ) {
228 return false;
229 }
230
231 $sale_value = $sale[ $key ];
232
233 if ( str_ends_with( $key, '_link' ) && ! str_starts_with( $sale_value, 'https://formidableforms.com' ) ) {
234 return false;
235 }
236
237 return $sale_value;
238 }
239
240 /**
241 * @since 6.17
242 *
243 * @param array $sale
244 *
245 * @return bool True if the sale is a match for the applicable group (if one is defined).
246 */
247 private function matches_ab_group( $sale ) {
248 if ( ! is_numeric( $sale['test_group'] ) ) {
249 // No test group, so return true.
250 return true;
251 }
252
253 $ab_group = $this->get_ab_group_for_current_site();
254 return $ab_group === $sale['test_group'];
255 }
256
257 /**
258 * @since 6.17
259 *
260 * @return int 1 or 0.
261 */
262 private function get_ab_group_for_current_site() {
263 $option = get_option( 'frm_sale_ab_group' );
264
265 if ( ! is_numeric( $option ) ) {
266 // Generate either 0 or 1.
267 $option = mt_rand( 0, 1 );
268 update_option( 'frm_sale_ab_group', $option, false );
269 }
270
271 return (int) $option;
272 }
273
274 /**
275 * Maybe show banner for the best sale.
276 *
277 * @since 6.21
278 *
279 * @return bool
280 */
281 public static function maybe_show_banner() {
282 if ( ! current_user_can( 'frm_change_settings' ) ) {
283 return false;
284 }
285
286 if ( ! isset( self::$instance ) ) {
287 self::$instance = new FrmSalesApi();
288 }
289
290 $sale = self::$instance->get_best_sale();
291
292 if ( ! $sale || ! is_array( $sale ) ) {
293 return false;
294 }
295
296 $banner_title = ! empty( $sale['banner_title'] ) ? $sale['banner_title'] : false;
297 $banner_body = ! empty( $sale['banner_body'] ) ? $sale['banner_body'] : false;
298
299 if ( false === $banner_title || false === $banner_body ) {
300 return false;
301 }
302
303 if ( self::is_banner_dismissed( $sale['key'] ) ) {
304 return false;
305 }
306
307 $banner_icon = ! empty( $sale['banner_icon'] ) ? $sale['banner_icon'] : 'generic';
308 $banner_bg_color = ! empty( $sale['banner_bg_color'] ) ? $sale['banner_bg_color'] : false;
309 $banner_text_color = ! empty( $sale['banner_text_color'] ) ? $sale['banner_text_color'] : false;
310 $banner_cta_link = ! empty( $sale['banner_cta_link'] ) ? $sale['banner_cta_link'] : false;
311
312 // translators: %s is the discount percentage.
313 $banner_cta_text = ! empty( $sale['banner_cta_text'] ) ? $sale['banner_cta_text'] : sprintf( __( 'GET %s OFF NOW', 'formidable' ), $sale['discount_percent'] . '%' );
314 $banner_cta_text_color = ! empty( $sale['banner_cta_text_color'] ) ? $sale['banner_cta_text_color'] : false;
315 $banner_cta_bg_color = ! empty( $sale['banner_cta_bg_color'] ) ? $sale['banner_cta_bg_color'] : false;
316
317 if ( false === $banner_cta_link ) {
318 $banner_cta_link = FrmAppHelper::admin_upgrade_link(
319 array(
320 'medium' => 'sales-api-banner',
321 'content' => $sale['key'],
322 )
323 );
324 }
325
326 $banner_attrs = array(
327 'id' => 'frm_sale_banner',
328 'data-url' => $banner_cta_link,
329 );
330
331 if ( false === $banner_bg_color || 'gradient' === $banner_bg_color ) {
332 $banner_attrs['class'] = 'frm-gradient';
333 } else {
334 $banner_attrs['style'] = 'background-color: ' . esc_attr( $banner_bg_color ) . ';';
335 }
336
337 $cta_attrs = array(
338 'href' => '#',
339 'style' => '',
340 );
341
342 if ( false !== $banner_cta_text_color ) {
343 $cta_attrs['style'] .= 'color: ' . esc_attr( $banner_cta_text_color ) . ';';
344 }
345
346 if ( false !== $banner_cta_bg_color ) {
347 $cta_attrs['style'] .= 'background-color: ' . esc_attr( $banner_cta_bg_color ) . ';';
348 }
349
350 $dismiss_attrs = array(
351 'href' => '#',
352 'class' => 'dismiss',
353 );
354
355 $content_attrs = array();
356
357 if ( false !== $banner_text_color ) {
358 $content_attrs['style'] = 'color: ' . esc_attr( $banner_text_color ) . ';';
359 $dismiss_attrs['style'] = 'color: ' . esc_attr( $banner_text_color ) . ';';
360 }
361
362 // phpcs:disable Generic.WhiteSpace.ScopeIndent
363 ?>
364 <div <?php FrmAppHelper::array_to_html_params( $banner_attrs, true ); ?>>
365 <div>
366 <img src="<?php echo esc_url( FrmAppHelper::plugin_url() . '/images/sales/' . $banner_icon . '.svg' ); ?>" alt="<?php echo esc_attr( $banner_title ); ?>" />
367 </div>
368 <div <?php FrmAppHelper::array_to_html_params( $content_attrs, true ); ?>>
369 <div class="frm-text-md frm-font-semibold">
370 <?php echo esc_html( $banner_title ); ?>
371 </div>
372 <div>
373 <?php echo esc_html( $banner_body ); ?>
374 </div>
375 </div>
376 <div>
377 <a <?php FrmAppHelper::array_to_html_params( $cta_attrs, true ); ?>>
378 <?php echo esc_html( $banner_cta_text ); ?>
379 </a>
380 </div>
381 <a <?php FrmAppHelper::array_to_html_params( $dismiss_attrs, true ); ?>><?php FrmAppHelper::icon_by_class( 'frmfont frm_close_icon' ); ?></a>
382 </div>
383 <?php
384 // phpcs:enable Generic.WhiteSpace.ScopeIndent
385
386 return true;
387 }
388
389 /**
390 * Dismiss a banner via AJAX hook.
391 *
392 * @since 6.21
393 */
394 public static function dismiss_banner() {
395 FrmAppHelper::permission_check( 'frm_view_forms' );
396 check_ajax_referer( 'frm_ajax', 'nonce' );
397
398 if ( ! isset( self::$instance ) ) {
399 self::$instance = new FrmSalesApi();
400 }
401
402 $sale = self::$instance->get_best_sale();
403
404 if ( ! $sale || ! is_array( $sale ) ) {
405 wp_send_json_error();
406 }
407
408 $dismissed_sales = get_user_option( 'frm_dismissed_sales', get_current_user_id() );
409
410 if ( ! is_array( $dismissed_sales ) ) {
411 $dismissed_sales = array();
412 }
413
414 $dismissed_sales[] = $sale['key'];
415 update_user_option( get_current_user_id(), 'frm_dismissed_sales', $dismissed_sales );
416
417 wp_send_json_success();
418 }
419
420 /**
421 * @param string $key
422 *
423 * @return bool
424 */
425 private static function is_banner_dismissed( $key ) {
426 $dismissed_sales = get_user_option( 'frm_dismissed_sales', get_current_user_id() );
427 return is_array( $dismissed_sales ) && in_array( $key, $dismissed_sales, true );
428 }
429 }
430