ads
1 week ago
groups
1 week ago
metaboxes
1 year ago
pages
1 day ago
placements
1 day ago
class-action-links.php
1 week ago
class-addon-box.php
1 day ago
class-addon-updater.php
1 day ago
class-admin-menu.php
1 day ago
class-admin-notices.php
1 year ago
class-ajax.php
1 day ago
class-app.php
1 day ago
class-assets.php
1 day ago
class-authors.php
1 year ago
class-edd-updater.php
1 day ago
class-license-admin-post.php
1 day ago
class-list-filters.php
1 week ago
class-marketing.php
1 year ago
class-metabox-ad-settings.php
1 year ago
class-metabox-ad.php
1 year ago
class-misc.php
1 week ago
class-page-quick-edit.php
1 day ago
class-plugin-auto-update.php
1 day ago
class-plugin-installer.php
1 day ago
class-post-list.php
1 year ago
class-post-types.php
1 week ago
class-screen-options.php
3 months ago
class-settings.php
1 day ago
class-shortcode-creator.php
1 year ago
class-system-info.php
1 year ago
class-tinymce.php
2 years ago
class-translation-promo.php
1 year ago
class-upgrades.php
1 year ago
class-version-control.php
3 months ago
class-welcome.php
1 year ago
class-wordpress-dashboard.php
1 year ago
index.php
2 years ago
class-license-admin-post.php
244 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Admin-post handler for shop token exchange. |
| 4 | * |
| 5 | * @package AdvancedAds |
| 6 | * @author Advanced Ads <info@wpadvancedads.com> |
| 7 | */ |
| 8 | |
| 9 | namespace AdvancedAds\Admin; |
| 10 | |
| 11 | use AdvancedAds\Framework\Interfaces\Integration_Interface; |
| 12 | use AdvancedAds\Framework\Utilities\Params; |
| 13 | use AdvancedAds\License\License; |
| 14 | use AdvancedAds\License\License_Exchange; |
| 15 | use AdvancedAds\License\License_Utils; |
| 16 | use AdvancedAds\Utilities\Conditional; |
| 17 | use WP_Error; |
| 18 | |
| 19 | defined( 'ABSPATH' ) || exit; |
| 20 | |
| 21 | /** |
| 22 | * Exchanges one-time shop tokens server-side after connect or checkout. |
| 23 | */ |
| 24 | class License_Admin_Post implements Integration_Interface { |
| 25 | |
| 26 | public const ACTION = 'advanced-ads-license'; |
| 27 | |
| 28 | /** |
| 29 | * Register WordPress hooks. |
| 30 | * |
| 31 | * @return void |
| 32 | */ |
| 33 | public function hooks(): void { |
| 34 | add_action( 'admin_post_' . self::ACTION, [ $this, 'handle' ] ); |
| 35 | } |
| 36 | |
| 37 | /** |
| 38 | * Exchange token, optionally activate on shop, save licenses, redirect. |
| 39 | * |
| 40 | * @return void |
| 41 | */ |
| 42 | public function handle(): void { |
| 43 | if ( ! Conditional::user_can( 'advanced_ads_manage_options' ) ) { |
| 44 | wp_die( esc_html__( 'Forbidden.', 'advanced-ads' ), '', [ 'response' => 403 ] ); |
| 45 | } |
| 46 | |
| 47 | $token = sanitize_text_field( (string) Params::request( 'token', '' ) ); |
| 48 | $license_id = absint( Params::request( 'license_id', 0, FILTER_VALIDATE_INT ) ); |
| 49 | $purchase_id = absint( Params::request( 'purchase_id', 0, FILTER_VALIDATE_INT ) ); |
| 50 | $checkout_intent = sanitize_key( (string) Params::request( 'checkout_intent', '' ) ); |
| 51 | |
| 52 | if ( '' === $token ) { |
| 53 | $this->redirect_with_error( 'invalid_token' ); |
| 54 | } |
| 55 | |
| 56 | $rows = License_Exchange::request_by_token( $token ); |
| 57 | if ( is_wp_error( $rows ) ) { |
| 58 | $this->redirect_with_error( $this->map_error_code( $rows ) ); |
| 59 | } |
| 60 | if ( empty( $rows ) ) { |
| 61 | $this->redirect_with_error( 'no_licenses' ); |
| 62 | } |
| 63 | |
| 64 | $activating_key = $this->resolve_checkout_activating_key( $rows, $license_id ); |
| 65 | |
| 66 | $saved = License::save_licenses( |
| 67 | $rows, |
| 68 | '' !== $activating_key, |
| 69 | $activating_key, |
| 70 | '', |
| 71 | false, |
| 72 | '', |
| 73 | '', |
| 74 | true |
| 75 | ); |
| 76 | |
| 77 | if ( is_wp_error( $saved ) ) { |
| 78 | $this->redirect_with_activation_error( $saved ); |
| 79 | } |
| 80 | |
| 81 | $args = []; |
| 82 | if ( $purchase_id > 0 ) { |
| 83 | $args['purchase_id'] = $purchase_id; |
| 84 | } |
| 85 | if ( in_array( $checkout_intent, [ 'buy', 'upgrade', 'renew' ], true ) ) { |
| 86 | $args['checkout_intent'] = $checkout_intent; |
| 87 | } |
| 88 | if ( $license_id > 0 ) { |
| 89 | $args['license_id'] = $license_id; |
| 90 | } |
| 91 | |
| 92 | wp_safe_redirect( $this->license_admin_url( $args ) ); |
| 93 | exit; |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * License key to activate after checkout when this site is not on the shop row yet. |
| 98 | * |
| 99 | * @param array<int, array<string, mixed>> $rows Exchange rows. |
| 100 | * @param int $license_id EDD SL license post ID from redirect. |
| 101 | * @return string License key or empty. |
| 102 | */ |
| 103 | private function resolve_checkout_activating_key( array $rows, int $license_id ): string { |
| 104 | if ( $license_id < 1 ) { |
| 105 | return ''; |
| 106 | } |
| 107 | |
| 108 | $row = $this->resolve_checkout_row( $rows, $license_id ); |
| 109 | if ( null === $row ) { |
| 110 | return ''; |
| 111 | } |
| 112 | |
| 113 | $key = (string) ( $row['licenseKey'] ?? '' ); |
| 114 | if ( '' === $key || $this->is_site_active_on_row( $row ) ) { |
| 115 | return ''; |
| 116 | } |
| 117 | |
| 118 | return $key; |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Pick license row for post-checkout activation. |
| 123 | * |
| 124 | * @param array<int, array<string, mixed>> $rows Exchange rows. |
| 125 | * @param int $license_id EDD SL license post ID. |
| 126 | * @return array<string, mixed>|null |
| 127 | */ |
| 128 | private function resolve_checkout_row( array $rows, int $license_id ): ?array { |
| 129 | foreach ( $rows as $row ) { |
| 130 | if ( (int) ( $row['licenseId'] ?? 0 ) === $license_id ) { |
| 131 | return $row; |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | return $rows[0] ?? null; |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Whether this site is already activated on the license row. |
| 140 | * |
| 141 | * @param array<string, mixed> $row License exchange row. |
| 142 | * @return bool |
| 143 | */ |
| 144 | private function is_site_active_on_row( array $row ): bool { |
| 145 | $host = wp_parse_url( home_url(), PHP_URL_HOST ); |
| 146 | $host = is_string( $host ) ? preg_replace( '/^www\./', '', strtolower( $host ) ) : ''; |
| 147 | $sites = isset( $row['sitesActivated'] ) && is_array( $row['sitesActivated'] ) ? $row['sitesActivated'] : []; |
| 148 | |
| 149 | foreach ( $sites as $site ) { |
| 150 | $domain = is_array( $site ) ? (string) ( $site['domain'] ?? '' ) : ''; |
| 151 | $parsed = wp_parse_url( $domain, PHP_URL_HOST ) ?: $domain; |
| 152 | $parsed = preg_replace( '/^www\./', '', strtolower( (string) $parsed ) ); |
| 153 | if ( $parsed === $host ) { |
| 154 | return true; |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | return false; |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * Map exchange WP_Error to redirect error code. |
| 163 | * |
| 164 | * @param WP_Error $error Exchange error. |
| 165 | * @return string |
| 166 | */ |
| 167 | private function map_error_code( WP_Error $error ): string { |
| 168 | $error_code = $error->get_error_code(); |
| 169 | |
| 170 | if ( 'not_found' === $error_code ) { |
| 171 | return 'no_licenses'; |
| 172 | } |
| 173 | |
| 174 | if ( 'invalid_token' === $error_code ) { |
| 175 | return 'invalid_token'; |
| 176 | } |
| 177 | |
| 178 | $data = $error->get_error_data(); |
| 179 | $status = is_array( $data ) ? (int) ( $data['status'] ?? 0 ) : 0; |
| 180 | |
| 181 | if ( 403 === $status ) { |
| 182 | $message = strtolower( $error->get_error_message() ); |
| 183 | if ( |
| 184 | false !== strpos( $message, 'no license' ) |
| 185 | || false !== strpos( $message, 'any licenses' ) |
| 186 | ) { |
| 187 | return 'no_licenses'; |
| 188 | } |
| 189 | |
| 190 | return 'invalid_token'; |
| 191 | } |
| 192 | |
| 193 | return 'network'; |
| 194 | } |
| 195 | |
| 196 | /** |
| 197 | * Redirect to license screen with exchange error query arg. |
| 198 | * |
| 199 | * @param string $code Error code slug. |
| 200 | * @return void |
| 201 | */ |
| 202 | private function redirect_with_error( string $code ): void { |
| 203 | wp_safe_redirect( |
| 204 | $this->license_admin_url( [ 'advads_exchange_error' => $code ] ) |
| 205 | ); |
| 206 | exit; |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * Redirect to license screen when shop activation failed during save. |
| 211 | * |
| 212 | * @param WP_Error $error Save or sync activation error. |
| 213 | * @return void |
| 214 | */ |
| 215 | private function redirect_with_activation_error( WP_Error $error ): void { |
| 216 | $data = $error->get_error_data(); |
| 217 | $status = is_array( $data ) ? (int) ( $data['status'] ?? 0 ) : 0; |
| 218 | $code = 'activate_failed'; |
| 219 | |
| 220 | if ( 0 === $status && in_array( $error->get_error_code(), [ 'http_request_failed' ], true ) ) { |
| 221 | $code = 'network'; |
| 222 | } |
| 223 | |
| 224 | $args = [ 'advads_activation_error' => $code ]; |
| 225 | $message = sanitize_text_field( $error->get_error_message() ); |
| 226 | if ( '' !== $message ) { |
| 227 | $args['advads_activation_message'] = substr( $message, 0, 200 ); |
| 228 | } |
| 229 | |
| 230 | wp_safe_redirect( $this->license_admin_url( $args ) ); |
| 231 | exit; |
| 232 | } |
| 233 | |
| 234 | /** |
| 235 | * License admin screen URL with optional query args. |
| 236 | * |
| 237 | * @param array<string, int|string> $query_args Query arguments. |
| 238 | * @return string |
| 239 | */ |
| 240 | private function license_admin_url( array $query_args = [] ): string { |
| 241 | return License_Utils::admin_screen_url( $query_args ); |
| 242 | } |
| 243 | } |
| 244 |