├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── examples
└── oauth-flow.php
├── phpunit.xml
├── src
└── Iamstuartwilson
│ └── StravaApi.php
└── tests
└── StravaApiTest.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.idea/
3 | /composer.lock
4 |
5 | /vendor/
6 |
7 | .phpunit.result.cache
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | cache:
4 | directories:
5 | - vendor
6 | - $HOME/.composer/cache
7 |
8 | env:
9 | matrix:
10 | - DEPENDENCIES=latest
11 | - DEPENDENCIES=oldest
12 |
13 | install:
14 | - >
15 | echo;
16 | if [ "$DEPENDENCIES" = "latest" ]; then
17 | echo "Installing the latest dependencies";
18 | composer update --with-dependencies
19 | else
20 | echo "Installing the lowest dependencies";
21 | composer update --with-dependencies --prefer-lowest
22 | fi;
23 | composer show;
24 |
25 | php:
26 | - 5.6
27 | - 7.0
28 | - 7.1
29 | - 7.2
30 | - 7.3
31 |
32 | script:
33 | - >
34 | echo;
35 | echo "Validating the composer.json";
36 | composer validate --no-check-all --no-check-lock --strict;
37 |
38 | - >
39 | echo;
40 | echo "Linting all PHP files";
41 | composer ci:lint;
42 |
43 | - >
44 | echo;
45 | echo "Running the PHPUnit tests";
46 | composer ci:tests;
47 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to `iamstuartwilson/strava` will be documented in this file.
4 | Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
5 |
6 | ## [1.4.0] - 2019-09-16
7 |
8 | ### Added
9 |
10 | - Support for Strava's new oAuth2 flow (short-lived access tokens, refresh tokens).
11 | - Example file which demonstrates how the oAuth2 flow works.
12 |
13 | ### Changed
14 |
15 | - Unified Markdown style in README.
16 |
17 | ## [1.3.0] - 2017-03-02
18 |
19 | ### Added
20 |
21 | - Possibility to use absolute URL for an endpoint to work with new [webhook functionality](https://developers.strava.com/docs/webhooks/).
22 |
23 | ## [1.2.2] - 2016-10-26
24 |
25 | ### Added
26 |
27 | * MIT LICENSE file added to project root
28 |
29 | ## [1.2.1] - 2016-10-04
30 |
31 | ### Changed
32 |
33 | * `CURLOPT_FOLLOWLOCATION` is no longer set to `true`
34 |
35 | ## [1.2.0] - 2016-07-12
36 |
37 | ### Added
38 |
39 | * It's now possible to access the HTTP response headers with two added methods:
40 |
41 | - `getResponseHeaders()`
42 | - `getResponseHeader($header)`
43 |
44 | The first one returns all HTTP headers as an array while the second returns
45 | the header value for the given header name (and throws an exception if the
46 | header name does not exist).
47 |
48 | The existing public API of the StravaAPI class is unchanged, only two new
49 | public methods were introduced.
50 |
51 | ## [1.1.2] - 2016-05-28
52 |
53 | ### Fixed
54 |
55 | * The exception for a failed API request could not be thrown because of a missing
56 | namespace declaration.
57 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Stuart Wilson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/iamstuartwilson/strava)
2 | 
3 | 
4 | 
5 |
6 | # StravaApi
7 |
8 | The class simply houses methods to help send data to and receive data from the API. Please read the [API documentation](https://developers.strava.com/docs/reference/) to see what endpoints are available.
9 |
10 | *There is no file upload support at this time.*
11 |
12 | ## Installation
13 |
14 | ### With Composer
15 |
16 | ``` shell
17 | composer require iamstuartwilson/strava
18 | ```
19 |
20 | Or add it manually to your `composer.json`:
21 |
22 | ``` json
23 | {
24 | "require" : {
25 | "iamstuartwilson/strava" : "^1.4"
26 | }
27 | }
28 | ```
29 |
30 | ### Manually
31 |
32 | Copy `StravaApi.php` to your project and *require* it in your application as described in the next section.
33 |
34 | ## Getting Started
35 |
36 | Instantiate the class with your **client_id** and **client_secret** from your [registered app](https://www.strava.com/settings/api):
37 |
38 | ``` php
39 | require_once 'StravaApi.php';
40 |
41 | $api = new Iamstuartwilson\StravaApi(
42 | $clientId,
43 | $clientSecret
44 | );
45 | ```
46 |
47 | If you're just testing endpoints/methods you can skip the authentication flow and just use the access token from your [settings page](https://www.strava.com/settings/api).
48 |
49 | You will then need to [authenticate](https://developers.strava.com/docs/authentication/) your strava account by requesting an access code. You can generate a URL for authentication using the following method:
50 |
51 | ``` php
52 | $api->authenticationUrl($redirect, $approvalPrompt = 'auto', $scope = null, $state = null);
53 | ```
54 |
55 | When a code is returned you must then exchange it for an [access token and a refresh token](http://developers.strava.com/docs/authentication/#token-exchange) for the authenticated user:
56 |
57 | ``` php
58 | $result = $api->tokenExchange($code);
59 | ```
60 |
61 | The token exchange result contains among other data the tokens. You can access them as attributes of the result object:
62 |
63 | ```php
64 | $accessToken = $result->access_token;
65 | $refreshToken = $result->refresh_token;
66 | $expiresAt = $result->expires_at;
67 | ```
68 |
69 | Before making any requests you must set the access and refresh tokens as returned from your token exchange result or via your own private token from Strava:
70 |
71 | ``` php
72 | $api->setAccessToken($accessToken, $refreshToken, $expiresAt);
73 | ```
74 |
75 | ## Example oAuth2 Authentication Flow
76 |
77 | `examples/oauth-flow.php` demonstrates how the oAuth2 authentication flow works.
78 |
79 | 1. Choose how to load the `StravaApi.php` – either via Composer autoloader or by manually *requiring* it.
80 | 2. Replace the three config values `CALLBACK_URL`, `STRAVA_API_ID`, and `STRAVA_API_SECRET` at the top of the file
81 | 3. Place the file on your server so that it's accessible at `CALLBACK_URL`
82 | 4. Point your browser to `CALLBACK_URL` and start the authentication flow.
83 |
84 | The scripts prints a lot of verbose information so you get an idea on how the Strava oAuth flow works.
85 |
86 | ## Example Requests
87 |
88 | Once successfully authenticated you're able to communicate with Strava's API.
89 |
90 | All actions that change Strava contents (`post`, `put`, `delete`) will need the **scope** set to *write* in the authentication flow.
91 |
92 | ### Get Athlete Stats
93 |
94 | ``` php
95 | $api->get('athletes/:id/stats');
96 | ```
97 |
98 | ### List Athlete Activities
99 |
100 | Some API endpoints support GET parameters:
101 |
102 | ``` php
103 | $api->get(
104 | 'athlete/activities',
105 | [
106 | 'page' => 2,
107 | 'per_page' => 10,
108 | ]
109 | );
110 | ```
111 |
112 | ### Post a new activity
113 |
114 | ``` php
115 | $api->post(
116 | 'activities',
117 | [
118 | 'name' => 'API Test',
119 | 'type' => 'Ride',
120 | 'start_date_local' => date('Y-m-d\TH:i:s\Z'),
121 | 'elapsed_time' => 3600,
122 | ]
123 | );
124 | ```
125 |
126 | ### Update a athlete's weight
127 |
128 | ``` php
129 | $api->put('athlete', ['weight' => 70]);
130 | ```
131 |
132 | ## Releases
133 |
134 | See [CHANGELOG.md](https://github.com/iamstuartwilson/strava/blob/master/CHANGELOG.md).
135 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iamstuartwilson/strava",
3 | "description": "PHP implementation of the Strava V3 API",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Stuart Wilson",
8 | "email": "bonjour@iamstuartwilson.com"
9 | }
10 | ],
11 | "minimum-stability": "dev",
12 | "prefer-stable": true,
13 | "require": {
14 | "php": ">=5.5"
15 | },
16 | "require-dev": {
17 | "phpunit/phpunit": "~4.8"
18 | },
19 | "autoload": {
20 | "psr-0" : {
21 | "Iamstuartwilson\\" : "src/"
22 | }
23 | },
24 | "scripts": {
25 | "ci:lint": "find config src tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l",
26 | "ci:tests": "./vendor/bin/phpunit tests/",
27 | "ci:static": [
28 | "@ci:lint"
29 | ],
30 | "ci:dynamic": [
31 | "@ci:tests"
32 | ],
33 | "ci": [
34 | "@ci:static",
35 | "@ci:dynamic"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/oauth-flow.php:
--------------------------------------------------------------------------------
1 | setAccessToken(
27 | $_SESSION['strava_access_token'],
28 | $_SESSION['strava_refresh_token'],
29 | $_SESSION['strava_access_token_expires_at']
30 | );
31 | }
32 |
33 | $action = isset($_GET['action']) ? $_GET['action'] : 'default';
34 |
35 | switch ($action) {
36 | case 'auth':
37 | header('Location: ' . $api->authenticationUrl(CALLBACK_URL, 'auto', 'read_all'));
38 |
39 | return;
40 |
41 | case 'callback':
42 | echo '
Callback Response Data
';
43 | echo '';
44 | print_r($_GET);
45 | echo '
';
46 | $code = $_GET['code'];
47 | $response = $api->tokenExchange($code);
48 | echo 'Token Exchange Response Data
';
49 | echo '(after swapping the code from callback against tokens)
';
50 | echo '';
51 | print_r($response);
52 | echo '
';
53 |
54 | $_SESSION['strava_access_token'] = isset($response->access_token) ? $response->access_token : null;
55 | $_SESSION['strava_refresh_token'] = isset($response->refresh_token) ? $response->refresh_token : null;
56 | $_SESSION['strava_access_token_expires_at'] = isset($response->expires_at) ? $response->expires_at : null;
57 |
58 | echo 'Session Contents (after)
';
59 | echo '';
60 | print_r($_SESSION);
61 | echo '
';
62 |
63 | echo 'Send test request
';
64 | echo 'Refresh Access Token
';
65 |
66 | return;
67 |
68 | case 'refresh_token':
69 | echo 'Session Contents (before)
';
70 | echo '';
71 | print_r($_SESSION);
72 | echo '
';
73 |
74 | echo 'Refresh Token
';
75 | $response = $api->tokenExchangeRefresh();
76 | echo '';
77 | print_r($response);
78 | echo '
';
79 |
80 | $_SESSION['strava_access_token'] = isset($response->access_token) ? $response->access_token : null;
81 | $_SESSION['strava_refresh_token'] = isset($response->refresh_token) ? $response->refresh_token : null;
82 | $_SESSION['strava_access_token_expires_at'] = isset($response->expires_at) ? $response->expires_at : null;
83 |
84 | echo 'Session Contents (after)
';
85 | echo '';
86 | print_r($_SESSION);
87 | echo '
';
88 |
89 | return;
90 |
91 | case 'test_request':
92 | echo 'Session Contents
';
93 | echo '';
94 | print_r($_SESSION);
95 | echo '
';
96 |
97 | $response = $api->get('/athlete');
98 | echo 'Test Request (/athlete)
';
99 | echo '';
100 | print_r($response);
101 | echo '
';
102 |
103 | return;
104 |
105 | case 'default':
106 | default:
107 | echo 'Start authentication flow.
';
108 | echo 'Start oAuth Authentication Flow (Strava oAuth URL: '
109 | . $api->authenticationUrl(CALLBACK_URL)
110 | . '
)
';
111 | echo 'Session Contents
';
112 | echo '';
113 | print_r($_SESSION);
114 | echo '
';
115 | echo 'Refresh Access Token
';
116 | }
117 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | tests
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/Iamstuartwilson/StravaApi.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * @link https://github.com/iamstuartwilson/strava
11 | */
12 |
13 | class StravaApi
14 | {
15 | const BASE_URL = 'https://www.strava.com';
16 |
17 | /**
18 | * If the access token expires in less than 3600 seconds, a refresh is required.
19 | */
20 | const ACCESS_TOKEN_MINIMUM_VALIDITY = 3600;
21 |
22 | public $lastRequest;
23 | public $lastRequestData;
24 | public $lastRequestInfo;
25 |
26 | /**
27 | * Stores the HTTP headers from the last API response, e. g.:
28 | *
29 | * [
30 | * 'Cache-Control' => 'max-age=0, private, must-revalidate',
31 | * 'X-RateLimit-Limit' => '600,30000',
32 | * 'X-RateLimit-Usage' => '4,25',
33 | * 'Content-Length' => '2031',
34 | * ...
35 | * ]
36 | *
37 | * Access with the `getResponseHeader()` or `getResponseHeaders()` methods.
38 | *
39 | * @var array
40 | */
41 | protected $responseHeaders = array();
42 |
43 | protected $apiUrl;
44 | protected $authUrl;
45 | protected $clientId;
46 | protected $clientSecret;
47 |
48 | private $accessToken;
49 | private $refreshToken;
50 | private $expiresAt;
51 |
52 | /**
53 | * Sets up the class with the $clientId and $clientSecret
54 | *
55 | * @param int $clientId
56 | * @param string $clientSecret
57 | */
58 | public function __construct($clientId = 1, $clientSecret = '')
59 | {
60 | $this->clientId = $clientId;
61 | $this->clientSecret = $clientSecret;
62 | $this->apiUrl = self::BASE_URL . '/api/v3/';
63 | $this->authUrl = self::BASE_URL . '/oauth/';
64 | }
65 |
66 | /**
67 | * Returns the complete list of response headers.
68 | *
69 | * @return array
70 | */
71 | public function getResponseHeaders()
72 | {
73 | return $this->responseHeaders;
74 | }
75 |
76 | /**
77 | * @param string $header
78 | *
79 | * @return string
80 | */
81 | public function getResponseHeader($header)
82 | {
83 | if (! isset($this->responseHeaders[$header])) {
84 | throw new \InvalidArgumentException('Header does not exist');
85 | }
86 |
87 | return $this->responseHeaders[$header];
88 | }
89 |
90 | /**
91 | * Appends query array onto URL
92 | *
93 | * @param string $url
94 | * @param array $query
95 | *
96 | * @return string
97 | */
98 | protected function parseGet($url, $query)
99 | {
100 | $append = strpos($url, '?') === false ? '?' : '&';
101 |
102 | return $url . $append . http_build_query($query);
103 | }
104 |
105 | /**
106 | * Parses JSON as PHP object
107 | *
108 | * @param string $response
109 | *
110 | * @return object
111 | */
112 | protected function parseResponse($response)
113 | {
114 | return json_decode($response);
115 | }
116 |
117 | /**
118 | * Makes HTTP Request to the API
119 | *
120 | * @param string $url
121 | * @param array $parameters
122 | * @param bool|string $request the request method, default is POST
123 | *
124 | * @return mixed
125 | * @throws \Exception
126 | */
127 | protected function request($url, $parameters = array(), $request = false)
128 | {
129 | $this->lastRequest = $url;
130 | $this->lastRequestData = $parameters;
131 | $this->responseHeaders = array();
132 |
133 | if (strpos($url, '/oauth/token') === false && $this->isTokenRefreshNeeded()) {
134 | throw new \RuntimeException('Strava access token needs to be refreshed');
135 | }
136 |
137 | $curl = curl_init($url);
138 |
139 | $curlOptions = array(
140 | CURLOPT_SSL_VERIFYPEER => false,
141 | CURLOPT_REFERER => $url,
142 | CURLOPT_RETURNTRANSFER => true,
143 | CURLOPT_HEADERFUNCTION => array($this, 'parseHeader'),
144 | );
145 |
146 | if (! empty($parameters) || ! empty($request)) {
147 | if (! empty($request)) {
148 | $curlOptions[ CURLOPT_CUSTOMREQUEST ] = $request;
149 | $parameters = http_build_query($parameters);
150 | } else {
151 | $curlOptions[ CURLOPT_POST ] = true;
152 | }
153 |
154 | $curlOptions[ CURLOPT_POSTFIELDS ] = $parameters;
155 | }
156 |
157 | curl_setopt_array($curl, $curlOptions);
158 |
159 | $response = curl_exec($curl);
160 | $error = curl_error($curl);
161 |
162 | $this->lastRequestInfo = curl_getinfo($curl);
163 |
164 | curl_close($curl);
165 |
166 | if (! empty($error)) {
167 | throw new \Exception($error);
168 | }
169 |
170 | return $this->parseResponse($response);
171 | }
172 |
173 | /**
174 | * Creates authentication URL for your app
175 | *
176 | * @param string $redirect
177 | * @param string $approvalPrompt
178 | * @param string $scope
179 | * @param string $state
180 | *
181 | * @link http://developers.strava.com/docs/authentication/
182 | *
183 | * @return string
184 | */
185 | public function authenticationUrl($redirect, $approvalPrompt = 'auto', $scope = null, $state = null)
186 | {
187 | $parameters = array(
188 | 'client_id' => $this->clientId,
189 | 'redirect_uri' => $redirect,
190 | 'response_type' => 'code',
191 | 'approval_prompt' => $approvalPrompt,
192 | 'state' => $state,
193 | );
194 |
195 | if (! is_null($scope)) {
196 | $parameters['scope'] = $scope;
197 | }
198 |
199 | return $this->parseGet(
200 | $this->authUrl . 'authorize',
201 | $parameters
202 | );
203 | }
204 |
205 | /**
206 | * Authenticates token returned from API
207 | *
208 | * @param string $code
209 | *
210 | * @link http://developers.strava.com/docs/authentication/#token-exchange
211 | *
212 | * @return string
213 | */
214 | public function tokenExchange($code)
215 | {
216 | $parameters = array(
217 | 'client_id' => $this->clientId,
218 | 'client_secret' => $this->clientSecret,
219 | 'code' => $code,
220 | 'grant_type' => 'authorization_code'
221 | );
222 |
223 | return $this->request(
224 | $this->authUrl . 'token',
225 | $parameters
226 | );
227 | }
228 |
229 | /**
230 | * Refresh expired access tokens
231 | *
232 | * @link https://developers.strava.com/docs/authentication/#refresh-expired-access-tokens
233 | *
234 | * @return mixed
235 | */
236 | public function tokenExchangeRefresh()
237 | {
238 | if (! isset($this->refreshToken)) {
239 | return null;
240 | }
241 | $parameters = array(
242 | 'client_id' => $this->clientId,
243 | 'client_secret' => $this->clientSecret,
244 | 'refresh_token' => $this->refreshToken,
245 | 'grant_type' => 'refresh_token'
246 | );
247 |
248 | return $this->request(
249 | $this->authUrl . 'token',
250 | $parameters
251 | );
252 | }
253 |
254 | /**
255 | * Deauthorises application
256 | *
257 | * @link http://strava.github.io/api/v3/oauth/#deauthorize
258 | *
259 | * @return string
260 | */
261 | public function deauthorize()
262 | {
263 | return $this->request(
264 | $this->authUrl . 'deauthorize',
265 | $this->generateParameters(array())
266 | );
267 | }
268 |
269 | /**
270 | * Sets the access token used to authenticate API requests
271 | *
272 | * @param string $token
273 | * @param string $refreshToken
274 | * @param int $expiresAt
275 | *
276 | * @return string
277 | */
278 | public function setAccessToken($token, $refreshToken = null, $expiresAt = null)
279 | {
280 | if (isset($refreshToken)) {
281 | $this->refreshToken = $refreshToken;
282 | }
283 | if (isset($expiresAt)) {
284 | $this->expiresAt = $expiresAt;
285 | if ($this->isTokenRefreshNeeded()) {
286 | throw new \RuntimeException('Strava access token needs to be refreshed');
287 | }
288 | }
289 |
290 | return $this->accessToken = $token;
291 | }
292 |
293 | /**
294 | * Sends GET request to specified API endpoint
295 | *
296 | * @param string $request
297 | * @param array $parameters
298 | *
299 | * @example http://strava.github.io/api/v3/athlete/#koms
300 | *
301 | * @return string
302 | */
303 | public function get($request, $parameters = array())
304 | {
305 | $parameters = $this->generateParameters($parameters);
306 | $requestUrl = $this->parseGet($this->getAbsoluteUrl($request), $parameters);
307 |
308 | return $this->request($requestUrl);
309 | }
310 |
311 | /**
312 | * Sends PUT request to specified API endpoint
313 | *
314 | * @param string $request
315 | * @param array $parameters
316 | *
317 | * @example http://strava.github.io/api/v3/athlete/#update
318 | *
319 | * @return string
320 | */
321 | public function put($request, $parameters = array())
322 | {
323 | return $this->request(
324 | $this->getAbsoluteUrl($request),
325 | $this->generateParameters($parameters),
326 | 'PUT'
327 | );
328 | }
329 |
330 | /**
331 | * Sends POST request to specified API endpoint
332 | *
333 | * @param string $request
334 | * @param array $parameters
335 | *
336 | * @example http://strava.github.io/api/v3/activities/#create
337 | *
338 | * @return string
339 | */
340 | public function post($request, $parameters = array())
341 | {
342 | return $this->request(
343 | $this->getAbsoluteUrl($request),
344 | $this->generateParameters($parameters)
345 | );
346 | }
347 |
348 | /**
349 | * Sends DELETE request to specified API endpoint
350 | *
351 | * @param string $request
352 | * @param array $parameters
353 | *
354 | * @example http://strava.github.io/api/v3/activities/#delete
355 | *
356 | * @return string
357 | */
358 | public function delete($request, $parameters = array())
359 | {
360 | return $this->request(
361 | $this->getAbsoluteUrl($request),
362 | $this->generateParameters($parameters),
363 | 'DELETE'
364 | );
365 | }
366 |
367 | /**
368 | * Adds access token to paramters sent to API
369 | *
370 | * @param array $parameters
371 | *
372 | * @return array
373 | */
374 | protected function generateParameters($parameters)
375 | {
376 | return array_merge(
377 | $parameters,
378 | array( 'access_token' => $this->accessToken )
379 | );
380 | }
381 |
382 | /**
383 | * Parses the header lines into the $responseHeaders attribute
384 | *
385 | * Skips the first header line (HTTP response status) and the last header
386 | * line (empty).
387 | *
388 | * @param resource $curl
389 | * @param string $headerLine
390 | *
391 | * @return int length of the currently parsed header line in bytes
392 | */
393 | protected function parseHeader($curl, $headerLine)
394 | {
395 | $size = strlen($headerLine);
396 | $trimmed = trim($headerLine);
397 |
398 | // skip empty line(s)
399 | if (empty($trimmed)) {
400 | return $size;
401 | }
402 |
403 | // skip first header line (HTTP status code)
404 | if (strpos($trimmed, 'HTTP/') === 0) {
405 | return $size;
406 | }
407 |
408 | $parts = explode(':', $headerLine);
409 | $key = array_shift($parts);
410 | $value = implode(':', $parts);
411 |
412 | $this->responseHeaders[$key] = trim($value);
413 |
414 | return $size;
415 | }
416 |
417 | /**
418 | * Checks the given request string and returns the absolute URL to make
419 | * the necessary API call
420 | *
421 | * @param string $request
422 | *
423 | * @return string
424 | */
425 | protected function getAbsoluteUrl($request)
426 | {
427 | $request = ltrim($request);
428 |
429 | if (strpos($request, 'http') === 0) {
430 | return $request;
431 | }
432 |
433 | return $this->apiUrl . $request;
434 | }
435 |
436 | /**
437 | * @return bool
438 | */
439 | public function isTokenRefreshNeeded()
440 | {
441 | if (empty($this->expiresAt)) {
442 | return false;
443 | }
444 |
445 | return $this->expiresAt - time() < self::ACCESS_TOKEN_MINIMUM_VALIDITY;
446 | }
447 | }
448 |
--------------------------------------------------------------------------------
/tests/StravaApiTest.php:
--------------------------------------------------------------------------------
1 | stravaApi = new \Iamstuartwilson\StravaApi(999, '_SECRET_');
15 | }
16 |
17 | public function tearDown()
18 | {
19 | unset($this->stravaApi);
20 | }
21 |
22 | public function testIfAuthenticationUrlWorksAsExpected()
23 | {
24 | $expected = 'https://www.strava.com/oauth/authorize'
25 | . '?client_id=999'
26 | . '&redirect_uri=' . urlencode('https://example.org/')
27 | . '&response_type=code'
28 | . '&approval_prompt=auto';
29 |
30 | $url = $this->stravaApi->authenticationUrl('https://example.org/', 'auto', null, null);
31 |
32 | $this->assertEquals($expected, $url);
33 | }
34 |
35 | public function testIfAuthenticationUrlWithScopeWorksAsExpected()
36 | {
37 | $expected = 'https://www.strava.com/oauth/authorize'
38 | . '?client_id=999'
39 | . '&redirect_uri=' . urlencode('https://example.org/')
40 | . '&response_type=code'
41 | . '&approval_prompt=auto'
42 | . '&scope=read';
43 |
44 | $url = $this->stravaApi->authenticationUrl('https://example.org/', 'auto', 'read', null);
45 |
46 | $this->assertEquals($expected, $url);
47 | }
48 |
49 | public function testIfTokenRefreshCheckReturnsTrueIfNoExpiresTimestampIsSet()
50 | {
51 | $this->stravaApi->setAccessToken('access_token', 'refresh_token', null);
52 |
53 | self::assertFalse($this->stravaApi->isTokenRefreshNeeded());
54 | }
55 |
56 | /**
57 | * @expectedException \RuntimeException
58 | */
59 | public function testIfTokenRefreshCheckReturnsTrueIfExpiresTimestampIsInThePast()
60 | {
61 | $this->stravaApi->setAccessToken('access_token', 'refresh_token', time() - 86400);
62 |
63 | self::assertTrue($this->stravaApi->isTokenRefreshNeeded());
64 | }
65 |
66 | /**
67 | * @expectedException \RuntimeException
68 | */
69 | public function testIfTokenRefreshCheckReturnsTrueIfExpiresTimestampIsDueInLessThanOneHour()
70 | {
71 | $this->stravaApi->setAccessToken('access_token', 'refresh_token', time() + 1800);
72 |
73 | self::assertTrue($this->stravaApi->isTokenRefreshNeeded());
74 | }
75 |
76 | public function testIfTokenRefreshCheckReturnsFalseIfExpiresTimestampIsMoreThanOneHourInTheFuture()
77 | {
78 | $this->stravaApi->setAccessToken('access_token', 'refresh_token', time() + 7200);
79 |
80 | self::assertFalse($this->stravaApi->isTokenRefreshNeeded());
81 | }
82 | }
83 |
--------------------------------------------------------------------------------