PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
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
extendify / app / Shared / Services / PluginsActivation / Metricool.php

Metricool.php in Extendify 3.2.1, at app/Shared/Services/PluginsActivation/Metricool.php

175 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Extendify\Shared\Services\PluginsActivation;
4
5 defined('ABSPATH') || die('No direct access.');
6
7 class Metricool extends PluginActivation
8 {
9 public static function slug(): string
10 {
11 return 'metricool';
12 }
13
14 public static function createAccountRoute(): string
15 {
16 // Metricool registers its logout route only when the request URL contains "/metricool/v".
17 return '/' . static::slug() . '/v1/create-account';
18 }
19
20 public static function scriptData(): array
21 {
22 return [
23 'recaptchaSiteKey' => static::recaptchaSiteKey(),
24 'recaptchaAction' => 'signup',
25 ];
26 }
27
28 public static function isEligible(): bool
29 {
30 return empty(\get_option('metricool_auth_token'));
31 }
32
33 // Metricool assesses the captcha itself, so a token minted with any other site key fails.
34 protected static function recaptchaSiteKey(): string
35 {
36 $config = WP_PLUGIN_DIR . '/' . static::slug() . '/config/env.php';
37 $env = is_readable($config) ? require $config : [];
38 $key = $env['metricool']['google_recaptcha_key'] ?? '';
39
40 return is_string($key) ? $key : '';
41 }
42
43 public static function createAccount(\WP_REST_Request $request): \WP_REST_Response
44 {
45 if (!static::isActive()) {
46 return static::pluginNotActiveResponse();
47 }
48
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', [
61 'email' => \sanitize_email($request->get_param('email')),
62 'terms' => (bool) $request->get_param('termsAgreed'),
63 'marketing' => (bool) $request->get_param('marketingConsent'),
64 'captcha' => \sanitize_text_field($request->get_param('captcha_token')),
65 'password' => static::generatePassword(),
66 ]);
67 if ($create->is_error()) {
68 return static::withFallbackCode($create, 'metricool_create_account_failed');
69 }
70
71 $finish = static::dispatch($steps, 'onboarding/finish_onboarding');
72 if ($finish->is_error()) {
73 return static::withFallbackCode($finish, 'metricool_finish_onboarding_failed');
74 }
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
102 return new \WP_REST_Response(['success' => true], 200);
103 }
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
131 protected static function generatePassword(): string
132 {
133 // wp_generate_password() draws uniformly — a class Metricool requires can be missing.
134 do {
135 $password = \wp_generate_password(20, true);
136 } while (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]).{8,20}$/', $password));
137
138 return $password;
139 }
140
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
166 {
167 $key = basename($route);
168 for ($n = 2; isset($steps[$key]); $n++) {
169 $key = basename($route) . '_' . $n;
170 }
171
172 return $key;
173 }
174 }
175