PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
← All changes | app/Features/Onboarding/Services/CreateAccountService.php +128 -0 2.0.1 → 2.1.1 View file →
@@ -1,0 +1,128 @@
1 +<?php
2 +
3 +namespace Metricool\Features\Onboarding\Services;
4 +
5 +use Metricool\Exceptions\RestDataException;
6 +use Metricool\Features\Onboarding\Exceptions\CreateAccountException;
7 +use Metricool\Features\Onboarding\OnboardingService;
8 +use Metricool\Http\Metricool\Exceptions\ApiException;
9 +use Metricool\Http\Metricool\MetricoolApi;
10 +use Metricool\Http\RSPAL\RspalApiClient;
11 +use Metricool\Services\DashboardService;
12 +use Metricool\Support\Helpers\Storages\EnvironmentConfig;
13 +use Throwable;
14 +
15 +class CreateAccountService
16 +{
17 + private RspalApiClient $rspal;
18 + private MetricoolApi $api;
19 + private EnvironmentConfig $env;
20 + private OnboardingService $onboarding;
21 + private OAuthService $oauth;
22 + private DashboardService $dashboard;
23 +
24 + public function __construct(
25 + RspalApiClient $rspal,
26 + MetricoolApi $api,
27 + EnvironmentConfig $env,
28 + OnboardingService $onboarding,
29 + OAuthService $oauth,
30 + DashboardService $dashboard
31 + ) {
32 + $this->rspal = $rspal;
33 + $this->api = $api;
34 + $this->env = $env;
35 + $this->onboarding = $onboarding;
36 + $this->oauth = $oauth;
37 + $this->dashboard = $dashboard;
38 + }
39 +
40 + /**
41 + * Creates a new Metricool account and authenticate the MetricoolClient.
42 + * @throws CreateAccountException
43 + */
44 + public function createAccount(string $captcha, string $email, string $password, bool $newsletters): bool
45 + {
46 + $globalError = sprintf(
47 + /* translators: %1$s is opening link and %2$s is closing link */
48 + __('Something went wrong. Please try again or %1$sleave a support message%2$s.', 'metricool'),
49 + '<a href="' . $this->env->get('frontend.trusted_urls.new_support_ticket') . '" target="_blank">',
50 + '</a>',
51 + );
52 +
53 + // First, check if the password is valid
54 + if (!$this->isValidPassword($password)) {
55 + throw new CreateAccountException(esc_html__('Password is not valid.', 'metricool'), 'Password is not valid', 422);
56 + }
57 +
58 + // Attempt to create the account
59 + try {
60 + $signupResponse = $this->rspal->signUp([
61 + 'username' => $email,
62 + 'newsletters' => $newsletters,
63 + ], [
64 + 'RSPAL-RecaptchaV3Token' => $captcha
65 + ]);
66 + } catch (RestDataException $e) {
67 + throw new CreateAccountException(wp_kses_post($globalError), esc_html($e->getMessage()), $e->getResponseCode());
68 + }
69 +
70 + // Parse the user ID from the access token
71 + $userId = $this->oauth->parseUserIdFromAccessToken($signupResponse->data->accessToken);
72 + if (empty($userId)) {
73 + throw new CreateAccountException(wp_kses_post($globalError), 'Failed to parse user ID from access token', 500);
74 + }
75 +
76 + // Authenticate the Metricool API Client
77 + $this->api->authenticate(
78 + $userId,
79 + (string) $signupResponse->data->accessToken,
80 + (string) $signupResponse->data->refreshToken,
81 + $signupResponse->data->expires ?? 300
82 + );
83 +
84 + // Update the user's password
85 + try {
86 + $this->api->userCredentials()
87 + ->updatePassword('', $password);
88 + } catch (ApiException $e) {
89 + $this->api->logout();
90 +
91 + throw new CreateAccountException(wp_kses_post($globalError), esc_html($e->getMessage()), 500);
92 + }
93 +
94 + // Attempt to automatically set the blog information, complete the onboarding process on success
95 + try {
96 + $this->onboarding->finalizeOnboarding($this->findConnectedBrandId());
97 + } catch (Throwable $e) {
98 + throw new CreateAccountException(wp_kses_post($globalError), esc_html($e->getMessage()), $e->getCode());
99 + }
100 +
101 + $this->dashboard->setShowWelcomeScreen();
102 +
103 + return true;
104 + }
105 +
106 +
107 + /**
108 + * Get the blogId from the API. This is needed to connect the brand and complete the onboarding process.
109 + * @throws ApiException
110 + */
111 + private function findConnectedBrandId(): ?string
112 + {
113 + $brands = $this->api->brands()->all();
114 +
115 + // Get the brand when there is only one, abort if there are more
116 + $brand = (count($brands) === 1 ? (array) $brands[0] : []);
117 +
118 + return $brand['id'] ?? null;
119 + }
120 +
121 + /**
122 + * Check if the password is: between 8 and 20 characters long, contains at least one uppercase letter, one lowercase letter, one number, and one special character
123 + */
124 + protected function isValidPassword(string $password): bool
125 + {
126 + return (bool) preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]).{8,20}$/', $password);
127 + }
128 +}