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/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 }