rspal = $rspal; $this->api = $api; $this->env = $env; $this->onboarding = $onboarding; $this->oauth = $oauth; $this->dashboard = $dashboard; } /** * Creates a new Metricool account and authenticate the MetricoolClient. * @throws CreateAccountException */ public function createAccount(string $captcha, string $email, string $password, bool $newsletters): bool { $globalError = sprintf( /* translators: %1$s is opening link and %2$s is closing link */ __('Something went wrong. Please try again or %1$sleave a support message%2$s.', 'metricool'), '', '', ); // First, check if the password is valid if (!$this->isValidPassword($password)) { throw new CreateAccountException(esc_html__('Password is not valid.', 'metricool'), 'Password is not valid', 422); } // Attempt to create the account try { $signupResponse = $this->rspal->signUp([ 'username' => $email, 'newsletters' => $newsletters, ], [ 'RSPAL-RecaptchaV3Token' => $captcha ]); } catch (Throwable $e) { throw new CreateAccountException(wp_kses_post($globalError), esc_html($e->getMessage()), 500); } // A 400 response means e-mail exists if ($signupResponse->getStatusCode() == 400) { throw new CreateAccountException(wp_kses_post($globalError), 'Email or password error', 422); } // Check if the response contains the required fields if (empty($signupResponse->data->accessToken) || empty($signupResponse->data->refreshToken)) { throw new CreateAccountException(wp_kses_post($globalError), 'Signup response is missing required fields', 500); } // Parse the user ID from the access token $userId = $this->oauth->parseUserIdFromAccessToken($signupResponse->data->accessToken); if (empty($userId)) { throw new CreateAccountException(wp_kses_post($globalError), 'Failed to parse user ID from access token', 500); } // Authenticate the Metricool API Client $this->api->authenticate( $userId, (string) $signupResponse->data->accessToken, (string) $signupResponse->data->refreshToken, $signupResponse->data->expires ?? 300 ); // Update the user's password try { $this->api->userCredentials() ->updatePassword('', $password); } catch (Throwable $e) { $this->api->logout(); throw new CreateAccountException(wp_kses_post($globalError), esc_html($e->getMessage()), 500); } // Attempt to automatically set the blog information, complete the onboarding process on success try { $this->onboarding->finalizeOnboarding($this->findConnectedBrandId()); } catch (Throwable $e) { throw new CreateAccountException(wp_kses_post($globalError), esc_html($e->getMessage()), 500); } $this->dashboard->setShowWelcomeScreen(); return true; } /** * Get the blogId from the API. This is needed to connect the brand and complete the onboarding process. * @throws GuzzleException */ private function findConnectedBrandId(): ?string { $brands = $this->api->brands()->all(); // Get the brand when there is only one, abort if there are more $brand = (count($brands) === 1 ? (array) $brands[0] : []); return $brand['id'] ?? null; } /** * 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 */ protected function isValidPassword(string $password): bool { return (bool) preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]).{8,20}$/', $password); } }