Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
StravaUtils
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 7
420
0.00% covered (danger)
0.00%
0 / 1
 getClientId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getClientSecret
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRegistrationUrl
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 linkStrava
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
72
 getAccessToken
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 fetchTokenDataForCode
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 callStravaApi
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace Olz\Utils;
4
5use Olz\Entity\StravaLink;
6use PhpTypeScriptApi\HttpError;
7
8class StravaUtils {
9    use WithUtilsTrait;
10
11    public function getClientId(): string {
12        return $this->envUtils()->getStravaClientId();
13    }
14
15    protected function getClientSecret(): string {
16        return $this->envUtils()->getStravaClientSecret();
17    }
18
19    /** @param array<string> $scopes */
20    public function getRegistrationUrl(
21        array $scopes = ['read'],
22        ?string $redirect_url = null,
23    ): string {
24        $client_id = $this->getClientId();
25        $base_href = $this->envUtils()->getBaseHref();
26        $code_href = $this->envUtils()->getCodeHref();
27        $redirect_url_suffix = $redirect_url ? "?redirect_url=".urlencode($redirect_url) : '';
28        $strava_redirect_url = "{$base_href}{$code_href}strava_redirect{$redirect_url_suffix}";
29        $enc_strava_redirect_url = urlencode($strava_redirect_url);
30        $enc_scopes = urlencode(implode(',', $scopes));
31        return "https://www.strava.com/oauth/authorize?client_id={$client_id}&response_type=code&redirect_uri={$enc_strava_redirect_url}&approval_prompt=force&scope={$enc_scopes}";
32    }
33
34    public function linkStrava(string $code): ?StravaLink {
35        $data = $this->fetchTokenDataForCode([
36            'client_id' => $this->getClientId(),
37            'client_secret' => $this->getClientSecret(),
38            'code' => $code,
39            'grant_type' => 'authorization_code',
40        ]);
41        $athlete_id = $data['athlete']['id'] ?? null;
42        $access_token = $data['access_token'] ?? null;
43        $refresh_token = $data['refresh_token'] ?? null;
44        $expires_at = $data['expires_at'] ?? null;
45        if ($athlete_id === null || $access_token === null || $refresh_token === null || $expires_at === null) {
46            return null;
47        }
48
49        $now = new \DateTime($this->dateUtils()->getIsoNow());
50        $user = $this->authUtils()->getCurrentUser();
51        if (!$user) {
52            throw new HttpError(401, 'Nicht eingeloggt!');
53        }
54
55        $strava_link_repo = $this->entityManager()->getRepository(StravaLink::class);
56        $strava_link = $strava_link_repo->findOneBy(['strava_user' => $athlete_id]);
57        if ($strava_link === null) {
58            $strava_link = new StravaLink();
59            $strava_link->setCreatedAt($now);
60        } else {
61            $previous_user_id = $strava_link->getUser()->getId();
62            $new_user_id = $user->getId();
63            if ($previous_user_id !== $new_user_id) {
64                $this->log()->notice("{$strava_link} changed user: {$previous_user_id} => {$new_user_id}");
65            }
66        }
67        $strava_link->setUser($user);
68        $strava_link->setAccessToken($access_token);
69        $strava_link->setRefreshToken($refresh_token);
70        $strava_link->setExpiresAt(new \DateTime(date('Y-m-d H:i:s', $expires_at)));
71        $strava_link->setStravaUser($athlete_id);
72        $strava_link->setLinkedAt($now);
73
74        $this->entityManager()->persist($strava_link);
75        $this->entityManager()->flush();
76        return $strava_link;
77    }
78
79    public function getAccessToken(StravaLink $strava_link): ?string {
80        $now_iso = $this->dateUtils()->getIsoNow();
81        $expires_at_iso = $strava_link->getExpiresAt()->format('Y-m-d H:i:s');
82        $access_token = $strava_link->getAccessToken();
83        if ($access_token && $expires_at_iso > $now_iso) {
84            return $access_token;
85        }
86        $refresh_token = $strava_link->getRefreshToken();
87
88        $data = $this->fetchTokenDataForCode([
89            'client_id' => $this->getClientId(),
90            'client_secret' => $this->getClientSecret(),
91            'refresh_token' => $refresh_token,
92            'grant_type' => 'refresh_token',
93        ]);
94        $access_token = $data['access_token'] ?? null;
95        $refresh_token = $data['refresh_token'] ?? null;
96        $expires_at = $data['expires_at'] ?? null;
97
98        $strava_link->setAccessToken($access_token);
99        $strava_link->setRefreshToken($refresh_token);
100        $strava_link->setExpiresAt(new \DateTime(date('Y-m-d H:i:s', $expires_at)));
101
102        return $access_token;
103    }
104
105    /**
106     * @param array<string, mixed> $token_request_data
107     *
108     * @return ?array<string, mixed>
109     */
110    public function fetchTokenDataForCode(array $token_request_data): ?array {
111        $strava_token_url = 'https://www.strava.com/api/v3/oauth/token';
112
113        $ch = curl_init();
114        curl_setopt($ch, CURLOPT_URL, $strava_token_url);
115        curl_setopt($ch, CURLOPT_POST, true);
116        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($token_request_data, '', '&'));
117        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
118        $token_result = curl_exec($ch);
119        return json_decode(!is_bool($token_result) ? $token_result : '', true);
120    }
121
122    /**
123     * @param 'GET'|'POST'          $method
124     * @param array<string, string> $query
125     */
126    public function callStravaApi(
127        string $method,
128        string $path,
129        array $query,
130        string $access_token,
131    ): mixed {
132        $strava_url = 'https://www.strava.com/api/v3';
133        $query_string = http_build_query($query, '', '&');
134
135        $ch = curl_init();
136        curl_setopt($ch, CURLOPT_URL, "{$strava_url}{$path}");
137        curl_setopt($ch, CURLOPT_HTTPHEADER, [
138            "Authorization: Bearer {$access_token}",
139            "Content-Type: application/x-www-form-urlencoded",
140        ]);
141        curl_setopt($ch, CURLOPT_HEADER, false);
142        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
143        curl_setopt($ch, CURLOPT_POST, $method === 'POST');
144        if ($query_string) {
145            curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string);
146        }
147        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
148        $result = curl_exec($ch);
149        return json_decode(!is_bool($result) ? $result : '', true);
150    }
151}