← 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 | // ========================================== |