PluginProbe
Extendify / trunk
Extendify vtrunk
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 0.6.0 All 127 releases
← 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 }