├── .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 | [![Build Status](https://travis-ci.org/iamstuartwilson/strava.svg)](https://travis-ci.org/iamstuartwilson/strava) 2 | ![Minimum PHP Version](http://img.shields.io/badge/php->=5.5-8892BF.svg?style=flat) 3 | ![Packagist](https://img.shields.io/packagist/v/iamstuartwilson/strava.svg) 4 | ![Packagist Downloads](https://img.shields.io/packagist/dt/iamstuartwilson/strava.svg) 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 | --------------------------------------------------------------------------------