PluginProbe
wpForo Forum / 3.2.2
wpForo Forum v3.2.2
3.2.2 3.2.1 3.2.0 3.1.7 3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 All 142 releases
← All changes | admin/pages/license/src/Services/ActionsService.php +107 -4 3.1.6 → 3.2.2 View file →
@@ -49,8 +49,10 @@
49 49 add_action( $p . 'install_addon', [ $this, 'ajax_install_addon' ] );
50 50 add_action( $p . 'activate_addon', [ $this, 'ajax_activate_addon' ] );
51 51 add_action( $p . 'deactivate_addon', [ $this, 'ajax_deactivate_addon' ] );
52 52 add_action( $p . 'install_activate_addon', [ $this, 'ajax_install_activate_addon' ] );
53 + add_action( $p . 'download_addon', [ $this, 'ajax_download_addon' ] );
54 + add_action( $p . 'install_verified_copy', [ $this, 'ajax_install_verified_copy' ] );
53 55
54 56 // Subscription
55 57 add_action( $p . 'cancel_subscription', [ $this, 'ajax_cancel_subscription' ] );
56 58 add_action( $p . 'resume_subscription', [ $this, 'ajax_resume_subscription' ] );
@@ -105,8 +107,12 @@
105 107 }
106 108 $pid = $product['id'];
107 109 $is_bundle = ! empty( $product['is_bundle'] );
108 110
111 + // Marketing links from the Paddle product custom attributes (empty = no button on the card)
112 + $product['product_url'] = $this->product_link( $product, 'product_url' );
113 + $product['demo_url'] = $this->product_link( $product, 'demo_url' );
114 +
109 115 // For bundle products, check if all included addons are licensed
110 116 if( $is_bundle && ! empty( $product['bundle_slugs'] ) ) {
111 117 $bundle_all_licensed = true;
112 118 $bundle_addon_names = [];
@@ -172,8 +178,28 @@
172 178 $product['active_price_interval'] = $matched_price['interval_label'] ?? '';
173 179 }
174 180 }
175 181 unset( $product );
182 +
183 + // Probe the filesystem only when a licensed addon still needs installing (the probe writes a temp file)
184 + $needs_install = false;
185 + foreach( $products as $product ) {
186 + if( ( $product['is_licensed'] || $product['is_trial'] ) && empty( $product['is_bundle'] ) && $product['addon_status'] === 'not_installed' ) {
187 + $needs_install = true;
188 + break;
189 + }
190 + }
191 + $can_install = ! $needs_install || $this->addonsService->can_install_addons();
192 + foreach( $products as &$product ) {
193 + $product['can_install'] = $can_install;
194 + // Download ZIP is offered only when WordPress can't install a licensed addon (admin.js also sets it after an install fails for that reason)
195 + $product['offer_download'] = ! $can_install && ( $product['is_licensed'] || $product['is_trial'] ) && empty( $product['is_bundle'] ) && $product['addon_status'] === 'not_installed';
196 + // "Reinstall Addon" for every installed licensed addon; emphasized when the installed copy isn't verified
197 + $product['offer_verified_copy'] = ( $product['is_licensed'] || $product['is_trial'] ) && empty( $product['is_bundle'] ) && $product['addon_status'] !== 'not_installed';
198 + $product['needs_verified_copy'] = $product['offer_verified_copy'] && $this->addonsService->needs_verified_copy( $product['plugin_slug'] ?? '' );
199 + }
200 + unset( $product );
201 +
176 202 $products = array_values( $products );
177 203 wp_send_json_success( [ 'products' => $products, 'checkout_mode' => $checkout_mode ] );
178 204 } else {
179 205 wp_send_json_error( [ 'message' => __( 'Failed to load products', 'gvectors' ) ] );
@@ -179,8 +205,18 @@
179 205 wp_send_json_error( [ 'message' => __( 'Failed to load products', 'gvectors' ) ] );
180 206 }
181 207 }
182 208
209 + /**
210 + * A link from the product's Paddle custom data (http/https only) — '' when missing, empty or not a web URL.
211 + */
212 + private function product_link( array $product, string $key ): string {
213 + $url = $product['custom_data'][ $key ] ?? '';
214 + if( ! is_string( $url ) || ( $url = trim( $url ) ) === '' ) return '';
215 +
216 + return esc_url_raw( $url, [ 'http', 'https' ] );
217 + }
218 +
183 219 private function verify_admin(): bool {
184 220 if( ! current_user_can( 'administrator' ) ) {
185 221 wp_send_json_error( [ 'message' => __( 'Permission denied', 'gvectors' ) ] );
186 222
@@ -496,11 +532,12 @@
496 532 $response = $this->addonsService->licenseService->activate( $license_key, $product_id );
497 533 if( ! empty( $response['success'] ) ) {
498 534 // Revalidate all licenses to clear expired notices for newly purchased/renewed addons
499 535 $this->addonsService->check_all_license_validity();
536 + $this->addonsService->reverify_licensed_addons();
500 537 wp_send_json_success( [
501 538 'license' => $response['data'],
502 - 'message' => __( 'License activated successfully', 'gvectors' ),
539 + 'message' => self::activated_message( $response['data'] ),
503 540 ] );
504 541 } else {
505 542 $error = $response['error'] ?? __( 'Failed to activate license', 'gvectors' );
506 543 wp_send_json_error( [ 'message' => $error ] );
@@ -521,8 +558,9 @@
521 558
522 559 $response = $this->addonsService->licenseService->activate_by_transaction( $transaction_id, $product_id );
523 560 if( ! empty( $response['success'] ) ) {
524 561 $this->addonsService->check_all_license_validity();
562 + $this->addonsService->reverify_licensed_addons();
525 563 wp_send_json_success( [
526 564 'activated' => $response['data']['activated'] ?? [],
527 565 'errors' => $response['data']['errors'] ?? [],
528 566 'message' => $response['data']['message'] ?? __( 'Licenses activated successfully', 'gvectors' ),
@@ -543,8 +581,9 @@
543 581 $response = $this->addonsService->licenseService->activate_unified( $key, $product_id );
544 582
545 583 if( ! empty( $response['success'] ) ) {
546 584 $this->addonsService->check_all_license_validity();
585 + $this->addonsService->reverify_licensed_addons();
547 586 // Multi-license response (txn_* or by-domain)
548 587 if( ! empty( $response['data']['activated'] ) ) {
549 588 wp_send_json_success( [
550 589 'activated' => $response['data']['activated'],
@@ -554,9 +593,9 @@
554 593 } else {
555 594 // Single license response (gvl_* or legacy key)
556 595 wp_send_json_success( [
557 596 'license' => $response['data'],
558 - 'message' => __( 'License activated successfully', 'gvectors' ),
597 + 'message' => self::activated_message( $response['data'] ),
559 598 ] );
560 599 }
561 600
562 601 return;
@@ -564,8 +603,20 @@
564 603
565 604 wp_send_json_error( [ 'message' => $response['error'] ?? __( 'Could not activate. Please check your key.', 'gvectors' ) ] );
566 605 }
567 606
607 + /**
608 + * Success message for a single activated license. An expired old-store key is still linked to the site
609 + * (the addon keeps working), but it grants no updates until renewed.
610 + */
611 + private static function activated_message( $license ): string {
612 + if( is_array( $license ) && ( $license['status'] ?? '' ) === 'expired' ) {
613 + return __( 'Your license was linked to this site. It has expired: the addon keeps working, renew it to get updates.', 'gvectors' );
614 + }
615 +
616 + return __( 'License activated successfully', 'gvectors' );
617 + }
618 +
568 619 public function ajax_activate_by_domain(): void {
569 620 if( ! $this->verify_admin() ) return;
570 621
571 622 $response = $this->addonsService->licenseService->activate_by_domain();
@@ -570,8 +621,9 @@
570 621
571 622 $response = $this->addonsService->licenseService->activate_by_domain();
572 623 if( ! empty( $response['success'] ) ) {
573 624 $this->addonsService->check_all_license_validity();
625 + $this->addonsService->reverify_licensed_addons();
574 626 wp_send_json_success( [
575 627 'activated' => $response['data']['activated'] ?? [],
576 628 'errors' => $response['data']['errors'] ?? [],
577 629 'message' => $response['data']['message'] ?? __( 'Licenses activated successfully', 'gvectors' ),
@@ -662,9 +714,12 @@
662 714 $result = $this->addonsService->install( $product_id );
663 715 if( ! empty( $result['success'] ) ) {
664 716 wp_send_json_success( $result );
665 717 } else {
666 - wp_send_json_error( [ 'message' => $result['error'] ?? __( 'Installation failed', 'gvectors' ) ] );
718 + wp_send_json_error( [
719 + 'message' => $result['error'] ?? __( 'Installation failed', 'gvectors' ),
720 + 'manual_install' => ! empty( $result['manual_install'] ),
721 + ] );
667 722 }
668 723 }
669 724
670 725 public function ajax_activate_addon(): void {
@@ -716,9 +771,57 @@
716 771 $result = $this->addonsService->install_and_activate( $product_id );
717 772 if( ! empty( $result['success'] ) ) {
718 773 wp_send_json_success( $result );
719 774 } else {
720 - wp_send_json_error( [ 'message' => $result['error'] ?? __( 'Install & activate failed', 'gvectors' ) ] );
775 + wp_send_json_error( [
776 + 'message' => $result['error'] ?? __( 'Install & activate failed', 'gvectors' ),
777 + 'manual_install' => ! empty( $result['manual_install'] ),
778 + ] );
779 + }
780 + }
781 +
782 + /**
783 + * "Reinstall Addon": replace an installed licensed addon with a fresh copy signed for this site
784 + */
785 + public function ajax_install_verified_copy(): void {
786 + if( ! $this->verify_admin() ) return;
787 +
788 + $product_id = isset( $_POST['product_id'] ) ? sanitize_text_field( $_POST['product_id'] ) : '';
789 + if( empty( $product_id ) ) {
790 + wp_send_json_error( [ 'message' => __( 'Product ID required', 'gvectors' ) ] );
791 +
792 + return;
793 + }
794 +
795 + $result = $this->addonsService->install_verified_copy( $product_id );
796 + if( ! empty( $result['success'] ) ) {
797 + wp_send_json_success( $result );
798 + } else {
799 + wp_send_json_error( [
800 + 'message' => $result['error'] ?? __( 'Could not reinstall the addon', 'gvectors' ),
801 + 'manual_install' => ! empty( $result['manual_install'] ),
802 + ] );
803 + }
804 + }
805 +
806 + /**
807 + * Signed addon ZIP URL for a manual (FTP) install — the browser downloads it straight from the proxy
808 + */
809 + public function ajax_download_addon(): void {
810 + if( ! $this->verify_admin() ) return;
811 +
812 + $product_id = isset( $_POST['product_id'] ) ? sanitize_text_field( $_POST['product_id'] ) : '';
813 + if( empty( $product_id ) ) {
814 + wp_send_json_error( [ 'message' => __( 'Product ID required', 'gvectors' ) ] );
815 +
816 + return;
817 + }
818 +
819 + $result = $this->addonsService->get_download_link( $product_id );
820 + if( ! empty( $result['success'] ) ) {
821 + wp_send_json_success( $result );
822 + } else {
823 + wp_send_json_error( [ 'message' => $result['error'] ?? __( 'Failed to get download URL', 'gvectors' ) ] );
721 824 }
722 825 }
723 826
724 827 // ==========================================