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 / FrmAddon.php

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

1,138 lines 28.5 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 // The Sales API tries to pull API data in the constructor.
448 // But we do not need sales data here.
449 FrmSalesApi::prevent_new_sales_requests();
450
451 $api = new FrmSalesApi();
452 $api->reset_cached();
453
454 FrmSalesApi::allow_new_sales_requests();
455
456 $api = new FrmStyleApi();
457 $api->reset_cached();
458 }
459
460 /**
461 * The Pro version includes the show_license_message function.
462 * We need an extra check before we allow it to show a message.
463 *
464 * @since 3.04.03
465 *
466 * @param string $file File path of the plugin.
467 * @param array $plugin Plugin data.
468 *
469 * @return void
470 */
471 public function maybe_show_license_message( $file, $plugin ) {
472 if ( $this->is_expired_addon || isset( $plugin['package'] ) ) {
473 // Let's not show a ton of duplicate messages
474 return;
475 }
476
477 $this->show_license_message( $file, $plugin );
478 }
479
480 /**
481 * @param string $file File path of the plugin.
482 * @param array $plugin Plugin data.
483 *
484 * @return void
485 */
486 public function show_license_message( $file, $plugin ) {
487 $message = '';
488
489 if ( $this->license ) {
490 $api = new FrmFormApi( $this->license );
491 $errors = $api->error_for_license();
492
493 if ( $errors ) {
494 $message = reset( $errors );
495 }
496 } else {
497 /* translators: %1$s: Plugin name, %2$s: Start link HTML, %3$s: end link HTML */
498 $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
499 }
500
501 if ( ! $message ) {
502 return;
503 }
504
505 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
506 $id = sanitize_title( $plugin['Name'] ) . '-next';
507
508 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
509 FrmAppHelper::kses_echo( $message, 'a' );
510 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
511 echo '</p></div></td></tr>';
512 }
513
514 /**
515 * @param mixed $transient
516 *
517 * @return mixed
518 */
519 public function clear_expired_download( $transient ) {
520 if ( ! is_object( $transient ) ) {
521 return $transient;
522 }
523
524 if ( $this->is_current_version( $transient ) ) {
525 // Make sure it doesn't show there is an update if plugin is up-to-date.
526 if ( isset( $transient->response[ $this->plugin_folder ] ) ) {
527 unset( $transient->response[ $this->plugin_folder ] );
528 }
529 } elseif ( isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) ) {
530 $this->prepare_update_details( $transient->response[ $this->plugin_folder ] );
531
532 // If the transient has expired, clear the update and trigger it again
533 if ( $transient->response[ $this->plugin_folder ] === false ) {
534 if ( ! $this->has_been_cleared() ) {
535 $this->cleared_plugins();
536 $this->manually_queue_update();
537 }
538 unset( $transient->response[ $this->plugin_folder ] );
539 }
540 }
541
542 return $transient;
543 }
544
545 /**
546 * Check if the plugin information is correct to allow an update
547 *
548 * @since 3.04.03
549 *
550 * @param object $transient The current plugin info saved for update.
551 *
552 * @return void
553 */
554 private function prepare_update_details( &$transient ) {
555 $version_info = $transient;
556 $has_beta_url = ! empty( $version_info->beta );
557
558 if ( $this->get_beta && ! $has_beta_url ) {
559 $version_info = (object) $this->get_api_info( $this->license );
560 }
561
562 if ( empty( $version_info->new_version ) ) {
563 return;
564 }
565
566 $this->clear_old_plugin_version( $version_info );
567
568 if ( $version_info === false ) {
569 // Was cleared with timeout.
570 $transient = false;
571
572 return;
573 }
574
575 $this->maybe_use_beta_url( $version_info );
576
577 if ( version_compare( $version_info->new_version, $this->version, '>' ) ) {
578 $transient = $version_info;
579 }
580 }
581
582 /**
583 * Get the API info for this plugin
584 *
585 * @since 3.04.03
586 *
587 * @param string $license The license key.
588 *
589 * @return array The API info for the plugin.
590 */
591 protected function get_api_info( $license ) {
592 $api = new FrmFormApi( $license );
593 $addon = $api->get_addon_for_license( $this );
594
595 // If there is no download url, this license does not apply to the addon
596 if ( isset( $addon['package'] ) ) {
597 $this->is_parent_licence = true;
598 } elseif ( isset( $addon['error'] ) ) {
599 // If the license is expired, we must assume all add-ons were packaged
600 $this->is_parent_licence = true;
601 $this->is_expired_addon = true;
602 }
603
604 return $addon;
605 }
606
607 /**
608 * Make sure transients don't stick around on sites that
609 * don't save the transient expiration
610 *
611 * @since 2.05.05
612 *
613 * @param object $version_info The version info for the plugin.
614 *
615 * @return void
616 */
617 private function clear_old_plugin_version( &$version_info ) {
618 $timeout = ! empty( $version_info->timeout ) ? $version_info->timeout : 0;
619
620 if ( ! $timeout || time() <= $timeout ) {
621 return;
622 }
623
624 // Cache is expired.
625 $version_info = false;
626 $api = new FrmFormApi( $this->license );
627 $api->reset_cached();
628 }
629
630 /**
631 * The beta url is always included if the download has a beta.
632 * Check if the beta should be downloaded.
633 *
634 * @since 3.04.03
635 *
636 * @param object $version_info The version info for the plugin.
637 *
638 * @return void
639 */
640 private function maybe_use_beta_url( &$version_info ) {
641 if ( ! $this->get_beta || empty( $version_info->beta ) ) {
642 return;
643 }
644
645 $version_info->new_version = $version_info->beta['version'];
646 $version_info->package = $version_info->beta['package'];
647
648 if ( ! empty( $version_info->plugin ) ) {
649 $version_info->plugin = $version_info->beta['plugin'];
650 }
651 }
652
653 /**
654 * @param object $transient
655 *
656 * @return bool
657 */
658 private function is_current_version( $transient ) {
659 if ( empty( $transient->checked ) || ! isset( $transient->checked[ $this->plugin_folder ] ) ) {
660 return false;
661 }
662
663 $response = empty( $transient->response );
664
665 if ( $response ) {
666 return true;
667 }
668
669 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
670 }
671
672 /**
673 * @return bool
674 */
675 private function has_been_cleared() {
676 $last_cleared = get_option( 'frm_last_cleared' );
677 return $last_cleared && $last_cleared > gmdate( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) );
678 }
679
680 /**
681 * @return void
682 */
683 private function cleared_plugins() {
684 update_option( 'frm_last_cleared', gmdate( 'Y-m-d H:i:s' ) );
685 }
686
687 /**
688 * @return void
689 */
690 private function is_license_revoked() {
691 if ( ! $this->license || ! $this->plugin_slug || isset( $_POST['license'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
692 return;
693 }
694
695 if ( $this->get_defined_license() ) {
696 // Don't check if the license is defined in wp-config.php since we can't remove it.
697 return;
698 }
699
700 // Only check weekly.
701 if ( $this->checked_recently( '7 days' ) || $this->is_running() ) {
702 return;
703 }
704
705 $response = $this->get_license_status();
706
707 if ( ! empty( $this->save_status['response_code'] ) && 429 === $this->save_status['response_code'] ) {
708 // If we got a rate limit response, don't clear the license.
709 return;
710 }
711
712 if ( 'revoked' === $response['status'] || 'blocked' === $response['status'] || 'disabled' === $response['status'] || 'missing' === $response['status'] ) {
713 $this->clear_license();
714 }
715 }
716
717 /**
718 * Has this been checked too recently?
719 *
720 * @param string $time ie. '1 day'.
721 *
722 * @return bool True if the last check time was recent.
723 */
724 private function checked_recently( $time ) {
725 $last_checked = $this->last_checked();
726
727 if ( ! $last_checked ) {
728 return false;
729 }
730
731 $is_429 = isset( $last_checked['response_code'] ) && 429 === $last_checked['response_code'];
732
733 // If the last check was a a rate limit error, adjust the check time.
734 if ( $is_429 ) {
735 // If the time is 2 minutes, do not adjust it so people
736 // can still try to activate again in 2 minutes (and not 5).
737 switch ( $time ) {
738 case '7 days':
739 // 7 days is used to check for license revocation.
740 $time = '30 minutes';
741 break;
742 case '1 day':
743 // 1 day is used for defined license constants.
744 // The response won't change often, so we can check less frequently.
745 $time = '2 hours';
746 break;
747 }
748 }
749
750 if ( empty( $last_checked['time'] ) ) {
751 // If we do not have time, data is really old.
752 return false;
753 }
754
755 $time_ago = gmdate( 'Y-m-d H:i:s', strtotime( '-' . $time ) );
756 return $last_checked['time'] > $time_ago;
757 }
758
759 /**
760 * @since 6.8.3 Switched to an array to store extra response info.
761 *
762 * @return array
763 */
764 private function last_checked() {
765 $last_checked = is_multisite() ? get_site_option( $this->transient_key() ) : get_option( $this->transient_key() );
766
767 if ( $last_checked && ! is_array( $last_checked ) ) {
768 // Get string into array for existing values.
769 $last_checked = array( 'time' => $last_checked );
770 }
771
772 return $last_checked ? $last_checked : array();
773 }
774
775 /**
776 * @since 6.30 Added the $is_valid param.
777 *
778 * @param bool $is_valid
779 *
780 * @return void
781 */
782 private function update_last_checked( $is_valid ) {
783 $this->save_response['time'] = gmdate( 'Y-m-d H:i:s' );
784 $this->save_response['is_valid'] = $is_valid;
785
786 if ( is_multisite() ) {
787 update_site_option( $this->transient_key(), $this->save_response );
788 } else {
789 update_option( $this->transient_key(), $this->save_response );
790 }
791 }
792
793 /**
794 * Use a new cache after the license is changed, or Formidable is updated.
795 *
796 * @return string
797 */
798 private function transient_key() {
799 return 'frm_' . md5( sanitize_key( $this->license . '_' . $this->plugin_slug ) );
800 }
801
802 /**
803 * @return void
804 */
805 public static function activate() {
806 FrmAppHelper::permission_check( 'frm_change_settings' );
807 check_ajax_referer( 'frm_ajax', 'nonce' );
808
809 $license = stripslashes( FrmAppHelper::get_param( 'license', '', 'post', 'sanitize_text_field' ) );
810
811 if ( ! $license ) {
812 wp_send_json(
813 array(
814 'message' => __( 'Oops! You forgot to enter your license number.', 'formidable' ),
815 'success' => false,
816 )
817 );
818 }
819
820 $plugin_slug = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
821 $response = self::activate_license_for_plugin( $license, $plugin_slug );
822
823 FrmInbox::clear_cache();
824
825 wp_send_json( $response );
826 }
827
828 /**
829 * @since 4.08
830 *
831 * @param string $license The license key.
832 * @param string $plugin_slug The plugin slug.
833 *
834 * @return array The response from the license activation.
835 */
836 public static function activate_license_for_plugin( $license, $plugin_slug ) {
837 $this_plugin = self::get_addon( $plugin_slug );
838 return $this_plugin->activate_license( $license );
839 }
840
841 /**
842 * @param string $license The license key.
843 *
844 * @return array The response from the license activation.
845 */
846 private function activate_license( $license ) {
847 $this->set_license( $license );
848 $this->license = $license;
849
850 $this->die_if_not_allowed();
851
852 $response = $this->get_license_status();
853 $response['message'] = '';
854 $response['success'] = false;
855
856 $is_valid = false;
857
858 if ( $response['error'] ) {
859 $response['message'] = $response['status'];
860 } else {
861 $messages = $this->get_messages();
862
863 if ( is_string( $response['status'] ) && isset( $messages[ $response['status'] ] ) ) {
864 $response['message'] = $messages[ $response['status'] ];
865 } else {
866 $response['message'] = FrmAppHelper::kses( $response['status'], array( 'a' ) );
867 }
868
869 if ( 'valid' === $response['status'] ) {
870 $is_valid = 'valid';
871 $response['success'] = true;
872 }
873
874 $this->maybe_set_active( $is_valid );
875 }
876
877 $this->update_last_checked( (bool) $is_valid );
878
879 return $response;
880 }
881
882 /**
883 * Prevent this check from happening more than once per minute with the same license.
884 *
885 * @return void
886 */
887 private function die_if_not_allowed() {
888 if ( ! $this->checked_recently( '2 minutes' ) ) {
889 return;
890 }
891
892 // Don't check more than once per minute.
893 wp_send_json(
894 array(
895 'message' => __( 'Please wait two minutes before trying again.', 'formidable' ),
896 'success' => false,
897 )
898 );
899 }
900
901 /**
902 * @return array
903 */
904 private function get_license_status() {
905 $this->set_running();
906
907 $response = array(
908 'status' => 'missing',
909 'error' => true,
910 );
911
912 if ( ! $this->license ) {
913 $response['error'] = false;
914 return $response;
915 }
916
917 $is_valid = false;
918
919 try {
920 $response['error'] = false;
921 $license_data = $this->send_mothership_request( 'activate_license' );
922
923 // $license_data->license will be either "valid" or "invalid"
924 if ( is_array( $license_data ) ) {
925 if ( ! empty( $license_data['license'] ) && in_array( $license_data['license'], array( 'valid', 'invalid' ), true ) ) {
926 $response['status'] = $license_data['license'];
927 $this->save_status['status'] = $license_data['license'];
928 $is_valid = 'valid' === $license_data['license'];
929 }
930 } else {
931 $response['status'] = $license_data;
932 }
933 } catch ( Exception $e ) {
934 $response['status'] = $e->getMessage();
935 }
936
937 $this->update_last_checked( $is_valid );
938 $this->done_running();
939 return $response;
940 }
941
942 /**
943 * @return array
944 */
945 private function get_messages() {
946 return array(
947 'valid' => __( 'Your license has been activated. Enjoy!', 'formidable' ),
948 'invalid' => __( 'That license key is invalid', 'formidable' ),
949 'expired' => __( 'That license is expired', 'formidable' ),
950 'revoked' => __( 'That license has been refunded', 'formidable' ),
951 'no_activations_left' => __( 'That license has been used on too many sites', 'formidable' ),
952 'invalid_item_id' => __( 'Oops! That is the wrong license key for this plugin.', 'formidable' ),
953 'missing' => __( 'That license key is invalid', 'formidable' ),
954 );
955 }
956
957 /**
958 * @since 4.03
959 *
960 * @return void
961 */
962 public static function reset_cache() {
963 FrmAppHelper::permission_check( 'frm_change_settings' );
964 check_ajax_referer( 'frm_ajax', 'nonce' );
965
966 $this_plugin = self::set_license_from_post();
967 $this_plugin->delete_cache();
968
969 $response = array(
970 'success' => true,
971 'message' => __( 'Cache cleared', 'formidable' ),
972 );
973
974 wp_send_json( $response );
975 }
976
977 /**
978 * @return void
979 */
980 public static function deactivate() {
981 FrmAppHelper::permission_check( 'frm_change_settings' );
982 check_ajax_referer( 'frm_ajax', 'nonce' );
983
984 $this_plugin = self::set_license_from_post();
985
986 $response = array(
987 'success' => false,
988 'message' => '',
989 );
990
991 try {
992 // $license_data->license will be either "deactivated" or "failed"
993 $license_data = $this_plugin->send_mothership_request( 'deactivate_license' );
994
995 if ( is_array( $license_data ) && 'deactivated' === $license_data['license'] ) {
996 $response['success'] = true;
997 $response['message'] = __( 'That license was removed successfully', 'formidable' );
998 } else {
999 $response['message'] = __( 'There was an error deactivating your license.', 'formidable' );
1000 }
1001 } catch ( Exception $e ) {
1002 $response['message'] = $e->getMessage();
1003 }
1004
1005 $this_plugin->clear_license();
1006 FrmInbox::clear_cache();
1007
1008 wp_send_json( $response );
1009 }
1010
1011 /**
1012 * @since 4.03
1013 *
1014 * @return FrmAddon
1015 */
1016 private static function set_license_from_post() {
1017 $plugin_slug = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
1018 $this_plugin = self::get_addon( $plugin_slug );
1019 $this_plugin->license = $this_plugin->get_license();
1020 return $this_plugin;
1021 }
1022
1023 /**
1024 * @param string $action
1025 *
1026 * @return string
1027 */
1028 public function send_mothership_request( $action ) {
1029 $api_params = array(
1030 'edd_action' => $action,
1031 'license' => $this->license,
1032 'url' => home_url(),
1033 );
1034
1035 if ( is_numeric( $this->download_id ) ) {
1036 $api_params['item_id'] = absint( $this->download_id );
1037 } else {
1038 $api_params['item_name'] = rawurlencode( $this->plugin_name );
1039 }
1040
1041 $arg_array = array(
1042 'body' => $api_params,
1043 'timeout' => 25,
1044 'user-agent' => $this->plugin_slug . '/' . $this->version . '; ' . get_bloginfo( 'url' ),
1045 );
1046
1047 $resp = wp_remote_post(
1048 $this->store_url . '?l=' . urlencode( base64_encode( $this->license ) ),
1049 $arg_array
1050 );
1051 $response_code = wp_remote_retrieve_response_code( $resp );
1052 $body = wp_remote_retrieve_body( $resp );
1053 $this->save_status = array( 'response_code' => $response_code );
1054 $this->save_response['response_code'] = $response_code;
1055
1056 $message = __( 'Your License Key was invalid', 'formidable' );
1057
1058 if ( is_wp_error( $resp ) ) {
1059 $link = FrmAppHelper::admin_upgrade_link( 'api', 'knowledgebase/why-cant-i-activate-formidable-pro/' );
1060 /* translators: %1$s: Start link HTML, %2$s: End link HTML */
1061 $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
1062 $message .= ' ' . $resp->get_error_message();
1063 } elseif ( 'error' === $body || is_wp_error( $body ) ) {
1064 $message = __( 'You had an HTTP error connecting to the Formidable API', 'formidable' );
1065 } else {
1066 $json_res = json_decode( $body, true );
1067
1068 if ( null !== $json_res ) {
1069 $message = is_array( $json_res ) && isset( $json_res['error'] ) ? $json_res['error'] : $json_res;
1070 } elseif ( ! empty( $resp['response'] ) && ! empty( $resp['response']['code'] ) ) {
1071 $resp['body'] = wp_strip_all_tags( $resp['body'] );
1072
1073 $message = sprintf(
1074 /* translators: %1$s: Error code, %2$s: Error message */
1075 esc_html__( 'There was a %1$s error: %2$s', 'formidable' ),
1076 esc_html( $resp['response']['code'] ),
1077 $resp['response']['message'] . ' ' . $resp['body']
1078 );
1079 }
1080 }//end if
1081
1082 return $message;
1083 }
1084
1085 /**
1086 * @return void
1087 */
1088 public function manually_queue_update() {
1089 $updates = new stdClass();
1090 $updates->last_checked = 0;
1091 $updates->response = array();
1092 $updates->translations = array();
1093 $updates->no_update = array();
1094 $updates->checked = array();
1095 set_site_transient( 'update_plugins', $updates );
1096 }
1097
1098 /**
1099 * Set the transient key for the lock. It should be unique to the license.
1100 *
1101 * @since 6.8.3
1102 *
1103 * @return bool
1104 */
1105 protected function lock_key() {
1106 return $this->transient_lock_key . '_' . $this->license;
1107 }
1108
1109 /**
1110 * Prevent multiple requests from running at the same time.
1111 *
1112 * @since 6.8.3
1113 *
1114 * @return bool
1115 */
1116 protected function is_running() {
1117 return get_transient( $this->lock_key() );
1118 }
1119
1120 /**
1121 * @since 6.8.3
1122 *
1123 * @return void
1124 */
1125 protected function set_running() {
1126 set_transient( $this->lock_key(), true, 2 * MINUTE_IN_SECONDS );
1127 }
1128
1129 /**
1130 * @since 6.8.3
1131 *
1132 * @return void
1133 */
1134 protected function done_running() {
1135 delete_transient( $this->lock_key() );
1136 }
1137 }
1138