PluginProbe
Metricool – Social media and site statistics / trunk
Metricool – Social media and site statistics vtrunk
2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
metricool / app / Features / Onboarding / OnboardingController.php

OnboardingController.php in Metricool – Social media and site statistics trunk, at app/Features/Onboarding/OnboardingController.php

211 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Metricool\Features\Onboarding;
6
7 if (!defined('ABSPATH')) {
8 exit;
9 }
10
11 use Metricool\Support\Helpers\Storages\RequestStorage;
12 use Metricool\Traits\HasRestAccess;
13 use Metricool\Services\DashboardService;
14 use Metricool\Support\Validation\Validator;
15 use Metricool\Interfaces\FeatureInterface;
16 use Metricool\Services\MetricoolAccountService;
17 use Metricool\Features\Onboarding\Services\OAuthService;
18 use Metricool\Support\Helpers\Storages\EnvironmentConfig;
19 use Metricool\Features\Onboarding\Services\CreateAccountService;
20 use Metricool\Features\Onboarding\Exceptions\CreateAccountException;
21 use Metricool\Features\Onboarding\Exceptions\BrandAccessDeniedException;
22 use Throwable;
23
24 class OnboardingController implements FeatureInterface
25 {
26 use HasRestAccess;
27
28 private OnboardingService $onboarding;
29 private EnvironmentConfig $env;
30 private CreateAccountService $accounts;
31 private OAuthService $oauth;
32 private DashboardService $dashboard;
33 private MetricoolAccountService $account;
34 private RequestStorage $request;
35
36 public function __construct(
37 OnboardingService $onboarding,
38 CreateAccountService $accounts,
39 EnvironmentConfig $env,
40 OAuthService $oauth,
41 DashboardService $dashboard,
42 MetricoolAccountService $account,
43 RequestStorage $request
44 ) {
45 $this->onboarding = $onboarding;
46 $this->accounts = $accounts;
47 $this->env = $env;
48 $this->oauth = $oauth;
49 $this->dashboard = $dashboard;
50 $this->account = $account;
51 $this->request = $request;
52 }
53
54 public function register(): void
55 {
56 add_filter('metricool_rest_routes', [$this, 'addRoutes']);
57
58 // Intercept OAuth callback on the dashboard page
59 add_action('load-toplevel_page_metricool', [$this, 'oauthCallback']);
60 }
61
62 /**
63 * Add onboarding routes to the existing routes of our plugin
64 */
65 public function addRoutes(array $routes): array
66 {
67 $routes['onboarding/create_account'] = [
68 'methods' => 'POST',
69 'callback' => [$this, 'createAccount'],
70 ];
71
72 $routes['onboarding/finish_onboarding'] = [
73 'methods' => 'POST',
74 'callback' => [$this, 'finishOnboarding'],
75 ];
76
77 $routes['onboarding/oauth_redirect'] = [
78 'methods' => 'GET',
79 'callback' => [$this, 'getOauthRedirectUrl'],
80 ];
81
82 return $routes;
83 }
84
85 /**
86 * Create a new Metricool account. The created user is authenticated
87 * automatically.
88 */
89 public function createAccount(\WP_REST_Request $request): \WP_REST_Response
90 {
91 $validated = Validator::validate($request->get_params(), [
92 'email' => 'required|email',
93 'password' => 'required|string',
94 'captcha' => 'required|string',
95 'terms' => 'accepted',
96 'marketing' => 'boolean',
97 ]);
98
99 // Attempt to create the account
100 try {
101 $this->accounts->createAccount(
102 $validated['captcha'],
103 $validated['email'],
104 $validated['password'],
105 rest_sanitize_boolean($validated['marketing'] ?? false)
106 );
107 } catch (CreateAccountException $e) {
108 return $this->sendHttpErrorResponse($e->getMessage(), ['reason' => $e->getReason()], $e->getCode());
109 }
110
111 return $this->onboardedResponse();
112 }
113
114 /**
115 * Method is used to finish the onboarding process. It is called when the
116 * user has completed the onboarding process and wants to finish it.
117 */
118 public function finishOnboarding(\WP_REST_Request $request): \WP_REST_Response
119 {
120 $blogId = (string) $request->get_param('blogId');
121
122 if (empty($blogId)) {
123 return $this->onboardedResponse();
124 }
125
126 // Store the blogId if it was provided by the client, to retrieve the necessary blog information
127 try {
128 $this->onboarding->finalizeOnboarding($blogId);
129 } catch (BrandAccessDeniedException $e) {
130 return $this->sendHttpErrorResponse(
131 __('You don’t have sufficient permissions to connect this brand. Please pick another brand or sign in with a different account.', 'metricool'),
132 [],
133 403
134 );
135 } catch (Throwable $e) {
136 return $this->sendHttpErrorResponse(wp_kses_post(sprintf(
137 /* translators: %1$s is opening link and %2$s is closing link */
138 __('Something went wrong. Please try again or %1$sleave a support message%2$s.', 'metricool'),
139 '<a href="' . $this->env->get('frontend.trusted_urls.new_support_ticket') . '" target="_blank">',
140 '</a>',
141 )), ['data' => $e->getMessage()], $e->getCode() ?: 500);
142 }
143
144 return $this->onboardedResponse();
145 }
146
147 /**
148 * Build and return the Metricool OAuth authorize URL.
149 */
150 public function getOauthRedirectUrl(\WP_REST_Request $request): \WP_REST_Response
151 {
152 if (is_ssl() === false) {
153 return $this->sendHttpErrorResponse(__('HTTPS is required to be able to authorize this website.', 'metricool'), [], 400);
154 }
155
156 return $this->sendHttpResponse([
157 'redirect_url' => $this->oauth->getAuthorizationUrl(),
158 ]);
159 }
160
161 /**
162 * Handle the OAuth callback from Metricool. Exchanges the authorization
163 * code for tokens, authenticates the user, and redirects to the dashboard.
164 */
165 public function oauthCallback(): void
166 {
167 $action = $this->request->getString('global.metricool_action');
168
169 if ($action !== OAuthService::REDIRECT_ACTION) {
170 return;
171 }
172
173 $code = $this->request->getString('global.code');
174 $state = $this->request->getString('global.state');
175
176 try {
177 $this->oauth->authenticateWithCode($code, $state);
178 } catch (Throwable $e) {
179 wp_safe_redirect(add_query_arg('oauth_error', $e->getMessage(), $this->env->getString('plugin.dashboard_url')));
180 exit;
181 }
182
183 // Attempt to automatically set the blog information, completes the onboarding process on success
184 try {
185 $this->onboarding->finalizeOnboarding();
186 } catch (Throwable $e) {
187 wp_safe_redirect(add_query_arg('oauth_error', 'onboarding_failed', $this->env->getString('plugin.dashboard_url')));
188 exit;
189 }
190
191 // Redirect to the WordPress dashboard
192 wp_safe_redirect($this->env->getString('plugin.dashboard_url'));
193 exit;
194 }
195
196 /**
197 * Response that is returned when the onboarding process has been completed successfully.
198 * Includes the onboarding state and mode, as well as the account data of the user.
199 */
200 private function onboardedResponse(): \WP_REST_Response
201 {
202 return $this->sendHttpResponse([
203 'onboarding' => [
204 'state' => $this->dashboard->state(),
205 'mode' => $this->dashboard->mode(),
206 ],
207 'account' => $this->account->accountData(),
208 ]);
209 }
210 }
211