PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 5.4
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v5.4
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
← All changes | classes/models/FrmFormApi.php +36 -379 6.355.4 View file →
@@ -4,54 +4,14 @@
4 4 }
5 5
6 6 class FrmFormApi {
7 7
8 - /**
9 - * @var string
10 - */
11 8 protected $license = '';
12 -
13 - /**
14 - * @var string
15 - */
16 9 protected $cache_key = '';
17 -
18 - /**
19 - * @var string
20 - */
21 10 protected $cache_timeout = '+6 hours';
22 11
23 12 /**
24 - * The number of days an add-on is new.
25 - *
26 - * @var int
27 - */
28 - protected $new_days = 90;
29 -
30 - /**
31 - * If true, calls to get_api_info will bypass cache.
32 - * This is set true by calling force_api_request.
33 - *
34 - * @var bool
35 - */
36 - protected $force = false;
37 -
38 - /**
39 - * The most recent decode of each cache key's payload.
40 - *
41 - * Each entry is array( 'raw' => string, 'decoded' => mixed ). The raw JSON is kept so a reuse
42 - * can be checked against what is stored right now, rather than assumed to still be current.
43 - *
44 - * @var array<string,array>
45 - */
46 - private static $decoded_cache = array();
47 -
48 - /**
49 13 * @since 3.06
50 - *
51 - * @param string|null $license The license key.
52 - *
53 - * @return void
54 14 */
55 15 public function __construct( $license = null ) {
56 16 $this->set_license( $license );
57 17 $this->set_cache_key();
@@ -58,18 +18,13 @@
58 18 }
59 19
60 20 /**
61 21 * @since 3.06
62 - *
63 - * @param string|null $license The license key.
64 - *
65 - * @return void
66 22 */
67 23 private function set_license( $license ) {
68 24 if ( $license === null ) {
69 25 $edd_update = $this->get_pro_updater();
70 -
71 - if ( $edd_update ) {
26 + if ( ! empty( $edd_update ) ) {
72 27 $license = $edd_update->license;
73 28 }
74 29 }
75 30 $this->license = $license;
@@ -76,9 +31,8 @@
76 31 }
77 32
78 33 /**
79 34 * @since 3.06
80 - *
81 35 * @return string
82 36 */
83 37 public function get_license() {
84 38 return $this->license;
@@ -85,18 +39,15 @@
85 39 }
86 40
87 41 /**
88 42 * @since 3.06
89 - *
90 - * @return void
91 43 */
92 44 protected function set_cache_key() {
93 - $this->cache_key = 'frm_addons_l' . ( $this->license ? md5( $this->license ) : '' );
45 + $this->cache_key = 'frm_addons_l' . ( empty( $this->license ) ? '' : md5( $this->license ) );
94 46 }
95 47
96 48 /**
97 49 * @since 3.06
98 - *
99 50 * @return string
100 51 */
101 52 public function get_cache_key() {
102 53 return $this->cache_key;
@@ -102,52 +53,24 @@
102 53 return $this->cache_key;
103 54 }
104 55
105 56 /**
106 - * Flag the force property as true, so the next API request bypasses cache.
107 - * This is used to pull API data for change logs, which are excluded from the cached data.
108 - *
109 - * @since 6.28
110 - *
111 - * @return void
112 - */
113 - public function force_api_request() {
114 - $this->force = true;
115 - }
116 -
117 - /**
118 57 * @since 3.06
119 - *
120 58 * @return array
121 59 */
122 60 public function get_api_info() {
123 61 $url = $this->api_url();
124 -
125 - if ( $this->license ) {
62 + if ( ! empty( $this->license ) ) {
126 63 $url .= '?l=' . urlencode( base64_encode( $this->license ) );
127 64 }
128 65
129 - if ( $this->force ) {
130 - $addons = false;
131 - $this->force = false;
132 - } else {
133 - $addons = $this->get_cached();
134 - }
135 -
66 + $addons = $this->get_cached();
136 67 if ( is_array( $addons ) ) {
137 68 return $addons;
138 69 }
139 70
140 - if ( $this->is_running() ) {
141 - // If there's no saved cache, we'll need to wait for the current request to finish.
142 - return array();
143 - }
144 -
145 - $this->set_running();
146 -
147 71 // We need to know the version number to allow different downloads.
148 72 $agent = 'formidable/' . FrmAppHelper::plugin_version();
149 -
150 73 if ( class_exists( 'FrmProDb' ) ) {
151 74 $agent = 'formidable-pro/' . FrmProDb::$plug_version;
152 75 }
153 76
@@ -166,115 +89,32 @@
166 89 if ( ! is_array( $addons ) ) {
167 90 $addons = array();
168 91 }
169 92
170 - $addons['response_code'] = wp_remote_retrieve_response_code( $response );
171 -
172 93 foreach ( $addons as $k => $addon ) {
173 - if ( ! is_array( $addon ) ) {
94 + if ( ! isset( $addon['categories'] ) ) {
174 95 continue;
175 96 }
176 -
177 - if ( isset( $addon['categories'] ) ) {
178 - $cats = array_intersect( $this->skip_categories(), $addon['categories'] );
179 -
180 - if ( $cats ) {
181 - unset( $addons[ $k ] );
182 - continue;
183 - }
97 + $cats = array_intersect( $this->skip_categories(), $addon['categories'] );
98 + if ( ! empty( $cats ) ) {
99 + unset( $addons[ $k ] );
184 100 }
185 -
186 - if ( ! array_key_exists( 'is_new', $addon ) && array_key_exists( 'released', $addon ) ) {
187 - $addons[ $k ]['is_new'] = $this->is_new( $addon );
188 - }
189 101 }
190 102
191 103 $this->set_cached( $addons );
192 - $this->done_running();
193 104
194 105 return $addons;
195 106 }
196 107
197 108 /**
198 - * Prevent multiple requests from running at the same time.
199 - *
200 - * @since 6.8.3
201 - *
202 - * @return bool
203 - */
204 - protected function is_running() {
205 - if ( $this->run_as_multisite() ) {
206 - return get_site_transient( $this->transient_key() );
207 - }
208 - return get_transient( $this->transient_key() );
209 - }
210 -
211 - /**
212 - * @since 6.8.3
213 - *
214 - * @return void
215 - */
216 - protected function set_running() {
217 - $expires = 2 * MINUTE_IN_SECONDS;
218 -
219 - if ( $this->run_as_multisite() ) {
220 - set_site_transient( $this->transient_key(), true, $expires );
221 - return;
222 - }
223 -
224 - set_transient( $this->transient_key(), true, $expires );
225 - }
226 -
227 - /**
228 - * @since 6.8.3
229 - *
230 - * @return void
231 - */
232 - protected function done_running() {
233 - if ( $this->run_as_multisite() ) {
234 - delete_site_transient( $this->transient_key() );
235 - }
236 - delete_transient( $this->transient_key() );
237 - }
238 -
239 - /**
240 - * Only allow one site in the network to make the api request at a time.
241 - * If there is a license for the request, run individually.
242 - *
243 - * @since 6.8.3
244 - *
245 - * @return bool
246 - */
247 - protected function run_as_multisite() {
248 - return is_multisite() && ! $this->license;
249 - }
250 -
251 - /**
252 - * @since 6.8.3
253 - *
254 - * @return string
255 - */
256 - protected function transient_key() {
257 - return strtolower( static::class ) . '_request_lock';
258 - }
259 -
260 - /**
261 109 * @since 3.06
262 - *
263 - * @return string
264 110 */
265 111 protected function api_url() {
266 - if ( ! $this->license ) {
267 - // Direct traffic to Cloudflare worker when there is no license.
268 - return 'https://plapi.formidableforms.com/list/';
269 - }
270 112 return 'https://formidableforms.com/wp-json/s11edd/v1/updates/';
271 113 }
272 114
273 115 /**
274 116 * @since 3.06
275 - *
276 - * @return string[]
277 117 */
278 118 protected function skip_categories() {
279 119 return array( 'WordPress Form Templates', 'WordPress Form Style Templates' );
280 120 }
@@ -281,24 +121,21 @@
281 121
282 122 /**
283 123 * @since 3.06
284 124 *
285 - * @param object $license_plugin The FrmAddon object.
286 - * @param array $addons
125 + * @param object $license_plugin The FrmAddon object
287 126 *
288 127 * @return array
289 128 */
290 129 public function get_addon_for_license( $license_plugin, $addons = array() ) {
291 - if ( ! $addons ) {
130 + if ( empty( $addons ) ) {
292 131 $addons = $this->get_api_info();
293 132 }
294 -
295 133 $download_id = $license_plugin->download_id;
296 134 $plugin = array();
297 -
298 - if ( ! $download_id && $addons ) {
135 + if ( empty( $download_id ) && ! empty( $addons ) ) {
299 136 foreach ( $addons as $addon ) {
300 - if ( is_array( $addon ) && ! empty( $addon['title'] ) && 0 === strcasecmp( $license_plugin->plugin_name, $addon['title'] ) ) {
137 + if ( strtolower( $license_plugin->plugin_name ) == strtolower( $addon['title'] ) ) {
301 138 return $addon;
302 139 }
303 140 }
304 141 } elseif ( isset( $addons[ $download_id ] ) ) {
@@ -309,10 +146,8 @@
309 146 }
310 147
311 148 /**
312 149 * @since 3.06
313 - *
314 - * @return false|object
315 150 */
316 151 public function get_pro_updater() {
317 152 if ( FrmAppHelper::pro_is_installed() && is_callable( 'FrmProAppHelper::get_updater' ) ) {
318 153 $updater = FrmProAppHelper::get_updater();
@@ -325,255 +160,77 @@
325 160 }
326 161
327 162 /**
328 163 * @since 3.06
329 - *
330 - * @return array|bool
164 + * @return array
331 165 */
332 166 protected function get_cached() {
333 - $cache = $this->get_cached_option();
167 + $cache = get_option( $this->cache_key );
334 168
335 - if ( ! $cache ) {
336 - return false;
337 - }
169 + FrmAppHelper::filter_gmt_offset();
338 170
339 - $is_expired = empty( $cache['timeout'] ) || time() > $cache['timeout'];
340 -
341 - if ( ! $is_expired && isset( $cache['version'] ) && $cache['version'] !== FrmAppHelper::plugin_version() ) {
342 - $is_expired = true;
171 + if ( empty( $cache ) || empty( $cache['timeout'] ) || current_time( 'timestamp' ) > $cache['timeout'] ) {
172 + return false; // Cache is expired
343 173 }
344 174
345 - // Avoid old cached data, unless we're currently trying to query for new data.
346 - // The call to $this->is_running likely triggers a database query, so only call if if we're expired.
347 - // (Rather than the other way around, which is less efficient).
348 - if ( $is_expired && ! $this->is_running() ) {
175 + $version = FrmAppHelper::plugin_version();
176 + $for_current = isset( $cache['version'] ) && $cache['version'] == $version;
177 + if ( ! $for_current ) {
178 + // Force a new check.
349 179 return false;
350 180 }
351 181
352 - return $this->decode_cached( $cache['value'] );
182 + return json_decode( $cache['value'], true );
353 183 }
354 184
355 185 /**
356 - * Decode a cached API payload, reusing the previous decode of an unchanged payload.
357 - *
358 - * The payload runs to tens of kilobytes of JSON, and decoding it costs far more than reading
359 - * the option it came from. That matters because a single form builder page decodes it once per
360 - * upsell it renders. The reuse is keyed on the raw JSON rather than on the cache key, so
361 - * anything that changes what is stored -- a new license, a cleared cache, a fresh API response,
362 - * a test replacing the option -- is picked up on the very next read instead of being masked.
363 - *
364 - * @since 6.35
365 - *
366 - * @param string $value The cached JSON.
367 - *
368 - * @return mixed
369 - */
370 - private function decode_cached( $value ) {
371 - $previous = self::$decoded_cache[ $this->cache_key ] ?? false;
372 -
373 - if ( $previous && $previous['raw'] === $value ) {
374 - return $previous['decoded'];
375 - }
376 -
377 - $decoded = json_decode( $value, true );
378 -
379 - self::$decoded_cache[ $this->cache_key ] = array(
380 - 'raw' => $value,
381 - 'decoded' => $decoded,
382 - );
383 -
384 - return $decoded;
385 - }
386 -
387 - /**
388 - * Get the cache for the network if multisite.
389 - *
390 - * @since 6.8.3
391 - *
392 - * @return mixed
393 - */
394 - protected function get_cached_option() {
395 - if ( is_multisite() ) {
396 - $cached = get_site_option( $this->cache_key );
397 -
398 - if ( $cached ) {
399 - return $cached;
400 - }
401 - }
402 -
403 - return get_option( $this->cache_key );
404 - }
405 -
406 - /**
407 186 * @since 3.06
408 - *
409 - * @param array $addons
410 - *
411 - * @return void
412 187 */
413 188 protected function set_cached( $addons ) {
414 - $addons = $this->reduce_addon_data_before_caching( $addons );
189 + FrmAppHelper::filter_gmt_offset();
415 190
416 191 $data = array(
417 - 'timeout' => strtotime( $this->get_cache_timeout( $addons ), time() ),
418 - 'value' => wp_json_encode( $addons ),
192 + 'timeout' => strtotime( $this->cache_timeout, current_time( 'timestamp' ) ),
193 + 'value' => json_encode( $addons ),
419 194 'version' => FrmAppHelper::plugin_version(),
420 195 );
421 196
422 - if ( is_multisite() ) {
423 - update_site_option( $this->cache_key, $data );
424 - } else {
425 - // Autoload the license cache because it gets called everywhere.
426 - $autoload = str_starts_with( $this->cache_key, 'frm_addons_l' );
427 - update_option( $this->cache_key, $data, $autoload );
428 - }
197 + update_option( $this->cache_key, $data, 'no' );
429 198 }
430 199
431 200 /**
432 - * Remove certain add-on API data that we don't need to cache.
433 - * This is to help keep the option data (which is auto-loaded) small.
434 - *
435 - * @since 6.28
436 - *
437 - * @param array $addons
438 - *
439 - * @return array
440 - */
441 - private function reduce_addon_data_before_caching( $addons ) {
442 - if ( is_subclass_of( $this, 'FrmFormApi' ) ) {
443 - // We only want to modify FrmFormApi. Leave the other APIs alone for now.
444 - return $addons;
445 - }
446 -
447 - $reduced_addons = array();
448 -
449 - foreach ( $addons as $key => $addon ) {
450 - if ( ! is_array( $addon ) ) {
451 - $reduced_addons[ $key ] = $addon;
452 - continue;
453 - }
454 -
455 - if ( ! $this->should_include_addon_in_cached_data( $addon ) ) {
456 - continue;
457 - }
458 -
459 - if ( isset( $addon['changelog'] ) ) {
460 - unset( $addon['changelog'], $addon['banners'] );
461 - }
462 -
463 - $reduced_addons[ $key ] = $addon;
464 - }
465 -
466 - return $reduced_addons;
467 - }
468 -
469 - /**
470 - * @since 6.28
471 - *
472 - * @param array $addon
473 - *
474 - * @return bool True if the add-on should be included in cached data.
475 - */
476 - private function should_include_addon_in_cached_data( $addon ) {
477 - if ( isset( $addon['version'] ) && '' === $addon['version'] ) {
478 - // If version is set but blank, the plugin is not actually live.
479 - return false;
480 - }
481 -
482 - if ( isset( $addon['categories'] ) ) {
483 - if ( isset( $addon['slug'] ) && 'views' === $addon['slug'] ) {
484 - // Legacy views has no categories set, but we should still
485 - // Include it in cache since it is a valid add-on.
486 - return true;
487 - }
488 -
489 - $categories_are_empty = ! $addon['categories'] || $addon['categories'] === array( 'Strategy11' );
490 -
491 - if ( $categories_are_empty ) {
492 - return false;
493 - }
494 - }
495 -
496 - return true;
497 - }
498 -
499 - /**
500 - * If the last check was a a rate limit, we'll need to check again sooner.
501 - *
502 - * @since 6.8.3
503 - *
504 - * @param array $addons
505 - *
506 - * @return string
507 - */
508 - protected function get_cache_timeout( $addons ) {
509 - if ( isset( $addons['response_code'] ) && 429 === $addons['response_code'] ) {
510 - return '+5 minutes';
511 - }
512 - return $this->cache_timeout;
513 - }
514 -
515 - /**
516 201 * @since 3.06
517 - *
518 - * @return void
519 202 */
520 203 public function reset_cached() {
521 - if ( is_multisite() ) {
522 - delete_site_option( $this->cache_key );
523 - } else {
524 - delete_option( $this->cache_key );
525 - }
526 - $this->done_running();
204 + delete_option( $this->cache_key );
527 205 }
528 206
529 207 /**
530 208 * @since 3.06
531 - *
532 209 * @return array
533 210 */
534 211 public function error_for_license() {
535 - return $this->license ? $this->get_error_from_response() : array();
212 + $errors = array();
213 + if ( ! empty( $this->license ) ) {
214 + $errors = $this->get_error_from_response();
215 + }
216 +
217 + return $errors;
536 218 }
537 219
538 220 /**
539 221 * @since 3.06
540 - *
541 - * @param array $addons
542 - *
543 222 * @return array
544 223 */
545 224 public function get_error_from_response( $addons = array() ) {
546 - if ( ! $addons ) {
225 + if ( empty( $addons ) ) {
547 226 $addons = $this->get_api_info();
548 227 }
549 -
550 228 $errors = array();
551 -
552 - if ( ! isset( $addons['error'] ) ) {
553 - return $errors;
554 - }
555 -
556 - if ( is_string( $addons['error'] ) ) {
557 - $errors[] = $addons['error'];
558 - } elseif ( ! empty( $addons['error']['message'] ) ) {
229 + if ( isset( $addons['error'] ) ) {
559 230 $errors[] = $addons['error']['message'];
231 + do_action( 'frm_license_error', $addons['error'] );
560 232 }
561 233
562 - do_action( 'frm_license_error', $addons['error'] );
563 -
564 234 return $errors;
565 - }
566 -
567 - /**
568 - * Check if a template is new.
569 - *
570 - * @since 6.0
571 - *
572 - * @param array $addon
573 - *
574 - * @return bool
575 - */
576 - protected function is_new( $addon ) {
577 - return strtotime( $addon['released'] ) > strtotime( '-' . $this->new_days . ' days' );
578 235 }
579 236 }