PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 3.06.06
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v3.06.06
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 +41 -418 6.35 → 3.06.06 View file →
@@ -1,57 +1,14 @@
1 1 <?php
2 -if ( ! defined( 'ABSPATH' ) ) {
3 - die( 'You are not allowed to call this page directly.' );
4 -}
5 2
6 3 class FrmFormApi {
7 4
8 - /**
9 - * @var string
10 - */
11 - protected $license = '';
12 -
13 - /**
14 - * @var string
15 - */
5 + protected $license = '';
16 6 protected $cache_key = '';
17 -
18 - /**
19 - * @var string
20 - */
21 7 protected $cache_timeout = '+6 hours';
22 8
23 9 /**
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 10 * @since 3.06
50 - *
51 - * @param string|null $license The license key.
52 - *
53 - * @return void
54 11 */
55 12 public function __construct( $license = null ) {
56 13 $this->set_license( $license );
57 14 $this->set_cache_key();
@@ -58,18 +15,13 @@
58 15 }
59 16
60 17 /**
61 18 * @since 3.06
62 - *
63 - * @param string|null $license The license key.
64 - *
65 - * @return void
66 19 */
67 20 private function set_license( $license ) {
68 21 if ( $license === null ) {
69 22 $edd_update = $this->get_pro_updater();
70 -
71 - if ( $edd_update ) {
23 + if ( ! empty( $edd_update ) ) {
72 24 $license = $edd_update->license;
73 25 }
74 26 }
75 27 $this->license = $license;
@@ -76,9 +28,8 @@
76 28 }
77 29
78 30 /**
79 31 * @since 3.06
80 - *
81 32 * @return string
82 33 */
83 34 public function get_license() {
84 35 return $this->license;
@@ -85,18 +36,15 @@
85 36 }
86 37
87 38 /**
88 39 * @since 3.06
89 - *
90 - * @return void
91 40 */
92 41 protected function set_cache_key() {
93 - $this->cache_key = 'frm_addons_l' . ( $this->license ? md5( $this->license ) : '' );
42 + $this->cache_key = 'frm_addons_l' . ( empty( $this->license ) ? '' : md5( $this->license ) );
94 43 }
95 44
96 45 /**
97 46 * @since 3.06
98 - *
99 47 * @return string
100 48 */
101 49 public function get_cache_key() {
102 50 return $this->cache_key;
@@ -102,179 +50,54 @@
102 50 return $this->cache_key;
103 51 }
104 52
105 53 /**
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 54 * @since 3.06
119 - *
120 55 * @return array
121 56 */
122 57 public function get_api_info() {
123 58 $url = $this->api_url();
124 -
125 - if ( $this->license ) {
59 + if ( ! empty( $this->license ) ) {
126 60 $url .= '?l=' . urlencode( base64_encode( $this->license ) );
127 61 }
128 62
129 - if ( $this->force ) {
130 - $addons = false;
131 - $this->force = false;
132 - } else {
133 - $addons = $this->get_cached();
134 - }
135 -
136 - if ( is_array( $addons ) ) {
63 + $addons = $this->get_cached();
64 + if ( ! empty( $addons ) ) {
137 65 return $addons;
138 66 }
139 67
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 - // We need to know the version number to allow different downloads.
148 - $agent = 'formidable/' . FrmAppHelper::plugin_version();
149 -
150 - if ( class_exists( 'FrmProDb' ) ) {
151 - $agent = 'formidable-pro/' . FrmProDb::$plug_version;
152 - }
153 -
154 - $response = wp_remote_get(
155 - $url,
156 - array(
157 - 'timeout' => 25,
158 - 'user-agent' => $agent . '; ' . get_bloginfo( 'url' ),
159 - )
160 - );
161 -
68 + $response = wp_remote_get( $url );
162 69 if ( is_array( $response ) && ! is_wp_error( $response ) ) {
163 - $addons = $response['body'] ? json_decode( $response['body'], true ) : array();
164 - }
70 + $addons = $response['body'];
71 + if ( ! empty( $addons ) ) {
72 + $addons = json_decode( $addons, true );
165 73
166 - if ( ! is_array( $addons ) ) {
167 - $addons = array();
168 - }
169 -
170 - $addons['response_code'] = wp_remote_retrieve_response_code( $response );
171 -
172 - foreach ( $addons as $k => $addon ) {
173 - if ( ! is_array( $addon ) ) {
174 - continue;
175 - }
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;
74 + foreach ( $addons as $k => $addon ) {
75 + if ( ! isset( $addon['categories'] ) ) {
76 + continue;
77 + }
78 + $cats = array_intersect( $this->skip_categories(), $addon['categories'] );
79 + if ( ! empty( $cats ) ) {
80 + unset( $addons[ $k ] );
81 + }
183 82 }
184 - }
185 83
186 - if ( ! array_key_exists( 'is_new', $addon ) && array_key_exists( 'released', $addon ) ) {
187 - $addons[ $k ]['is_new'] = $this->is_new( $addon );
84 + $this->set_cached( $addons );
188 85 }
189 86 }
190 87
191 - $this->set_cached( $addons );
192 - $this->done_running();
193 -
194 88 return $addons;
195 89 }
196 90
197 91 /**
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 92 * @since 3.06
262 - *
263 - * @return string
264 93 */
265 94 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 95 return 'https://formidableforms.com/wp-json/s11edd/v1/updates/';
271 96 }
272 97
273 98 /**
274 99 * @since 3.06
275 - *
276 - * @return string[]
277 100 */
278 101 protected function skip_categories() {
279 102 return array( 'WordPress Form Templates', 'WordPress Form Style Templates' );
280 103 }
@@ -280,25 +103,20 @@
280 103 }
281 104
282 105 /**
283 106 * @since 3.06
284 - *
285 - * @param object $license_plugin The FrmAddon object.
286 - * @param array $addons
287 - *
107 + * @param object $license_plugin The FrmAddon object
288 108 * @return array
289 109 */
290 110 public function get_addon_for_license( $license_plugin, $addons = array() ) {
291 - if ( ! $addons ) {
111 + if ( empty( $addons ) ) {
292 112 $addons = $this->get_api_info();
293 113 }
294 -
295 114 $download_id = $license_plugin->download_id;
296 - $plugin = array();
297 -
298 - if ( ! $download_id && $addons ) {
115 + $plugin = array();
116 + if ( empty( $download_id ) && ! empty( $addons ) ) {
299 117 foreach ( $addons as $addon ) {
300 - if ( is_array( $addon ) && ! empty( $addon['title'] ) && 0 === strcasecmp( $license_plugin->plugin_name, $addon['title'] ) ) {
118 + if ( strtolower( $license_plugin->plugin_name ) == strtolower( $addon['title'] ) ) {
301 119 return $addon;
302 120 }
303 121 }
304 122 } elseif ( isset( $addons[ $download_id ] ) ) {
@@ -309,16 +127,13 @@
309 127 }
310 128
311 129 /**
312 130 * @since 3.06
313 - *
314 - * @return false|object
315 131 */
316 132 public function get_pro_updater() {
317 133 if ( FrmAppHelper::pro_is_installed() && is_callable( 'FrmProAppHelper::get_updater' ) ) {
318 134 $updater = FrmProAppHelper::get_updater();
319 135 $this->set_license( $updater->license );
320 -
321 136 return $updater;
322 137 }
323 138
324 139 return false;
@@ -325,255 +140,63 @@
325 140 }
326 141
327 142 /**
328 143 * @since 3.06
329 - *
330 - * @return array|bool
144 + * @return array
331 145 */
332 146 protected function get_cached() {
333 - $cache = $this->get_cached_option();
147 + $cache = get_option( $this->cache_key );
334 148
335 - if ( ! $cache ) {
336 - return false;
149 + if ( empty( $cache ) || empty( $cache['timeout'] ) || current_time( 'timestamp' ) > $cache['timeout'] ) {
150 + return false; // Cache is expired
337 151 }
338 152
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;
343 - }
344 -
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() ) {
349 - return false;
350 - }
351 -
352 - return $this->decode_cached( $cache['value'] );
153 + return json_decode( $cache['value'], true );
353 154 }
354 155
355 156 /**
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 157 * @since 3.06
408 - *
409 - * @param array $addons
410 - *
411 - * @return void
412 158 */
413 159 protected function set_cached( $addons ) {
414 - $addons = $this->reduce_addon_data_before_caching( $addons );
415 -
416 160 $data = array(
417 - 'timeout' => strtotime( $this->get_cache_timeout( $addons ), time() ),
418 - 'value' => wp_json_encode( $addons ),
419 - 'version' => FrmAppHelper::plugin_version(),
161 + 'timeout' => strtotime( $this->cache_timeout, current_time( 'timestamp' ) ),
162 + 'value' => json_encode( $addons ),
420 163 );
421 164
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 - }
165 + update_option( $this->cache_key, $data, 'no' );
429 166 }
430 167
431 168 /**
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 169 * @since 3.06
517 - *
518 - * @return void
519 170 */
520 171 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();
172 + delete_option( $this->cache_key );
527 173 }
528 174
529 175 /**
530 176 * @since 3.06
531 - *
532 177 * @return array
533 178 */
534 179 public function error_for_license() {
535 - return $this->license ? $this->get_error_from_response() : array();
180 + $errors = array();
181 + if ( ! empty( $this->license ) ) {
182 + $errors = $this->get_error_from_response();
183 + }
184 + return $errors;
536 185 }
537 186
538 187 /**
539 188 * @since 3.06
540 - *
541 - * @param array $addons
542 - *
543 189 * @return array
544 190 */
545 191 public function get_error_from_response( $addons = array() ) {
546 - if ( ! $addons ) {
192 + if ( empty( $addons ) ) {
547 193 $addons = $this->get_api_info();
548 194 }
549 -
550 195 $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'] ) ) {
196 + if ( isset( $addons['error'] ) ) {
559 197 $errors[] = $addons['error']['message'];
198 + do_action( 'frm_license_error', $addons['error'] );
560 199 }
561 -
562 - do_action( 'frm_license_error', $addons['error'] );
563 -
564 200 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 201 }
579 202 }