← All changes
|
app/Shared/Services/PluginsActivation/SimplyBook.php
+55
-63
3.1.1
→
trunk
View file →
| @@ -5,39 +5,45 @@ | ||
| 5 | 5 | defined('ABSPATH') || die('No direct access.'); |
| 6 | 6 | |
| 7 | 7 | class SimplyBook extends PluginActivation |
| 8 | 8 | { |
| 9 | + // Plugin requires PHP 7.0; constant visibility modifiers are PHP 7.1+. | |
| 10 | + // phpcs:ignore PSR12.Properties.ConstantVisibility.NotFound | |
| 11 | + const AWAITING_CALLBACK = 'extendify_simplybook_awaiting_callback'; | |
| 12 | + | |
| 9 | 13 | public static function slug(): string |
| 10 | 14 | { |
| 11 | 15 | return 'simplybook'; |
| 12 | 16 | } |
| 13 | 17 | |
| 14 | - protected static function requestHeaders(): array | |
| 18 | + protected static function simplybookNonce(): string | |
| 15 | 19 | { |
| 16 | - return ['Content-Type' => 'application/json', 'X-WP-Nonce' => \wp_create_nonce('wp_rest')]; | |
| 20 | + return \wp_create_nonce('simplybook_nonce'); | |
| 17 | 21 | } |
| 18 | 22 | |
| 19 | - protected static function simplybookNonce(): string | |
| 23 | + public static function scriptData(): array | |
| 20 | 24 | { |
| 21 | - return \wp_create_nonce('simplybook_nonce'); | |
| 25 | + // Only their React bundle carries the action, so it can't be read alongside the key. | |
| 26 | + return [ | |
| 27 | + 'recaptchaSiteKey' => static::recaptchaSiteKey(), | |
| 28 | + 'recaptchaAction' => 'create_company', | |
| 29 | + ]; | |
| 22 | 30 | } |
| 23 | 31 | |
| 24 | - protected static function sslVerify(): bool | |
| 32 | + public static function isEligible(): bool | |
| 25 | 33 | { |
| 26 | - return !(defined('EXTENDIFY_DEVMODE') && \constant('EXTENDIFY_DEVMODE')); | |
| 34 | + // simplybook_onboarding_completed is set before the account exists, so it would hide eligible sites. | |
| 35 | + return empty(\get_option('simplybook_token_admin')); | |
| 27 | 36 | } |
| 28 | 37 | |
| 29 | - protected static function authCookies(): array | |
| 38 | + // SimplyBook assesses the captcha itself, so a token minted with any other site key fails. | |
| 39 | + protected static function recaptchaSiteKey(): string | |
| 30 | 40 | { |
| 31 | - $cookies = []; | |
| 32 | - foreach ([\AUTH_COOKIE, \SECURE_AUTH_COOKIE, \LOGGED_IN_COOKIE] as $cookieName) { | |
| 33 | - if (isset($_COOKIE[$cookieName])) { | |
| 34 | - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput | |
| 35 | - $cookieValue = \wp_unslash($_COOKIE[$cookieName]); | |
| 36 | - $cookies[] = new \WP_Http_Cookie(['name' => $cookieName, 'value' => $cookieValue]); | |
| 37 | - } | |
| 38 | - } | |
| 39 | - return $cookies; | |
| 41 | + $config = WP_PLUGIN_DIR . '/' . static::slug() . '/config/env.php'; | |
| 42 | + $env = is_readable($config) ? require $config : []; | |
| 43 | + $key = $env['simplybook']['recaptcha']['site_key'] ?? ''; | |
| 44 | + | |
| 45 | + return is_string($key) ? $key : ''; | |
| 40 | 46 | } |
| 41 | 47 | |
| 42 | 48 | public static function createAccount(\WP_REST_Request $request): \WP_REST_Response |
| 43 | 49 | { |
| @@ -44,65 +50,51 @@ | ||
| 44 | 50 | if (!static::isActive()) { |
| 45 | 51 | return static::pluginNotActiveResponse(); |
| 46 | 52 | } |
| 47 | 53 | |
| 48 | - $nonce = static::simplybookNonce(); | |
| 49 | - $headers = static::requestHeaders(); | |
| 50 | - $sslVerify = static::sslVerify(); | |
| 51 | - | |
| 52 | 54 | // Reset onboarding data to have a fresh start |
| 53 | 55 | delete_option('simplybook_onboarding_completed'); |
| 54 | - $retryResponse = \wp_remote_post(\rest_url('simplybook/v1/onboarding/retry_onboarding'), [ | |
| 55 | - 'headers' => $headers, | |
| 56 | - 'cookies' => static::authCookies(), | |
| 57 | - 'sslverify' => $sslVerify, | |
| 58 | - 'body' => \wp_json_encode(['nonce' => $nonce]), | |
| 59 | - ]); | |
| 56 | + static::dispatchOnboarding('retry_onboarding'); | |
| 60 | 57 | |
| 61 | - if (\is_wp_error($retryResponse)) { | |
| 62 | - return new \WP_REST_Response(['message' => $retryResponse->get_error_message()], 500); | |
| 63 | - } | |
| 58 | + // Matches the callback URL lifetime they mint; nothing arrives later. | |
| 59 | + \set_transient(self::AWAITING_CALLBACK, true, 10 * MINUTE_IN_SECONDS); | |
| 64 | 60 | |
| 65 | - $createResponse = \wp_remote_post(\rest_url('simplybook/v1/onboarding/create_account'), [ | |
| 66 | - 'headers' => $headers, | |
| 67 | - 'cookies' => static::authCookies(), | |
| 68 | - 'sslverify' => $sslVerify, | |
| 69 | - 'timeout' => 10, | |
| 70 | - 'body' => \wp_json_encode([ | |
| 71 | - 'email' => \sanitize_email($request->get_param('email')), | |
| 72 | - 'terms-and-conditions' => (bool) $request->get_param('termsAgreed'), | |
| 73 | - 'marketing-consent' => (bool) $request->get_param('marketingConsent'), | |
| 74 | - 'captcha_token' => \sanitize_text_field($request->get_param('captcha_token')), | |
| 75 | - 'nonce' => $nonce, | |
| 76 | - ]), | |
| 61 | + $create = static::dispatchOnboarding('create_account', [ | |
| 62 | + 'email' => \sanitize_email($request->get_param('email')), | |
| 63 | + 'terms-and-conditions' => (bool) $request->get_param('termsAgreed'), | |
| 64 | + 'marketing-consent' => (bool) $request->get_param('marketingConsent'), | |
| 65 | + 'captcha_token' => \sanitize_text_field($request->get_param('captcha_token')), | |
| 77 | 66 | ]); |
| 78 | - | |
| 79 | - if (\is_wp_error($createResponse)) { | |
| 80 | - return new \WP_REST_Response(['message' => $createResponse->get_error_message()], 500); | |
| 67 | + if ($create->is_error()) { | |
| 68 | + \delete_transient(self::AWAITING_CALLBACK); | |
| 69 | + return $create; | |
| 81 | 70 | } |
| 82 | 71 | |
| 83 | - $createStatus = \wp_remote_retrieve_response_code($createResponse); | |
| 84 | - if ($createStatus >= 400) { | |
| 85 | - $data = json_decode(\wp_remote_retrieve_body($createResponse), true) ?? []; | |
| 86 | - return new \WP_REST_Response($data, $createStatus); | |
| 87 | - } | |
| 72 | + return new \WP_REST_Response(['success' => true], 200); | |
| 73 | + } | |
| 88 | 74 | |
| 89 | - $finishResponse = \wp_remote_post(\rest_url('simplybook/v1/onboarding/finish_onboarding'), [ | |
| 90 | - 'headers' => $headers, | |
| 91 | - 'cookies' => static::authCookies(), | |
| 92 | - 'sslverify' => $sslVerify, | |
| 93 | - 'body' => \wp_json_encode(['nonce' => $nonce]), | |
| 94 | - ]); | |
| 75 | + // Marking onboarding complete unregisters the route their token-saving callback arrives on. | |
| 76 | + public static function register() | |
| 77 | + { | |
| 78 | + \add_action('simplybook_after_company_registered', [self::class, 'finishOnboarding'], 10, 0); | |
| 79 | + } | |
| 95 | 80 | |
| 96 | - if (\is_wp_error($finishResponse)) { | |
| 97 | - return new \WP_REST_Response(['message' => $finishResponse->get_error_message()], 500); | |
| 81 | + public static function finishOnboarding() | |
| 82 | + { | |
| 83 | + // Their own wizard finishes this itself, at the end of its step 2. | |
| 84 | + if (!\get_transient(self::AWAITING_CALLBACK)) { | |
| 85 | + return; | |
| 98 | 86 | } |
| 99 | 87 | |
| 100 | - $finishStatus = \wp_remote_retrieve_response_code($finishResponse); | |
| 101 | - if ($finishStatus >= 400) { | |
| 102 | - $data = json_decode(\wp_remote_retrieve_body($finishResponse), true) ?? []; | |
| 103 | - return new \WP_REST_Response($data, $finishStatus); | |
| 104 | - } | |
| 88 | + \delete_transient(self::AWAITING_CALLBACK); | |
| 89 | + static::dispatchOnboarding('finish_onboarding'); | |
| 90 | + } | |
| 105 | 91 | |
| 106 | - return new \WP_REST_Response(['success' => true], 200); | |
| 92 | + protected static function dispatchOnboarding(string $action, array $body = []): \WP_REST_Response | |
| 93 | + { | |
| 94 | + $request = new \WP_REST_Request('POST', '/simplybook/v1/onboarding/' . $action); | |
| 95 | + $request->set_header('Content-Type', 'application/json'); | |
| 96 | + $request->set_body(\wp_json_encode(array_merge($body, ['nonce' => static::simplybookNonce()]))); | |
| 97 | + | |
| 98 | + return \rest_do_request($request); | |
| 107 | 99 | } |
| 108 | 100 | } |