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/Metricool.php +103 -22 3.1.5 → 3.2.2 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 }