PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.34
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.34
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 6.5.4 All 140 releases
formidable / classes / models / FrmFormApi.php

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

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