PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
← All changes | app/Shared/Services/PluginsActivation/SimplyBook.php +55 -63 3.1.1 → 3.2.2 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 }