← All changes
|
app/Shared/Services/PluginsActivation/Metricool.php
+103
-22
3.1.5
→
trunk
View file →
| @@ -24,8 +24,13 @@ | ||
| 24 | 24 | 'recaptchaAction' => 'signup', |
| 25 | 25 | ]; |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | + public static function isEligible(): bool | |
| 29 | + { | |
| 30 | + return empty(\get_option('metricool_auth_token')); | |
| 31 | + } | |
| 32 | + | |
| 28 | 33 | // Metricool assesses the captcha itself, so a token minted with any other site key fails. |
| 29 | 34 | protected static function recaptchaSiteKey(): string |
| 30 | 35 | { |
| 31 | 36 | $config = WP_PLUGIN_DIR . '/' . static::slug() . '/config/env.php'; |
| @@ -40,9 +45,20 @@ | ||
| 40 | 45 | if (!static::isActive()) { |
| 41 | 46 | return static::pluginNotActiveResponse(); |
| 42 | 47 | } |
| 43 | 48 | |
| 44 | - $create = static::dispatch('onboarding/create_account', [ | |
| 49 | + $steps = []; | |
| 50 | + $response = static::signUp($steps, $request); | |
| 51 | + | |
| 52 | + return new \WP_REST_Response( | |
| 53 | + array_merge((array) $response->get_data(), ['stepTimeInMs' => $steps]), | |
| 54 | + $response->get_status() | |
| 55 | + ); | |
| 56 | + } | |
| 57 | + | |
| 58 | + protected static function signUp(array &$steps, \WP_REST_Request $request): \WP_REST_Response | |
| 59 | + { | |
| 60 | + $create = static::dispatch($steps, 'onboarding/create_account', [ | |
| 45 | 61 | 'email' => \sanitize_email($request->get_param('email')), |
| 46 | 62 | 'terms' => (bool) $request->get_param('termsAgreed'), |
| 47 | 63 | 'marketing' => (bool) $request->get_param('marketingConsent'), |
| 48 | 64 | 'captcha' => \sanitize_text_field($request->get_param('captcha_token')), |
| @@ -48,28 +64,71 @@ | ||
| 48 | 64 | 'captcha' => \sanitize_text_field($request->get_param('captcha_token')), |
| 49 | 65 | 'password' => static::generatePassword(), |
| 50 | 66 | ]); |
| 51 | 67 | if ($create->is_error()) { |
| 52 | - // Metricool 500 = account created but a later step failed; not retryable. | |
| 53 | - if ($create->get_status() >= 500) { | |
| 54 | - static::dispatch('logout'); | |
| 55 | - } | |
| 56 | - return $create; | |
| 68 | + return static::withFallbackCode($create, 'metricool_create_account_failed'); | |
| 57 | 69 | } |
| 58 | 70 | |
| 59 | - $finish = static::dispatch('onboarding/finish_onboarding'); | |
| 60 | - $completed = $finish->get_data()['data']['onboarding']['state']['completed'] ?? false; | |
| 61 | - if ($finish->is_error() || !$completed) { | |
| 62 | - static::dispatch('logout'); | |
| 63 | - return $finish->is_error() ? $finish : new \WP_REST_Response([ | |
| 64 | - 'code' => 'metricool_onboarding_incomplete', | |
| 65 | - 'message' => \__('Metricool onboarding did not complete.', 'extendify-local'), | |
| 66 | - ], 500); | |
| 71 | + $finish = static::dispatch($steps, 'onboarding/finish_onboarding'); | |
| 72 | + if ($finish->is_error()) { | |
| 73 | + return static::withFallbackCode($finish, 'metricool_finish_onboarding_failed'); | |
| 67 | 74 | } |
| 68 | 75 | |
| 76 | + if (static::isComplete($finish)) { | |
| 77 | + return new \WP_REST_Response(['success' => true], 200); | |
| 78 | + } | |
| 79 | + | |
| 80 | + return static::connectTheOnlyBrand($steps, $finish); | |
| 81 | + } | |
| 82 | + | |
| 83 | + // Their onboarding stops at a brand picker no user will reach here. | |
| 84 | + protected static function connectTheOnlyBrand(array &$steps, \WP_REST_Response $finish): \WP_REST_Response | |
| 85 | + { | |
| 86 | + $response = static::dispatch($steps, 'connected_brands', [], 'GET'); | |
| 87 | + $brands = $response->is_error() ? [] : (array) ($response->get_data()['data'] ?? []); | |
| 88 | + $brandCount = count($brands); | |
| 89 | + | |
| 90 | + // Their own signup connects a brand only when the account owns exactly one. | |
| 91 | + $brand = $brandCount === 1 ? (array) reset($brands) : []; | |
| 92 | + $blogId = (string) ($brand['id'] ?? ''); | |
| 93 | + if ($blogId === '') { | |
| 94 | + return static::brandNotConnected($finish, $brandCount); | |
| 95 | + } | |
| 96 | + | |
| 97 | + $retry = static::dispatch($steps, 'onboarding/finish_onboarding', ['blogId' => $blogId]); | |
| 98 | + if ($retry->is_error() || !static::isComplete($retry)) { | |
| 99 | + return static::brandNotConnected($finish, $brandCount); | |
| 100 | + } | |
| 101 | + | |
| 69 | 102 | return new \WP_REST_Response(['success' => true], 200); |
| 70 | 103 | } |
| 71 | 104 | |
| 105 | + protected static function brandNotConnected(\WP_REST_Response $finish, int $brandCount): \WP_REST_Response | |
| 106 | + { | |
| 107 | + $state = static::onboardingState($finish); | |
| 108 | + | |
| 109 | + return new \WP_REST_Response([ | |
| 110 | + 'code' => 'metricool_brand_not_connected', | |
| 111 | + 'message' => \__('Metricool could not connect a brand to this site.', 'extendify-local'), | |
| 112 | + // authenticated means the account exists and only the brand connection failed. | |
| 113 | + 'data' => [ | |
| 114 | + 'authenticated' => (bool) ($state['authenticated'] ?? false), | |
| 115 | + 'blog_id_selected' => (bool) ($state['blog_id_selected'] ?? false), | |
| 116 | + 'brand_count' => $brandCount, | |
| 117 | + ], | |
| 118 | + ], 500); | |
| 119 | + } | |
| 120 | + | |
| 121 | + protected static function isComplete(\WP_REST_Response $response): bool | |
| 122 | + { | |
| 123 | + return !empty(static::onboardingState($response)['completed']); | |
| 124 | + } | |
| 125 | + | |
| 126 | + protected static function onboardingState(\WP_REST_Response $response): array | |
| 127 | + { | |
| 128 | + return (array) ($response->get_data()['data']['onboarding']['state'] ?? []); | |
| 129 | + } | |
| 130 | + | |
| 72 | 131 | protected static function generatePassword(): string |
| 73 | 132 | { |
| 74 | 133 | // wp_generate_password() draws uniformly — a class Metricool requires can be missing. |
| 75 | 134 | do { |
| @@ -78,16 +137,38 @@ | ||
| 78 | 137 | |
| 79 | 138 | return $password; |
| 80 | 139 | } |
| 81 | 140 | |
| 82 | - protected static function dispatch(string $route, array $body = []): \WP_REST_Response | |
| 141 | + protected static function dispatch( | |
| 142 | + array &$steps, | |
| 143 | + string $route, | |
| 144 | + array $body = [], | |
| 145 | + string $method = 'POST' | |
| 146 | + ): \WP_REST_Response { | |
| 147 | + $request = new \WP_REST_Request($method, '/metricool/v1/' . $route); | |
| 148 | + // Their nonce middleware only enforces on the verbs that write. | |
| 149 | + if ($method !== 'GET') { | |
| 150 | + $request->set_header('Content-Type', 'application/json'); | |
| 151 | + $request->set_body(\wp_json_encode(array_merge( | |
| 152 | + $body, | |
| 153 | + ['nonce' => \wp_create_nonce('metricool_nonce')] | |
| 154 | + ))); | |
| 155 | + } | |
| 156 | + | |
| 157 | + $start = microtime(true); | |
| 158 | + $response = \rest_do_request($request); | |
| 159 | + $steps[static::stepKey($steps, $route)] = (int) round((microtime(true) - $start) * 1000); | |
| 160 | + | |
| 161 | + return $response; | |
| 162 | + } | |
| 163 | + | |
| 164 | + // finish_onboarding can run twice; a plain key would drop the first timing. | |
| 165 | + protected static function stepKey(array $steps, string $route): string | |
| 83 | 166 | { |
| 84 | - $request = new \WP_REST_Request('POST', '/metricool/v1/' . $route); | |
| 85 | - $request->set_header('Content-Type', 'application/json'); | |
| 86 | - $request->set_body(\wp_json_encode(array_merge( | |
| 87 | - $body, | |
| 88 | - ['nonce' => \wp_create_nonce('metricool_nonce')] | |
| 89 | - ))); | |
| 167 | + $key = basename($route); | |
| 168 | + for ($n = 2; isset($steps[$key]); $n++) { | |
| 169 | + $key = basename($route) . '_' . $n; | |
| 170 | + } | |
| 90 | 171 | |
| 91 | - return \rest_do_request($request); | |
| 172 | + return $key; | |
| 92 | 173 | } |
| 93 | 174 | } |