PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
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/FrmAddon.php +810 -146 6.5.3 → trunk View file →
@@ -4,29 +4,144 @@
4 4 }
5 5
6 6 /** @phpstan-consistent-constructor */
7 7 class FrmAddon {
8 +
9 + /**
10 + * @var string
11 + */
8 12 public $store_url = 'https://formidableforms.com';
13 +
14 + /**
15 + * @var int|string|null
16 + */
9 17 public $download_id;
18 +
19 + /**
20 + * @var string|null
21 + */
10 22 public $plugin_file;
23 +
24 + /**
25 + * @var string
26 + */
11 27 public $plugin_folder;
28 +
29 + /**
30 + * @var string
31 + */
12 32 public $plugin_name;
33 +
34 + /**
35 + * @var string
36 + */
13 37 public $plugin_slug;
38 +
39 + /**
40 + * @var string
41 + */
14 42 public $option_name;
43 +
44 + /**
45 + * @var string|null
46 + */
15 47 public $version;
48 +
49 + /**
50 + * @var string
51 + */
16 52 public $author = 'Strategy11';
53 +
54 + /**
55 + * @var bool
56 + */
17 57 public $is_parent_licence = false;
58 +
59 + /**
60 + * @var bool
61 + */
18 62 public $needs_license = true;
63 +
64 + /**
65 + * @var bool
66 + */
19 67 private $is_expired_addon = false;
68 +
69 + /**
70 + * @var string|null
71 + */
20 72 public $license;
73 +
74 + /**
75 + * @var bool
76 + */
21 77 protected $get_beta = false;
22 78
79 + /**
80 + * @var array|null
81 + */
82 + protected $save_status;
83 +
84 + /**
85 + * This is used to decide whether the license checks should continue.
86 + * The point is to avoid license issues when a site url changes.
87 + *
88 + * @since 6.8.3
89 + *
90 + * @var array
91 + */
92 + private $save_response = array();
93 +
94 + /**
95 + * This is used to flag other add ons not to send a request.
96 + * We only want to send a single API request per page load.
97 + *
98 + * @since 6.8.3
99 + *
100 + * @var string
101 + */
102 + private $transient_lock_key = 'frm_activate_request_lock';
103 +
104 + /**
105 + * @since 6.8.3
106 + *
107 + * @var bool
108 + */
109 + protected $should_clear_cache = true;
110 +
111 + /**
112 + * Cached for the request, since every add-on asks for the same values.
113 + *
114 + * @since 6.35
115 + *
116 + * @var array<string,string>|null
117 + */
118 + private static $lite_requirements;
119 +
120 + /**
121 + * Add-ons that require more than Lite does, keyed by plugin slug.
122 + *
123 + * Nearly every add-on is plain PHP with no dependency of its own, so it runs anywhere Lite
124 + * runs and Lite's own requirements describe it. The exceptions bundle a third party
125 + * library, which sets a floor of its own. PDFs bundles Dompdf, which needs PHP 7.1.
126 + *
127 + * Values here are floors, never ceilings, so raising Lite's minimum still raises every
128 + * add-on's. Keys match the names WordPress uses, requires and requires_php.
129 + *
130 + * @since 6.35
131 + *
132 + * @var array<string,array<string,string>>
133 + */
134 + private static $addon_requirements = array(
135 + 'pdfs' => array( 'requires_php' => '7.1' ),
136 + );
137 +
23 138 public function __construct() {
24 -
25 - if ( empty( $this->plugin_slug ) ) {
139 + if ( ! $this->plugin_slug ) {
26 140 $this->plugin_slug = preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $this->plugin_name ) ) );
27 141 }
28 - if ( empty( $this->option_name ) ) {
142 +
143 + if ( ! $this->option_name ) {
29 144 $this->option_name = 'edd_' . $this->plugin_slug . '_license_';
30 145 }
31 146
32 147 $this->plugin_folder = plugin_basename( $this->plugin_file );
@@ -35,71 +150,103 @@
35 150 add_filter( 'frm_installed_addons', array( &$this, 'insert_installed_addon' ) );
36 151 $this->edd_plugin_updater();
37 152 }
38 153
154 + /**
155 + * @return void
156 + */
39 157 public static function load_hooks() {
40 158 add_filter( 'frm_include_addon_page', '__return_true' );
41 159 new static();
42 160 }
43 161
162 + /**
163 + * @param array $plugins
164 + *
165 + * @return array
166 + */
44 167 public function insert_installed_addon( $plugins ) {
45 168 $plugins[ $this->plugin_slug ] = $this;
46 -
47 169 return $plugins;
48 170 }
49 171
172 + /**
173 + * @param string $plugin_slug
174 + *
175 + * @return false|FrmAddon
176 + */
50 177 public static function get_addon( $plugin_slug ) {
51 178 $plugins = apply_filters( 'frm_installed_addons', array() );
52 - $plugin = false;
53 - if ( isset( $plugins[ $plugin_slug ] ) ) {
54 - $plugin = $plugins[ $plugin_slug ];
55 - }
56 -
57 - return $plugin;
179 + return $plugins[ $plugin_slug ] ?? false;
58 180 }
59 181
182 + /**
183 + * @return void
184 + */
60 185 public function edd_plugin_updater() {
61 -
62 186 $this->is_license_revoked();
63 187 $license = $this->license;
64 188
65 189 add_action( 'after_plugin_row_' . plugin_basename( $this->plugin_file ), array( $this, 'maybe_show_license_message' ), 10, 2 );
66 190
67 - if ( ! empty( $license ) ) {
191 + $is_addon = 'formidable/formidable.php' !== $this->plugin_folder;
68 192
69 - if ( 'formidable/formidable.php' !== $this->plugin_folder ) {
70 - add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
71 - }
193 + if ( $is_addon ) {
194 + // Lite gets its tested and required versions from wordpress.org, add-ons have no
195 + // such source. Added before the license check, since a site without a license
196 + // still needs to know what its add-ons support.
197 + add_filter( 'site_transient_update_plugins', array( $this, 'add_version_requirements' ), 20 );
198 + add_filter( 'plugins_api', array( $this, 'add_requirements_to_plugin_info' ), 20, 3 );
199 + }
72 200
73 - add_filter( 'site_transient_update_plugins', array( &$this, 'clear_expired_download' ) );
201 + if ( ! $license ) {
202 + return;
74 203 }
204 +
205 + if ( $is_addon ) {
206 + add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
207 + }
208 +
209 + add_filter( 'site_transient_update_plugins', array( &$this, 'clear_expired_download' ) );
75 210 }
76 211
77 212 /**
78 - * Updates information on the "View version x.x details" page with custom data.
213 + * Updates information on the "View version 6.15 details" page with custom data.
79 214 *
80 215 * @uses api_request()
81 216 *
82 - * @param mixed $_data
83 - * @param string $_action
84 - * @param object $_args
217 + * @param mixed $_data
218 + * @param string $_action
219 + * @param object|null $_args
85 220 *
86 - * @return object $_data
221 + * @return object Data.
87 222 */
88 223 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
89 -
90 - if ( $_action != 'plugin_information' ) {
224 + if ( $_action !== 'plugin_information' ) {
91 225 return $_data;
92 226 }
93 227
94 - $slug = basename( $this->plugin_file, '.php' );
95 - $slug2 = str_replace( '/' . $slug . '.php', '', $this->plugin_folder );
96 - if ( empty( $_args->slug ) || ( $_args->slug != $slug && $_args->slug !== $slug2 ) ) {
228 + if ( empty( $_args->slug ) || ! $this->is_plugin_slug( $_args->slug ) ) {
97 229 return $_data;
98 230 }
99 231
100 232 $item_id = $this->download_id;
101 - if ( empty( $item_id ) ) {
233 +
234 + if ( $item_id ) {
235 + $api = new FrmFormApi( $this->license );
236 +
237 + // Force new API info so we can pull changelog data.
238 + // Change log data is intentionally omitted from the cached API response
239 + // To help reduce the size of the autoloaded option.
240 + $api->force_api_request();
241 + $plugins = $api->get_api_info();
242 +
243 + if ( $plugins ) {
244 + $_data = $plugins[ $item_id ];
245 + }
246 + }
247 +
248 + if ( empty( $plugins ) ) {
102 249 $_data = array(
103 250 'name' => $this->plugin_name,
104 251 'excerpt' => '',
105 252 'changelog' => 'See the full changelog at <a href="' . esc_url( $this->store_url . '/changelog/' ) . '"></a>',
@@ -107,12 +254,8 @@
107 254 'high' => '',
108 255 'low' => 'https://ps.w.org/formidable/assets/banner-1544x500.png',
109 256 ),
110 257 );
111 - } else {
112 - $api = new FrmFormApi( $this->license );
113 - $plugins = $api->get_api_info();
114 - $_data = $plugins[ $item_id ];
115 258 }
116 259
117 260 $_data['sections'] = array(
118 261 'description' => $_data['excerpt'],
@@ -123,28 +266,295 @@
123 266
124 267 return (object) $_data;
125 268 }
126 269
270 + /**
271 + * Checks if a requested plugin_information slug refers to this plugin.
272 + *
273 + * Either the plugin file name or the plugin folder may be used as an add-on's slug.
274 + *
275 + * @since 6.35
276 + *
277 + * @param string $slug The requested slug.
278 + *
279 + * @return bool
280 + */
281 + private function is_plugin_slug( $slug ) {
282 + $file_slug = basename( $this->plugin_file, '.php' );
283 + $folder_slug = str_replace( '/' . $file_slug . '.php', '', $this->plugin_folder );
284 +
285 + return $slug === $file_slug || $slug === $folder_slug;
286 + }
287 +
288 + /**
289 + * Adds the versions this add-on supports to its update data.
290 + *
291 + * The API reports no tested or required versions, so anything reading the update transient
292 + * has no way to tell whether an add-on has been tried with the WordPress release in use, or
293 + * whether the site meets what the add-on needs.
294 + *
295 + * @since 6.35
296 + *
297 + * @param mixed $transient The update_plugins site transient.
298 + *
299 + * @return mixed
300 + */
301 + public function add_version_requirements( $transient ) {
302 + if ( ! is_object( $transient ) ) {
303 + return $transient;
304 + }
305 +
306 + $requirements = $this->get_version_requirements();
307 +
308 + if ( ! $requirements ) {
309 + return $transient;
310 + }
311 +
312 + foreach ( array( 'response', 'no_update' ) as $group ) {
313 + $plugins = $transient->$group ?? false;
314 +
315 + if ( ! is_array( $plugins ) || empty( $plugins[ $this->plugin_folder ] ) ) {
316 + continue;
317 + }
318 +
319 + // The entries are objects, so this updates the transient in place.
320 + $plugin = $plugins[ $this->plugin_folder ];
321 +
322 + if ( ! is_object( $plugin ) ) {
323 + continue;
324 + }
325 +
326 + foreach ( $requirements as $key => $value ) {
327 + if ( empty( $plugin->$key ) ) {
328 + $plugin->$key = $value;
329 + }
330 + }
331 + }
332 +
333 + return $transient;
334 + }
335 +
336 + /**
337 + * Adds the versions this add-on supports to its plugin information.
338 + *
339 + * This is where WordPress itself reads the values, for the "Compatible up to" and
340 + * "Requires PHP" lines and the compatibility warnings in the plugin details modal. It is
341 + * separate from plugins_api_filter because that filter is only added when a license is set.
342 + *
343 + * @since 6.35
344 + *
345 + * @param mixed $data Plugin information from an earlier filter, or false if none.
346 + * @param string $action The requested action.
347 + * @param object|null $args Arguments for the request, including the slug.
348 + *
349 + * @return mixed
350 + */
351 + public function add_requirements_to_plugin_info( $data, $action = '', $args = null ) {
352 + if ( 'plugin_information' !== $action || empty( $args->slug ) || ! $this->is_plugin_slug( $args->slug ) ) {
353 + return $data;
354 + }
355 +
356 + $requirements = $this->get_version_requirements();
357 +
358 + if ( ! $requirements ) {
359 + return $data;
360 + }
361 +
362 + $is_object = is_object( $data ) && ! is_wp_error( $data );
363 +
364 + if ( ! $is_object && ! is_array( $data ) ) {
365 + return $data;
366 + }
367 +
368 + foreach ( $requirements as $key => $value ) {
369 + if ( ! $is_object ) {
370 + if ( empty( $data[ $key ] ) ) {
371 + $data[ $key ] = $value;
372 + }
373 + continue;
374 + }
375 +
376 + if ( empty( $data->$key ) ) {
377 + $data->$key = $value;
378 + }
379 + }
380 +
381 + return $data;
382 + }
383 +
384 + /**
385 + * Gets the versions this add-on reports to WordPress.
386 + *
387 + * Lite's own values describe every add-on, apart from the few that bundle a library with a
388 + * higher floor of its own.
389 + *
390 + * @since 6.35
391 + *
392 + * @return array<string,string> Keyed by the names WordPress uses, tested, requires and requires_php.
393 + */
394 + public function get_version_requirements() {
395 + $requirements = self::get_lite_requirements();
396 + $addon = self::$addon_requirements[ $this->plugin_slug ] ?? array();
397 +
398 + foreach ( $addon as $key => $version ) {
399 + $is_higher = empty( $requirements[ $key ] ) || version_compare( $version, $requirements[ $key ], '>' );
400 +
401 + if ( $is_higher ) {
402 + $requirements[ $key ] = $version;
403 + }
404 + }
405 +
406 + /**
407 + * Filters the versions a single add-on reports to WordPress.
408 + *
409 + * @since 6.35
410 + *
411 + * @param array<string,string> $requirements Keyed by tested, requires and requires_php.
412 + * @param string $plugin_slug The add-on's slug, for example pdfs.
413 + */
414 + $requirements = apply_filters( 'frm_addon_version_requirements', $requirements, $this->plugin_slug );
415 +
416 + return is_array( $requirements ) ? array_filter( $requirements ) : array();
417 + }
418 +
419 + /**
420 + * Gets the WordPress version the add-ons report as tested.
421 + *
422 + * @since 6.35
423 + *
424 + * @return string The tested version, or an empty string if the readme has none.
425 + */
426 + public static function get_tested_wp_version() {
427 + return self::get_lite_requirements()['tested'];
428 + }
429 +
430 + /**
431 + * Gets the versions Lite declares, which the add-ons report as their own.
432 + *
433 + * Read from Lite's readme, which is the same place wordpress.org reads them from, so the
434 + * add-ons report what Lite reports without anything being added to the API.
435 + *
436 + * @since 6.35
437 + *
438 + * @return array<string,string> Keyed by tested, requires and requires_php. Values may be empty.
439 + */
440 + public static function get_lite_requirements() {
441 + if ( null !== self::$lite_requirements ) {
442 + return self::$lite_requirements;
443 + }
444 +
445 + $headers = array(
446 + 'tested' => 'Tested up to',
447 + 'requires' => 'Requires at least',
448 + 'requires_php' => 'Requires PHP',
449 + );
450 +
451 + $versions = array(
452 + 'tested' => '',
453 + 'requires' => '',
454 + 'requires_php' => '',
455 + );
456 +
457 + $readme = FrmAppHelper::plugin_path() . '/readme.txt';
458 +
459 + if ( is_readable( $readme ) ) {
460 + $data = get_file_data( $readme, $headers );
461 +
462 + foreach ( $versions as $key => $version ) {
463 + $versions[ $key ] = isset( $data[ $key ] ) ? trim( $data[ $key ] ) : '';
464 + }
465 + }
466 +
467 + /**
468 + * Filters the versions read from Lite's readme before the add-ons report them.
469 + *
470 + * @since 6.35
471 + *
472 + * @param array<string,string> $versions Keyed by tested, requires and requires_php.
473 + */
474 + $versions = apply_filters( 'frm_lite_version_requirements', $versions );
475 +
476 + if ( ! is_array( $versions ) ) {
477 + $versions = array();
478 + }
479 +
480 + $versions['tested'] = self::expand_tested_branch( $versions['tested'] ?? '' );
481 +
482 + self::$lite_requirements = $versions;
483 +
484 + return self::$lite_requirements;
485 + }
486 +
487 + /**
488 + * Expands a tested release branch to the release running on this site.
489 + *
490 + * A readme names a branch, like 7.0, while wordpress.org reports the newest release in
491 + * that branch, like 7.0.4. The difference matters because WordPress compares the tested
492 + * version to the current one exactly, so a site on 7.0.4 reading 7.0 is warned that the
493 + * plugin is untested when it is not. Only versions within the tested branch are expanded,
494 + * so a genuinely older tested version is still reported as older.
495 + *
496 + * @since 6.35
497 + *
498 + * @param string $tested The tested version from the readme.
499 + *
500 + * @return string
501 + */
502 + private static function expand_tested_branch( $tested ) {
503 + if ( ! $tested ) {
504 + return $tested;
505 + }
506 +
507 + // Drop any pre-release suffix, since 7.1-RC4 is not a release to report as tested.
508 + $current = preg_replace( '/[^0-9.].*$/', '', get_bloginfo( 'version' ) );
509 +
510 + if ( self::get_version_branch( $current ) !== self::get_version_branch( $tested ) ) {
511 + return $tested;
512 + }
513 +
514 + return version_compare( $current, $tested, '>' ) ? $current : $tested;
515 + }
516 +
517 + /**
518 + * Reduces a version to its major.minor release branch.
519 + *
520 + * @since 6.35
521 + *
522 + * @param string $version A WordPress version.
523 + *
524 + * @return string The branch, or an empty string if the version cannot be read.
525 + */
526 + private static function get_version_branch( $version ) {
527 + if ( ! preg_match( '/^(\d+)\.(\d+)/', $version, $matches ) ) {
528 + return '';
529 + }
530 +
531 + return $matches[1] . '.' . $matches[2];
532 + }
533 +
534 + /**
535 + * @return string
536 + */
127 537 public function get_license() {
128 538 $license = $this->maybe_get_pro_license();
129 - if ( ! empty( $license ) ) {
539 +
540 + if ( $license ) {
130 541 return $license;
131 542 }
132 543
133 544 $license = trim( get_option( $this->option_name . 'key' ) );
134 - if ( empty( $license ) ) {
135 - $license = $this->activate_defined_license();
136 - }
137 545
138 - return $license;
546 + return $license ? $license : $this->activate_defined_license();
139 547 }
140 548
141 549 /**
142 550 * @since 3.04.03
551 + *
552 + * @return false|string
143 553 */
144 554 protected function maybe_get_pro_license() {
145 - // prevent a loop if $this is the pro plugin
146 - $get_license = FrmAppHelper::pro_is_installed() && is_callable( 'FrmProAppHelper::get_updater' ) && $this->plugin_name != 'Formidable Pro';
555 + // Prevent a loop if $this is the pro plugin
556 + $get_license = FrmAppHelper::pro_is_installed() && is_callable( 'FrmProAppHelper::get_updater' ) && $this->plugin_name !== 'Formidable Pro';
147 557
148 558 if ( ! $get_license ) {
149 559 return false;
150 560 }
@@ -151,18 +561,16 @@
151 561
152 562 $api = new FrmFormApi();
153 563 $api->get_pro_updater();
154 564 $license = $api->get_license();
155 - if ( empty( $license ) ) {
565 +
566 + if ( ! $license ) {
156 567 return false;
157 568 }
158 569
159 570 $this->get_api_info( $license );
160 - if ( ! $this->is_parent_licence ) {
161 - $license = false;
162 - }
163 571
164 - return $license;
572 + return $this->is_parent_licence ? $license : false;
165 573 }
166 574
167 575 /**
168 576 * Activate the license in wp-config.php
@@ -167,14 +575,17 @@
167 575 /**
168 576 * Activate the license in wp-config.php
169 577 *
170 578 * @since 2.04
579 + *
580 + * @return string
171 581 */
172 582 public function activate_defined_license() {
173 583 $license = $this->get_defined_license();
174 - if ( ! empty( $license ) && ! $this->is_active() && $this->is_time_to_auto_activate() ) {
584 +
585 + if ( $license && ! $this->is_active() && ! $this->checked_recently( '1 day' ) ) {
175 586 $response = $this->activate_license( $license );
176 - $this->set_auto_activate_time();
587 +
177 588 if ( ! $response['success'] ) {
178 589 $license = '';
179 590 }
180 591 }
@@ -185,35 +596,28 @@
185 596 /**
186 597 * Check the wp-config.php for the license key
187 598 *
188 599 * @since 2.04
600 + *
601 + * @return false|string
189 602 */
190 603 public function get_defined_license() {
191 - $consant_name = 'FRM_' . strtoupper( $this->plugin_slug ) . '_LICENSE';
192 -
193 - return defined( $consant_name ) ? constant( $consant_name ) : false;
604 + $constant_name = 'FRM_' . strtoupper( $this->plugin_slug ) . '_LICENSE';
605 + return defined( $constant_name ) ? constant( $constant_name ) : false;
194 606 }
195 607
608 + /**
609 + * @param string $license
610 + *
611 + * @return void
612 + */
196 613 public function set_license( $license ) {
197 614 update_option( $this->option_name . 'key', $license );
198 615 }
199 616
200 617 /**
201 - * If the license is in the config, limit the frequency of checks.
202 - * The license may be entered incorrectly, so we don't want to check on every page load.
203 - *
204 - * @since 2.04
618 + * @return mixed
205 619 */
206 - private function is_time_to_auto_activate() {
207 - $last_try = get_option( $this->option_name . 'last_activate' );
208 -
209 - return ( ! $last_try || $last_try < strtotime( '-1 day' ) );
210 - }
211 -
212 - private function set_auto_activate_time() {
213 - update_option( $this->option_name . 'last_activate', time() );
214 - }
215 -
216 620 public function is_active() {
217 621 return get_option( $this->option_name . 'active' );
218 622 }
219 623
@@ -219,26 +623,65 @@
219 623
220 624 /**
221 625 * @since 3.04.03
222 626 *
223 - * @param array error
627 + * @param array|string $error
628 + *
629 + * @return void
224 630 */
225 631 public function maybe_clear_license( $error ) {
226 - if ( $error['code'] === 'disabled' && $error['license'] === $this->license ) {
632 + if ( is_array( $error ) && $error['code'] === 'disabled' && $error['license'] === $this->license ) {
227 633 $this->clear_license();
228 634 }
229 635 }
230 636
637 + /**
638 + * @return void
639 + */
231 640 public function clear_license() {
232 641 delete_option( $this->option_name . 'active' );
233 642 delete_option( $this->option_name . 'key' );
643 +
644 + if ( ! $this->should_clear_cache ) {
645 + return;
646 + }
647 +
234 648 delete_site_option( $this->transient_key() );
235 649 delete_option( $this->transient_key() );
236 650 $this->delete_cache();
651 + $this->should_clear_cache = true;
237 652 }
238 653
654 + /**
655 + * Don't save an invalid license.
656 + *
657 + * @since 6.8.3
658 + *
659 + * @param bool|string $is_valid If license activation was successful. May be a string 'valid'.
660 + *
661 + * @return void
662 + */
663 + protected function maybe_set_active( $is_valid ) {
664 + update_option( $this->option_name . 'active', $is_valid );
665 +
666 + if ( $is_valid ) {
667 + $this->set_active( $is_valid );
668 + return;
669 + }
670 +
671 + // Don't save the license if it's invalid.
672 + $this->should_clear_cache = false;
673 + $this->clear_license();
674 + delete_option( $this->option_name . 'key' );
675 + $this->license = '';
676 + }
677 +
678 + /**
679 + * @param bool|string $is_active Value may include 'valid'.
680 + *
681 + * @return void
682 + */
239 683 public function set_active( $is_active ) {
240 - update_option( $this->option_name . 'active', $is_active );
241 684 $this->delete_cache();
242 685 FrmAppHelper::save_combined_js();
243 686 $this->update_pro_capabilities();
244 687 }
@@ -246,8 +689,10 @@
246 689 /**
247 690 * Updates roles capabilities after pro license is active.
248 691 *
249 692 * @since 5.0
693 + *
694 + * @return void
250 695 */
251 696 protected function update_pro_capabilities() {
252 697 global $wp_roles;
253 698
@@ -257,18 +702,19 @@
257 702
258 703 $caps = FrmAppHelper::frm_capabilities( 'pro_only' );
259 704 $roles = get_editable_roles();
260 705 $settings = new FrmSettings();
706 +
261 707 foreach ( $caps as $cap => $cap_desc ) {
262 - $cap_roles = (array) ( isset( $settings->$cap ) ? $settings->$cap : 'administrator' );
708 + $cap_roles = (array) ( $settings->$cap ?? 'administrator' );
263 709
264 710 // Make sure administrators always have permissions.
265 - if ( ! in_array( 'administrator', $cap_roles ) ) {
711 + if ( ! in_array( 'administrator', $cap_roles, true ) ) {
266 712 array_push( $cap_roles, 'administrator' );
267 713 }
268 714
269 715 foreach ( $roles as $role => $details ) {
270 - if ( in_array( $role, $cap_roles ) ) {
716 + if ( in_array( $role, $cap_roles, true ) ) {
271 717 $wp_roles->add_cap( $role, $cap );
272 718 } else {
273 719 $wp_roles->remove_cap( $role, $cap );
274 720 }
@@ -277,12 +723,17 @@
277 723 }
278 724
279 725 /**
280 726 * @since 3.04.03
727 + *
728 + * @return void
281 729 */
282 730 protected function delete_cache() {
283 731 delete_transient( 'frm_api_licence' );
284 732
733 + // Cleanup option that has been removed.
734 + delete_option( $this->option_name . 'last_activate' );
735 +
285 736 $api = new FrmFormApi( $this->license );
286 737 $api->reset_cached();
287 738
288 739 $api = new FrmFormTemplateApi( $this->license );
@@ -289,8 +740,20 @@
289 740 $api->reset_cached();
290 741
291 742 $api = new FrmApplicationApi( $this->license );
292 743 $api->reset_cached();
744 +
745 + // The Sales API tries to pull API data in the constructor.
746 + // But we do not need sales data here.
747 + FrmSalesApi::prevent_new_sales_requests();
748 +
749 + $api = new FrmSalesApi();
750 + $api->reset_cached();
751 +
752 + FrmSalesApi::allow_new_sales_requests();
753 +
754 + $api = new FrmStyleApi();
755 + $api->reset_cached();
293 756 }
294 757
295 758 /**
296 759 * The Pro version includes the show_license_message function.
@@ -296,12 +759,17 @@
296 759 * The Pro version includes the show_license_message function.
297 760 * We need an extra check before we allow it to show a message.
298 761 *
299 762 * @since 3.04.03
763 + *
764 + * @param string $file File path of the plugin.
765 + * @param array $plugin Plugin data.
766 + *
767 + * @return void
300 768 */
301 769 public function maybe_show_license_message( $file, $plugin ) {
302 770 if ( $this->is_expired_addon || isset( $plugin['package'] ) ) {
303 - // let's not show a ton of duplicate messages
771 + // Let's not show a ton of duplicate messages
304 772 return;
305 773 }
306 774
307 775 $this->show_license_message( $file, $plugin );
@@ -306,22 +774,30 @@
306 774
307 775 $this->show_license_message( $file, $plugin );
308 776 }
309 777
778 + /**
779 + * @param string $file File path of the plugin.
780 + * @param array $plugin Plugin data.
781 + *
782 + * @return void
783 + */
310 784 public function show_license_message( $file, $plugin ) {
311 785 $message = '';
312 - if ( empty( $this->license ) ) {
313 - /* translators: %1$s: Plugin name, %2$s: Start link HTML, %3$s: end link HTML */
314 - $message = sprintf( esc_html__( 'Your %1$s license key is missing. Please add it on the %2$slicenses page%3$s.', 'formidable' ), esc_html( $this->plugin_name ), '<a href="' . esc_url( admin_url( 'admin.php?page=formidable-settings' ) ) . '">', '</a>' );
315 - } else {
786 +
787 + if ( $this->license ) {
316 788 $api = new FrmFormApi( $this->license );
317 789 $errors = $api->error_for_license();
318 - if ( ! empty( $errors ) ) {
790 +
791 + if ( $errors ) {
319 792 $message = reset( $errors );
320 793 }
794 + } else {
795 + /* translators: %1$s: Plugin name, %2$s: Start link HTML, %3$s: end link HTML */
796 + $message = sprintf( esc_html__( 'Your %1$s license key is missing. Please add it on the %2$slicenses page%3$s.', 'formidable' ), esc_html( $this->plugin_name ), '<a href="' . esc_url( admin_url( 'admin.php?page=formidable-settings' ) ) . '">', '</a>' ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
321 797 }
322 798
323 - if ( empty( $message ) ) {
799 + if ( ! $message ) {
324 800 return;
325 801 }
326 802
327 803 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
@@ -326,14 +802,19 @@
326 802
327 803 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
328 804 $id = sanitize_title( $plugin['Name'] ) . '-next';
329 805
330 - echo '<tr class="plugin-update-tr active" id="' . esc_attr( $id ) . '"><td colspan="' . esc_attr( $wp_list_table->get_column_count() ) . '" class="plugin-update colspanchange"><div class="update-message notice error inline notice-error notice-alt"><p>';
331 - echo FrmAppHelper::kses( $message, 'a' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
332 - echo '<script type="text/javascript">var d = document.getElementById("' . esc_attr( $id ) . '").previousSibling;if ( d !== null ){ d.className = d.className + " update"; }</script>';
806 + echo '<tr class="plugin-update-tr active" id="' . esc_attr( $id ) . '"><td colspan="' . esc_attr( $wp_list_table->get_column_count() ) . '" class="plugin-update colspanchange"><div class="update-message notice error inline notice-error notice-alt"><p>'; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
807 + FrmAppHelper::kses_echo( $message, 'a' );
808 + echo '<script type="text/javascript">var d = document.getElementById("' . esc_attr( $id ) . '").previousSibling;if ( d !== null ){ d.className = d.className + " update"; }</script>'; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
333 809 echo '</p></div></td></tr>';
334 810 }
335 811
812 + /**
813 + * @param mixed $transient
814 + *
815 + * @return mixed
816 + */
336 817 public function clear_expired_download( $transient ) {
337 818 if ( ! is_object( $transient ) ) {
338 819 return $transient;
339 820 }
@@ -338,9 +819,9 @@
338 819 return $transient;
339 820 }
340 821
341 822 if ( $this->is_current_version( $transient ) ) {
342 - //make sure it doesn't show there is an update if plugin is up-to-date
823 + // Make sure it doesn't show there is an update if plugin is up-to-date.
343 824 if ( isset( $transient->response[ $this->plugin_folder ] ) ) {
344 825 unset( $transient->response[ $this->plugin_folder ] );
345 826 }
346 827 } elseif ( isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) ) {
@@ -345,9 +826,9 @@
345 826 }
346 827 } elseif ( isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) ) {
347 828 $this->prepare_update_details( $transient->response[ $this->plugin_folder ] );
348 829
349 - // if the transient has expired, clear the update and trigger it again
830 + // If the transient has expired, clear the update and trigger it again
350 831 if ( $transient->response[ $this->plugin_folder ] === false ) {
351 832 if ( ! $this->has_been_cleared() ) {
352 833 $this->cleared_plugins();
353 834 $this->manually_queue_update();
@@ -363,29 +844,38 @@
363 844 * Check if the plugin information is correct to allow an update
364 845 *
365 846 * @since 3.04.03
366 847 *
367 - * @param object $transient - the current plugin info saved for update
848 + * @param object $transient The current plugin info saved for update.
849 + *
850 + * @return void
368 851 */
369 852 private function prepare_update_details( &$transient ) {
370 853 $version_info = $transient;
371 - $has_beta_url = isset( $version_info->beta ) && ! empty( $version_info->beta );
854 + $has_beta_url = ! empty( $version_info->beta );
855 +
372 856 if ( $this->get_beta && ! $has_beta_url ) {
373 857 $version_info = (object) $this->get_api_info( $this->license );
374 858 }
375 859
376 - if ( isset( $version_info->new_version ) && ! empty( $version_info->new_version ) ) {
377 - $this->clear_old_plugin_version( $version_info );
378 - if ( $version_info === false ) { // was cleared with timeout
379 - $transient = false;
380 - } else {
381 - $this->maybe_use_beta_url( $version_info );
860 + if ( empty( $version_info->new_version ) ) {
861 + return;
862 + }
382 863
383 - if ( version_compare( $version_info->new_version, $this->version, '>' ) ) {
384 - $transient = $version_info;
385 - }
386 - }
864 + $this->clear_old_plugin_version( $version_info );
865 +
866 + if ( $version_info === false ) {
867 + // Was cleared with timeout.
868 + $transient = false;
869 +
870 + return;
387 871 }
872 +
873 + $this->maybe_use_beta_url( $version_info );
874 +
875 + if ( version_compare( $version_info->new_version, $this->version, '>' ) ) {
876 + $transient = $version_info;
877 + }
388 878 }
389 879
390 880 /**
391 881 * Get the API info for this plugin
@@ -390,18 +880,22 @@
390 880 /**
391 881 * Get the API info for this plugin
392 882 *
393 883 * @since 3.04.03
884 + *
885 + * @param string $license The license key.
886 + *
887 + * @return array The API info for the plugin.
394 888 */
395 889 protected function get_api_info( $license ) {
396 890 $api = new FrmFormApi( $license );
397 891 $addon = $api->get_addon_for_license( $this );
398 892
399 - // if there is no download url, this license does not apply to the addon
893 + // If there is no download url, this license does not apply to the addon
400 894 if ( isset( $addon['package'] ) ) {
401 895 $this->is_parent_licence = true;
402 896 } elseif ( isset( $addon['error'] ) ) {
403 - // if the license is expired, we must assume all add-ons were packaged
897 + // If the license is expired, we must assume all add-ons were packaged
404 898 $this->is_parent_licence = true;
405 899 $this->is_expired_addon = true;
406 900 }
407 901
@@ -412,16 +906,24 @@
412 906 * Make sure transients don't stick around on sites that
413 907 * don't save the transient expiration
414 908 *
415 909 * @since 2.05.05
910 + *
911 + * @param object $version_info The version info for the plugin.
912 + *
913 + * @return void
416 914 */
417 915 private function clear_old_plugin_version( &$version_info ) {
418 - $timeout = ( isset( $version_info->timeout ) && ! empty( $version_info->timeout ) ) ? $version_info->timeout : 0;
419 - if ( ! empty( $timeout ) && time() > $timeout ) {
420 - $version_info = false; // Cache is expired
421 - $api = new FrmFormApi( $this->license );
422 - $api->reset_cached();
916 + $timeout = ! empty( $version_info->timeout ) ? $version_info->timeout : 0;
917 +
918 + if ( ! $timeout || time() <= $timeout ) {
919 + return;
423 920 }
921 +
922 + // Cache is expired.
923 + $version_info = false;
924 + $api = new FrmFormApi( $this->license );
925 + $api->reset_cached();
424 926 }
425 927
426 928 /**
427 929 * The beta url is always included if the download has a beta.
@@ -427,55 +929,85 @@
427 929 * The beta url is always included if the download has a beta.
428 930 * Check if the beta should be downloaded.
429 931 *
430 932 * @since 3.04.03
933 + *
934 + * @param object $version_info The version info for the plugin.
935 + *
936 + * @return void
431 937 */
432 938 private function maybe_use_beta_url( &$version_info ) {
433 - if ( $this->get_beta && isset( $version_info->beta ) && ! empty( $version_info->beta ) ) {
434 - $version_info->new_version = $version_info->beta['version'];
435 - $version_info->package = $version_info->beta['package'];
436 - if ( isset( $version_info->plugin ) && ! empty( $version_info->plugin ) ) {
437 - $version_info->plugin = $version_info->beta['plugin'];
438 - }
939 + if ( ! $this->get_beta || empty( $version_info->beta ) ) {
940 + return;
439 941 }
942 +
943 + $version_info->new_version = $version_info->beta['version'];
944 + $version_info->package = $version_info->beta['package'];
945 +
946 + if ( ! empty( $version_info->plugin ) ) {
947 + $version_info->plugin = $version_info->beta['plugin'];
948 + }
440 949 }
441 950
951 + /**
952 + * @param object $transient
953 + *
954 + * @return bool
955 + */
442 956 private function is_current_version( $transient ) {
443 957 if ( empty( $transient->checked ) || ! isset( $transient->checked[ $this->plugin_folder ] ) ) {
444 958 return false;
445 959 }
446 960
447 - $response = ! isset( $transient->response ) || empty( $transient->response );
961 + $response = empty( $transient->response );
962 +
448 963 if ( $response ) {
449 964 return true;
450 965 }
451 966
452 - return isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) && $transient->checked[ $this->plugin_folder ] === $transient->response[ $this->plugin_folder ]->new_version;
967 + return isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) && $transient->checked[ $this->plugin_folder ] === $transient->response[ $this->plugin_folder ]->new_version; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
453 968 }
454 969
970 + /**
971 + * @return bool
972 + */
455 973 private function has_been_cleared() {
456 974 $last_cleared = get_option( 'frm_last_cleared' );
457 -
458 - return ( $last_cleared && $last_cleared > gmdate( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) ) );
975 + return $last_cleared && $last_cleared > gmdate( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) );
459 976 }
460 977
978 + /**
979 + * @return void
980 + */
461 981 private function cleared_plugins() {
462 982 update_option( 'frm_last_cleared', gmdate( 'Y-m-d H:i:s' ) );
463 983 }
464 984
985 + /**
986 + * @return void
987 + */
465 988 private function is_license_revoked() {
466 - if ( empty( $this->license ) || empty( $this->plugin_slug ) || isset( $_POST['license'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
989 + if ( ! $this->license || ! $this->plugin_slug || isset( $_POST['license'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
467 990 return;
468 991 }
469 992
993 + if ( $this->get_defined_license() ) {
994 + // Don't check if the license is defined in wp-config.php since we can't remove it.
995 + return;
996 + }
997 +
470 998 // Only check weekly.
471 - if ( $this->checked_recently( '7 days' ) ) {
999 + if ( $this->checked_recently( '7 days' ) || $this->is_running() ) {
472 1000 return;
473 1001 }
474 1002
475 - $this->update_last_checked();
1003 + $response = $this->get_license_status();
476 1004
477 - $response = $this->get_license_status();
1005 + if ( ! empty( $this->save_status['response_code'] ) && 429 === $this->save_status['response_code'] ) {
1006 + // If we got a rate limit response, don't clear the license.
1007 + return;
1008 + }
1009 +
478 1010 if ( 'revoked' === $response['status'] || 'blocked' === $response['status'] || 'disabled' === $response['status'] || 'missing' === $response['status'] ) {
479 1011 $this->clear_license();
480 1012 }
481 1013 }
@@ -482,53 +1014,100 @@
482 1014
483 1015 /**
484 1016 * Has this been checked too recently?
485 1017 *
486 - * @param string $time ie. '1 day'
487 - * @return bool
1018 + * @param string $time ie. '1 day'.
1019 + *
1020 + * @return bool True if the last check time was recent.
488 1021 */
489 1022 private function checked_recently( $time ) {
490 1023 $last_checked = $this->last_checked();
491 - $time_ago = gmdate( 'Y-m-d H:i:s', strtotime( '-' . $time ) );
492 - return $last_checked && $last_checked > $time_ago;
1024 +
1025 + if ( ! $last_checked ) {
1026 + return false;
1027 + }
1028 +
1029 + $is_429 = isset( $last_checked['response_code'] ) && 429 === $last_checked['response_code'];
1030 +
1031 + // If the last check was a a rate limit error, adjust the check time.
1032 + if ( $is_429 ) {
1033 + // If the time is 2 minutes, do not adjust it so people
1034 + // can still try to activate again in 2 minutes (and not 5).
1035 + switch ( $time ) {
1036 + case '7 days':
1037 + // 7 days is used to check for license revocation.
1038 + $time = '30 minutes';
1039 + break;
1040 + case '1 day':
1041 + // 1 day is used for defined license constants.
1042 + // The response won't change often, so we can check less frequently.
1043 + $time = '2 hours';
1044 + break;
1045 + }
1046 + }
1047 +
1048 + if ( empty( $last_checked['time'] ) ) {
1049 + // If we do not have time, data is really old.
1050 + return false;
1051 + }
1052 +
1053 + $time_ago = gmdate( 'Y-m-d H:i:s', strtotime( '-' . $time ) );
1054 + return $last_checked['time'] > $time_ago;
493 1055 }
494 1056
495 1057 /**
496 - * @return bool|string
1058 + * @since 6.8.3 Switched to an array to store extra response info.
1059 + *
1060 + * @return array
497 1061 */
498 1062 private function last_checked() {
499 - if ( is_multisite() ) {
500 - $last_checked = get_site_option( $this->transient_key() );
501 - } else {
502 - $last_checked = get_option( $this->transient_key() );
1063 + $last_checked = is_multisite() ? get_site_option( $this->transient_key() ) : get_option( $this->transient_key() );
1064 +
1065 + if ( $last_checked && ! is_array( $last_checked ) ) {
1066 + // Get string into array for existing values.
1067 + $last_checked = array( 'time' => $last_checked );
503 1068 }
504 - return $last_checked;
1069 +
1070 + return $last_checked ? $last_checked : array();
505 1071 }
506 1072
507 1073 /**
1074 + * @since 6.30 Added the $is_valid param.
1075 + *
1076 + * @param bool $is_valid
1077 + *
508 1078 * @return void
509 1079 */
510 - private function update_last_checked() {
1080 + private function update_last_checked( $is_valid ) {
1081 + $this->save_response['time'] = gmdate( 'Y-m-d H:i:s' );
1082 + $this->save_response['is_valid'] = $is_valid;
1083 +
511 1084 if ( is_multisite() ) {
512 - update_site_option( $this->transient_key(), gmdate( 'Y-m-d H:i:s' ) );
1085 + update_site_option( $this->transient_key(), $this->save_response );
513 1086 } else {
514 - update_option( $this->transient_key(), gmdate( 'Y-m-d H:i:s' ) );
1087 + update_option( $this->transient_key(), $this->save_response );
515 1088 }
516 1089 }
517 1090
518 1091 /**
519 1092 * Use a new cache after the license is changed, or Formidable is updated.
1093 + *
1094 + * @return string
520 1095 */
521 1096 private function transient_key() {
522 1097 return 'frm_' . md5( sanitize_key( $this->license . '_' . $this->plugin_slug ) );
523 1098 }
524 1099
1100 + /**
1101 + * @return void
1102 + */
525 1103 public static function activate() {
526 1104 FrmAppHelper::permission_check( 'frm_change_settings' );
527 1105 check_ajax_referer( 'frm_ajax', 'nonce' );
528 1106
529 1107 $license = stripslashes( FrmAppHelper::get_param( 'license', '', 'post', 'sanitize_text_field' ) );
530 - if ( empty( $license ) ) {
1108 +
1109 + if ( ! $license ) {
531 1110 wp_send_json(
532 1111 array(
533 1112 'message' => __( 'Oops! You forgot to enter your license number.', 'formidable' ),
534 1113 'success' => false,
@@ -538,13 +1117,20 @@
538 1117
539 1118 $plugin_slug = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
540 1119 $response = self::activate_license_for_plugin( $license, $plugin_slug );
541 1120
1121 + FrmInbox::clear_cache();
1122 +
542 1123 wp_send_json( $response );
543 1124 }
544 1125
545 1126 /**
546 1127 * @since 4.08
1128 + *
1129 + * @param string $license The license key.
1130 + * @param string $plugin_slug The plugin slug.
1131 + *
1132 + * @return array The response from the license activation.
547 1133 */
548 1134 public static function activate_license_for_plugin( $license, $plugin_slug ) {
549 1135 $this_plugin = self::get_addon( $plugin_slug );
550 1136 return $this_plugin->activate_license( $license );
@@ -549,8 +1135,13 @@
549 1135 $this_plugin = self::get_addon( $plugin_slug );
550 1136 return $this_plugin->activate_license( $license );
551 1137 }
552 1138
1139 + /**
1140 + * @param string $license The license key.
1141 + *
1142 + * @return array The response from the license activation.
1143 + */
553 1144 private function activate_license( $license ) {
554 1145 $this->set_license( $license );
555 1146 $this->license = $license;
556 1147
@@ -559,12 +1150,15 @@
559 1150 $response = $this->get_license_status();
560 1151 $response['message'] = '';
561 1152 $response['success'] = false;
562 1153
1154 + $is_valid = false;
1155 +
563 1156 if ( $response['error'] ) {
564 1157 $response['message'] = $response['status'];
565 1158 } else {
566 1159 $messages = $this->get_messages();
1160 +
567 1161 if ( is_string( $response['status'] ) && isset( $messages[ $response['status'] ] ) ) {
568 1162 $response['message'] = $messages[ $response['status'] ];
569 1163 } else {
570 1164 $response['message'] = FrmAppHelper::kses( $response['status'], array( 'a' ) );
@@ -569,17 +1163,17 @@
569 1163 } else {
570 1164 $response['message'] = FrmAppHelper::kses( $response['status'], array( 'a' ) );
571 1165 }
572 1166
573 - $is_valid = false;
574 1167 if ( 'valid' === $response['status'] ) {
575 1168 $is_valid = 'valid';
576 1169 $response['success'] = true;
577 1170 }
578 - $this->set_active( $is_valid );
1171 +
1172 + $this->maybe_set_active( $is_valid );
579 1173 }
580 1174
581 - $this->update_last_checked();
1175 + $this->update_last_checked( (bool) $is_valid );
582 1176
583 1177 return $response;
584 1178 }
585 1179
@@ -601,19 +1195,26 @@
601 1195 )
602 1196 );
603 1197 }
604 1198
1199 + /**
1200 + * @return array
1201 + */
605 1202 private function get_license_status() {
1203 + $this->set_running();
1204 +
606 1205 $response = array(
607 1206 'status' => 'missing',
608 1207 'error' => true,
609 1208 );
610 - if ( empty( $this->license ) ) {
1209 +
1210 + if ( ! $this->license ) {
611 1211 $response['error'] = false;
612 -
613 1212 return $response;
614 1213 }
615 1214
1215 + $is_valid = false;
1216 +
616 1217 try {
617 1218 $response['error'] = false;
618 1219 $license_data = $this->send_mothership_request( 'activate_license' );
619 1220
@@ -618,10 +1219,12 @@
618 1219 $license_data = $this->send_mothership_request( 'activate_license' );
619 1220
620 1221 // $license_data->license will be either "valid" or "invalid"
621 1222 if ( is_array( $license_data ) ) {
622 - if ( isset( $license_data['license'] ) && in_array( $license_data['license'], array( 'valid', 'invalid' ), true ) ) {
623 - $response['status'] = $license_data['license'];
1223 + if ( ! empty( $license_data['license'] ) && in_array( $license_data['license'], array( 'valid', 'invalid' ), true ) ) {
1224 + $response['status'] = $license_data['license'];
1225 + $this->save_status['status'] = $license_data['license'];
1226 + $is_valid = 'valid' === $license_data['license'];
624 1227 }
625 1228 } else {
626 1229 $response['status'] = $license_data;
627 1230 }
@@ -628,11 +1231,16 @@
628 1231 } catch ( Exception $e ) {
629 1232 $response['status'] = $e->getMessage();
630 1233 }
631 1234
1235 + $this->update_last_checked( $is_valid );
1236 + $this->done_running();
632 1237 return $response;
633 1238 }
634 1239
1240 + /**
1241 + * @return array
1242 + */
635 1243 private function get_messages() {
636 1244 return array(
637 1245 'valid' => __( 'Your license has been activated. Enjoy!', 'formidable' ),
638 1246 'invalid' => __( 'That license key is invalid', 'formidable' ),
@@ -645,8 +1253,10 @@
645 1253 }
646 1254
647 1255 /**
648 1256 * @since 4.03
1257 + *
1258 + * @return void
649 1259 */
650 1260 public static function reset_cache() {
651 1261 FrmAppHelper::permission_check( 'frm_change_settings' );
652 1262 check_ajax_referer( 'frm_ajax', 'nonce' );
@@ -661,8 +1271,11 @@
661 1271
662 1272 wp_send_json( $response );
663 1273 }
664 1274
1275 + /**
1276 + * @return void
1277 + */
665 1278 public static function deactivate() {
666 1279 FrmAppHelper::permission_check( 'frm_change_settings' );
667 1280 check_ajax_referer( 'frm_ajax', 'nonce' );
668 1281
@@ -671,11 +1284,13 @@
671 1284 $response = array(
672 1285 'success' => false,
673 1286 'message' => '',
674 1287 );
1288 +
675 1289 try {
676 1290 // $license_data->license will be either "deactivated" or "failed"
677 1291 $license_data = $this_plugin->send_mothership_request( 'deactivate_license' );
1292 +
678 1293 if ( is_array( $license_data ) && 'deactivated' === $license_data['license'] ) {
679 1294 $response['success'] = true;
680 1295 $response['message'] = __( 'That license was removed successfully', 'formidable' );
681 1296 } else {
@@ -685,8 +1300,9 @@
685 1300 $response['message'] = $e->getMessage();
686 1301 }
687 1302
688 1303 $this_plugin->clear_license();
1304 + FrmInbox::clear_cache();
689 1305
690 1306 wp_send_json( $response );
691 1307 }
692 1308
@@ -691,18 +1307,21 @@
691 1307 }
692 1308
693 1309 /**
694 1310 * @since 4.03
1311 + *
1312 + * @return FrmAddon
695 1313 */
696 1314 private static function set_license_from_post() {
697 1315 $plugin_slug = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
698 1316 $this_plugin = self::get_addon( $plugin_slug );
699 - $license = $this_plugin->get_license();
700 - $this_plugin->license = $license;
1317 + $this_plugin->license = $this_plugin->get_license();
701 1318 return $this_plugin;
702 1319 }
703 1320
704 1321 /**
1322 + * @param string $action
1323 + *
705 1324 * @return string
706 1325 */
707 1326 public function send_mothership_request( $action ) {
708 1327 $api_params = array(
@@ -709,8 +1328,9 @@
709 1328 'edd_action' => $action,
710 1329 'license' => $this->license,
711 1330 'url' => home_url(),
712 1331 );
1332 +
713 1333 if ( is_numeric( $this->download_id ) ) {
714 1334 $api_params['item_id'] = absint( $this->download_id );
715 1335 } else {
716 1336 $api_params['item_name'] = rawurlencode( $this->plugin_name );
@@ -721,31 +1341,32 @@
721 1341 'timeout' => 25,
722 1342 'user-agent' => $this->plugin_slug . '/' . $this->version . '; ' . get_bloginfo( 'url' ),
723 1343 );
724 1344
725 - $resp = wp_remote_post(
1345 + $resp = wp_remote_post(
726 1346 $this->store_url . '?l=' . urlencode( base64_encode( $this->license ) ),
727 1347 $arg_array
728 1348 );
729 - $body = wp_remote_retrieve_body( $resp );
1349 + $response_code = wp_remote_retrieve_response_code( $resp );
1350 + $body = wp_remote_retrieve_body( $resp );
1351 + $this->save_status = array( 'response_code' => $response_code );
1352 + $this->save_response['response_code'] = $response_code;
730 1353
731 1354 $message = __( 'Your License Key was invalid', 'formidable' );
1355 +
732 1356 if ( is_wp_error( $resp ) ) {
733 1357 $link = FrmAppHelper::admin_upgrade_link( 'api', 'knowledgebase/why-cant-i-activate-formidable-pro/' );
734 1358 /* translators: %1$s: Start link HTML, %2$s: End link HTML */
735 - $message = sprintf( __( 'You had an error communicating with the Formidable API. %1$sClick here%2$s for more information.', 'formidable' ), '<a href="' . esc_url( $link ) . '" target="_blank">', '</a>' );
1359 + $message = sprintf( __( 'You had an error communicating with the Formidable API. %1$sClick here%2$s for more information.', 'formidable' ), '<a href="' . esc_url( $link ) . '" target="_blank">', '</a>' ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
736 1360 $message .= ' ' . $resp->get_error_message();
737 1361 } elseif ( 'error' === $body || is_wp_error( $body ) ) {
738 1362 $message = __( 'You had an HTTP error connecting to the Formidable API', 'formidable' );
739 1363 } else {
740 1364 $json_res = json_decode( $body, true );
1365 +
741 1366 if ( null !== $json_res ) {
742 - if ( is_array( $json_res ) && isset( $json_res['error'] ) ) {
743 - $message = $json_res['error'];
744 - } else {
745 - $message = $json_res;
746 - }
747 - } elseif ( isset( $resp['response'] ) && isset( $resp['response']['code'] ) ) {
1367 + $message = is_array( $json_res ) && isset( $json_res['error'] ) ? $json_res['error'] : $json_res;
1368 + } elseif ( ! empty( $resp['response'] ) && ! empty( $resp['response']['code'] ) ) {
748 1369 $resp['body'] = wp_strip_all_tags( $resp['body'] );
749 1370
750 1371 $message = sprintf(
751 1372 /* translators: %1$s: Error code, %2$s: Error message */
@@ -753,13 +1374,16 @@
753 1374 esc_html( $resp['response']['code'] ),
754 1375 $resp['response']['message'] . ' ' . $resp['body']
755 1376 );
756 1377 }
757 - }
1378 + }//end if
758 1379
759 1380 return $message;
760 1381 }
761 1382
1383 + /**
1384 + * @return void
1385 + */
762 1386 public function manually_queue_update() {
763 1387 $updates = new stdClass();
764 1388 $updates->last_checked = 0;
765 1389 $updates->response = array();
@@ -766,6 +1390,46 @@
766 1390 $updates->translations = array();
767 1391 $updates->no_update = array();
768 1392 $updates->checked = array();
769 1393 set_site_transient( 'update_plugins', $updates );
1394 + }
1395 +
1396 + /**
1397 + * Set the transient key for the lock. It should be unique to the license.
1398 + *
1399 + * @since 6.8.3
1400 + *
1401 + * @return bool
1402 + */
1403 + protected function lock_key() {
1404 + return $this->transient_lock_key . '_' . $this->license;
1405 + }
1406 +
1407 + /**
1408 + * Prevent multiple requests from running at the same time.
1409 + *
1410 + * @since 6.8.3
1411 + *
1412 + * @return bool
1413 + */
1414 + protected function is_running() {
1415 + return get_transient( $this->lock_key() );
1416 + }
1417 +
1418 + /**
1419 + * @since 6.8.3
1420 + *
1421 + * @return void
1422 + */
1423 + protected function set_running() {
1424 + set_transient( $this->lock_key(), true, 2 * MINUTE_IN_SECONDS );
1425 + }
1426 +
1427 + /**
1428 + * @since 6.8.3
1429 + *
1430 + * @return void
1431 + */
1432 + protected function done_running() {
1433 + delete_transient( $this->lock_key() );
770 1434 }
771 1435 }