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

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

1,132 lines 28.3 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 /** @phpstan-consistent-constructor */
7 class FrmAddon {
8
9 /**
10 * @var string
11 */
12 public $store_url = 'https://formidableforms.com';
13
14 /**
15 * @var int|string|null
16 */
17 public $download_id;
18
19 /**
20 * @var string|null
21 */
22 public $plugin_file;
23
24 /**
25 * @var string
26 */
27 public $plugin_folder;
28
29 /**
30 * @var string
31 */
32 public $plugin_name;
33
34 /**
35 * @var string
36 */
37 public $plugin_slug;
38
39 /**
40 * @var string
41 */
42 public $option_name;
43
44 /**
45 * @var string|null
46 */
47 public $version;
48
49 /**
50 * @var string
51 */
52 public $author = 'Strategy11';
53
54 /**
55 * @var bool
56 */
57 public $is_parent_licence = false;
58
59 /**
60 * @var bool
61 */
62 public $needs_license = true;
63
64 /**
65 * @var bool
66 */
67 private $is_expired_addon = false;
68
69 /**
70 * @var string|null
71 */
72 public $license;
73
74 /**
75 * @var bool
76 */
77 protected $get_beta = false;
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 public function __construct() {
112 if ( ! $this->plugin_slug ) {
113 $this->plugin_slug = preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $this->plugin_name ) ) );
114 }
115
116 if ( ! $this->option_name ) {
117 $this->option_name = 'edd_' . $this->plugin_slug . '_license_';
118 }
119
120 $this->plugin_folder = plugin_basename( $this->plugin_file );
121 $this->license = $this->get_license();
122
123 add_filter( 'frm_installed_addons', array( &$this, 'insert_installed_addon' ) );
124 $this->edd_plugin_updater();
125 }
126
127 /**
128 * @return void
129 */
130 public static function load_hooks() {
131 add_filter( 'frm_include_addon_page', '__return_true' );
132 new static();
133 }
134
135 /**
136 * @param array $plugins
137 *
138 * @return array
139 */
140 public function insert_installed_addon( $plugins ) {
141 $plugins[ $this->plugin_slug ] = $this;
142 return $plugins;
143 }
144
145 /**
146 * @param string $plugin_slug
147 *
148 * @return false|FrmAddon
149 */
150 public static function get_addon( $plugin_slug ) {
151 $plugins = apply_filters( 'frm_installed_addons', array() );
152 return $plugins[ $plugin_slug ] ?? false;
153 }
154
155 /**
156 * @return void
157 */
158 public function edd_plugin_updater() {
159 $this->is_license_revoked();
160 $license = $this->license;
161
162 add_action( 'after_plugin_row_' . plugin_basename( $this->plugin_file ), array( $this, 'maybe_show_license_message' ), 10, 2 );
163
164 if ( ! $license ) {
165 return;
166 }
167
168 if ( 'formidable/formidable.php' !== $this->plugin_folder ) {
169 add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
170 }
171
172 add_filter( 'site_transient_update_plugins', array( &$this, 'clear_expired_download' ) );
173 }
174
175 /**
176 * Updates information on the "View version 6.15 details" page with custom data.
177 *
178 * @uses api_request()
179 *
180 * @param mixed $_data
181 * @param string $_action
182 * @param object|null $_args
183 *
184 * @return object Data.
185 */
186 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
187 if ( $_action !== 'plugin_information' ) {
188 return $_data;
189 }
190
191 $slug = basename( $this->plugin_file, '.php' );
192 $slug2 = str_replace( '/' . $slug . '.php', '', $this->plugin_folder );
193
194 if ( empty( $_args->slug ) || ( $_args->slug !== $slug && $_args->slug !== $slug2 ) ) {
195 return $_data;
196 }
197
198 $item_id = $this->download_id;
199
200 if ( $item_id ) {
201 $api = new FrmFormApi( $this->license );
202
203 // Force new API info so we can pull changelog data.
204 // Change log data is intentionally omitted from the cached API response
205 // To help reduce the size of the autoloaded option.
206 $api->force_api_request();
207 $plugins = $api->get_api_info();
208
209 if ( $plugins ) {
210 $_data = $plugins[ $item_id ];
211 }
212 }
213
214 if ( empty( $plugins ) ) {
215 $_data = array(
216 'name' => $this->plugin_name,
217 'excerpt' => '',
218 'changelog' => 'See the full changelog at <a href="' . esc_url( $this->store_url . '/changelog/' ) . '"></a>',
219 'banners' => array(
220 'high' => '',
221 'low' => 'https://ps.w.org/formidable/assets/banner-1544x500.png',
222 ),
223 );
224 }
225
226 $_data['sections'] = array(
227 'description' => $_data['excerpt'],
228 'changelog' => $_data['changelog'],
229 );
230 $_data['author'] = '<a href="' . esc_url( $this->store_url ) . '">' . esc_html( $this->author ) . '</a>';
231 $_data['homepage'] = $this->store_url;
232
233 return (object) $_data;
234 }
235
236 /**
237 * @return string
238 */
239 public function get_license() {
240 $license = $this->maybe_get_pro_license();
241
242 if ( $license ) {
243 return $license;
244 }
245
246 $license = trim( get_option( $this->option_name . 'key' ) );
247
248 return $license ? $license : $this->activate_defined_license();
249 }
250
251 /**
252 * @since 3.04.03
253 *
254 * @return false|string
255 */
256 protected function maybe_get_pro_license() {
257 // Prevent a loop if $this is the pro plugin
258 $get_license = FrmAppHelper::pro_is_installed() && is_callable( 'FrmProAppHelper::get_updater' ) && $this->plugin_name !== 'Formidable Pro';
259
260 if ( ! $get_license ) {
261 return false;
262 }
263
264 $api = new FrmFormApi();
265 $api->get_pro_updater();
266 $license = $api->get_license();
267
268 if ( ! $license ) {
269 return false;
270 }
271
272 $this->get_api_info( $license );
273
274 return $this->is_parent_licence ? $license : false;
275 }
276
277 /**
278 * Activate the license in wp-config.php
279 *
280 * @since 2.04
281 *
282 * @return string
283 */
284 public function activate_defined_license() {
285 $license = $this->get_defined_license();
286
287 if ( $license && ! $this->is_active() && ! $this->checked_recently( '1 day' ) ) {
288 $response = $this->activate_license( $license );
289
290 if ( ! $response['success'] ) {
291 $license = '';
292 }
293 }
294
295 return $license;
296 }
297
298 /**
299 * Check the wp-config.php for the license key
300 *
301 * @since 2.04
302 *
303 * @return false|string
304 */
305 public function get_defined_license() {
306 $constant_name = 'FRM_' . strtoupper( $this->plugin_slug ) . '_LICENSE';
307 return defined( $constant_name ) ? constant( $constant_name ) : false;
308 }
309
310 /**
311 * @param string $license
312 *
313 * @return void
314 */
315 public function set_license( $license ) {
316 update_option( $this->option_name . 'key', $license );
317 }
318
319 /**
320 * @return mixed
321 */
322 public function is_active() {
323 return get_option( $this->option_name . 'active' );
324 }
325
326 /**
327 * @since 3.04.03
328 *
329 * @param array|string $error
330 *
331 * @return void
332 */
333 public function maybe_clear_license( $error ) {
334 if ( is_array( $error ) && $error['code'] === 'disabled' && $error['license'] === $this->license ) {
335 $this->clear_license();
336 }
337 }
338
339 /**
340 * @return void
341 */
342 public function clear_license() {
343 delete_option( $this->option_name . 'active' );
344 delete_option( $this->option_name . 'key' );
345
346 if ( ! $this->should_clear_cache ) {
347 return;
348 }
349
350 delete_site_option( $this->transient_key() );
351 delete_option( $this->transient_key() );
352 $this->delete_cache();
353 $this->should_clear_cache = true;
354 }
355
356 /**
357 * Don't save an invalid license.
358 *
359 * @since 6.8.3
360 *
361 * @param bool|string $is_valid If license activation was successful. May be a string 'valid'.
362 *
363 * @return void
364 */
365 protected function maybe_set_active( $is_valid ) {
366 update_option( $this->option_name . 'active', $is_valid );
367
368 if ( $is_valid ) {
369 $this->set_active( $is_valid );
370 return;
371 }
372
373 // Don't save the license if it's invalid.
374 $this->should_clear_cache = false;
375 $this->clear_license();
376 delete_option( $this->option_name . 'key' );
377 $this->license = '';
378 }
379
380 /**
381 * @param bool|string $is_active Value may include 'valid'.
382 *
383 * @return void
384 */
385 public function set_active( $is_active ) {
386 $this->delete_cache();
387 FrmAppHelper::save_combined_js();
388 $this->update_pro_capabilities();
389 }
390
391 /**
392 * Updates roles capabilities after pro license is active.
393 *
394 * @since 5.0
395 *
396 * @return void
397 */
398 protected function update_pro_capabilities() {
399 global $wp_roles;
400
401 if ( ! function_exists( 'get_editable_roles' ) ) {
402 require_once ABSPATH . 'wp-admin/includes/user.php';
403 }
404
405 $caps = FrmAppHelper::frm_capabilities( 'pro_only' );
406 $roles = get_editable_roles();
407 $settings = new FrmSettings();
408
409 foreach ( $caps as $cap => $cap_desc ) {
410 $cap_roles = (array) ( $settings->$cap ?? 'administrator' );
411
412 // Make sure administrators always have permissions.
413 if ( ! in_array( 'administrator', $cap_roles, true ) ) {
414 array_push( $cap_roles, 'administrator' );
415 }
416
417 foreach ( $roles as $role => $details ) {
418 if ( in_array( $role, $cap_roles, true ) ) {
419 $wp_roles->add_cap( $role, $cap );
420 } else {
421 $wp_roles->remove_cap( $role, $cap );
422 }
423 }
424 }
425 }
426
427 /**
428 * @since 3.04.03
429 *
430 * @return void
431 */
432 protected function delete_cache() {
433 delete_transient( 'frm_api_licence' );
434
435 // Cleanup option that has been removed.
436 delete_option( $this->option_name . 'last_activate' );
437
438 $api = new FrmFormApi( $this->license );
439 $api->reset_cached();
440
441 $api = new FrmFormTemplateApi( $this->license );
442 $api->reset_cached();
443
444 $api = new FrmApplicationApi( $this->license );
445 $api->reset_cached();
446
447 $api = new FrmSalesApi();
448 $api->reset_cached();
449
450 $api = new FrmStyleApi();
451 $api->reset_cached();
452 }
453
454 /**
455 * The Pro version includes the show_license_message function.
456 * We need an extra check before we allow it to show a message.
457 *
458 * @since 3.04.03
459 *
460 * @param string $file File path of the plugin.
461 * @param array $plugin Plugin data.
462 *
463 * @return void
464 */
465 public function maybe_show_license_message( $file, $plugin ) {
466 if ( $this->is_expired_addon || isset( $plugin['package'] ) ) {
467 // Let's not show a ton of duplicate messages
468 return;
469 }
470
471 $this->show_license_message( $file, $plugin );
472 }
473
474 /**
475 * @param string $file File path of the plugin.
476 * @param array $plugin Plugin data.
477 *
478 * @return void
479 */
480 public function show_license_message( $file, $plugin ) {
481 $message = '';
482
483 if ( $this->license ) {
484 $api = new FrmFormApi( $this->license );
485 $errors = $api->error_for_license();
486
487 if ( $errors ) {
488 $message = reset( $errors );
489 }
490 } else {
491 /* translators: %1$s: Plugin name, %2$s: Start link HTML, %3$s: end link HTML */
492 $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
493 }
494
495 if ( ! $message ) {
496 return;
497 }
498
499 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
500 $id = sanitize_title( $plugin['Name'] ) . '-next';
501
502 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
503 FrmAppHelper::kses_echo( $message, 'a' );
504 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
505 echo '</p></div></td></tr>';
506 }
507
508 /**
509 * @param mixed $transient
510 *
511 * @return mixed
512 */
513 public function clear_expired_download( $transient ) {
514 if ( ! is_object( $transient ) ) {
515 return $transient;
516 }
517
518 if ( $this->is_current_version( $transient ) ) {
519 // Make sure it doesn't show there is an update if plugin is up-to-date.
520 if ( isset( $transient->response[ $this->plugin_folder ] ) ) {
521 unset( $transient->response[ $this->plugin_folder ] );
522 }
523 } elseif ( isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) ) {
524 $this->prepare_update_details( $transient->response[ $this->plugin_folder ] );
525
526 // If the transient has expired, clear the update and trigger it again
527 if ( $transient->response[ $this->plugin_folder ] === false ) {
528 if ( ! $this->has_been_cleared() ) {
529 $this->cleared_plugins();
530 $this->manually_queue_update();
531 }
532 unset( $transient->response[ $this->plugin_folder ] );
533 }
534 }
535
536 return $transient;
537 }
538
539 /**
540 * Check if the plugin information is correct to allow an update
541 *
542 * @since 3.04.03
543 *
544 * @param object $transient The current plugin info saved for update.
545 *
546 * @return void
547 */
548 private function prepare_update_details( &$transient ) {
549 $version_info = $transient;
550 $has_beta_url = ! empty( $version_info->beta );
551
552 if ( $this->get_beta && ! $has_beta_url ) {
553 $version_info = (object) $this->get_api_info( $this->license );
554 }
555
556 if ( empty( $version_info->new_version ) ) {
557 return;
558 }
559
560 $this->clear_old_plugin_version( $version_info );
561
562 if ( $version_info === false ) {
563 // Was cleared with timeout.
564 $transient = false;
565
566 return;
567 }
568
569 $this->maybe_use_beta_url( $version_info );
570
571 if ( version_compare( $version_info->new_version, $this->version, '>' ) ) {
572 $transient = $version_info;
573 }
574 }
575
576 /**
577 * Get the API info for this plugin
578 *
579 * @since 3.04.03
580 *
581 * @param string $license The license key.
582 *
583 * @return array The API info for the plugin.
584 */
585 protected function get_api_info( $license ) {
586 $api = new FrmFormApi( $license );
587 $addon = $api->get_addon_for_license( $this );
588
589 // If there is no download url, this license does not apply to the addon
590 if ( isset( $addon['package'] ) ) {
591 $this->is_parent_licence = true;
592 } elseif ( isset( $addon['error'] ) ) {
593 // If the license is expired, we must assume all add-ons were packaged
594 $this->is_parent_licence = true;
595 $this->is_expired_addon = true;
596 }
597
598 return $addon;
599 }
600
601 /**
602 * Make sure transients don't stick around on sites that
603 * don't save the transient expiration
604 *
605 * @since 2.05.05
606 *
607 * @param object $version_info The version info for the plugin.
608 *
609 * @return void
610 */
611 private function clear_old_plugin_version( &$version_info ) {
612 $timeout = ! empty( $version_info->timeout ) ? $version_info->timeout : 0;
613
614 if ( ! $timeout || time() <= $timeout ) {
615 return;
616 }
617
618 // Cache is expired.
619 $version_info = false;
620 $api = new FrmFormApi( $this->license );
621 $api->reset_cached();
622 }
623
624 /**
625 * The beta url is always included if the download has a beta.
626 * Check if the beta should be downloaded.
627 *
628 * @since 3.04.03
629 *
630 * @param object $version_info The version info for the plugin.
631 *
632 * @return void
633 */
634 private function maybe_use_beta_url( &$version_info ) {
635 if ( ! $this->get_beta || empty( $version_info->beta ) ) {
636 return;
637 }
638
639 $version_info->new_version = $version_info->beta['version'];
640 $version_info->package = $version_info->beta['package'];
641
642 if ( ! empty( $version_info->plugin ) ) {
643 $version_info->plugin = $version_info->beta['plugin'];
644 }
645 }
646
647 /**
648 * @param object $transient
649 *
650 * @return bool
651 */
652 private function is_current_version( $transient ) {
653 if ( empty( $transient->checked ) || ! isset( $transient->checked[ $this->plugin_folder ] ) ) {
654 return false;
655 }
656
657 $response = empty( $transient->response );
658
659 if ( $response ) {
660 return true;
661 }
662
663 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
664 }
665
666 /**
667 * @return bool
668 */
669 private function has_been_cleared() {
670 $last_cleared = get_option( 'frm_last_cleared' );
671 return $last_cleared && $last_cleared > gmdate( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) );
672 }
673
674 /**
675 * @return void
676 */
677 private function cleared_plugins() {
678 update_option( 'frm_last_cleared', gmdate( 'Y-m-d H:i:s' ) );
679 }
680
681 /**
682 * @return void
683 */
684 private function is_license_revoked() {
685 if ( ! $this->license || ! $this->plugin_slug || isset( $_POST['license'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
686 return;
687 }
688
689 if ( $this->get_defined_license() ) {
690 // Don't check if the license is defined in wp-config.php since we can't remove it.
691 return;
692 }
693
694 // Only check weekly.
695 if ( $this->checked_recently( '7 days' ) || $this->is_running() ) {
696 return;
697 }
698
699 $response = $this->get_license_status();
700
701 if ( ! empty( $this->save_status['response_code'] ) && 429 === $this->save_status['response_code'] ) {
702 // If we got a rate limit response, don't clear the license.
703 return;
704 }
705
706 if ( 'revoked' === $response['status'] || 'blocked' === $response['status'] || 'disabled' === $response['status'] || 'missing' === $response['status'] ) {
707 $this->clear_license();
708 }
709 }
710
711 /**
712 * Has this been checked too recently?
713 *
714 * @param string $time ie. '1 day'.
715 *
716 * @return bool True if the last check time was recent.
717 */
718 private function checked_recently( $time ) {
719 $last_checked = $this->last_checked();
720
721 if ( ! $last_checked ) {
722 return false;
723 }
724
725 $is_429 = isset( $last_checked['response_code'] ) && 429 === $last_checked['response_code'];
726
727 // If the last check was a a rate limit error, adjust the check time.
728 if ( $is_429 ) {
729 // If the time is 2 minutes, do not adjust it so people
730 // can still try to activate again in 2 minutes (and not 5).
731 switch ( $time ) {
732 case '7 days':
733 // 7 days is used to check for license revocation.
734 $time = '30 minutes';
735 break;
736 case '1 day':
737 // 1 day is used for defined license constants.
738 // The response won't change often, so we can check less frequently.
739 $time = '2 hours';
740 break;
741 }
742 }
743
744 if ( empty( $last_checked['time'] ) ) {
745 // If we do not have time, data is really old.
746 return false;
747 }
748
749 $time_ago = gmdate( 'Y-m-d H:i:s', strtotime( '-' . $time ) );
750 return $last_checked['time'] > $time_ago;
751 }
752
753 /**
754 * @since 6.8.3 Switched to an array to store extra response info.
755 *
756 * @return array
757 */
758 private function last_checked() {
759 $last_checked = is_multisite() ? get_site_option( $this->transient_key() ) : get_option( $this->transient_key() );
760
761 if ( $last_checked && ! is_array( $last_checked ) ) {
762 // Get string into array for existing values.
763 $last_checked = array( 'time' => $last_checked );
764 }
765
766 return $last_checked ? $last_checked : array();
767 }
768
769 /**
770 * @since 6.30 Added the $is_valid param.
771 *
772 * @param bool $is_valid
773 *
774 * @return void
775 */
776 private function update_last_checked( $is_valid ) {
777 $this->save_response['time'] = gmdate( 'Y-m-d H:i:s' );
778 $this->save_response['is_valid'] = $is_valid;
779
780 if ( is_multisite() ) {
781 update_site_option( $this->transient_key(), $this->save_response );
782 } else {
783 update_option( $this->transient_key(), $this->save_response );
784 }
785 }
786
787 /**
788 * Use a new cache after the license is changed, or Formidable is updated.
789 *
790 * @return string
791 */
792 private function transient_key() {
793 return 'frm_' . md5( sanitize_key( $this->license . '_' . $this->plugin_slug ) );
794 }
795
796 /**
797 * @return void
798 */
799 public static function activate() {
800 FrmAppHelper::permission_check( 'frm_change_settings' );
801 check_ajax_referer( 'frm_ajax', 'nonce' );
802
803 $license = stripslashes( FrmAppHelper::get_param( 'license', '', 'post', 'sanitize_text_field' ) );
804
805 if ( ! $license ) {
806 wp_send_json(
807 array(
808 'message' => __( 'Oops! You forgot to enter your license number.', 'formidable' ),
809 'success' => false,
810 )
811 );
812 }
813
814 $plugin_slug = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
815 $response = self::activate_license_for_plugin( $license, $plugin_slug );
816
817 FrmInbox::clear_cache();
818
819 wp_send_json( $response );
820 }
821
822 /**
823 * @since 4.08
824 *
825 * @param string $license The license key.
826 * @param string $plugin_slug The plugin slug.
827 *
828 * @return array The response from the license activation.
829 */
830 public static function activate_license_for_plugin( $license, $plugin_slug ) {
831 $this_plugin = self::get_addon( $plugin_slug );
832 return $this_plugin->activate_license( $license );
833 }
834
835 /**
836 * @param string $license The license key.
837 *
838 * @return array The response from the license activation.
839 */
840 private function activate_license( $license ) {
841 $this->set_license( $license );
842 $this->license = $license;
843
844 $this->die_if_not_allowed();
845
846 $response = $this->get_license_status();
847 $response['message'] = '';
848 $response['success'] = false;
849
850 $is_valid = false;
851
852 if ( $response['error'] ) {
853 $response['message'] = $response['status'];
854 } else {
855 $messages = $this->get_messages();
856
857 if ( is_string( $response['status'] ) && isset( $messages[ $response['status'] ] ) ) {
858 $response['message'] = $messages[ $response['status'] ];
859 } else {
860 $response['message'] = FrmAppHelper::kses( $response['status'], array( 'a' ) );
861 }
862
863 if ( 'valid' === $response['status'] ) {
864 $is_valid = 'valid';
865 $response['success'] = true;
866 }
867
868 $this->maybe_set_active( $is_valid );
869 }
870
871 $this->update_last_checked( (bool) $is_valid );
872
873 return $response;
874 }
875
876 /**
877 * Prevent this check from happening more than once per minute with the same license.
878 *
879 * @return void
880 */
881 private function die_if_not_allowed() {
882 if ( ! $this->checked_recently( '2 minutes' ) ) {
883 return;
884 }
885
886 // Don't check more than once per minute.
887 wp_send_json(
888 array(
889 'message' => __( 'Please wait two minutes before trying again.', 'formidable' ),
890 'success' => false,
891 )
892 );
893 }
894
895 /**
896 * @return array
897 */
898 private function get_license_status() {
899 $this->set_running();
900
901 $response = array(
902 'status' => 'missing',
903 'error' => true,
904 );
905
906 if ( ! $this->license ) {
907 $response['error'] = false;
908 return $response;
909 }
910
911 $is_valid = false;
912
913 try {
914 $response['error'] = false;
915 $license_data = $this->send_mothership_request( 'activate_license' );
916
917 // $license_data->license will be either "valid" or "invalid"
918 if ( is_array( $license_data ) ) {
919 if ( ! empty( $license_data['license'] ) && in_array( $license_data['license'], array( 'valid', 'invalid' ), true ) ) {
920 $response['status'] = $license_data['license'];
921 $this->save_status['status'] = $license_data['license'];
922 $is_valid = 'valid' === $license_data['license'];
923 }
924 } else {
925 $response['status'] = $license_data;
926 }
927 } catch ( Exception $e ) {
928 $response['status'] = $e->getMessage();
929 }
930
931 $this->update_last_checked( $is_valid );
932 $this->done_running();
933 return $response;
934 }
935
936 /**
937 * @return array
938 */
939 private function get_messages() {
940 return array(
941 'valid' => __( 'Your license has been activated. Enjoy!', 'formidable' ),
942 'invalid' => __( 'That license key is invalid', 'formidable' ),
943 'expired' => __( 'That license is expired', 'formidable' ),
944 'revoked' => __( 'That license has been refunded', 'formidable' ),
945 'no_activations_left' => __( 'That license has been used on too many sites', 'formidable' ),
946 'invalid_item_id' => __( 'Oops! That is the wrong license key for this plugin.', 'formidable' ),
947 'missing' => __( 'That license key is invalid', 'formidable' ),
948 );
949 }
950
951 /**
952 * @since 4.03
953 *
954 * @return void
955 */
956 public static function reset_cache() {
957 FrmAppHelper::permission_check( 'frm_change_settings' );
958 check_ajax_referer( 'frm_ajax', 'nonce' );
959
960 $this_plugin = self::set_license_from_post();
961 $this_plugin->delete_cache();
962
963 $response = array(
964 'success' => true,
965 'message' => __( 'Cache cleared', 'formidable' ),
966 );
967
968 wp_send_json( $response );
969 }
970
971 /**
972 * @return void
973 */
974 public static function deactivate() {
975 FrmAppHelper::permission_check( 'frm_change_settings' );
976 check_ajax_referer( 'frm_ajax', 'nonce' );
977
978 $this_plugin = self::set_license_from_post();
979
980 $response = array(
981 'success' => false,
982 'message' => '',
983 );
984
985 try {
986 // $license_data->license will be either "deactivated" or "failed"
987 $license_data = $this_plugin->send_mothership_request( 'deactivate_license' );
988
989 if ( is_array( $license_data ) && 'deactivated' === $license_data['license'] ) {
990 $response['success'] = true;
991 $response['message'] = __( 'That license was removed successfully', 'formidable' );
992 } else {
993 $response['message'] = __( 'There was an error deactivating your license.', 'formidable' );
994 }
995 } catch ( Exception $e ) {
996 $response['message'] = $e->getMessage();
997 }
998
999 $this_plugin->clear_license();
1000 FrmInbox::clear_cache();
1001
1002 wp_send_json( $response );
1003 }
1004
1005 /**
1006 * @since 4.03
1007 *
1008 * @return FrmAddon
1009 */
1010 private static function set_license_from_post() {
1011 $plugin_slug = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
1012 $this_plugin = self::get_addon( $plugin_slug );
1013 $this_plugin->license = $this_plugin->get_license();
1014 return $this_plugin;
1015 }
1016
1017 /**
1018 * @param string $action
1019 *
1020 * @return string
1021 */
1022 public function send_mothership_request( $action ) {
1023 $api_params = array(
1024 'edd_action' => $action,
1025 'license' => $this->license,
1026 'url' => home_url(),
1027 );
1028
1029 if ( is_numeric( $this->download_id ) ) {
1030 $api_params['item_id'] = absint( $this->download_id );
1031 } else {
1032 $api_params['item_name'] = rawurlencode( $this->plugin_name );
1033 }
1034
1035 $arg_array = array(
1036 'body' => $api_params,
1037 'timeout' => 25,
1038 'user-agent' => $this->plugin_slug . '/' . $this->version . '; ' . get_bloginfo( 'url' ),
1039 );
1040
1041 $resp = wp_remote_post(
1042 $this->store_url . '?l=' . urlencode( base64_encode( $this->license ) ),
1043 $arg_array
1044 );
1045 $response_code = wp_remote_retrieve_response_code( $resp );
1046 $body = wp_remote_retrieve_body( $resp );
1047 $this->save_status = array( 'response_code' => $response_code );
1048 $this->save_response['response_code'] = $response_code;
1049
1050 $message = __( 'Your License Key was invalid', 'formidable' );
1051
1052 if ( is_wp_error( $resp ) ) {
1053 $link = FrmAppHelper::admin_upgrade_link( 'api', 'knowledgebase/why-cant-i-activate-formidable-pro/' );
1054 /* translators: %1$s: Start link HTML, %2$s: End link HTML */
1055 $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
1056 $message .= ' ' . $resp->get_error_message();
1057 } elseif ( 'error' === $body || is_wp_error( $body ) ) {
1058 $message = __( 'You had an HTTP error connecting to the Formidable API', 'formidable' );
1059 } else {
1060 $json_res = json_decode( $body, true );
1061
1062 if ( null !== $json_res ) {
1063 $message = is_array( $json_res ) && isset( $json_res['error'] ) ? $json_res['error'] : $json_res;
1064 } elseif ( ! empty( $resp['response'] ) && ! empty( $resp['response']['code'] ) ) {
1065 $resp['body'] = wp_strip_all_tags( $resp['body'] );
1066
1067 $message = sprintf(
1068 /* translators: %1$s: Error code, %2$s: Error message */
1069 esc_html__( 'There was a %1$s error: %2$s', 'formidable' ),
1070 esc_html( $resp['response']['code'] ),
1071 $resp['response']['message'] . ' ' . $resp['body']
1072 );
1073 }
1074 }//end if
1075
1076 return $message;
1077 }
1078
1079 /**
1080 * @return void
1081 */
1082 public function manually_queue_update() {
1083 $updates = new stdClass();
1084 $updates->last_checked = 0;
1085 $updates->response = array();
1086 $updates->translations = array();
1087 $updates->no_update = array();
1088 $updates->checked = array();
1089 set_site_transient( 'update_plugins', $updates );
1090 }
1091
1092 /**
1093 * Set the transient key for the lock. It should be unique to the license.
1094 *
1095 * @since 6.8.3
1096 *
1097 * @return bool
1098 */
1099 protected function lock_key() {
1100 return $this->transient_lock_key . '_' . $this->license;
1101 }
1102
1103 /**
1104 * Prevent multiple requests from running at the same time.
1105 *
1106 * @since 6.8.3
1107 *
1108 * @return bool
1109 */
1110 protected function is_running() {
1111 return get_transient( $this->lock_key() );
1112 }
1113
1114 /**
1115 * @since 6.8.3
1116 *
1117 * @return void
1118 */
1119 protected function set_running() {
1120 set_transient( $this->lock_key(), true, 2 * MINUTE_IN_SECONDS );
1121 }
1122
1123 /**
1124 * @since 6.8.3
1125 *
1126 * @return void
1127 */
1128 protected function done_running() {
1129 delete_transient( $this->lock_key() );
1130 }
1131 }
1132