├── .gitignore ├── src └── Strava │ └── API │ ├── Exception.php │ ├── Service │ ├── Exception.php │ ├── ServiceInterface.php │ ├── Stub.php │ └── REST.php │ ├── Factory.php │ ├── OAuth.php │ ├── Webhook.php │ └── Client.php ├── tests ├── Strava │ └── API │ │ ├── TestCase.php │ │ ├── FactoryTest.php │ │ ├── ExceptionTest.php │ │ ├── Service │ │ ├── ExceptionTest.php │ │ └── StubTest.php │ │ ├── OAuthTest.php │ │ ├── WebhookTest.php │ │ └── ClientTest.php └── _support │ └── TestCase.php ├── phpstan.neon.dist ├── .github ├── workflows │ ├── stale.yml │ ├── phpmd.yml │ └── php.yml └── release.yml ├── LICENSE ├── composer.json ├── phpunit.xml.dist ├── examples ├── webhook-endpoint.php └── webhook-example.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | .project 4 | /vendor/ 5 | /build/ 6 | composer.lock 7 | example.php 8 | *.cache 9 | -------------------------------------------------------------------------------- /src/Strava/API/Exception.php: -------------------------------------------------------------------------------- 1 | getOAuthClient('123', 'TOKEN', 'URL'); 18 | $this->assertInstanceOf('Strava\API\OAuth', $client); 19 | } 20 | 21 | public function testGetAPIClientInstance() 22 | { 23 | $factory = new Strava\API\Factory(); 24 | $client = $factory->getAPIClient('TOKEN'); 25 | $this->assertInstanceOf('Strava\API\Client', $client); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Strava/API/ExceptionTest.php: -------------------------------------------------------------------------------- 1 | expectException('Strava\API\Exception'); 17 | throw new Strava\API\Exception(); 18 | } 19 | 20 | public function testInstanceOfException() 21 | { 22 | try { 23 | throw new Strava\API\Exception(); 24 | } catch (Exception $ex) { 25 | $this->assertInstanceOf('Strava\API\Exception', $ex); 26 | $this->assertInstanceOf('\Exception', $ex); 27 | } 28 | } 29 | 30 | public function testExceptionMessageAndCode() 31 | { 32 | try { 33 | throw new Strava\API\Exception('test', 100); 34 | } catch (Exception $ex) { 35 | $this->assertEquals('test', $ex->getMessage()); 36 | $this->assertEquals(100, $ex->getCode()); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Strava/API/Service/ExceptionTest.php: -------------------------------------------------------------------------------- 1 | expectException('Strava\API\Service\Exception'); 17 | throw new Strava\API\Service\Exception(); 18 | } 19 | 20 | public function testInstanceOfException() 21 | { 22 | try { 23 | throw new Strava\API\Service\Exception(); 24 | } catch (Exception $ex) { 25 | $this->assertInstanceOf('Strava\API\Service\Exception', $ex); 26 | $this->assertInstanceOf('\Exception', $ex); 27 | } 28 | } 29 | 30 | public function testExceptionMessageAndCode() 31 | { 32 | try { 33 | throw new Strava\API\Service\Exception('test', 100); 34 | } catch (Exception $ex) { 35 | $this->assertEquals('test', $ex->getMessage()); 36 | $this->assertEquals(100, $ex->getCode()); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2022 Bas van Dorst 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 | 23 | -------------------------------------------------------------------------------- /src/Strava/API/Factory.php: -------------------------------------------------------------------------------- 1 | $client_id, 30 | 'clientSecret' => $client_secret, 31 | 'redirectUri' => $redirect_uri 32 | ]; 33 | return new OAuth($options); 34 | } 35 | 36 | /** 37 | * Return a new instance of the API Client 38 | * 39 | * @param string $token 40 | * @return Client 41 | */ 42 | public function getAPIClient(string $token): Client 43 | { 44 | $adapter = new \GuzzleHttp\Client(['base_uri' => self::$endpoint]); 45 | $service = new Service\REST($token, $adapter); 46 | 47 | return new Client($service); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basvandorst/stravaphp", 3 | "description": "Strava V3 API PHP client with OAuth authentication", 4 | "keywords": [ 5 | "Strava", 6 | "API", 7 | "OAuth", 8 | "PHP", 9 | "StravaPHP" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Bas van Dorst", 15 | "email": "basvandorst@gmail.com" 16 | }, 17 | { 18 | "name": "Bas Vredeling", 19 | "email": "bas@vredeling.nl" 20 | } 21 | ], 22 | "require": { 23 | "php": "^8.1", 24 | "ext-curl": "*", 25 | "ext-json": "*", 26 | "guzzlehttp/guzzle": "^6.3 || ^7.0.1", 27 | "league/oauth2-client": "~2.3" 28 | }, 29 | "require-dev": { 30 | "friendsofphp/php-cs-fixer": "^3.4", 31 | "phpstan/phpstan": "^2.1", 32 | "phpunit/phpunit": "^9" 33 | }, 34 | "autoload": { 35 | "psr-0": { 36 | "Strava": "src/" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Tests\\Support\\": "tests/_support" 42 | } 43 | }, 44 | "minimum-stability": "stable", 45 | "scripts": { 46 | "analyze": "phpstan analyze", 47 | "ci": [ 48 | "Composer\\Config::disableProcessTimeout", 49 | "@analyze", 50 | "@style", 51 | "@test" 52 | ], 53 | "style": "php-cs-fixer fix --verbose --ansi --using-cache=no src/", 54 | "test": "phpunit" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/phpmd.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # PHPMD is a spin-off project of PHP Depend and 6 | # aims to be a PHP equivalent of the well known Java tool PMD. 7 | # What PHPMD does is: It takes a given PHP source code base 8 | # and look for several potential problems within that source. 9 | # These problems can be things like: 10 | # Possible bugs 11 | # Suboptimal code 12 | # Overcomplicated expressions 13 | # Unused parameters, methods, properties 14 | # More details at https://phpmd.org/ 15 | 16 | name: PHPMD 17 | 18 | on: 19 | push: 20 | branches: [ "develop" ] 21 | pull_request: 22 | # The branches below must be a subset of the branches above 23 | branches: [ "develop" ] 24 | schedule: 25 | - cron: '20 3 * * 4' 26 | 27 | permissions: 28 | contents: read 29 | 30 | jobs: 31 | PHPMD: 32 | name: Run PHPMD scanning 33 | runs-on: ubuntu-latest 34 | permissions: 35 | contents: read # for checkout to fetch code 36 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 37 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 38 | 39 | steps: 40 | - name: Checkout code 41 | uses: actions/checkout@v4 42 | 43 | - name: Setup PHP 44 | uses: shivammathur/setup-php@aa1fe473f9c687b6fb896056d771232c0bc41161 45 | with: 46 | coverage: none 47 | tools: phpmd 48 | 49 | - name: Run PHPMD 50 | run: phpmd . sarif codesize --reportfile phpmd-results.sarif 51 | continue-on-error: true 52 | 53 | - name: Upload analysis results to GitHub 54 | uses: github/codeql-action/upload-sarif@v3 55 | with: 56 | sarif_file: phpmd-results.sarif 57 | wait-for-processing: true 58 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 25 | ./src/ 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ./tests/ 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /examples/webhook-endpoint.php: -------------------------------------------------------------------------------- 1 | oauth = new Strava\API\OAuth(array()); 24 | } 25 | 26 | private function getResponseMock() 27 | { 28 | $json = '{"id": 12345, "firstname": "mock_first_name", "lastname": "mock_last_name", "email": "mock_email", "country": "NL", "sex": "M", "profile": "profile_url"}'; 29 | $response = json_decode($json); 30 | return $response; 31 | } 32 | 33 | public function testUrlAuthorize() 34 | { 35 | $url = $this->oauth->urlAuthorize(); 36 | $this->assertNotEmpty($url); 37 | } 38 | 39 | public function testUrlAccessToken() 40 | { 41 | $url = $this->oauth->urlAccessToken(); 42 | $this->assertNotEmpty($url); 43 | } 44 | 45 | public function testUrlUserDetails() 46 | { 47 | $url = $this->oauth->urlUserDetails(); 48 | $this->assertNotEmpty($url); 49 | } 50 | 51 | public function testUserDetails() 52 | { 53 | $reponseMock = $this->getResponseMock(); 54 | 55 | $output = $this->oauth->userDetails($reponseMock); 56 | $this->assertInstanceOf('stdClass', $output); 57 | } 58 | 59 | public function testUserUid() 60 | { 61 | $reponseMock = $this->getResponseMock(); 62 | 63 | $output = $this->oauth->userUid($reponseMock); 64 | $this->assertEquals(12345, $output); 65 | } 66 | 67 | public function testUserEmail() 68 | { 69 | $reponseMock = $this->getResponseMock(); 70 | 71 | $output = $this->oauth->userEmail($reponseMock); 72 | $this->assertEquals('mock_email', $output); 73 | } 74 | 75 | public function testUserScreenName() 76 | { 77 | $reponseMock = $this->getResponseMock(); 78 | 79 | $output = $this->oauth->userScreenName($reponseMock); 80 | $this->assertEquals('mock_first_name mock_last_name', $output); 81 | } 82 | 83 | public function testBaseAuthorizationUrl() 84 | { 85 | $result = $this->oauth->getBaseAuthorizationUrl(); 86 | 87 | $this->assertSame('https://www.strava.com/oauth/authorize', $result); 88 | } 89 | 90 | public function testBaseAccessTokenUrl() 91 | { 92 | $result = $this->oauth->getBaseAccessTokenUrl(array()); 93 | 94 | $this->assertSame('https://www.strava.com/oauth/token', $result); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | - master 8 | paths: 9 | - '**.php' 10 | - 'composer.*' 11 | - 'php*' 12 | - '.github/workflows/php.yml' 13 | pull_request: 14 | branches: 15 | - develop 16 | - master 17 | paths: 18 | - '**.php' 19 | - 'composer.*' 20 | - 'php*' 21 | - '.github/workflows/php.yml' 22 | 23 | jobs: 24 | build: 25 | name: PHP ${{ matrix.php-versions }} 26 | runs-on: ubuntu-latest 27 | strategy: 28 | matrix: 29 | php-versions: ['8.1', '8.2', '8.3', '8.4'] 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - name: Set up PHP 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: ${{ matrix.php-versions }} 38 | tools: composer, phpunit, phive 39 | extensions: intl, json, mbstring, xdebug 40 | coverage: xdebug 41 | env: 42 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | 44 | - name: Validate composer.json 45 | run: composer validate 46 | 47 | - name: Cache Composer packages 48 | id: composer-cache 49 | run: | 50 | echo "::set-output name=dir::$(composer config cache-files-dir)" 51 | - uses: actions/cache@v3 52 | with: 53 | path: ${{ steps.composer-cache.outputs.dir }} 54 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 55 | restore-keys: | 56 | ${{ runner.os }}-composer- 57 | 58 | - name: Install dependencies 59 | if: steps.composer-cache.outputs.cache-hit != 'true' 60 | run: composer update --prefer-dist --no-progress --no-suggest 61 | 62 | - name: Run static analysis 63 | run: vendor/bin/phpstan analyze 64 | env: 65 | TERM: xterm-256color 66 | 67 | - name: Check code for standards compliance 68 | run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --using-cache=no --diff src/ 69 | 70 | - name: Run test suite 71 | run: vendor/bin/phpunit --verbose 72 | env: 73 | TERM: xterm-256color 74 | 75 | - if: matrix.php-versions == '8.1' 76 | name: Run Coveralls 77 | continue-on-error: true 78 | run: | 79 | sudo phive --no-progress install --global --trust-gpg-keys E82B2FB314E9906E php-coveralls 80 | php-coveralls --verbose --coverage_clover=build/phpunit/clover.xml --json_path build/phpunit/coveralls-upload.json 81 | env: 82 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | COVERALLS_PARALLEL: true 84 | COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} 85 | 86 | coveralls: 87 | needs: [build] 88 | name: Coveralls Finished 89 | runs-on: ubuntu-latest 90 | steps: 91 | - name: Upload Coveralls results 92 | uses: coverallsapp/github-action@master 93 | continue-on-error: true 94 | with: 95 | github-token: ${{ secrets.GITHUB_TOKEN }} 96 | parallel-finished: true 97 | -------------------------------------------------------------------------------- /src/Strava/API/OAuth.php: -------------------------------------------------------------------------------- 1 | uid = $response->id; 57 | $user->name = implode(' ', [$response->firstname, $response->lastname]); 58 | $user->firstName = $response->firstname; 59 | $user->lastName = $response->lastname; 60 | $user->email = $response->email; 61 | $user->location = $response->country; 62 | $user->imageUrl = $response->profile; 63 | $user->gender = $response->sex; 64 | 65 | return $user; 66 | } 67 | 68 | /** 69 | * @see AbstractProvider::userUid 70 | */ 71 | public function userUid($response) 72 | { 73 | return $response->id; 74 | } 75 | 76 | /** 77 | * @see AbstractProvider::userEmail 78 | */ 79 | public function userEmail($response) 80 | { 81 | return isset($response->email) && $response->email ? $response->email : null; 82 | } 83 | 84 | /** 85 | * @see AbstractProvider::userScreenName 86 | */ 87 | public function userScreenName($response): string 88 | { 89 | return implode(' ', [$response->firstname, $response->lastname]); 90 | } 91 | 92 | /** 93 | * @see AbstractProvider::getBaseAuthorizationUrl 94 | */ 95 | public function getBaseAuthorizationUrl(): string 96 | { 97 | return 'https://www.strava.com/oauth/authorize'; 98 | } 99 | 100 | /** 101 | * @see AbstractProvider::getBaseAccessTokenUrl 102 | */ 103 | public function getBaseAccessTokenUrl(array $params): string 104 | { 105 | return 'https://www.strava.com/oauth/token'; 106 | } 107 | 108 | /** 109 | * @see AbstractProvider::getResourceOwnerDetailsUrl 110 | */ 111 | public function getResourceOwnerDetailsUrl(AccessToken $token): string 112 | { 113 | return ''; 114 | } 115 | 116 | /** 117 | * @see AbstractProvider::getDefaultScopes 118 | */ 119 | protected function getDefaultScopes(): array 120 | { 121 | return $this->scopes; 122 | } 123 | 124 | /** 125 | * @see AbstractProvider::checkResponse 126 | */ 127 | protected function checkResponse(ResponseInterface $response, $data) 128 | { 129 | } 130 | 131 | /** 132 | * @see AbstractProvider::createResourceOwner 133 | */ 134 | protected function createResourceOwner(array $response, AccessToken $token) 135 | { 136 | throw new RuntimeException('Not implemented'); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /examples/webhook-example.php: -------------------------------------------------------------------------------- 1 | 'https://www.strava.com/api/v3/']); 29 | $service = new REST($accessToken, $adapter); 30 | $client = new Client($service); 31 | 32 | try { 33 | // 1. Create a webhook subscription 34 | echo "Creating webhook subscription...\n"; 35 | $subscription = $client->createWebhookSubscription( 36 | $clientId, 37 | $clientSecret, 38 | $callbackUrl, 39 | $verifyToken 40 | ); 41 | 42 | echo "Subscription created successfully!\n"; 43 | echo "Subscription ID: " . $subscription['id'] . "\n"; 44 | echo "Callback URL: " . $subscription['callback_url'] . "\n"; 45 | echo "Created at: " . $subscription['created_at'] . "\n\n"; 46 | 47 | // 2. List existing webhook subscriptions 48 | echo "Listing webhook subscriptions...\n"; 49 | $subscriptions = $client->listWebhookSubscriptions($clientId, $clientSecret); 50 | 51 | echo "Found " . count($subscriptions) . " subscription(s):\n"; 52 | foreach ($subscriptions as $sub) { 53 | echo "- ID: " . $sub['id'] . ", URL: " . $sub['callback_url'] . "\n"; 54 | } 55 | echo "\n"; 56 | 57 | // 3. Delete a webhook subscription (uncomment to use) 58 | /* 59 | echo "Deleting webhook subscription...\n"; 60 | $deleted = $client->deleteWebhookSubscription( 61 | $clientId, 62 | $clientSecret, 63 | $subscription['id'] 64 | ); 65 | 66 | if ($deleted) { 67 | echo "Subscription deleted successfully!\n"; 68 | } else { 69 | echo "Failed to delete subscription.\n"; 70 | } 71 | */ 72 | 73 | } catch (Exception $e) { 74 | echo "Error: " . $e->getMessage() . "\n"; 75 | } 76 | 77 | /** 78 | * Example webhook endpoint handler 79 | * 80 | * Save this as webhook-endpoint.php on your server 81 | */ 82 | function webhookEndpointExample() 83 | { 84 | // Handle subscription challenge 85 | $verifyToken = 'your_verify_token_here'; // Same token used when creating subscription 86 | 87 | $challengeResult = Webhook::handleSubscriptionChallenge($verifyToken); 88 | 89 | if ($challengeResult['success']) { 90 | // Send challenge response to Strava 91 | Webhook::sendChallengeResponse($challengeResult['challenge']); 92 | } else { 93 | // Handle challenge error 94 | http_response_code(400); 95 | echo json_encode(['error' => $challengeResult['error']]); 96 | exit; 97 | } 98 | 99 | // Process webhook events 100 | $eventResult = Webhook::processEvent(); 101 | 102 | if (!$eventResult['success']) { 103 | http_response_code(400); 104 | echo json_encode(['error' => $eventResult['error']]); 105 | exit; 106 | } 107 | 108 | $event = $eventResult['event']; 109 | 110 | // Handle different event types 111 | switch (Webhook::getEventType($event)) { 112 | case 'activity.create': 113 | echo "New activity created: " . $event['object_id'] . "\n"; 114 | // Process new activity 115 | break; 116 | 117 | case 'activity.update': 118 | echo "Activity updated: " . $event['object_id'] . "\n"; 119 | // Process activity update 120 | break; 121 | 122 | case 'activity.delete': 123 | echo "Activity deleted: " . $event['object_id'] . "\n"; 124 | // Process activity deletion 125 | break; 126 | 127 | case 'athlete.update': 128 | echo "Athlete updated: " . $event['object_id'] . "\n"; 129 | // Process athlete update 130 | break; 131 | 132 | default: 133 | echo "Unknown event type: " . Webhook::getEventType($event) . "\n"; 134 | } 135 | 136 | // Send success response 137 | http_response_code(200); 138 | echo json_encode(['status' => 'success']); 139 | } 140 | 141 | // Uncomment to test the webhook endpoint 142 | // webhookEndpointExample(); 143 | -------------------------------------------------------------------------------- /tests/Strava/API/WebhookTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($result['success']); 25 | $this->assertEquals('test_challenge_123', $result['challenge']); 26 | } 27 | 28 | public function testHandleSubscriptionChallengeInvalidMethod() 29 | { 30 | $_SERVER['REQUEST_METHOD'] = 'POST'; 31 | $_SERVER['QUERY_STRING'] = ''; 32 | $_GET = []; 33 | 34 | $result = Webhook::handleSubscriptionChallenge('test_verify_token'); 35 | 36 | $this->assertFalse($result['success']); 37 | $this->assertEquals('Invalid request method. Expected GET.', $result['error']); 38 | } 39 | 40 | public function testHandleSubscriptionChallengeInvalidMode() 41 | { 42 | $_SERVER['REQUEST_METHOD'] = 'GET'; 43 | $_SERVER['QUERY_STRING'] = 'hub_mode=unsubscribe&hub_challenge=test_challenge_123&hub_verify_token=test_verify_token'; 44 | $_GET = []; 45 | 46 | $result = Webhook::handleSubscriptionChallenge('test_verify_token'); 47 | 48 | $this->assertFalse($result['success']); 49 | $this->assertEquals('Invalid hub_mode. Expected "subscribe".', $result['error']); 50 | } 51 | 52 | public function testHandleSubscriptionChallengeMissingChallenge() 53 | { 54 | $_SERVER['REQUEST_METHOD'] = 'GET'; 55 | $_SERVER['QUERY_STRING'] = 'hub_mode=subscribe&hub_verify_token=test_verify_token'; 56 | $_GET = []; 57 | 58 | $result = Webhook::handleSubscriptionChallenge('test_verify_token'); 59 | 60 | $this->assertFalse($result['success']); 61 | $this->assertEquals('Missing hub_challenge parameter.', $result['error']); 62 | } 63 | 64 | public function testHandleSubscriptionChallengeInvalidToken() 65 | { 66 | $_SERVER['REQUEST_METHOD'] = 'GET'; 67 | $_SERVER['QUERY_STRING'] = 'hub_mode=subscribe&hub_challenge=test_challenge_123&hub_verify_token=wrong_token'; 68 | $_GET = []; 69 | 70 | $result = Webhook::handleSubscriptionChallenge('test_verify_token'); 71 | 72 | $this->assertFalse($result['success']); 73 | $this->assertEquals('Invalid verify token.', $result['error']); 74 | } 75 | 76 | public function testGetEventType() 77 | { 78 | $event = [ 79 | 'object_type' => 'activity', 80 | 'aspect_type' => 'create', 81 | 'object_id' => 12345 82 | ]; 83 | 84 | $eventType = Webhook::getEventType($event); 85 | 86 | $this->assertEquals('activity.create', $eventType); 87 | } 88 | 89 | public function testIsObjectType() 90 | { 91 | $event = [ 92 | 'object_type' => 'activity', 93 | 'aspect_type' => 'create', 94 | 'object_id' => 12345 95 | ]; 96 | 97 | $this->assertTrue(Webhook::isObjectType($event, 'activity')); 98 | $this->assertFalse(Webhook::isObjectType($event, 'athlete')); 99 | } 100 | 101 | public function testIsAspectType() 102 | { 103 | $event = [ 104 | 'object_type' => 'activity', 105 | 'aspect_type' => 'create', 106 | 'object_id' => 12345 107 | ]; 108 | 109 | $this->assertTrue(Webhook::isAspectType($event, 'create')); 110 | $this->assertFalse(Webhook::isAspectType($event, 'update')); 111 | } 112 | 113 | public function testVerifySignature() 114 | { 115 | $payload = '{"test": "data"}'; 116 | $secret = 'test_secret'; 117 | $signature = 'sha256=' . hash_hmac('sha256', $payload, $secret); 118 | 119 | $this->assertTrue(Webhook::verifySignature($payload, $signature, $secret)); 120 | $this->assertFalse(Webhook::verifySignature($payload, 'wrong_signature', $secret)); 121 | $this->assertTrue(Webhook::verifySignature($payload, $signature, '')); // No secret configured 122 | } 123 | 124 | public function testGenerateVerifyToken() 125 | { 126 | $token1 = Webhook::generateVerifyToken(); 127 | $token2 = Webhook::generateVerifyToken(); 128 | 129 | // Tokens should be 32 characters long by default 130 | $this->assertEquals(32, strlen($token1)); 131 | $this->assertEquals(32, strlen($token2)); 132 | 133 | // Tokens should be different 134 | $this->assertNotEquals($token1, $token2); 135 | 136 | // Test custom length 137 | $token3 = Webhook::generateVerifyToken(16); 138 | $this->assertEquals(16, strlen($token3)); 139 | } 140 | 141 | public function testIsActivityCreationEvent() 142 | { 143 | $activityCreateEvent = [ 144 | 'object_type' => 'activity', 145 | 'aspect_type' => 'create', 146 | 'object_id' => 12345, 147 | 'owner_id' => 67890 148 | ]; 149 | 150 | $activityUpdateEvent = [ 151 | 'object_type' => 'activity', 152 | 'aspect_type' => 'update', 153 | 'object_id' => 12345, 154 | 'owner_id' => 67890 155 | ]; 156 | 157 | $athleteUpdateEvent = [ 158 | 'object_type' => 'athlete', 159 | 'aspect_type' => 'update', 160 | 'object_id' => 67890, 161 | 'owner_id' => 67890 162 | ]; 163 | 164 | $this->assertTrue(Webhook::isActivityCreationEvent($activityCreateEvent)); 165 | $this->assertFalse(Webhook::isActivityCreationEvent($activityUpdateEvent)); 166 | $this->assertFalse(Webhook::isActivityCreationEvent($athleteUpdateEvent)); 167 | } 168 | 169 | public function testGetAthleteId() 170 | { 171 | $event = [ 172 | 'object_type' => 'activity', 173 | 'aspect_type' => 'create', 174 | 'object_id' => 12345, 175 | 'owner_id' => 67890 176 | ]; 177 | 178 | $this->assertEquals(67890, Webhook::getAthleteId($event)); 179 | 180 | $eventWithoutOwner = [ 181 | 'object_type' => 'activity', 182 | 'aspect_type' => 'create', 183 | 'object_id' => 12345 184 | ]; 185 | 186 | $this->assertNull(Webhook::getAthleteId($eventWithoutOwner)); 187 | } 188 | 189 | public function testGetObjectId() 190 | { 191 | $event = [ 192 | 'object_type' => 'activity', 193 | 'aspect_type' => 'create', 194 | 'object_id' => 12345, 195 | 'owner_id' => 67890 196 | ]; 197 | 198 | $this->assertEquals(12345, Webhook::getObjectId($event)); 199 | 200 | $eventWithoutObject = [ 201 | 'object_type' => 'activity', 202 | 'aspect_type' => 'create', 203 | 'owner_id' => 67890 204 | ]; 205 | 206 | $this->assertNull(Webhook::getObjectId($eventWithoutObject)); 207 | } 208 | 209 | public function testValidateEventPayload() 210 | { 211 | $validEvent = [ 212 | 'object_type' => 'activity', 213 | 'aspect_type' => 'create', 214 | 'object_id' => 12345, 215 | 'owner_id' => 67890 216 | ]; 217 | 218 | $result = Webhook::validateEventPayload($validEvent); 219 | $this->assertTrue($result['valid']); 220 | $this->assertNull($result['error']); 221 | 222 | // Test missing fields 223 | $invalidEvent = [ 224 | 'object_type' => 'activity', 225 | 'aspect_type' => 'create' 226 | // Missing object_id and owner_id 227 | ]; 228 | 229 | $result = Webhook::validateEventPayload($invalidEvent); 230 | $this->assertFalse($result['valid']); 231 | $this->assertStringContainsString('Missing required field', $result['error']); 232 | 233 | // Test invalid field types 234 | $invalidTypesEvent = [ 235 | 'object_type' => 123, // Should be string 236 | 'aspect_type' => 'create', 237 | 'object_id' => 'not_numeric', // Should be numeric 238 | 'owner_id' => 67890 239 | ]; 240 | 241 | $result = Webhook::validateEventPayload($invalidTypesEvent); 242 | $this->assertFalse($result['valid']); 243 | $this->assertStringContainsString('must be', $result['error']); 244 | } 245 | 246 | protected function tearDown(): void 247 | { 248 | // Clean up global variables after each test 249 | $_SERVER = []; 250 | $_GET = []; 251 | parent::tearDown(); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /tests/Strava/API/Service/StubTest.php: -------------------------------------------------------------------------------- 1 | getAthlete(1234); 18 | $this->assertTrue(is_array($output)); 19 | } 20 | 21 | public function testGetAthleteStats() 22 | { 23 | $service = new Strava\API\Service\Stub(); 24 | $output = $service->getAthleteStats(1234); 25 | $this->assertTrue(is_array($output)); 26 | } 27 | 28 | public function testGetAthleteRoutes() 29 | { 30 | $service = new Strava\API\Service\Stub(); 31 | $output = $service->getAthleteRoutes(1234); 32 | $this->assertTrue(is_array($output)); 33 | } 34 | 35 | public function testGetAthleteClubs() 36 | { 37 | $service = new Strava\API\Service\Stub(); 38 | $output = $service->getAthleteClubs(); 39 | $this->assertTrue(is_array($output)); 40 | } 41 | 42 | public function testGetAthleteActivities() 43 | { 44 | $service = new Strava\API\Service\Stub(); 45 | $output = $service->getAthleteActivities(); 46 | $this->assertTrue(is_array($output)); 47 | } 48 | 49 | public function testGetAthleteFriends() 50 | { 51 | $service = new Strava\API\Service\Stub(); 52 | $output = $service->getAthleteFriends(); 53 | $this->assertTrue(is_array($output)); 54 | } 55 | 56 | public function testGetAthleteFollowers() 57 | { 58 | $service = new Strava\API\Service\Stub(); 59 | $output = $service->getAthleteFollowers(); 60 | $this->assertTrue(is_array($output)); 61 | } 62 | 63 | public function testGetAthleteBothFollowing() 64 | { 65 | $service = new Strava\API\Service\Stub(); 66 | $output = $service->getAthleteBothFollowing(1234); 67 | $this->assertTrue(is_array($output)); 68 | } 69 | 70 | public function testGetAthleteKom() 71 | { 72 | $service = new Strava\API\Service\Stub(); 73 | $output = $service->getAthleteKom(1234); 74 | $this->assertTrue(is_array($output)); 75 | } 76 | 77 | public function testGetAthleteZones() 78 | { 79 | $service = new Strava\API\Service\Stub(); 80 | $output = $service->getAthleteZones(); 81 | $this->assertTrue(is_array($output)); 82 | } 83 | 84 | public function testGetAthleteStarredSegments() 85 | { 86 | $service = new Strava\API\Service\Stub(); 87 | $output = $service->getAthleteStarredSegments(); 88 | $this->assertTrue(is_array($output)); 89 | } 90 | 91 | public function testUpdateAthlete() 92 | { 93 | $service = new Strava\API\Service\Stub(); 94 | $output = $service->updateAthlete('Xyz', 'ABC', 'The Netherlands', 'M', 83.00); 95 | $this->assertTrue(is_array($output)); 96 | } 97 | 98 | public function testGetActivity() 99 | { 100 | $service = new Strava\API\Service\Stub(); 101 | $output = $service->getActivity(1234); 102 | $this->assertTrue(is_array($output)); 103 | } 104 | 105 | public function testGetActivityComments() 106 | { 107 | $service = new Strava\API\Service\Stub(); 108 | $output = $service->getActivityComments(1234); 109 | $this->assertTrue(is_array($output)); 110 | } 111 | 112 | public function testGetActivityKudos() 113 | { 114 | $service = new Strava\API\Service\Stub(); 115 | $output = $service->getActivityKudos(1234); 116 | $this->assertTrue(is_array($output)); 117 | } 118 | 119 | public function testGetActivityPhotos() 120 | { 121 | $service = new Strava\API\Service\Stub(); 122 | $output = $service->getActivityPhotos(1234, 1024); 123 | $this->assertTrue(is_array($output)); 124 | } 125 | 126 | public function testGetActivityZones() 127 | { 128 | $service = new Strava\API\Service\Stub(); 129 | $output = $service->getActivityZones(1234); 130 | $this->assertTrue(is_array($output)); 131 | } 132 | 133 | public function testGetActivityLaps() 134 | { 135 | $service = new Strava\API\Service\Stub(); 136 | $output = $service->getActivityLaps(1234); 137 | $this->assertTrue(is_array($output)); 138 | } 139 | 140 | public function testGetActivityUploadStatus() 141 | { 142 | $service = new Strava\API\Service\Stub(); 143 | $output = $service->getActivityUploadStatus(1234); 144 | $this->assertTrue(is_array($output)); 145 | } 146 | 147 | public function testCreateActivity() 148 | { 149 | $service = new Strava\API\Service\Stub(); 150 | $output = $service->createActivity('cycling ride', 'cycling', '20140101', 100); 151 | $this->assertTrue(is_array($output)); 152 | } 153 | 154 | public function testUploadActivity() 155 | { 156 | $service = new Strava\API\Service\Stub(); 157 | $output = $service->uploadActivity("abc23487fsdfds"); 158 | $this->assertTrue(is_array($output)); 159 | } 160 | 161 | public function testUpdateActivity() 162 | { 163 | $service = new Strava\API\Service\Stub(); 164 | $output = $service->updateActivity(123); 165 | $this->assertTrue(is_array($output)); 166 | } 167 | 168 | public function testDeleteActivity() 169 | { 170 | $service = new Strava\API\Service\Stub(); 171 | $output = $service->deleteActivity(1234); 172 | $this->assertTrue(is_array($output)); 173 | } 174 | 175 | public function testGetGear() 176 | { 177 | $service = new Strava\API\Service\Stub(); 178 | $output = $service->getGear(1234); 179 | $this->assertTrue(is_array($output)); 180 | } 181 | 182 | public function testGetClub() 183 | { 184 | $service = new Strava\API\Service\Stub(); 185 | $output = $service->getClub(1234); 186 | $this->assertTrue(is_array($output)); 187 | } 188 | 189 | public function testGetClubMembers() 190 | { 191 | $service = new Strava\API\Service\Stub(); 192 | $output = $service->getClubMembers(1234); 193 | $this->assertTrue(is_array($output)); 194 | } 195 | 196 | public function testGetClubActivities() 197 | { 198 | $service = new Strava\API\Service\Stub(); 199 | $output = $service->getClubActivities(1234); 200 | $this->assertTrue(is_array($output)); 201 | } 202 | 203 | public function testGetClubAnnouncements() 204 | { 205 | $service = new Strava\API\Service\Stub(); 206 | $output = $service->getClubAnnouncements(1234); 207 | $this->assertTrue(is_array($output)); 208 | } 209 | 210 | public function testGetClubGroupEvents() 211 | { 212 | $service = new Strava\API\Service\Stub(); 213 | $output = $service->getClubGroupEvents(1234); 214 | $this->assertTrue(is_array($output)); 215 | } 216 | 217 | public function testJoinClub() 218 | { 219 | $service = new Strava\API\Service\Stub(); 220 | $output = $service->joinClub(1234); 221 | $this->assertTrue(is_array($output)); 222 | } 223 | 224 | public function testLeaveClub() 225 | { 226 | $service = new Strava\API\Service\Stub(); 227 | $output = $service->leaveClub(1234); 228 | $this->assertTrue(is_array($output)); 229 | } 230 | 231 | public function testGetRoute() 232 | { 233 | $service = new Strava\API\Service\Stub(); 234 | $output = $service->getRoute(1234); 235 | $this->assertTrue(is_array($output)); 236 | } 237 | 238 | public function testGetRouteAsGPX() 239 | { 240 | $service = new Strava\API\Service\Stub(); 241 | $output = $service->getRouteAsGPX(1234); 242 | $this->assertTrue(is_string($output)); 243 | } 244 | 245 | public function testGetRouteAsTCX() 246 | { 247 | $service = new Strava\API\Service\Stub(); 248 | $output = $service->getRouteAsTCX(1234); 249 | $this->assertTrue(is_string($output)); 250 | } 251 | 252 | public function testGetSegment() 253 | { 254 | $service = new Strava\API\Service\Stub(); 255 | $output = $service->getSegment(1234); 256 | $this->assertTrue(is_array($output)); 257 | } 258 | 259 | public function testGetSegmentLeaderboard() 260 | { 261 | $service = new Strava\API\Service\Stub(); 262 | $output = $service->getSegmentLeaderboard(1234); 263 | $this->assertTrue(is_array($output)); 264 | } 265 | 266 | public function testGetSegmentExplorer() 267 | { 268 | $service = new Strava\API\Service\Stub(); 269 | $output = $service->getSegmentExplorer("lng.lat"); 270 | $this->assertTrue(is_array($output)); 271 | } 272 | 273 | public function testGetSegmentEffort() 274 | { 275 | $service = new Strava\API\Service\Stub(); 276 | $output = $service->getSegmentEffort(1234); 277 | $this->assertTrue(is_array($output)); 278 | } 279 | 280 | public function testGetStreamsActivity() 281 | { 282 | $service = new Strava\API\Service\Stub(); 283 | $output = $service->getStreamsActivity(1234, 'abc'); 284 | $this->assertTrue(is_array($output)); 285 | } 286 | 287 | public function testGetStreamsEffort() 288 | { 289 | $service = new Strava\API\Service\Stub(); 290 | $output = $service->getStreamsEffort(1234, 'abc'); 291 | $this->assertTrue(is_array($output)); 292 | } 293 | 294 | public function testGetStreamsSegment() 295 | { 296 | $service = new Strava\API\Service\Stub(); 297 | $output = $service->getStreamsSegment(1234, 'abc'); 298 | $this->assertTrue(is_array($output)); 299 | } 300 | 301 | public function testGetStreamsRoute() 302 | { 303 | $service = new Strava\API\Service\Stub(); 304 | $output = $service->getStreamsRoute(1234); 305 | $this->assertTrue(is_array($output)); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/Strava/API/Service/ServiceInterface.php: -------------------------------------------------------------------------------- 1 | format($json); 17 | } 18 | 19 | public function getAthleteStats(int $id) 20 | { 21 | $json = '[ { "biggest_ride_distance": 175454.0, "biggest_climb_elevation_gain": 1882.6999999999998, "recent_ride_totals": { "count": 3, "distance": 12054.900146484375, "moving_time": 2190, "elapsed_time": 2331, "elevation_gain": 36.0, "achievement_count": 0 }, "recent_run_totals": { "count": 23, "distance": 195948.40002441406, "moving_time": 65513, "elapsed_time": 75232, "elevation_gain": 2934.3999996185303, "achievement_count": 46 }, "recent_swim_totals": { "count": 2, "distance": 1117.2000122070312, "moving_time": 1744, "elapsed_time": 1942, "elevation_gain": 0.0, "achievement_count": 0 }, "ytd_ride_totals": { "count": 134, "distance": 4927252, "moving_time": 659982, "elapsed_time": 892644, "elevation_gain": 49940 }, "ytd_run_totals": { "count": 111, "distance": 917100, "moving_time": 272501, "elapsed_time": 328059, "elevation_gain": 7558 }, "ytd_swim_totals": { "count": 8, "distance": 10372, "moving_time": 8784, "elapsed_time": 11123, "elevation_gain": 0 }, "all_ride_totals": { "count": 375, "distance": 15760015, "moving_time": 2155741, "elapsed_time": 2684286, "elevation_gain": 189238 }, "all_run_totals": { "count": 272, "distance": 2269557, "moving_time": 673678, "elapsed_time": 812095, "elevation_gain": 23780 }, "all_swim_totals": { "count": 8, "distance": 10372, "moving_time": 8784, "elapsed_time": 11123, "elevation_gain": 0} } ]'; 22 | return $this->format($json); 23 | } 24 | 25 | public function getAthleteRoutes(int $id, ?string $type = null, ?int $after = null, ?int $page = null, ?int $per_page = null) 26 | { 27 | $json = '[{"athlete":{"id":19,"resource_state":2},"id":743064,"resource_state":2,"description":"","distance":17781.6,"elevation_gain":207.8}]'; 28 | return $this->format($json); 29 | } 30 | 31 | public function getAthleteClubs() 32 | { 33 | $json = '[ { "id": 1, "resource_state": 2, "name": "Team Strava Cycling", "profile_medium": "http://pics.com/clubs/1/medium.jpg", "profile": "http://pics.com/clubs/1/large.jpg" } ]'; 34 | return $this->format($json); 35 | } 36 | 37 | public function getAthleteActivities(?string $before = null, ?string $after = null, ?int $page = null, ?int $per_page = null) 38 | { 39 | $json = '{"response": 1}'; 40 | return $this->format($json); 41 | } 42 | 43 | public function getAthleteFriends(?int $id = null, ?int $page = null, ?int $per_page = null) 44 | { 45 | $json = '{"response": 1}'; 46 | return $this->format($json); 47 | } 48 | 49 | public function getAthleteFollowers(?int $id = null, ?int $page = null, ?int $per_page = null) 50 | { 51 | $json = '{"response": 1}'; 52 | return $this->format($json); 53 | } 54 | 55 | public function getAthleteBothFollowing(int $id, ?int $page = null, ?int $per_page = null) 56 | { 57 | $json = '{"response": 1}'; 58 | return $this->format($json); 59 | } 60 | 61 | public function getAthleteKom(int $id, ?int $page = null, ?int $per_page = null) 62 | { 63 | $json = '{"response": 1}'; 64 | return $this->format($json); 65 | } 66 | 67 | public function getAthleteZones() 68 | { 69 | $json = '{"heart_rate":{"custom_zones":false,"zones":[{"min":0,"max":115},{"min":115,"max":152},{"min":152,"max":171},{"min":171,"max":190},{"min":190,"max":-1}]},"power":{"zones":[{"min":0,"max":180},{"min":181,"max":246},{"min":247,"max":295},{"min":296,"max":344},{"min":345,"max":393},{"min":394,"max":492},{"min":493,"max":-1}]}}'; 70 | return $this->format($json); 71 | } 72 | 73 | public function getAthleteStarredSegments(?int $id = null, ?int $page = null, ?int $per_page = null) 74 | { 75 | $json = '{"response": 1}'; 76 | return $this->format($json); 77 | } 78 | 79 | public function updateAthlete(string $city, string $state, string $country, string $sex, float $weight) 80 | { 81 | $json = '{"response": 1}'; 82 | return $this->format($json); 83 | } 84 | 85 | public function getActivityFollowing($before = null, $page = null, $per_page = null) 86 | { 87 | $json = '{"response": 1}'; 88 | return $this->format($json); 89 | } 90 | 91 | public function getActivity(int $id, ?bool $include_all_efforts = null) 92 | { 93 | $json = '{"response": 1}'; 94 | return $this->format($json); 95 | } 96 | 97 | public function getActivityComments(int $id, ?bool $markdown = null, ?int $page = null, ?int $per_page = null) 98 | { 99 | $json = '{"response": 1}'; 100 | return $this->format($json); 101 | } 102 | 103 | public function getActivityKudos(int $id, ?int $page = null, ?int $per_page = null) 104 | { 105 | $json = '{"response": 1}'; 106 | return $this->format($json); 107 | } 108 | 109 | public function getActivityPhotos(int $id, int $size = 2048, string $photo_sources = 'true') 110 | { 111 | $json = '{"response": 1}'; 112 | return $this->format($json); 113 | } 114 | 115 | public function getActivityZones(int $id) 116 | { 117 | $json = '{"response": 1}'; 118 | return $this->format($json); 119 | } 120 | 121 | public function getActivityLaps(int $id) 122 | { 123 | $json = '{"response": 1}'; 124 | return $this->format($json); 125 | } 126 | 127 | public function getActivityUploadStatus(int $id) 128 | { 129 | $json = '{"response": 1}'; 130 | return $this->format($json); 131 | } 132 | 133 | public function createActivity(string $name, string $type, string $start_date_local, int $elapsed_time, ?string $description = null, ?float $distance = null, ?int $private = null, ?int $trainer = null) 134 | { 135 | $json = '{"response": 1}'; 136 | return $this->format($json); 137 | } 138 | 139 | public function uploadActivity(string $file, ?string $activity_type = null, ?string $name = null, ?string $description = null, ?int $private = null, ?int $trainer = null, ?int $commute = null, ?string $data_type = null, ?string $external_id = null) 140 | { 141 | $json = '{"response": 1}'; 142 | return $this->format($json); 143 | } 144 | 145 | public function updateActivity(int $id, ?string $name = null, ?string $type = null, bool $private = false, bool $commute = false, bool $trainer = false, ?string $gear_id = null, ?string $description = null) 146 | { 147 | $json = '{"response": 1}'; 148 | return $this->format($json); 149 | } 150 | 151 | public function deleteActivity(int $id) 152 | { 153 | $json = '{"response": 1}'; 154 | return $this->format($json); 155 | } 156 | 157 | public function getGear(int $id) 158 | { 159 | $json = '{"response": 1}'; 160 | return $this->format($json); 161 | } 162 | 163 | public function getClub(int $id) 164 | { 165 | $json = '{"response": 1}'; 166 | return $this->format($json); 167 | } 168 | 169 | public function getClubMembers(int $id, ?int $page = null, ?int $per_page = null) 170 | { 171 | $json = '{"response": 1}'; 172 | return $this->format($json); 173 | } 174 | 175 | public function getClubActivities(int $id, ?int $page = null, ?int $per_page = null) 176 | { 177 | $json = '{"response": 1}'; 178 | return $this->format($json); 179 | } 180 | 181 | public function getClubAnnouncements(int $id) 182 | { 183 | $json = '{"response": 1}'; 184 | return $this->format($json); 185 | } 186 | 187 | public function getClubGroupEvents(int $id) 188 | { 189 | $json = '{"response": 1}'; 190 | return $this->format($json); 191 | } 192 | 193 | public function joinClub(int $id) 194 | { 195 | $json = '{"response": 1}'; 196 | return $this->format($json); 197 | } 198 | 199 | public function leaveClub(int $id) 200 | { 201 | $json = '{"response": 1}'; 202 | return $this->format($json); 203 | } 204 | 205 | public function getRoute(int $id) 206 | { 207 | $json = '{"response": 1}'; 208 | return $this->format($json); 209 | } 210 | 211 | public function getRouteAsGPX(int $id) 212 | { 213 | $gpx = ''; 214 | return $gpx; 215 | } 216 | 217 | public function getRouteAsTCX(int $id) 218 | { 219 | $tcx = ''; 220 | return $tcx; 221 | } 222 | 223 | public function getSegment(int $id) 224 | { 225 | $json = '{"response": 1}'; 226 | return $this->format($json); 227 | } 228 | 229 | public function getSegmentLeaderboard(int $id, ?string $gender = null, ?string $age_group = null, $weight_class = null, $following = null, $club_id = null, $date_range = null, $context_entries = null, $page = null, $per_page = null) 230 | { 231 | $json = '{"response": 1}'; 232 | return $this->format($json); 233 | } 234 | 235 | public function getSegmentExplorer(string $bounds, string $activity_type = 'riding', ?int $min_cat = null, ?int $max_cat = null) 236 | { 237 | $json = '{"response": 1}'; 238 | return $this->format($json); 239 | } 240 | 241 | public function getSegmentEffort(int $id, ?int $athlete_id = null, ?string $start_date_local = null, ?string $end_date_local = null, ?int $page = null, ?int $per_page = null) 242 | { 243 | $json = '{"response": 1}'; 244 | return $this->format($json); 245 | } 246 | 247 | public function getStreamsActivity(int $id, string $types, $resolution = null, string $series_type = 'distance') 248 | { 249 | $json = '{"response": 1}'; 250 | return $this->format($json); 251 | } 252 | 253 | public function getStreamsEffort(int $id, string $types, $resolution = null, string $series_type = 'distance') 254 | { 255 | $json = '{"response": 1}'; 256 | return $this->format($json); 257 | } 258 | 259 | public function getStreamsSegment(int $id, string $types, $resolution = null, string $series_type = 'distance') 260 | { 261 | $json = '{"response": 1}'; 262 | return $this->format($json); 263 | } 264 | 265 | public function getStreamsRoute(int $id) 266 | { 267 | $json = '{"response": 1}'; 268 | return $this->format($json); 269 | } 270 | 271 | public function createWebhookSubscription(int $clientId, string $clientSecret, string $callbackUrl, string $verifyToken) 272 | { 273 | $json = '{"id": 123, "callback_url": "' . $callbackUrl . '", "created_at": "2023-01-01T00:00:00Z"}'; 274 | return $this->format($json); 275 | } 276 | 277 | public function listWebhookSubscriptions(int $clientId, string $clientSecret) 278 | { 279 | $json = '[{"id": 123, "callback_url": "https://example.com/webhook", "created_at": "2023-01-01T00:00:00Z"}]'; 280 | return $this->format($json); 281 | } 282 | 283 | public function deleteWebhookSubscription(int $clientId, string $clientSecret, int $subscriptionId) 284 | { 285 | $json = '{"success": true}'; 286 | return $this->format($json); 287 | } 288 | 289 | /** 290 | * @param string $result 291 | */ 292 | private function format($result) 293 | { 294 | return json_decode($result, true); 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/Strava/API/Webhook.php: -------------------------------------------------------------------------------- 1 | false, 42 | 'error' => 'Invalid request method. Expected GET.' 43 | ]; 44 | } 45 | 46 | if ($hubMode !== 'subscribe') { 47 | return [ 48 | 'success' => false, 49 | 'error' => 'Invalid hub_mode. Expected "subscribe".' 50 | ]; 51 | } 52 | 53 | if (empty($hubChallenge)) { 54 | return [ 55 | 'success' => false, 56 | 'error' => 'Missing hub_challenge parameter.' 57 | ]; 58 | } 59 | 60 | if ($hubVerifyToken !== $verifyToken) { 61 | return [ 62 | 'success' => false, 63 | 'error' => 'Invalid verify token.' 64 | ]; 65 | } 66 | 67 | $result = [ 68 | 'success' => true, 69 | 'challenge' => $hubChallenge 70 | ]; 71 | 72 | // Auto-respond if requested 73 | if ($autoRespond) { 74 | self::sendChallengeResponse($hubChallenge); 75 | } 76 | 77 | return $result; 78 | } 79 | 80 | /** 81 | * Send challenge response to Strava 82 | * 83 | * @param string $challenge The challenge string from Strava 84 | * @return void 85 | */ 86 | public static function sendChallengeResponse(string $challenge): void 87 | { 88 | header('Content-Type: application/json'); 89 | http_response_code(200); 90 | echo json_encode(['hub.challenge' => $challenge]); 91 | exit; 92 | } 93 | 94 | /** 95 | * Process incoming webhook event 96 | * 97 | * @param callable|null $eventHandler Optional callback function to handle the event 98 | * @return array Parsed webhook event data 99 | */ 100 | public static function processEvent(?callable $eventHandler = null): array 101 | { 102 | $method = $_SERVER['REQUEST_METHOD'] ?? ''; 103 | 104 | if ($method !== 'POST') { 105 | return [ 106 | 'success' => false, 107 | 'error' => 'Invalid request method. Expected POST.' 108 | ]; 109 | } 110 | 111 | $rawBody = file_get_contents('php://input'); 112 | if (empty($rawBody)) { 113 | return [ 114 | 'success' => false, 115 | 'error' => 'Empty request body.' 116 | ]; 117 | } 118 | 119 | $data = json_decode($rawBody, true); 120 | if (json_last_error() !== JSON_ERROR_NONE) { 121 | return [ 122 | 'success' => false, 123 | 'error' => 'Invalid JSON: ' . json_last_error_msg() 124 | ]; 125 | } 126 | 127 | // Validate required webhook event fields (matching your tested implementation) 128 | if (!isset($data['object_type']) || !isset($data['aspect_type']) || !isset($data['object_id']) || !isset($data['owner_id'])) { 129 | return [ 130 | 'success' => false, 131 | 'error' => 'Invalid webhook payload structure. Missing required fields: object_type, aspect_type, object_id, or owner_id.' 132 | ]; 133 | } 134 | 135 | // Process the webhook event with custom handler if provided 136 | if ($eventHandler) { 137 | try { 138 | $success = call_user_func($eventHandler, $data); 139 | if (!$success) { 140 | return [ 141 | 'success' => false, 142 | 'error' => 'Event processing failed.' 143 | ]; 144 | } 145 | } catch (\Exception $e) { 146 | return [ 147 | 'success' => false, 148 | 'error' => 'Event processing failed: ' . $e->getMessage() 149 | ]; 150 | } 151 | } 152 | 153 | return [ 154 | 'success' => true, 155 | 'event' => $data 156 | ]; 157 | } 158 | 159 | /** 160 | * Verify webhook event signature (if using signature verification) 161 | * 162 | * @param string $payload The raw request body 163 | * @param string $signature The X-Hub-Signature header value 164 | * @param string $secret Your webhook secret (if configured) 165 | * @return bool True if signature is valid 166 | */ 167 | public static function verifySignature(string $payload, string $signature, string $secret): bool 168 | { 169 | if (empty($secret)) { 170 | return true; // No secret configured, skip verification 171 | } 172 | 173 | $expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret); 174 | return hash_equals($expectedSignature, $signature); 175 | } 176 | 177 | /** 178 | * Get webhook event type 179 | * 180 | * @param array $event The parsed webhook event 181 | * @return string The event type (e.g., 'activity.create', 'activity.update') 182 | */ 183 | public static function getEventType(array $event): string 184 | { 185 | return $event['object_type'] . '.' . $event['aspect_type']; 186 | } 187 | 188 | /** 189 | * Check if event is for a specific object type 190 | * 191 | * @param array $event The parsed webhook event 192 | * @param string $objectType The object type to check (e.g., 'activity', 'athlete') 193 | * @return bool True if event is for the specified object type 194 | */ 195 | public static function isObjectType(array $event, string $objectType): bool 196 | { 197 | return isset($event['object_type']) && $event['object_type'] === $objectType; 198 | } 199 | 200 | /** 201 | * Check if event is a specific aspect type 202 | * 203 | * @param array $event The parsed webhook event 204 | * @param string $aspectType The aspect type to check (e.g., 'create', 'update', 'delete') 205 | * @return bool True if event is the specified aspect type 206 | */ 207 | public static function isAspectType(array $event, string $aspectType): bool 208 | { 209 | return isset($event['aspect_type']) && $event['aspect_type'] === $aspectType; 210 | } 211 | 212 | /** 213 | * Handle complete webhook endpoint (both GET and POST requests) 214 | * 215 | * This method provides a complete webhook endpoint handler that can be used 216 | * as the main entry point for your webhook endpoint. 217 | * 218 | * @param string $verifyToken The verify token for subscription validation 219 | * @param callable|null $eventHandler Optional callback function to handle events 220 | * @return void This method will exit after sending response 221 | */ 222 | public static function handleWebhookEndpoint(string $verifyToken, ?callable $eventHandler = null): void 223 | { 224 | $method = $_SERVER['REQUEST_METHOD'] ?? ''; 225 | 226 | // Set content type based on request method 227 | if ($method === 'GET') { 228 | header('Content-Type: application/json'); 229 | } else { 230 | header('Content-Type: text/plain'); 231 | } 232 | 233 | // Handle GET requests (webhook verification) 234 | if ($method === 'GET') { 235 | $result = self::handleSubscriptionChallenge($verifyToken, false); 236 | 237 | if ($result['success']) { 238 | // Return the challenge value as JSON as required by Strava 239 | echo json_encode(['hub.challenge' => $result['challenge']]); 240 | exit; 241 | } else { 242 | http_response_code(400); 243 | echo 'Invalid verification request'; 244 | exit; 245 | } 246 | } 247 | 248 | // Handle POST requests (webhook events) 249 | if ($method === 'POST') { 250 | $result = self::processEvent($eventHandler); 251 | 252 | if ($result['success']) { 253 | http_response_code(200); 254 | echo 'OK'; 255 | exit; 256 | } else { 257 | http_response_code(400); 258 | echo $result['error']; 259 | exit; 260 | } 261 | } 262 | 263 | // Invalid method 264 | http_response_code(405); 265 | echo 'Method not allowed'; 266 | exit; 267 | } 268 | 269 | /** 270 | * Generate a random verify token 271 | * 272 | * @param int $length Length of the token (default: 32) 273 | * @return string Random verify token 274 | */ 275 | public static function generateVerifyToken(int $length = 32): string 276 | { 277 | if (function_exists('random_bytes')) { 278 | return bin2hex(random_bytes($length / 2)); 279 | } elseif (function_exists('openssl_random_pseudo_bytes')) { 280 | return bin2hex(openssl_random_pseudo_bytes($length / 2)); 281 | } else { 282 | // Fallback for older PHP versions 283 | $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 284 | $token = ''; 285 | for ($i = 0; $i < $length; $i++) { 286 | $token .= $characters[rand(0, strlen($characters) - 1)]; 287 | } 288 | return $token; 289 | } 290 | } 291 | 292 | /** 293 | * Check if webhook event is an activity creation event 294 | * 295 | * @param array $event The webhook event data 296 | * @return bool True if this is an activity creation event 297 | */ 298 | public static function isActivityCreationEvent(array $event): bool 299 | { 300 | return self::isObjectType($event, 'activity') && self::isAspectType($event, 'create'); 301 | } 302 | 303 | /** 304 | * Get athlete ID from webhook event 305 | * 306 | * @param array $event The webhook event data 307 | * @return int|null The athlete ID or null if not found 308 | */ 309 | public static function getAthleteId(array $event): ?int 310 | { 311 | return isset($event['owner_id']) ? (int)$event['owner_id'] : null; 312 | } 313 | 314 | /** 315 | * Get object ID from webhook event 316 | * 317 | * @param array $event The webhook event data 318 | * @return int|null The object ID or null if not found 319 | */ 320 | public static function getObjectId(array $event): ?int 321 | { 322 | return isset($event['object_id']) ? (int)$event['object_id'] : null; 323 | } 324 | 325 | /** 326 | * Validate webhook event payload structure 327 | * 328 | * @param array $event The webhook event data 329 | * @return array Validation result with 'valid' boolean and 'error' message if invalid 330 | */ 331 | public static function validateEventPayload(array $event): array 332 | { 333 | // Check required fields (matching your tested implementation) 334 | $requiredFields = ['object_type', 'aspect_type', 'object_id', 'owner_id']; 335 | 336 | foreach ($requiredFields as $field) { 337 | if (!isset($event[$field])) { 338 | return [ 339 | 'valid' => false, 340 | 'error' => "Missing required field: {$field}" 341 | ]; 342 | } 343 | } 344 | 345 | // Validate field types 346 | if (!is_string($event['object_type'])) { 347 | return [ 348 | 'valid' => false, 349 | 'error' => 'object_type must be a string' 350 | ]; 351 | } 352 | 353 | if (!is_string($event['aspect_type'])) { 354 | return [ 355 | 'valid' => false, 356 | 'error' => 'aspect_type must be a string' 357 | ]; 358 | } 359 | 360 | if (!is_numeric($event['object_id'])) { 361 | return [ 362 | 'valid' => false, 363 | 'error' => 'object_id must be numeric' 364 | ]; 365 | } 366 | 367 | if (!is_numeric($event['owner_id'])) { 368 | return [ 369 | 'valid' => false, 370 | 'error' => 'owner_id must be numeric' 371 | ]; 372 | } 373 | 374 | return [ 375 | 'valid' => true, 376 | 'error' => null 377 | ]; 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | StravaPHP 2 | ========= 3 | [![Build Status](https://github.com/basvandorst/StravaPHP/workflows/PHP/badge.svg)](https://github.com/basvandorst/StravaPHP/actions/workflows/php.yml) [![Coverage Status](https://coveralls.io/repos/github/basvandorst/StravaPHP/badge.svg?branch=develop)](https://coveralls.io/github/basvandorst/StravaPHP?branch=develop) 4 | 5 | **TLDR;** Strava V3 API PHP client with OAuth authentication 6 | 7 | The Strava V3 API is a publicly available interface allowing developers access 8 | to the rich [Strava](https://www.strava.com/) dataset. The interface is stable and currently used by the 9 | Strava mobile applications. However, changes are occasionally made to improve 10 | performance and enhance features. See Strava's [changelog](https://strava.github.io/api/v3/changelog/) for more details. 11 | 12 | In this GitHub repository you can find the PHP implementation of the 13 | Strava V3 API. The current version of StravaPHP combines the V3 API 14 | with a proper OAuth authentication. 15 | 16 | ## Getting started 17 | ### Get your API key 18 | All calls to the Strava API require an access token defining the athlete and 19 | application making the call. Any registered Strava user can obtain an access 20 | token by first creating an application at [https://developers.strava.com](https://developers.strava.com/) 21 | 22 | ### Composer package 23 | Use composer to install this StravaPHP package. 24 | 25 | ``` 26 | { 27 | "require": { 28 | "basvandorst/stravaphp": "^2.0.0" 29 | } 30 | } 31 | ``` 32 | 33 | ### StravaPHP usage 34 | #### First, authorisation and authentication 35 | ```php 36 | 1234, 45 | 'clientSecret' => 'APP-TOKEN', 46 | 'redirectUri' => 'http://my-app/callback.php' 47 | ]; 48 | $oauth = new OAuth($options); 49 | 50 | if (!isset($_GET['code'])) { 51 | print 'Connect'; 63 | } else { 64 | $token = $oauth->getAccessToken('authorization_code', [ 65 | 'code' => $_GET['code'] 66 | ]); 67 | print $token->getToken(); 68 | } 69 | } catch(Exception $e) { 70 | print $e->getMessage(); 71 | } 72 | ``` 73 | #### Then, call your API method! 74 | ```php 75 | 'https://www.strava.com/api/v3/']); 84 | $service = new REST($token->getToken(), $adapter); // Define your user token here. 85 | $client = new Client($service); 86 | 87 | $athlete = $client->getAthlete(); 88 | print_r($athlete); 89 | 90 | $activities = $client->getAthleteActivities(); 91 | print_r($activities); 92 | 93 | $club = $client->getClub(9729); 94 | print_r($club); 95 | } catch(Exception $e) { 96 | print $e->getMessage(); 97 | } 98 | ``` 99 | 100 | ## Class documentation 101 | 102 | ### Strava\API\Factory 103 | #### Usage 104 | ```php 105 | use Strava\API\Factory; 106 | 107 | // Configure your app ID, app token and callback uri 108 | $factory = new Factory(); 109 | $OAuthClient = $factory->getOAuthClient(1234, 'APP-TOKEN', 'http://my-app/callback.php'); 110 | ``` 111 | #### Methods 112 | ```php 113 | $factory->getOAuthClient($client_id, $client_secret, $redirect_uri); 114 | $factory->getAPIClient($token); 115 | ``` 116 | 117 | ### Strava\API\OAuth 118 | #### Usage 119 | ```php 120 | use Strava\API\OAuth; 121 | 122 | // Parameter information: https://strava.github.io/api/v3/oauth/#get-authorize 123 | $options = [ 124 | 'clientId' => 1234, 125 | 'clientSecret' => 'APP-TOKEN', 126 | 'redirectUri' => 'http://my-app/callback.php' 127 | ]; 128 | $oauth = new OAuth($options); 129 | 130 | // The OAuth authorization procces (1st; let the user approve, 2nd; token exchange with Strava) 131 | if (!isset($_GET['code'])) { 132 | print 'Connect'; 144 | } else { 145 | $token = $oauth->getAccessToken('authorization_code', [ 146 | 'code' => $_GET['code'] 147 | ]); 148 | print $token->getToken(); 149 | } 150 | ``` 151 | #### Methods 152 | ```php 153 | $oauth->getAuthorizationUrl($options = []); 154 | $oauth->getAccessToken($grant = 'authorization_code', $params = []); 155 | ``` 156 | ### Strava\API\Client 157 | #### Usage 158 | ```php 159 | // REST adapter (We use `Guzzle` in this project) 160 | use GuzzleHttp\Client as GuzzleClient; 161 | use Strava\API\Service\REST; 162 | use Strava\API\Client; 163 | 164 | $adapter = new GuzzleClient(['base_uri' => 'https://www.strava.com/api/v3/']); 165 | // Service to use (Service\Stub is also available for test purposes) 166 | $service = new REST('RECEIVED-TOKEN', $adapter); 167 | 168 | // Receive the athlete! 169 | $client = new Client($service); 170 | $athlete = $client->getAthlete(); 171 | print_r($athlete); 172 | ``` 173 | #### Methods 174 | ```php 175 | $client->getAthlete($id = null); 176 | $client->getAthleteStats($id); 177 | $client->getAthleteClubs(); 178 | $client->getAthleteRoutes($id, $type = null, $after = null, $page = null, $per_page = null); 179 | $client->getAthleteActivities($before = null, $after = null, $page = null, $per_page = null); 180 | $client->getAthleteFriends($id = null, $page = null, $per_page = null); 181 | $client->getAthleteFollowers($id = null, $page = null, $per_page = null); 182 | $client->getAthleteBothFollowing($id, $page = null, $per_page = null); 183 | $client->getAthleteKom($id, $page = null, $per_page = null); 184 | $client->getAthleteZones(); 185 | $client->getAthleteStarredSegments($id = null, $page = null, $per_page = null); 186 | $client->updateAthlete($city, $state, $country, $sex, $weight); 187 | $client->getActivityFollowing($before = null, $page = null, $per_page = null); 188 | $client->getActivity($id, $include_all_efforts = null); 189 | $client->getActivityComments($id, $markdown = null, $page = null, $per_page = null); 190 | $client->getActivityKudos($id, $page = null, $per_page = null); 191 | $client->getActivityPhotos($id, $size = 2048, $photo_sources = 'true'); 192 | $client->getActivityZones($id); 193 | $client->getActivityLaps($id); 194 | $client->getActivityUploadStatus($id); 195 | $client->createActivity($name, $type, $start_date_local, $elapsed_time, $description = null, $distance = null); 196 | $client->uploadActivity($file, $activity_type = null, $name = null, $description = null, $private = null, $commute = null, $trainer = null, $data_type = null, $external_id = null); 197 | $client->updateActivity($id, $name = null, $type = null, $private = false, $commute = false, $trainer = false, $gear_id = null, $description = null); 198 | $client->deleteActivity($id); 199 | $client->getGear($id); 200 | $client->getClub($id); 201 | $client->getClubMembers($id, $page = null, $per_page = null); 202 | $client->getClubActivities($id, $page = null, $per_page = null); 203 | $client->getRoute($id); 204 | $client->getRouteAsGPX($id); 205 | $client->getRouteAsTCX($id); 206 | $client->getSegment($id); 207 | $client->getSegmentLeaderboard($id, $gender = null, $age_group = null, $weight_class = null, $following = null, $club_id = null, $date_range = null, $page = null, $per_page = null); 208 | $client->getSegmentExplorer($bounds, $activity_type = 'riding', $min_cat = null, $max_cat = null); 209 | $client->getSegmentEffort($id, $athlete_id = null, $start_date_local = null, $end_date_local = null, $page = null, $per_page = null); 210 | $client->getStreamsActivity($id, $types, $resolution = null, $series_type = 'distance'); 211 | $client->getStreamsEffort($id, $types, $resolution = null, $series_type = 'distance'); 212 | $client->getStreamsSegment($id, $types, $resolution = null, $series_type = 'distance'); 213 | $client->getStreamsRoute($id); 214 | $client->createWebhookSubscription($clientId, $clientSecret, $callbackUrl, $verifyToken); 215 | $client->listWebhookSubscriptions($clientId, $clientSecret); 216 | $client->deleteWebhookSubscription($clientId, $clientSecret, $subscriptionId); 217 | ``` 218 | 219 | ## Webhook Integration 220 | 221 | StravaPHP now includes comprehensive webhook support for real-time event notifications. This allows you to receive instant notifications when activities are created, updated, or deleted. 222 | 223 | ### Webhook Subscription Management 224 | 225 | ```php 226 | 'https://www.strava.com/api/v3/']); 235 | $service = new REST('YOUR_ACCESS_TOKEN', $adapter); 236 | $client = new Client($service); 237 | 238 | // Your Strava app credentials 239 | $clientId = 12345; 240 | $clientSecret = 'your_client_secret'; 241 | $callbackUrl = 'https://yourdomain.com/webhook-endpoint.php'; 242 | $verifyToken = 'your_random_verify_token'; 243 | 244 | try { 245 | // Create a webhook subscription 246 | $subscription = $client->createWebhookSubscription( 247 | $clientId, 248 | $clientSecret, 249 | $callbackUrl, 250 | $verifyToken 251 | ); 252 | 253 | echo "Webhook subscription created: " . $subscription['id'] . "\n"; 254 | 255 | // List existing subscriptions 256 | $subscriptions = $client->listWebhookSubscriptions($clientId, $clientSecret); 257 | echo "Active subscriptions: " . count($subscriptions) . "\n"; 258 | 259 | // Delete a subscription 260 | $client->deleteWebhookSubscription($clientId, $clientSecret, $subscription['id']); 261 | echo "Subscription deleted\n"; 262 | 263 | } catch (Exception $e) { 264 | echo "Error: " . $e->getMessage() . "\n"; 265 | } 266 | ``` 267 | 268 | ### Webhook Event Handling 269 | 270 | ```php 271 | 'success']); 318 | } else { 319 | // Handle error 320 | http_response_code(400); 321 | echo json_encode(['error' => $eventResult['error']]); 322 | } 323 | ``` 324 | 325 | ### Webhook Helper Methods 326 | 327 | The `Webhook` class provides several utility methods: 328 | 329 | ```php 330 | // Get event type (e.g., 'activity.create') 331 | $eventType = Webhook::getEventType($event); 332 | 333 | // Check if event is for specific object type 334 | $isActivity = Webhook::isObjectType($event, 'activity'); 335 | $isAthlete = Webhook::isObjectType($event, 'athlete'); 336 | 337 | // Check if event is specific aspect type 338 | $isCreate = Webhook::isAspectType($event, 'create'); 339 | $isUpdate = Webhook::isAspectType($event, 'update'); 340 | $isDelete = Webhook::isAspectType($event, 'delete'); 341 | 342 | // Verify webhook signature (if using signature verification) 343 | $isValid = Webhook::verifySignature($payload, $signature, $secret); 344 | ``` 345 | 346 | ### Webhook Events 347 | 348 | Strava webhooks support the following event types: 349 | 350 | - **Activity Events:** 351 | - `activity.create` - New activity created 352 | - `activity.update` - Activity updated 353 | - `activity.delete` - Activity deleted 354 | 355 | - **Athlete Events:** 356 | - `athlete.update` - Athlete profile updated 357 | 358 | For more information about webhook events, see the [Strava Webhook Documentation](https://developers.strava.com/docs/webhooks/). 359 | 360 | ## UML diagrams 361 | ### Class diagram 362 | ![class](https://cloud.githubusercontent.com/assets/1196963/4705696/764cd4e2-587e-11e4-8c9f-d265255ee0a2.png) 363 | ### Sequence diagram 364 | ![sequence](https://cloud.githubusercontent.com/assets/1196963/4781256/14ad07f2-5c93-11e4-9f2c-b304fe312f05.png) 365 | 366 | ## About StravaPHP 367 | ### Used libraries 368 | - [Strava API](https://strava.github.io/api/) 369 | - [thephpleague/oauth2-client](https://github.com/thephpleague/oauth2-client/) 370 | - [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) 371 | 372 | ### Development 373 | The StravaPHP library was created by Bas van Dorst, [software engineer](https://www.linkedin.com/in/basvandorst) and cyclist enthusiast. 374 | And of course, special thanks to all [contributors](https://github.com/basvandorst/StravaPHP/graphs/contributors) 375 | 376 | ### Contributing 377 | All issues and pull requests should be filled on the basvandorst/StravaPHP repository. 378 | 379 | ### License 380 | The StravaPHP library is open-source software licensed under MIT license. 381 | -------------------------------------------------------------------------------- /src/Strava/API/Service/REST.php: -------------------------------------------------------------------------------- 1 | getToken(); 46 | } 47 | $this->token = $token; 48 | $this->adapter = $adapter; 49 | $this->responseVerbosity = $responseVerbosity; 50 | } 51 | 52 | protected function getToken() 53 | { 54 | return $this->token; 55 | } 56 | 57 | /** 58 | * Get a request result. 59 | * Returns an array with a response body or and error code => reason. 60 | * @param ResponseInterface|string $response 61 | * @return array|string 62 | */ 63 | protected function getResult($response) 64 | { 65 | // Workaround for export methods getRouteAsGPX, getRouteAsTCX: 66 | if (is_string($response)) { 67 | return $response; 68 | } 69 | 70 | $status = $response->getStatusCode(); 71 | 72 | $expandedResponse = []; 73 | 74 | $expandedResponse['headers'] = $response->getHeaders(); 75 | $expandedResponse['body'] = json_decode($response->getBody(), true); 76 | $expandedResponse['success'] = $status === 200 || $status === 201; 77 | $expandedResponse['status'] = $status; 78 | 79 | return $expandedResponse; 80 | } 81 | 82 | /** 83 | * Get an API request response and handle possible exceptions. 84 | * 85 | * @param string $method 86 | * @param string $path 87 | * @param array $parameters 88 | * 89 | * @return array|mixed|string 90 | * @throws \GuzzleHttp\Exception\GuzzleException|Exception 91 | */ 92 | protected function getResponse(string $method, string $path, array $parameters) 93 | { 94 | try { 95 | $response = $this->adapter->request($method, $path, $parameters); 96 | $result = $this->getResult($response); 97 | 98 | if ($this->responseVerbosity === 0 && !is_string($result)) { 99 | return $result["body"]; 100 | } 101 | 102 | return $result; 103 | } catch (\Exception $e) { 104 | throw new Exception('[SERVICE] ' . $e->getMessage()); 105 | } 106 | } 107 | 108 | public function getAthlete(?int $id = null) 109 | { 110 | $path = 'athlete'; 111 | if (isset($id)) { 112 | $path = 'athletes/' . $id; 113 | } 114 | $parameters['query'] = ['access_token' => $this->getToken()]; 115 | 116 | return $this->getResponse('GET', $path, $parameters); 117 | } 118 | 119 | public function getAthleteStats(int $id) 120 | { 121 | $path = 'athletes/' . $id . '/stats'; 122 | $parameters['query'] = ['access_token' => $this->getToken()]; 123 | 124 | return $this->getResponse('GET', $path, $parameters); 125 | } 126 | 127 | public function getAthleteRoutes(int $id, ?string $type = null, ?int $after = null, ?int $page = null, ?int $per_page = null) 128 | { 129 | $path = 'athletes/' . $id . '/routes'; 130 | $parameters['query'] = [ 131 | 'type' => $type, 132 | 'after' => $after, 133 | 'page' => $page, 134 | 'per_page' => $per_page, 135 | 'access_token' => $this->getToken(), 136 | ]; 137 | 138 | return $this->getResponse('GET', $path, $parameters); 139 | } 140 | 141 | public function getAthleteClubs() 142 | { 143 | $path = 'athlete/clubs'; 144 | $parameters['query'] = ['access_token' => $this->getToken()]; 145 | 146 | return $this->getResponse('GET', $path, $parameters); 147 | } 148 | 149 | public function getAthleteActivities(?string $before = null, ?string $after = null, ?int $page = null, ?int $per_page = null) 150 | { 151 | $path = 'athlete/activities'; 152 | $parameters['query'] = [ 153 | 'before' => $before, 154 | 'after' => $after, 155 | 'page' => $page, 156 | 'per_page' => $per_page, 157 | 'access_token' => $this->getToken(), 158 | ]; 159 | 160 | return $this->getResponse('GET', $path, $parameters); 161 | } 162 | 163 | public function getAthleteFriends(?int $id = null, ?int $page = null, ?int $per_page = null) 164 | { 165 | $path = 'athlete/friends'; 166 | if (isset($id)) { 167 | $path = 'athletes/' . $id . '/friends'; 168 | } 169 | $parameters['query'] = [ 170 | 'page' => $page, 171 | 'per_page' => $per_page, 172 | 'access_token' => $this->getToken(), 173 | ]; 174 | 175 | return $this->getResponse('GET', $path, $parameters); 176 | } 177 | 178 | public function getAthleteFollowers(?int $id = null, ?int $page = null, ?int $per_page = null) 179 | { 180 | $path = 'athlete/followers'; 181 | if (isset($id)) { 182 | $path = 'athletes/' . $id . '/followers'; 183 | } 184 | $parameters['query'] = [ 185 | 'page' => $page, 186 | 'per_page' => $per_page, 187 | 'access_token' => $this->getToken(), 188 | ]; 189 | 190 | return $this->getResponse('GET', $path, $parameters); 191 | } 192 | 193 | public function getAthleteBothFollowing(int $id, ?int $page = null, ?int $per_page = null) 194 | { 195 | $path = 'athletes/' . $id . '/both-following'; 196 | $parameters['query'] = [ 197 | 'page' => $page, 198 | 'per_page' => $per_page, 199 | 'access_token' => $this->getToken(), 200 | ]; 201 | 202 | return $this->getResponse('GET', $path, $parameters); 203 | } 204 | 205 | public function getAthleteKom(int $id, ?int $page = null, ?int $per_page = null) 206 | { 207 | $path = 'athletes/' . $id . '/koms'; 208 | $parameters['query'] = [ 209 | 'page' => $page, 210 | 'per_page' => $per_page, 211 | 'access_token' => $this->getToken(), 212 | ]; 213 | 214 | return $this->getResponse('GET', $path, $parameters); 215 | } 216 | 217 | public function getAthleteZones() 218 | { 219 | $path = 'athlete/zones'; 220 | $parameters['query'] = ['access_token' => $this->getToken()]; 221 | 222 | return $this->getResponse('GET', $path, $parameters); 223 | } 224 | 225 | public function getAthleteStarredSegments(?int $id = null, ?int $page = null, ?int $per_page = null) 226 | { 227 | $path = 'segments/starred'; 228 | if (isset($id)) { 229 | $path = 'athletes/' . $id . '/segments/starred'; 230 | // ...wrong in Strava documentation 231 | } 232 | $parameters['query'] = [ 233 | 'page' => $page, 234 | 'per_page' => $per_page, 235 | 'access_token' => $this->getToken(), 236 | ]; 237 | 238 | return $this->getResponse('GET', $path, $parameters); 239 | } 240 | 241 | public function updateAthlete(string $city, string $state, string $country, string $sex, float $weight) 242 | { 243 | $path = 'athlete'; 244 | $parameters['query'] = [ 245 | 'city' => $city, 246 | 'state' => $state, 247 | 'country' => $country, 248 | 'sex' => $sex, 249 | 'weight' => $weight, 250 | 'access_token' => $this->getToken(), 251 | ]; 252 | 253 | return $this->getResponse('PUT', $path, $parameters); 254 | } 255 | 256 | public function getActivity(int $id, ?bool $include_all_efforts = null) 257 | { 258 | $path = 'activities/' . $id; 259 | $parameters['query'] = [ 260 | 'include_all_efforts' => $include_all_efforts, 261 | 'access_token' => $this->getToken(), 262 | ]; 263 | 264 | return $this->getResponse('GET', $path, $parameters); 265 | } 266 | 267 | public function getActivityComments(int $id, ?bool $markdown = null, ?int $page = null, ?int $per_page = null) 268 | { 269 | $path = 'activities/' . $id . '/comments'; 270 | $parameters['query'] = [ 271 | 'markdown' => $markdown, 272 | 'page' => $page, 273 | 'per_page' => $per_page, 274 | 'access_token' => $this->getToken(), 275 | ]; 276 | 277 | return $this->getResponse('GET', $path, $parameters); 278 | } 279 | 280 | public function getActivityKudos(int $id, ?int $page = null, ?int $per_page = null) 281 | { 282 | $path = 'activities/' . $id . '/kudos'; 283 | $parameters['query'] = [ 284 | 'page' => $page, 285 | 'per_page' => $per_page, 286 | 'access_token' => $this->getToken(), 287 | ]; 288 | 289 | return $this->getResponse('GET', $path, $parameters); 290 | } 291 | 292 | public function getActivityPhotos(int $id, int $size = 2048, string $photo_sources = 'true') 293 | { 294 | $path = 'activities/' . $id . '/photos'; 295 | $parameters['query'] = [ 296 | 'size' => $size, 297 | 'photo_sources' => $photo_sources, 298 | 'access_token' => $this->getToken(), 299 | ]; 300 | 301 | return $this->getResponse('GET', $path, $parameters); 302 | } 303 | 304 | public function getActivityZones(int $id) 305 | { 306 | $path = 'activities/' . $id . '/zones'; 307 | $parameters['query'] = ['access_token' => $this->getToken()]; 308 | 309 | return $this->getResponse('GET', $path, $parameters); 310 | } 311 | 312 | public function getActivityLaps(int $id) 313 | { 314 | $path = 'activities/' . $id . '/laps'; 315 | $parameters['query'] = ['access_token' => $this->getToken()]; 316 | 317 | return $this->getResponse('GET', $path, $parameters); 318 | } 319 | 320 | public function getActivityUploadStatus(int $id) 321 | { 322 | $path = 'uploads/' . $id; 323 | $parameters['query'] = ['access_token' => $this->getToken()]; 324 | 325 | return $this->getResponse('GET', $path, $parameters); 326 | } 327 | 328 | public function createActivity(string $name, string $type, string $start_date_local, int $elapsed_time, ?string $description = null, ?float $distance = null, ?int $private = null, ?int $trainer = null) 329 | { 330 | $path = 'activities'; 331 | $parameters['query'] = [ 332 | 'name' => $name, 333 | 'type' => $type, 334 | 'start_date_local' => $start_date_local, 335 | 'elapsed_time' => $elapsed_time, 336 | 'description' => $description, 337 | 'distance' => $distance, 338 | 'private' => $private, 339 | 'trainer' => $trainer, 340 | 'access_token' => $this->getToken(), 341 | ]; 342 | 343 | return $this->getResponse('POST', $path, $parameters); 344 | } 345 | 346 | public function uploadActivity(string $file, ?string $activity_type = null, ?string $name = null, ?string $description = null, ?int $private = null, ?int $trainer = null, ?int $commute = null, ?string $data_type = null, ?string $external_id = null) 347 | { 348 | $path = 'uploads'; 349 | $parameters['query'] = [ 350 | 'activity_type' => $activity_type, 351 | 'name' => $name, 352 | 'description' => $description, 353 | 'private' => $private, 354 | 'trainer' => $trainer, 355 | 'commute' => $commute, 356 | 'data_type' => $data_type, 357 | 'external_id' => $external_id, 358 | 'file' => curl_file_create($file), 359 | 'file_hack' => '@' . ltrim($file, '@'), 360 | 'access_token' => $this->getToken(), 361 | ]; 362 | 363 | return $this->getResponse('POST', $path, $parameters); 364 | } 365 | 366 | public function updateActivity(int $id, ?string $name = null, ?string $type = null, bool $private = false, bool $commute = false, bool $trainer = false, ?string $gear_id = null, ?string $description = null) 367 | { 368 | $path = 'activities/' . $id; 369 | $parameters['query'] = [ 370 | 'name' => $name, 371 | 'type' => $type, 372 | 'private' => $private, 373 | 'commute' => $commute, 374 | 'trainer' => $trainer, 375 | 'gear_id' => $gear_id, 376 | 'description' => $description, 377 | 'access_token' => $this->getToken(), 378 | ]; 379 | 380 | return $this->getResponse('PUT', $path, $parameters); 381 | } 382 | 383 | public function deleteActivity(int $id) 384 | { 385 | $path = 'activities/' . $id; 386 | $parameters['query'] = ['access_token' => $this->getToken()]; 387 | 388 | return $this->getResponse('DELETE', $path, $parameters); 389 | } 390 | 391 | public function getGear(int $id) 392 | { 393 | $path = 'gear/' . $id; 394 | $parameters['query'] = ['access_token' => $this->getToken()]; 395 | 396 | return $this->getResponse('GET', $path, $parameters); 397 | } 398 | 399 | public function getClub(int $id) 400 | { 401 | $path = 'clubs/' . $id; 402 | $parameters['query'] = ['access_token' => $this->getToken()]; 403 | 404 | return $this->getResponse('GET', $path, $parameters); 405 | } 406 | 407 | public function getClubMembers(int $id, ?int $page = null, ?int $per_page = null) 408 | { 409 | $path = 'clubs/' . $id . '/members'; 410 | $parameters['query'] = [ 411 | 'page' => $page, 412 | 'per_page' => $per_page, 413 | 'access_token' => $this->getToken(), 414 | ]; 415 | 416 | return $this->getResponse('GET', $path, $parameters); 417 | } 418 | 419 | public function getClubActivities(int $id, ?int $page = null, ?int $per_page = null) 420 | { 421 | $path = 'clubs/' . $id . '/activities'; 422 | $parameters['query'] = [ 423 | 'page' => $page, 424 | 'per_page' => $per_page, 425 | 'access_token' => $this->getToken(), 426 | ]; 427 | 428 | return $this->getResponse('GET', $path, $parameters); 429 | } 430 | 431 | public function getClubAnnouncements(int $id) 432 | { 433 | $path = 'clubs/' . $id . '/announcements'; 434 | $parameters['query'] = ['access_token' => $this->getToken()]; 435 | 436 | return $this->getResponse('GET', $path, $parameters); 437 | } 438 | 439 | public function getClubGroupEvents(int $id) 440 | { 441 | $path = 'clubs/' . $id . '/group_events'; 442 | $parameters['query'] = ['access_token' => $this->getToken()]; 443 | 444 | return $this->getResponse('GET', $path, $parameters); 445 | } 446 | 447 | public function joinClub(int $id) 448 | { 449 | $path = 'clubs/' . $id . '/join'; 450 | $parameters['query'] = ['access_token' => $this->getToken()]; 451 | 452 | return $this->getResponse('POST', $path, $parameters); 453 | } 454 | 455 | public function leaveClub(int $id) 456 | { 457 | $path = 'clubs/' . $id . '/leave'; 458 | $parameters['query'] = ['access_token' => $this->getToken()]; 459 | 460 | return $this->getResponse('POST', $path, $parameters); 461 | } 462 | 463 | public function getRoute(int $id) 464 | { 465 | $path = 'routes/' . $id; 466 | $parameters['query'] = ['access_token' => $this->getToken()]; 467 | 468 | return $this->getResponse('GET', $path, $parameters); 469 | } 470 | 471 | public function getRouteAsGPX(int $id) 472 | { 473 | $path = 'routes/' . $id . '/export_gpx'; 474 | $parameters['query'] = ['access_token' => $this->getToken()]; 475 | 476 | return $this->getResponse('GET', $path, $parameters); 477 | } 478 | 479 | public function getRouteAsTCX(int $id) 480 | { 481 | $path = 'routes/' . $id . '/export_tcx'; 482 | $parameters['query'] = ['access_token' => $this->getToken()]; 483 | 484 | return $this->getResponse('GET', $path, $parameters); 485 | } 486 | 487 | public function getSegment(int $id) 488 | { 489 | $path = 'segments/' . $id; 490 | $parameters['query'] = ['access_token' => $this->getToken()]; 491 | 492 | return $this->getResponse('GET', $path, $parameters); 493 | } 494 | 495 | public function getSegmentLeaderboard(int $id, ?string $gender = null, ?string $age_group = null, $weight_class = null, $following = null, $club_id = null, $date_range = null, $context_entries = null, $page = null, $per_page = null) 496 | { 497 | $path = 'segments/' . $id . '/leaderboard'; 498 | $parameters['query'] = [ 499 | 'gender' => $gender, 500 | 'age_group' => $age_group, 501 | 'weight_class' => $weight_class, 502 | 'following' => $following, 503 | 'club_id' => $club_id, 504 | 'date_range' => $date_range, 505 | 'context_entries' => $context_entries, 506 | 'page' => $page, 507 | 'per_page' => $per_page, 508 | 'access_token' => $this->getToken(), 509 | ]; 510 | 511 | return $this->getResponse('GET', $path, $parameters); 512 | } 513 | 514 | public function getSegmentExplorer(string $bounds, string $activity_type = 'riding', ?int $min_cat = null, ?int $max_cat = null) 515 | { 516 | $path = 'segments/explore'; 517 | $parameters['query'] = [ 518 | 'bounds' => $bounds, 519 | 'activity_type' => $activity_type, 520 | 'min_cat' => $min_cat, 521 | 'max_cat' => $max_cat, 522 | 'access_token' => $this->getToken(), 523 | ]; 524 | 525 | return $this->getResponse('GET', $path, $parameters); 526 | } 527 | 528 | public function getSegmentEffort(int $id, ?int $athlete_id = null, ?string $start_date_local = null, ?string $end_date_local = null, ?int $page = null, ?int $per_page = null) 529 | { 530 | $path = 'segments/' . $id . '/all_efforts'; 531 | $parameters['query'] = [ 532 | 'athlete_id' => $athlete_id, 533 | 'start_date_local' => $start_date_local, 534 | 'end_date_local' => $end_date_local, 535 | 'page' => $page, 536 | 'per_page' => $per_page, 537 | 'access_token' => $this->getToken(), 538 | ]; 539 | 540 | return $this->getResponse('GET', $path, $parameters); 541 | } 542 | 543 | public function getStreamsActivity(int $id, string $types, $resolution = null, string $series_type = 'distance') 544 | { 545 | $path = 'activities/' . $id . '/streams/' . $types; 546 | $parameters['query'] = [ 547 | 'resolution' => $resolution, 548 | 'series_type' => $series_type, 549 | 'access_token' => $this->getToken(), 550 | ]; 551 | 552 | return $this->getResponse('GET', $path, $parameters); 553 | } 554 | 555 | public function getStreamsEffort(int $id, string $types, $resolution = null, string $series_type = 'distance') 556 | { 557 | $path = 'segment_efforts/' . $id . '/streams/' . $types; 558 | $parameters['query'] = [ 559 | 'resolution' => $resolution, 560 | 'series_type' => $series_type, 561 | 'access_token' => $this->getToken(), 562 | ]; 563 | 564 | return $this->getResponse('GET', $path, $parameters); 565 | } 566 | 567 | public function getStreamsSegment(int $id, string $types, $resolution = null, string $series_type = 'distance') 568 | { 569 | $path = 'segments/' . $id . '/streams/' . $types; 570 | $parameters['query'] = [ 571 | 'resolution' => $resolution, 572 | 'series_type' => $series_type, 573 | 'access_token' => $this->getToken(), 574 | ]; 575 | 576 | return $this->getResponse('GET', $path, $parameters); 577 | } 578 | 579 | public function getStreamsRoute(int $id) 580 | { 581 | $path = 'routes/' . $id . '/streams'; 582 | $parameters['query'] = ['access_token' => $this->getToken()]; 583 | 584 | return $this->getResponse('GET', $path, $parameters); 585 | } 586 | 587 | public function createWebhookSubscription(int $clientId, string $clientSecret, string $callbackUrl, string $verifyToken) 588 | { 589 | $path = 'push_subscriptions'; 590 | $parameters['form_params'] = [ 591 | 'client_id' => $clientId, 592 | 'client_secret' => $clientSecret, 593 | 'callback_url' => $callbackUrl, 594 | 'verify_token' => $verifyToken, 595 | ]; 596 | 597 | return $this->getResponse('POST', $path, $parameters); 598 | } 599 | 600 | public function listWebhookSubscriptions(int $clientId, string $clientSecret) 601 | { 602 | $path = 'push_subscriptions'; 603 | $parameters['query'] = [ 604 | 'client_id' => $clientId, 605 | 'client_secret' => $clientSecret, 606 | ]; 607 | 608 | return $this->getResponse('GET', $path, $parameters); 609 | } 610 | 611 | public function deleteWebhookSubscription(int $clientId, string $clientSecret, int $subscriptionId) 612 | { 613 | $path = 'push_subscriptions/' . $subscriptionId; 614 | $parameters['form_params'] = [ 615 | 'client_id' => $clientId, 616 | 'client_secret' => $clientSecret, 617 | ]; 618 | 619 | $result = $this->getResponse('DELETE', $path, $parameters); 620 | 621 | // For DELETE operations that return 204, ensure we return a proper structure 622 | if ($result === null) { 623 | return ['success' => true]; 624 | } 625 | 626 | return $result; 627 | } 628 | } 629 | -------------------------------------------------------------------------------- /src/Strava/API/Client.php: -------------------------------------------------------------------------------- 1 | service = $service; 32 | } 33 | 34 | /** 35 | * Retrieve current athlete 36 | * 37 | * @link https://strava.github.io/api/v3/athlete/#get-details, 38 | * https://strava.github.io/api/v3/athlete/#get-another-details 39 | * @param ?int $id 40 | * @return array 41 | * @throws Exception 42 | */ 43 | public function getAthlete(?int $id = null): array 44 | { 45 | try { 46 | return $this->service->getAthlete($id); 47 | } catch (ServiceException $e) { 48 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 49 | } 50 | } 51 | 52 | /** 53 | * Retrieve athlete stats 54 | * 55 | * Only available for the authenticated athlete. 56 | * 57 | * @link https://strava.github.io/api/v3/athlete/#stats 58 | * @param int $id 59 | * @return array 60 | * @throws ClientException 61 | */ 62 | public function getAthleteStats(int $id): array 63 | { 64 | try { 65 | return $this->service->getAthleteStats($id); 66 | } catch (ServiceException $e) { 67 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 68 | } 69 | } 70 | 71 | /** 72 | * Retrieve athlete routes 73 | * 74 | * @link https://strava.github.io/api/v3/routes/#list 75 | * @param int $id 76 | * @param string|null $type 77 | * @param int|null $after 78 | * @param int|null $page 79 | * @param int|null $per_page 80 | * @return array 81 | * @throws Exception 82 | */ 83 | public function getAthleteRoutes(int $id, ?string $type = null, ?int $after = null, ?int $page = null, ?int $per_page = null): array 84 | { 85 | try { 86 | return $this->service->getAthleteRoutes($id, $type, $after, $page, $per_page); 87 | } catch (ServiceException $e) { 88 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 89 | } 90 | } 91 | 92 | /** 93 | * List athlete clubs 94 | * 95 | * @link https://strava.github.io/api/v3/clubs/#get-athletes 96 | * @return array 97 | * @throws Exception 98 | */ 99 | public function getAthleteClubs(): array 100 | { 101 | try { 102 | return $this->service->getAthleteClubs(); 103 | } catch (ServiceException $e) { 104 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 105 | } 106 | } 107 | 108 | /** 109 | * List athlete activities 110 | * 111 | * @link https://strava.github.io/api/v3/activities/#get-activities 112 | * @param string|null $before 113 | * @param string|null $after 114 | * @param int|null $page 115 | * @param int|null $per_page 116 | * @return array 117 | * @throws Exception 118 | */ 119 | public function getAthleteActivities(?string $before = null, ?string $after = null, ?int $page = null, ?int $per_page = null): array 120 | { 121 | try { 122 | return $this->service->getAthleteActivities($before, $after, $page, $per_page); 123 | } catch (ServiceException $e) { 124 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 125 | } 126 | } 127 | 128 | /** 129 | * List athlete friends 130 | * 131 | * @link https://strava.github.io/api/v3/follow/#friends 132 | * @param int|null $id 133 | * @param int|null $page 134 | * @param int|null $per_page 135 | * @return array 136 | * @throws Exception 137 | */ 138 | public function getAthleteFriends(?int $id = null, ?int $page = null, ?int $per_page = null): array 139 | { 140 | try { 141 | return $this->service->getAthleteFriends($id, $page, $per_page); 142 | } catch (ServiceException $e) { 143 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 144 | } 145 | } 146 | 147 | /** 148 | * List athlete followers 149 | * 150 | * @link https://strava.github.io/api/v3/follow/#followers 151 | * @param int|null $id 152 | * @param int|null $page 153 | * @param int|null $per_page 154 | * @return array 155 | * @throws Exception 156 | */ 157 | public function getAthleteFollowers(?int $id = null, ?int $page = null, ?int $per_page = null): array 158 | { 159 | try { 160 | return $this->service->getAthleteFollowers($id, $page, $per_page); 161 | } catch (ServiceException $e) { 162 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 163 | } 164 | } 165 | 166 | /** 167 | * List both following 168 | * 169 | * @link https://strava.github.io/api/v3/follow/#both 170 | * @param int $id 171 | * @param int|null $page 172 | * @param int|null $per_page 173 | * @return array 174 | * @throws Exception 175 | */ 176 | public function getAthleteBothFollowing($id, ?int $page = null, ?int $per_page = null): array 177 | { 178 | try { 179 | return $this->service->getAthleteBothFollowing($id, $page, $per_page); 180 | } catch (ServiceException $e) { 181 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 182 | } 183 | } 184 | 185 | /** 186 | * List athlete K/QOMs/CRs 187 | * 188 | * @link https://strava.github.io/api/v3/athlete/#koms 189 | * @param int $id 190 | * @param int|null $page 191 | * @param int|null $per_page 192 | * @return array 193 | * @throws Exception 194 | */ 195 | public function getAthleteKom(int $id, ?int $page = null, ?int $per_page = null): array 196 | { 197 | try { 198 | return $this->service->getAthleteKom($id, $page, $per_page); 199 | } catch (ServiceException $e) { 200 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 201 | } 202 | } 203 | 204 | /** 205 | * List athlete zones 206 | * 207 | * @link https://strava.github.io/api/v3/athlete/#zones 208 | * @return array 209 | * @throws Exception 210 | */ 211 | public function getAthleteZones(): array 212 | { 213 | try { 214 | return $this->service->getAthleteZones(); 215 | } catch (ServiceException $e) { 216 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 217 | } 218 | } 219 | 220 | 221 | /** 222 | * List starred segment 223 | * 224 | * @link https://strava.github.io/api/v3/segments/#starred 225 | * @param int|null $id 226 | * @param int|null $page 227 | * @param int|null $per_page 228 | * @return array 229 | * @throws Exception 230 | */ 231 | public function getAthleteStarredSegments(?int $id = null, ?int $page = null, ?int $per_page = null): array 232 | { 233 | try { 234 | return $this->service->getAthleteStarredSegments($id, $page, $per_page); 235 | } catch (ServiceException $e) { 236 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 237 | } 238 | } 239 | 240 | /** 241 | * Update current athlete 242 | * 243 | * @link https://strava.github.io/api/v3/athlete/#update 244 | * @param string $city 245 | * @param string $state 246 | * @param string $country 247 | * @param string $sex 248 | * @param float $weight 249 | * @return array 250 | * @throws Exception 251 | */ 252 | public function updateAthlete(string $city, string $state, string $country, string $sex, float $weight): array 253 | { 254 | try { 255 | return $this->service->updateAthlete($city, $state, $country, $sex, $weight); 256 | } catch (ServiceException $e) { 257 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 258 | } 259 | } 260 | 261 | /** 262 | * Retrieve an activity 263 | * 264 | * @link https://strava.github.io/api/v3/athlete/#get-details, 265 | * https://strava.github.io/api/v3/athlete/#get-another-details 266 | * @param int $id 267 | * @param boolean|null $include_all_efforts 268 | * @return array 269 | * @throws Exception 270 | */ 271 | public function getActivity(int $id, ?bool $include_all_efforts = null): array 272 | { 273 | try { 274 | return $this->service->getActivity($id, $include_all_efforts); 275 | } catch (ServiceException $e) { 276 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 277 | } 278 | } 279 | 280 | /** 281 | * List activity comments 282 | * 283 | * @link https://strava.github.io/api/v3/comments/#list 284 | * @param int $id 285 | * @param boolean|null $markdown 286 | * @param int|null $page 287 | * @param int|null $per_page 288 | * @return array 289 | * @throws Exception 290 | */ 291 | public function getActivityComments(int $id, ?bool $markdown = null, ?int $page = null, ?int $per_page = null): array 292 | { 293 | try { 294 | return $this->service->getActivityComments($id, $markdown, $page, $per_page); 295 | } catch (ServiceException $e) { 296 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 297 | } 298 | } 299 | 300 | /** 301 | * List activity kudoers 302 | * 303 | * @link https://strava.github.io/api/v3/kudos/#list 304 | * @param int $id 305 | * @param int|null $page 306 | * @param int|null $per_page 307 | * @return array 308 | * @throws Exception 309 | */ 310 | public function getActivityKudos(int $id, ?int $page = null, ?int $per_page = null): array 311 | { 312 | try { 313 | return $this->service->getActivityKudos($id, $page, $per_page); 314 | } catch (ServiceException $e) { 315 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 316 | } 317 | } 318 | 319 | /** 320 | * List activity photos 321 | * 322 | * @link https://strava.github.io/api/v3/photos/#list 323 | * @param int $id 324 | * @param int $size In pixels. 325 | * @param string $photo_sources Must be "true". 326 | * @return array 327 | * @throws Exception 328 | */ 329 | public function getActivityPhotos(int $id, int $size = 2048, string $photo_sources = 'true'): array 330 | { 331 | try { 332 | return $this->service->getActivityPhotos($id, $size, $photo_sources); 333 | } catch (ServiceException $e) { 334 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 335 | } 336 | } 337 | 338 | /** 339 | * List activity zones 340 | * 341 | * @link https://strava.github.io/api/v3/activities/#zones 342 | * @param int $id 343 | * @return array 344 | * @throws Exception 345 | */ 346 | public function getActivityZones(int $id): array 347 | { 348 | try { 349 | return $this->service->getActivityZones($id); 350 | } catch (ServiceException $e) { 351 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 352 | } 353 | } 354 | 355 | /** 356 | * List activity laps 357 | * 358 | * @link https://strava.github.io/api/v3/activities/#laps 359 | * @param int $id 360 | * @return array 361 | * @throws Exception 362 | */ 363 | public function getActivityLaps(int $id): array 364 | { 365 | try { 366 | return $this->service->getActivityLaps($id); 367 | } catch (ServiceException $e) { 368 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 369 | } 370 | } 371 | 372 | /** 373 | * Check upload status 374 | * 375 | * @link https://strava.github.io/api/v3/uploads/#get-status 376 | * @param int $id 377 | * @return array 378 | * @throws Exception 379 | */ 380 | public function getActivityUploadStatus(int $id): array 381 | { 382 | try { 383 | return $this->service->getActivityUploadStatus($id); 384 | } catch (ServiceException $e) { 385 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 386 | } 387 | } 388 | 389 | /** 390 | * Create an activity 391 | * 392 | * @link https://strava.github.io/api/v3/activities/#create 393 | * @param string $name 394 | * @param string $type 395 | * @param string $start_date_local 396 | * @param int $elapsed_time 397 | * @param string|null $description 398 | * @param float|null $distance 399 | * @param int|null $private 400 | * @param int|null $trainer 401 | * @return array 402 | * @throws Exception 403 | */ 404 | public function createActivity(string $name, string $type, string $start_date_local, int $elapsed_time, ?string $description = null, ?float $distance = null, ?int $private = null, ?int $trainer = null): array 405 | { 406 | try { 407 | return $this->service->createActivity($name, $type, $start_date_local, $elapsed_time, $description, $distance, $private, $trainer); 408 | } catch (ServiceException $e) { 409 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 410 | } 411 | } 412 | 413 | /** 414 | * Upload an activity 415 | * 416 | * @link https://strava.github.io/api/v3/uploads/#post-file 417 | * @param string $file Path to file 418 | * @param string|null $activity_type 419 | * @param string|null $name 420 | * @param string|null $description 421 | * @param int|null $private 422 | * @param int|null $trainer 423 | * @param int|null $commute 424 | * @param string|null $data_type 425 | * @param string|null $external_id 426 | * @return array 427 | * @throws Exception 428 | */ 429 | public function uploadActivity(string $file, ?string $activity_type = null, ?string $name = null, ?string $description = null, ?int $private = null, ?int $trainer = null, ?int $commute = null, ?string $data_type = null, ?string $external_id = null): array 430 | { 431 | try { 432 | return $this->service->uploadActivity($file, $activity_type, $name, $description, $private, $trainer, $commute, $data_type, $external_id); 433 | } catch (ServiceException $e) { 434 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 435 | } 436 | } 437 | 438 | /** 439 | * Update an activity 440 | * 441 | * @link https://strava.github.io/api/v3/activities/#put-updates 442 | * @param int $id 443 | * @param string|null $name 444 | * @param string|null $type 445 | * @param boolean $private 446 | * @param boolean $commute 447 | * @param boolean $trainer 448 | * @param string|null $gear_id 449 | * @param string|null $description 450 | * @return array 451 | * @throws Exception 452 | */ 453 | public function updateActivity(int $id, ?string $name = null, ?string $type = null, bool $private = false, bool $commute = false, bool $trainer = false, ?string $gear_id = null, ?string $description = null): array 454 | { 455 | try { 456 | return $this->service->updateActivity($id, $name, $type, $private, $commute, $trainer, $gear_id, $description); 457 | } catch (ServiceException $e) { 458 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 459 | } 460 | } 461 | 462 | /** 463 | * Delete an activity 464 | * 465 | * @link https://strava.github.io/api/v3/activities/#delete 466 | * @param int $id 467 | * @return array 468 | * @throws Exception 469 | */ 470 | public function deleteActivity(int $id): array 471 | { 472 | try { 473 | return $this->service->deleteActivity($id); 474 | } catch (ServiceException $e) { 475 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 476 | } 477 | } 478 | 479 | /** 480 | * Retrieve gear 481 | * 482 | * @link https://strava.github.io/api/v3/gear/ 483 | * @param int $id 484 | * @return array 485 | * @throws Exception 486 | */ 487 | public function getGear(int $id): array 488 | { 489 | try { 490 | return $this->service->getGear($id); 491 | } catch (ServiceException $e) { 492 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 493 | } 494 | } 495 | 496 | /** 497 | * Retrieve a club 498 | * 499 | * @link https://strava.github.io/api/v3/clubs/#get-details 500 | * @param int $id 501 | * @return array 502 | * @throws Exception 503 | */ 504 | public function getClub(int $id): array 505 | { 506 | try { 507 | return $this->service->getClub($id); 508 | } catch (ServiceException $e) { 509 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 510 | } 511 | } 512 | 513 | /** 514 | * List club members 515 | * 516 | * @link https://strava.github.io/api/v3/clubs/#get-members 517 | * @param int $id 518 | * @param int|null $page 519 | * @param int|null $per_page 520 | * @return array 521 | * @throws Exception 522 | */ 523 | public function getClubMembers(int $id, ?int $page = null, ?int $per_page = null): array 524 | { 525 | try { 526 | return $this->service->getClubMembers($id, $page, $per_page); 527 | } catch (ServiceException $e) { 528 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 529 | } 530 | } 531 | 532 | /** 533 | * List club activities 534 | * 535 | * @link https://strava.github.io/api/v3/clubs/#get-activities 536 | * @param int $id 537 | * @param int|null $page 538 | * @param int|null $per_page 539 | * @return array 540 | * @throws Exception 541 | */ 542 | public function getClubActivities(int $id, ?int $page = null, ?int $per_page = null): array 543 | { 544 | try { 545 | return $this->service->getClubActivities($id, $page, $per_page); 546 | } catch (ServiceException $e) { 547 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 548 | } 549 | } 550 | 551 | /** 552 | * List club announcements 553 | * 554 | * @link https://strava.github.io/api/v3/clubs/#get-announcements 555 | * @param int $id 556 | * @return array 557 | * @throws Exception 558 | */ 559 | public function getClubAnnouncements(int $id): array 560 | { 561 | try { 562 | return $this->service->getClubAnnouncements($id); 563 | } catch (ServiceException $e) { 564 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 565 | } 566 | } 567 | 568 | /** 569 | * List club group events 570 | * 571 | * @link https://strava.github.io/api/v3/clubs/#get-group-events 572 | * @param int $id 573 | * @return array 574 | * @throws Exception 575 | */ 576 | public function getClubGroupEvents(int $id): array 577 | { 578 | try { 579 | return $this->service->getClubGroupEvents($id); 580 | } catch (ServiceException $e) { 581 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 582 | } 583 | } 584 | 585 | /** 586 | * Join a club 587 | * 588 | * @link https://strava.github.io/api/v3/clubs/#join 589 | * @param int $id 590 | * @return array 591 | * @throws Exception 592 | */ 593 | public function joinClub(int $id): array 594 | { 595 | try { 596 | return $this->service->joinClub($id); 597 | } catch (ServiceException $e) { 598 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 599 | } 600 | } 601 | 602 | /** 603 | * Leave a club 604 | * 605 | * @link https://strava.github.io/api/v3/clubs/#leave 606 | * @param int $id 607 | * @return array 608 | * @throws Exception 609 | */ 610 | public function leaveClub(int $id): array 611 | { 612 | try { 613 | return $this->service->leaveClub($id); 614 | } catch (ServiceException $e) { 615 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 616 | } 617 | } 618 | 619 | /** 620 | * Get route details 621 | * 622 | * @link https://strava.github.io/api/v3/routes/#list 623 | * @param int $id 624 | * @return array 625 | * @throws Exception 626 | */ 627 | public function getRoute(int $id): array 628 | { 629 | try { 630 | return $this->service->getRoute($id); 631 | } catch (ServiceException $e) { 632 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 633 | } 634 | } 635 | 636 | /** 637 | * Get route as GPX. 638 | * 639 | * @link https://developers.strava.com/docs/reference/#api-Routes-getRouteAsGPX 640 | * @param int $id 641 | * @return string 642 | * @throws Exception 643 | */ 644 | public function getRouteAsGPX(int $id): string 645 | { 646 | try { 647 | return $this->service->getRouteAsGPX($id); 648 | } catch (ServiceException $e) { 649 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 650 | } 651 | } 652 | 653 | /** 654 | * Get route as TCX. 655 | * 656 | * @link https://developers.strava.com/docs/reference/#api-Routes-getRouteAsTCX 657 | * @param int $id 658 | * @return string 659 | * @throws Exception 660 | */ 661 | public function getRouteAsTCX(int $id): string 662 | { 663 | try { 664 | return $this->service->getRouteAsTCX($id); 665 | } catch (ServiceException $e) { 666 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 667 | } 668 | } 669 | 670 | /** 671 | * Retrieve a segment 672 | * 673 | * @link https://strava.github.io/api/v3/segments/#retrieve 674 | * @param int $id 675 | * @return array 676 | * @throws Exception 677 | */ 678 | public function getSegment(int $id): array 679 | { 680 | try { 681 | return $this->service->getSegment($id); 682 | } catch (ServiceException $e) { 683 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 684 | } 685 | } 686 | 687 | /** 688 | * Segment leaderboards 689 | * 690 | * @link https://strava.github.io/api/v3/segments/#leaderboard 691 | * @param int $id 692 | * @param string|null $gender 693 | * @param string|null $age_group 694 | * @param string|null $weight_class 695 | * @param boolean|null $following 696 | * @param int|null $club_id 697 | * @param string|null $date_range 698 | * @param int|null $context_entries 699 | * @param int|null $page 700 | * @param int|null $per_page 701 | * @return array 702 | * @throws Exception 703 | */ 704 | public function getSegmentLeaderboard(int $id, ?string $gender = null, ?string $age_group = null, ?string $weight_class = null, ?bool $following = null, ?int $club_id = null, ?string $date_range = null, ?int $context_entries = null, ?int $page = null, ?int $per_page = null): array 705 | { 706 | try { 707 | return $this->service->getSegmentLeaderboard($id, $gender, $age_group, $weight_class, $following, $club_id, $date_range, $context_entries, $page, $per_page); 708 | } catch (ServiceException $e) { 709 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 710 | } 711 | } 712 | 713 | /** 714 | * Segment explorer 715 | * 716 | * @link https://strava.github.io/api/v3/segments/#explore 717 | * @param string $bounds 718 | * @param string $activity_type 719 | * @param int|null $min_cat 720 | * @param int|null $max_cat 721 | * @return array 722 | * @throws Exception 723 | */ 724 | public function getSegmentExplorer(string $bounds, string $activity_type = 'riding', ?int $min_cat = null, ?int $max_cat = null): array 725 | { 726 | try { 727 | return $this->service->getSegmentExplorer($bounds, $activity_type, $min_cat, $max_cat); 728 | } catch (ServiceException $e) { 729 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 730 | } 731 | } 732 | 733 | /** 734 | * List efforts filtered by athlete and/or a date range 735 | * 736 | * @link https://strava.github.io/api/v3/segments/#efforts 737 | * @param int $id 738 | * @param int|null $athlete_id 739 | * @param string|null $start_date_local 740 | * @param string|null $end_date_local 741 | * @param int|null $page 742 | * @param int|null $per_page 743 | * @return array 744 | * @throws Exception 745 | */ 746 | public function getSegmentEffort(int $id, ?int $athlete_id = null, ?string $start_date_local = null, ?string $end_date_local = null, ?int $page = null, ?int $per_page = null): array 747 | { 748 | try { 749 | return $this->service->getSegmentEffort($id, $athlete_id, $start_date_local, $end_date_local, $page, $per_page); 750 | } catch (ServiceException $e) { 751 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 752 | } 753 | } 754 | 755 | /** 756 | * Retrieve activity streams 757 | * 758 | * @link https://strava.github.io/api/v3/streams/#activity 759 | * @param int $id 760 | * @param string $types 761 | * @param string|null $resolution 762 | * @param string $series_type 763 | * @return array 764 | * @throws Exception 765 | */ 766 | public function getStreamsActivity(int $id, string $types, ?string $resolution = null, string $series_type = 'distance'): array 767 | { 768 | try { 769 | return $this->service->getStreamsActivity($id, $types, $resolution, $series_type); 770 | } catch (ServiceException $e) { 771 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 772 | } 773 | } 774 | 775 | /** 776 | * Retrieve effort streams 777 | * 778 | * @link https://strava.github.io/api/v3/streams/#effort 779 | * @param int $id 780 | * @param string $types 781 | * @param ?string $resolution 782 | * @param string $series_type 783 | * @return array 784 | * @throws Exception 785 | */ 786 | public function getStreamsEffort(int $id, string $types, ?string $resolution = null, string $series_type = 'distance'): array 787 | { 788 | try { 789 | return $this->service->getStreamsEffort($id, $types, $resolution, $series_type); 790 | } catch (ServiceException $e) { 791 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 792 | } 793 | } 794 | 795 | /** 796 | * Retrieve segment streams 797 | * @link https://strava.github.io/api/v3/streams/#segment 798 | * @param int $id 799 | * @param string $types 800 | * @param ?string $resolution 801 | * @param string $series_type 802 | * @return array 803 | * @throws Exception 804 | */ 805 | public function getStreamsSegment(int $id, string $types, ?string $resolution = null, string $series_type = 'distance'): array 806 | { 807 | try { 808 | return $this->service->getStreamsSegment($id, $types, $resolution, $series_type); 809 | } catch (ServiceException $e) { 810 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 811 | } 812 | } 813 | 814 | /** 815 | * Retrieve route streams 816 | * 817 | * @link https://strava.github.io/api/v3/streams/#routes 818 | * @param int $id 819 | * @return array 820 | * @throws Exception 821 | */ 822 | public function getStreamsRoute(int $id): array 823 | { 824 | try { 825 | return $this->service->getStreamsRoute($id); 826 | } catch (ServiceException $e) { 827 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 828 | } 829 | } 830 | 831 | /** 832 | * Create a webhook subscription 833 | * 834 | * @link https://developers.strava.com/docs/webhooks/#create-a-subscription 835 | * @param int $clientId 836 | * @param string $clientSecret 837 | * @param string $callbackUrl 838 | * @param string $verifyToken 839 | * @return array 840 | * @throws Exception 841 | */ 842 | public function createWebhookSubscription(int $clientId, string $clientSecret, string $callbackUrl, string $verifyToken): array 843 | { 844 | try { 845 | return $this->service->createWebhookSubscription($clientId, $clientSecret, $callbackUrl, $verifyToken); 846 | } catch (ServiceException $e) { 847 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 848 | } 849 | } 850 | 851 | /** 852 | * List webhook subscriptions 853 | * 854 | * @link https://developers.strava.com/docs/webhooks/#list-subscriptions 855 | * @param int $clientId 856 | * @param string $clientSecret 857 | * @return array 858 | * @throws Exception 859 | */ 860 | public function listWebhookSubscriptions(int $clientId, string $clientSecret): array 861 | { 862 | try { 863 | return $this->service->listWebhookSubscriptions($clientId, $clientSecret); 864 | } catch (ServiceException $e) { 865 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 866 | } 867 | } 868 | 869 | /** 870 | * Delete a webhook subscription 871 | * 872 | * @link https://developers.strava.com/docs/webhooks/#delete-a-subscription 873 | * @param int $clientId 874 | * @param string $clientSecret 875 | * @param int $subscriptionId 876 | * @return array 877 | * @throws Exception 878 | */ 879 | public function deleteWebhookSubscription(int $clientId, string $clientSecret, int $subscriptionId): array 880 | { 881 | try { 882 | return $this->service->deleteWebhookSubscription($clientId, $clientSecret, $subscriptionId); 883 | } catch (ServiceException $e) { 884 | throw new ClientException('[SERVICE] ' . $e->getMessage()); 885 | } 886 | } 887 | } 888 | -------------------------------------------------------------------------------- /tests/Strava/API/ClientTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('Strava\API\Service\Stub') 18 | ->disableOriginalConstructor() 19 | ->getMock(); 20 | return $serviceMock; 21 | } 22 | 23 | public function testGetAthlete() 24 | { 25 | $serviceMock = $this->getServiceMock(); 26 | $serviceMock->expects($this->once())->method('getAthlete') 27 | ->will($this->returnValue(['id' => 1234, 'name' => 'Test Athlete'])); 28 | 29 | $client = new Strava\API\Client($serviceMock); 30 | $output = $client->getAthlete(1234); 31 | 32 | $this->assertEquals(['id' => 1234, 'name' => 'Test Athlete'], $output); 33 | } 34 | 35 | public function testGetAthleteException() 36 | { 37 | $this->expectException('Strava\API\Exception'); 38 | 39 | $serviceMock = $this->getServiceMock(); 40 | $serviceMock->expects($this->once())->method('getAthlete') 41 | ->will($this->throwException(new ServiceException)); 42 | 43 | $client = new Strava\API\Client($serviceMock); 44 | $client->getAthlete(1234); 45 | } 46 | 47 | public function testGetAthleteStats() 48 | { 49 | $serviceMock = $this->getServiceMock(); 50 | $serviceMock->expects($this->once())->method('getAthleteStats') 51 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 52 | 53 | $client = new Strava\API\Client($serviceMock); 54 | $output = $client->getAthleteStats(1234); 55 | 56 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 57 | } 58 | 59 | public function testGetAthleteRoutes() 60 | { 61 | $serviceMock = $this->getServiceMock(); 62 | $serviceMock->expects($this->once())->method('getAthleteRoutes') 63 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 64 | 65 | $client = new Strava\API\Client($serviceMock); 66 | $output = $client->getAthleteRoutes(1234); 67 | 68 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 69 | } 70 | 71 | public function testGetAthleteClubs() 72 | { 73 | $serviceMock = $this->getServiceMock(); 74 | $serviceMock->expects($this->once())->method('getAthleteClubs') 75 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 76 | 77 | $client = new Strava\API\Client($serviceMock); 78 | $output = $client->getAthleteClubs(); 79 | 80 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 81 | } 82 | 83 | public function testGetAthleteClubsException() 84 | { 85 | $this->expectException('Strava\API\Exception'); 86 | 87 | $serviceMock = $this->getServiceMock(); 88 | $serviceMock->expects($this->once())->method('getAthleteClubs') 89 | ->will($this->throwException(new ServiceException)); 90 | 91 | $client = new Strava\API\Client($serviceMock); 92 | $client->getAthleteClubs(); 93 | } 94 | 95 | public function testGetAthleteActivities() 96 | { 97 | $serviceMock = $this->getServiceMock(); 98 | $serviceMock->expects($this->once())->method('getAthleteActivities') 99 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 100 | 101 | $client = new Strava\API\Client($serviceMock); 102 | $output = $client->getAthleteActivities(); 103 | 104 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 105 | } 106 | 107 | public function testGetAthleteActivitiesException() 108 | { 109 | $this->expectException('Strava\API\Exception'); 110 | 111 | $serviceMock = $this->getServiceMock(); 112 | $serviceMock->expects($this->once())->method('getAthleteActivities') 113 | ->will($this->throwException(new ServiceException)); 114 | 115 | $client = new Strava\API\Client($serviceMock); 116 | $client->getAthleteActivities(); 117 | } 118 | 119 | public function testGetAthleteFriends() 120 | { 121 | $serviceMock = $this->getServiceMock(); 122 | $serviceMock->expects($this->once())->method('getAthleteFriends') 123 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 124 | 125 | $client = new Strava\API\Client($serviceMock); 126 | $output = $client->getAthleteFriends(); 127 | 128 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 129 | } 130 | 131 | public function testGetAthleteFriendsException() 132 | { 133 | $this->expectException('Strava\API\Exception'); 134 | 135 | $serviceMock = $this->getServiceMock(); 136 | $serviceMock->expects($this->once())->method('getAthleteFriends') 137 | ->will($this->throwException(new ServiceException)); 138 | 139 | $client = new Strava\API\Client($serviceMock); 140 | $client->getAthleteFriends(); 141 | } 142 | 143 | public function testGetAthleteFollowers() 144 | { 145 | $serviceMock = $this->getServiceMock(); 146 | $serviceMock->expects($this->once())->method('getAthleteFollowers') 147 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 148 | 149 | $client = new Strava\API\Client($serviceMock); 150 | $output = $client->getAthleteFollowers(); 151 | 152 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 153 | } 154 | 155 | public function testGetAthleteFollowersException() 156 | { 157 | $this->expectException('Strava\API\Exception'); 158 | 159 | $serviceMock = $this->getServiceMock(); 160 | $serviceMock->expects($this->once())->method('getAthleteFollowers') 161 | ->will($this->throwException(new ServiceException)); 162 | 163 | $client = new Strava\API\Client($serviceMock); 164 | $client->getAthleteFollowers(); 165 | } 166 | 167 | public function testGetAthleteBothFollowing() 168 | { 169 | $serviceMock = $this->getServiceMock(); 170 | $serviceMock->expects($this->once())->method('getAthleteBothFollowing') 171 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 172 | 173 | $client = new Strava\API\Client($serviceMock); 174 | $output = $client->getAthleteBothFollowing(1234); 175 | 176 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 177 | } 178 | 179 | public function testGetAthleteBothFollowingException() 180 | { 181 | $this->expectException('Strava\API\Exception'); 182 | 183 | $serviceMock = $this->getServiceMock(); 184 | $serviceMock->expects($this->once())->method('getAthleteBothFollowing') 185 | ->will($this->throwException(new ServiceException)); 186 | 187 | $client = new Strava\API\Client($serviceMock); 188 | $client->getAthleteBothFollowing(1234); 189 | } 190 | 191 | public function testGetAthleteKom() 192 | { 193 | $serviceMock = $this->getServiceMock(); 194 | $serviceMock->expects($this->once())->method('getAthleteKom') 195 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 196 | 197 | $client = new Strava\API\Client($serviceMock); 198 | $output = $client->getAthleteKom(1234); 199 | 200 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 201 | } 202 | 203 | public function testGetAthleteKomException() 204 | { 205 | $this->expectException('Strava\API\Exception'); 206 | 207 | $serviceMock = $this->getServiceMock(); 208 | $serviceMock->expects($this->once())->method('getAthleteKom') 209 | ->will($this->throwException(new ServiceException)); 210 | 211 | $client = new Strava\API\Client($serviceMock); 212 | $client->getAthleteKom(1234); 213 | } 214 | 215 | 216 | public function testGetAthleteZones() 217 | { 218 | $serviceMock = $this->getServiceMock(); 219 | $serviceMock->expects($this->once())->method('getAthleteZones') 220 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 221 | 222 | $client = new Strava\API\Client($serviceMock); 223 | $output = $client->getAthleteZones(); 224 | 225 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 226 | } 227 | 228 | public function testGetAthleteZonesException() 229 | { 230 | $this->expectException('Strava\API\Exception'); 231 | 232 | $serviceMock = $this->getServiceMock(); 233 | $serviceMock->expects($this->once())->method('getAthleteZones') 234 | ->will($this->throwException(new ServiceException)); 235 | 236 | $client = new Strava\API\Client($serviceMock); 237 | $client->getAthleteZones(); 238 | } 239 | 240 | public function testGetAthleteStarredSegments() 241 | { 242 | $serviceMock = $this->getServiceMock(); 243 | $serviceMock->expects($this->once())->method('getAthleteStarredSegments') 244 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 245 | 246 | $client = new Strava\API\Client($serviceMock); 247 | $output = $client->getAthleteStarredSegments(); 248 | 249 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 250 | } 251 | 252 | public function testGetAthleteStarredSegmentsException() 253 | { 254 | $this->expectException('Strava\API\Exception'); 255 | 256 | $serviceMock = $this->getServiceMock(); 257 | $serviceMock->expects($this->once())->method('getAthleteStarredSegments') 258 | ->will($this->throwException(new ServiceException)); 259 | 260 | $client = new Strava\API\Client($serviceMock); 261 | $client->getAthleteStarredSegments(); 262 | } 263 | 264 | public function testUpdateAthlete() 265 | { 266 | $serviceMock = $this->getServiceMock(); 267 | $serviceMock->expects($this->once())->method('updateAthlete') 268 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 269 | 270 | $client = new Strava\API\Client($serviceMock); 271 | $output = $client->updateAthlete('Xyz', 'ABC', 'The Netherlands', 'M', 83.00); 272 | 273 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 274 | } 275 | 276 | public function testUpdateAthleteException() 277 | { 278 | $this->expectException('Strava\API\Exception'); 279 | 280 | $serviceMock = $this->getServiceMock(); 281 | $serviceMock->expects($this->once())->method('updateAthlete') 282 | ->will($this->throwException(new ServiceException)); 283 | 284 | $client = new Strava\API\Client($serviceMock); 285 | $client->updateAthlete('Xyz', 'ABC', 'The Netherlands', 'M', 83.00); 286 | } 287 | 288 | public function testGetActivity() 289 | { 290 | $serviceMock = $this->getServiceMock(); 291 | $serviceMock->expects($this->once())->method('getActivity') 292 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 293 | 294 | $client = new Strava\API\Client($serviceMock); 295 | $output = $client->getActivity(1234); 296 | 297 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 298 | } 299 | 300 | public function testGetActivityException() 301 | { 302 | $this->expectException('Strava\API\Exception'); 303 | 304 | $serviceMock = $this->getServiceMock(); 305 | $serviceMock->expects($this->once())->method('getActivity') 306 | ->will($this->throwException(new ServiceException)); 307 | 308 | $client = new Strava\API\Client($serviceMock); 309 | $client->getActivity(1234); 310 | } 311 | 312 | public function testGetActivityComments() 313 | { 314 | $serviceMock = $this->getServiceMock(); 315 | $serviceMock->expects($this->once())->method('getActivityComments') 316 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 317 | 318 | $client = new Strava\API\Client($serviceMock); 319 | $output = $client->getActivityComments(1234); 320 | 321 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 322 | } 323 | 324 | public function testGetActivityCommentsException() 325 | { 326 | $this->expectException('Strava\API\Exception'); 327 | 328 | $serviceMock = $this->getServiceMock(); 329 | $serviceMock->expects($this->once())->method('getActivityComments') 330 | ->will($this->throwException(new ServiceException)); 331 | 332 | $client = new Strava\API\Client($serviceMock); 333 | $client->getActivityComments(1234); 334 | } 335 | 336 | public function testGetActivityKudos() 337 | { 338 | $serviceMock = $this->getServiceMock(); 339 | $serviceMock->expects($this->once())->method('getActivityKudos') 340 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 341 | 342 | $client = new Strava\API\Client($serviceMock); 343 | $output = $client->getActivityKudos(1234); 344 | 345 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 346 | } 347 | 348 | public function testGetActivityKudosException() 349 | { 350 | $this->expectException('Strava\API\Exception'); 351 | 352 | $serviceMock = $this->getServiceMock(); 353 | $serviceMock->expects($this->once())->method('getActivityKudos') 354 | ->will($this->throwException(new ServiceException)); 355 | 356 | $client = new Strava\API\Client($serviceMock); 357 | $client->getActivityKudos(1234); 358 | } 359 | 360 | public function testGetActivityPhotos() 361 | { 362 | $serviceMock = $this->getServiceMock(); 363 | $serviceMock->expects($this->once())->method('getActivityPhotos') 364 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 365 | 366 | $client = new Strava\API\Client($serviceMock); 367 | $output = $client->getActivityPhotos(1234); 368 | 369 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 370 | } 371 | 372 | public function testGetActivityPhotosException() 373 | { 374 | $this->expectException('Strava\API\Exception'); 375 | 376 | $serviceMock = $this->getServiceMock(); 377 | $serviceMock->expects($this->once())->method('getActivityPhotos') 378 | ->will($this->throwException(new ServiceException)); 379 | 380 | $client = new Strava\API\Client($serviceMock); 381 | $client->getActivityPhotos(1234); 382 | } 383 | 384 | public function testGetActivityZones() 385 | { 386 | $serviceMock = $this->getServiceMock(); 387 | $serviceMock->expects($this->once())->method('getActivityZones') 388 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 389 | 390 | $client = new Strava\API\Client($serviceMock); 391 | $output = $client->getActivityZones(1234); 392 | 393 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 394 | } 395 | 396 | public function testGetActivityZonesException() 397 | { 398 | $this->expectException('Strava\API\Exception'); 399 | 400 | $serviceMock = $this->getServiceMock(); 401 | $serviceMock->expects($this->once())->method('getActivityZones') 402 | ->will($this->throwException(new ServiceException)); 403 | 404 | $client = new Strava\API\Client($serviceMock); 405 | $client->getActivityZones(1234); 406 | } 407 | 408 | public function testGetActivityLaps() 409 | { 410 | $serviceMock = $this->getServiceMock(); 411 | $serviceMock->expects($this->once())->method('getActivityLaps') 412 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 413 | 414 | $client = new Strava\API\Client($serviceMock); 415 | $output = $client->getActivityLaps(1234); 416 | 417 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 418 | } 419 | 420 | public function testGetActivityLapsException() 421 | { 422 | $this->expectException('Strava\API\Exception'); 423 | 424 | $serviceMock = $this->getServiceMock(); 425 | $serviceMock->expects($this->once())->method('getActivityLaps') 426 | ->will($this->throwException(new ServiceException)); 427 | 428 | $client = new Strava\API\Client($serviceMock); 429 | $client->getActivityLaps(1234); 430 | } 431 | 432 | public function testGetActivityUploadStatus() 433 | { 434 | $serviceMock = $this->getServiceMock(); 435 | $serviceMock->expects($this->once())->method('getActivityUploadStatus') 436 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 437 | 438 | $client = new Strava\API\Client($serviceMock); 439 | $output = $client->getActivityUploadStatus(1234); 440 | 441 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 442 | } 443 | 444 | public function testGetActivityUploadStatusException() 445 | { 446 | $this->expectException('Strava\API\Exception'); 447 | 448 | $serviceMock = $this->getServiceMock(); 449 | $serviceMock->expects($this->once())->method('getActivityUploadStatus') 450 | ->will($this->throwException(new ServiceException)); 451 | 452 | $client = new Strava\API\Client($serviceMock); 453 | $client->getActivityUploadStatus(1234); 454 | } 455 | 456 | public function testCreateActivity() 457 | { 458 | $serviceMock = $this->getServiceMock(); 459 | $serviceMock->expects($this->once())->method('createActivity') 460 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 461 | 462 | $client = new Strava\API\Client($serviceMock); 463 | $output = $client->createActivity('cycling ride', 'cycling', '20140101', 100); 464 | 465 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 466 | } 467 | 468 | public function testCreateActivityException() 469 | { 470 | $this->expectException('Strava\API\Exception'); 471 | 472 | $serviceMock = $this->getServiceMock(); 473 | $serviceMock->expects($this->once())->method('createActivity') 474 | ->will($this->throwException(new ServiceException)); 475 | 476 | $client = new Strava\API\Client($serviceMock); 477 | $client->createActivity('cycling ride', 'cycling', '20140101', 100); 478 | } 479 | 480 | public function testUploadActivity() 481 | { 482 | $serviceMock = $this->getServiceMock(); 483 | $serviceMock->expects($this->once())->method('uploadActivity') 484 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 485 | 486 | $client = new Strava\API\Client($serviceMock); 487 | $output = $client->uploadActivity("abc23487fsdfds"); 488 | 489 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 490 | } 491 | 492 | public function testUploadActivityException() 493 | { 494 | $this->expectException('Strava\API\Exception'); 495 | 496 | $serviceMock = $this->getServiceMock(); 497 | $serviceMock->expects($this->once())->method('uploadActivity') 498 | ->will($this->throwException(new ServiceException)); 499 | 500 | $client = new Strava\API\Client($serviceMock); 501 | $client->uploadActivity("abc23487fsdfds"); 502 | } 503 | 504 | public function testUpdateActivity() 505 | { 506 | $serviceMock = $this->getServiceMock(); 507 | $serviceMock->expects($this->once())->method('updateActivity') 508 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 509 | 510 | $client = new Strava\API\Client($serviceMock); 511 | $output = $client->updateActivity(123); 512 | 513 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 514 | } 515 | 516 | public function testUpdateActivityException() 517 | { 518 | $this->expectException('Strava\API\Exception'); 519 | 520 | $serviceMock = $this->getServiceMock(); 521 | $serviceMock->expects($this->once())->method('updateActivity') 522 | ->will($this->throwException(new ServiceException)); 523 | 524 | $client = new Strava\API\Client($serviceMock); 525 | $client->updateActivity(123); 526 | } 527 | 528 | public function testDeleteActivity() 529 | { 530 | $serviceMock = $this->getServiceMock(); 531 | $serviceMock->expects($this->once())->method('deleteActivity') 532 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 533 | 534 | $client = new Strava\API\Client($serviceMock); 535 | $output = $client->deleteActivity(1234); 536 | 537 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 538 | } 539 | 540 | public function testDeleteActivityException() 541 | { 542 | $this->expectException('Strava\API\Exception'); 543 | 544 | $serviceMock = $this->getServiceMock(); 545 | $serviceMock->expects($this->once())->method('deleteActivity') 546 | ->will($this->throwException(new ServiceException)); 547 | 548 | $client = new Strava\API\Client($serviceMock); 549 | $client->deleteActivity(1234); 550 | } 551 | 552 | public function testGetGear() 553 | { 554 | $serviceMock = $this->getServiceMock(); 555 | $serviceMock->expects($this->once())->method('getGear') 556 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 557 | 558 | $client = new Strava\API\Client($serviceMock); 559 | $output = $client->getGear(1234); 560 | 561 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 562 | } 563 | 564 | public function testGetGearException() 565 | { 566 | $this->expectException('Strava\API\Exception'); 567 | 568 | $serviceMock = $this->getServiceMock(); 569 | $serviceMock->expects($this->once())->method('getGear') 570 | ->will($this->throwException(new ServiceException)); 571 | 572 | $client = new Strava\API\Client($serviceMock); 573 | $client->getGear(1234); 574 | } 575 | 576 | public function testGetClub() 577 | { 578 | $serviceMock = $this->getServiceMock(); 579 | $serviceMock->expects($this->once())->method('getClub') 580 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 581 | 582 | $client = new Strava\API\Client($serviceMock); 583 | $output = $client->getClub(1234); 584 | 585 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 586 | } 587 | 588 | public function testGetClubException() 589 | { 590 | $this->expectException('Strava\API\Exception'); 591 | 592 | $serviceMock = $this->getServiceMock(); 593 | $serviceMock->expects($this->once())->method('getClub') 594 | ->will($this->throwException(new ServiceException)); 595 | 596 | $client = new Strava\API\Client($serviceMock); 597 | $client->getClub(1234); 598 | } 599 | 600 | public function testGetClubMembers() 601 | { 602 | $serviceMock = $this->getServiceMock(); 603 | $serviceMock->expects($this->once())->method('getClubMembers') 604 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 605 | 606 | $client = new Strava\API\Client($serviceMock); 607 | $output = $client->getClubMembers(1234); 608 | 609 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 610 | } 611 | 612 | public function testGetClubMembersException() 613 | { 614 | $this->expectException('Strava\API\Exception'); 615 | 616 | $serviceMock = $this->getServiceMock(); 617 | $serviceMock->expects($this->once())->method('getClubMembers') 618 | ->will($this->throwException(new ServiceException)); 619 | 620 | $client = new Strava\API\Client($serviceMock); 621 | $client->getClubMembers(1234); 622 | } 623 | 624 | public function testGetClubActivities() 625 | { 626 | $serviceMock = $this->getServiceMock(); 627 | $serviceMock->expects($this->once())->method('getClubActivities') 628 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 629 | 630 | $client = new Strava\API\Client($serviceMock); 631 | $output = $client->getClubActivities(1234); 632 | 633 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 634 | } 635 | 636 | public function testGetClubActivitiesException() 637 | { 638 | $this->expectException('Strava\API\Exception'); 639 | 640 | $serviceMock = $this->getServiceMock(); 641 | $serviceMock->expects($this->once())->method('getClubActivities') 642 | ->will($this->throwException(new ServiceException)); 643 | 644 | $client = new Strava\API\Client($serviceMock); 645 | $client->getClubActivities(1234); 646 | } 647 | 648 | public function testGetClubAnnouncements() 649 | { 650 | $serviceMock = $this->getServiceMock(); 651 | $serviceMock->expects($this->once())->method('getClubAnnouncements') 652 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 653 | 654 | $client = new Strava\API\Client($serviceMock); 655 | $output = $client->getClubAnnouncements(1234); 656 | 657 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 658 | } 659 | 660 | public function testGetClubAnnouncementsException() 661 | { 662 | $this->expectException('Strava\API\Exception'); 663 | 664 | $serviceMock = $this->getServiceMock(); 665 | $serviceMock->expects($this->once())->method('getClubAnnouncements') 666 | ->will($this->throwException(new ServiceException)); 667 | 668 | $client = new Strava\API\Client($serviceMock); 669 | $client->getClubAnnouncements(1234); 670 | } 671 | 672 | public function testGetClubGroupEvents() 673 | { 674 | $serviceMock = $this->getServiceMock(); 675 | $serviceMock->expects($this->once())->method('getClubGroupEvents') 676 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 677 | 678 | $client = new Strava\API\Client($serviceMock); 679 | $output = $client->getClubGroupEvents(1234); 680 | 681 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 682 | } 683 | 684 | public function testGetClubGroupEventsException() 685 | { 686 | $this->expectException('Strava\API\Exception'); 687 | 688 | $serviceMock = $this->getServiceMock(); 689 | $serviceMock->expects($this->once())->method('getClubGroupEvents') 690 | ->will($this->throwException(new ServiceException)); 691 | 692 | $client = new Strava\API\Client($serviceMock); 693 | $client->getClubGroupEvents(1234); 694 | } 695 | 696 | public function testJoinClub() 697 | { 698 | $serviceMock = $this->getServiceMock(); 699 | $serviceMock->expects($this->once())->method('joinClub') 700 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 701 | 702 | $client = new Strava\API\Client($serviceMock); 703 | $output = $client->joinClub(1234); 704 | 705 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 706 | } 707 | 708 | public function testJoinClubException() 709 | { 710 | $this->expectException('Strava\API\Exception'); 711 | 712 | $serviceMock = $this->getServiceMock(); 713 | $serviceMock->expects($this->once())->method('joinClub') 714 | ->will($this->throwException(new ServiceException)); 715 | 716 | $client = new Strava\API\Client($serviceMock); 717 | $client->joinClub(1234); 718 | } 719 | 720 | public function testLeaveClub() 721 | { 722 | $serviceMock = $this->getServiceMock(); 723 | $serviceMock->expects($this->once())->method('leaveClub') 724 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 725 | 726 | $client = new Strava\API\Client($serviceMock); 727 | $output = $client->leaveClub(1234); 728 | 729 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 730 | } 731 | 732 | public function testLeaveClubException() 733 | { 734 | $this->expectException('Strava\API\Exception'); 735 | 736 | $serviceMock = $this->getServiceMock(); 737 | $serviceMock->expects($this->once())->method('leaveClub') 738 | ->will($this->throwException(new ServiceException)); 739 | 740 | $client = new Strava\API\Client($serviceMock); 741 | $client->leaveClub(1234); 742 | } 743 | 744 | public function testGetRoute() 745 | { 746 | $serviceMock = $this->getServiceMock(); 747 | $serviceMock->expects($this->once())->method('getRoute') 748 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 749 | 750 | $client = new Strava\API\Client($serviceMock); 751 | $output = $client->getRoute(1234); 752 | 753 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 754 | } 755 | 756 | public function testGetRouteException() 757 | { 758 | $this->expectException('Strava\API\Exception'); 759 | 760 | $serviceMock = $this->getServiceMock(); 761 | $serviceMock->expects($this->once())->method('getRoute') 762 | ->will($this->throwException(new ServiceException)); 763 | 764 | $client = new Strava\API\Client($serviceMock); 765 | $client->getRoute(1234); 766 | } 767 | 768 | public function testGetSegment() 769 | { 770 | $serviceMock = $this->getServiceMock(); 771 | $serviceMock->expects($this->once())->method('getSegment') 772 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 773 | 774 | $client = new Strava\API\Client($serviceMock); 775 | $output = $client->getSegment(1234); 776 | 777 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 778 | } 779 | 780 | public function testGetSegmentException() 781 | { 782 | $this->expectException('Strava\API\Exception'); 783 | 784 | $serviceMock = $this->getServiceMock(); 785 | $serviceMock->expects($this->once())->method('getSegment') 786 | ->will($this->throwException(new ServiceException)); 787 | 788 | $client = new Strava\API\Client($serviceMock); 789 | $client->getSegment(1234); 790 | } 791 | 792 | public function testGetSegmentLeaderboard() 793 | { 794 | $serviceMock = $this->getServiceMock(); 795 | $serviceMock->expects($this->once())->method('getSegmentLeaderboard') 796 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 797 | 798 | $client = new Strava\API\Client($serviceMock); 799 | $output = $client->getSegmentLeaderboard(1234); 800 | 801 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 802 | } 803 | 804 | public function testGetSegmentLeaderboardException() 805 | { 806 | $this->expectException('Strava\API\Exception'); 807 | 808 | $serviceMock = $this->getServiceMock(); 809 | $serviceMock->expects($this->once())->method('getSegmentLeaderboard') 810 | ->will($this->throwException(new ServiceException)); 811 | 812 | $client = new Strava\API\Client($serviceMock); 813 | $client->getSegmentLeaderboard(1234); 814 | } 815 | 816 | public function testGetSegmentExplorer() 817 | { 818 | $serviceMock = $this->getServiceMock(); 819 | $serviceMock->expects($this->once())->method('getSegmentExplorer') 820 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 821 | 822 | $client = new Strava\API\Client($serviceMock); 823 | $output = $client->getSegmentExplorer("lng.lat"); 824 | 825 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 826 | } 827 | 828 | public function testGetSegmentExplorerException() 829 | { 830 | $this->expectException('Strava\API\Exception'); 831 | 832 | $serviceMock = $this->getServiceMock(); 833 | $serviceMock->expects($this->once())->method('getSegmentExplorer') 834 | ->will($this->throwException(new ServiceException)); 835 | 836 | $client = new Strava\API\Client($serviceMock); 837 | $client->getSegmentExplorer("lng.lat"); 838 | } 839 | 840 | public function testGetSegmentEffort() 841 | { 842 | $serviceMock = $this->getServiceMock(); 843 | $serviceMock->expects($this->once())->method('getSegmentEffort') 844 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 845 | 846 | $client = new Strava\API\Client($serviceMock); 847 | $output = $client->getSegmentEffort(1234); 848 | 849 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 850 | } 851 | 852 | public function testGetSegmentEffortException() 853 | { 854 | $this->expectException('Strava\API\Exception'); 855 | 856 | $serviceMock = $this->getServiceMock(); 857 | $serviceMock->expects($this->once())->method('getSegmentEffort') 858 | ->will($this->throwException(new ServiceException)); 859 | 860 | $client = new Strava\API\Client($serviceMock); 861 | $client->getSegmentEffort(1234); 862 | } 863 | 864 | public function testGetStreamsActivity() 865 | { 866 | $serviceMock = $this->getServiceMock(); 867 | $serviceMock->expects($this->once())->method('getStreamsActivity') 868 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 869 | 870 | $client = new Strava\API\Client($serviceMock); 871 | $output = $client->getStreamsActivity(1234, 'abc'); 872 | 873 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 874 | } 875 | 876 | public function testGetStreamsActivityException() 877 | { 878 | $this->expectException('Strava\API\Exception'); 879 | 880 | $serviceMock = $this->getServiceMock(); 881 | $serviceMock->expects($this->once())->method('getStreamsActivity') 882 | ->will($this->throwException(new ServiceException)); 883 | 884 | $client = new Strava\API\Client($serviceMock); 885 | $client->getStreamsActivity(1234, 'abc'); 886 | } 887 | 888 | public function testGetStreamsEffort() 889 | { 890 | $serviceMock = $this->getServiceMock(); 891 | $serviceMock->expects($this->once())->method('getStreamsEffort') 892 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 893 | 894 | $client = new Strava\API\Client($serviceMock); 895 | $output = $client->getStreamsEffort(1234, 'abc'); 896 | 897 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 898 | } 899 | 900 | public function testGetStreamsEffortException() 901 | { 902 | $this->expectException('Strava\API\Exception'); 903 | 904 | $serviceMock = $this->getServiceMock(); 905 | $serviceMock->expects($this->once())->method('getStreamsEffort') 906 | ->will($this->throwException(new ServiceException)); 907 | 908 | $client = new Strava\API\Client($serviceMock); 909 | $client->getStreamsEffort(1234, 'abc'); 910 | } 911 | 912 | public function testGetStreamsSegment() 913 | { 914 | $serviceMock = $this->getServiceMock(); 915 | $serviceMock->expects($this->once())->method('getStreamsSegment') 916 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 917 | 918 | $client = new Strava\API\Client($serviceMock); 919 | $output = $client->getStreamsSegment(1234, 'abc'); 920 | 921 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 922 | } 923 | 924 | public function testGetStreamsSegmentException() 925 | { 926 | $this->expectException('Strava\API\Exception'); 927 | 928 | $serviceMock = $this->getServiceMock(); 929 | $serviceMock->expects($this->once())->method('getStreamsSegment') 930 | ->will($this->throwException(new ServiceException)); 931 | 932 | $client = new Strava\API\Client($serviceMock); 933 | $client->getStreamsSegment(1234, 'abc'); 934 | } 935 | 936 | public function testGetStreamsRoute() 937 | { 938 | $serviceMock = $this->getServiceMock(); 939 | $serviceMock->expects($this->once())->method('getStreamsRoute') 940 | ->will($this->returnValue(['id' => 1234, 'data' => 'test'])); 941 | 942 | $client = new Strava\API\Client($serviceMock); 943 | $output = $client->getStreamsRoute(1234); 944 | 945 | $this->assertEquals(['id' => 1234, 'data' => 'test'], $output); 946 | } 947 | 948 | public function testGetStreamsRouteException() 949 | { 950 | $this->expectException('Strava\API\Exception'); 951 | 952 | $serviceMock = $this->getServiceMock(); 953 | $serviceMock->expects($this->once())->method('getStreamsRoute') 954 | ->will($this->throwException(new ServiceException)); 955 | 956 | $client = new Strava\API\Client($serviceMock); 957 | $client->getStreamsRoute(1234); 958 | } 959 | 960 | public function testCreateWebhookSubscription() 961 | { 962 | $serviceMock = $this->getServiceMock(); 963 | $serviceMock->expects($this->once())->method('createWebhookSubscription') 964 | ->with(12345, 'secret', 'https://example.com/webhook', 'verify_token') 965 | ->will($this->returnValue(['id' => 123, 'callback_url' => 'https://example.com/webhook'])); 966 | 967 | $client = new Strava\API\Client($serviceMock); 968 | $output = $client->createWebhookSubscription(12345, 'secret', 'https://example.com/webhook', 'verify_token'); 969 | 970 | $this->assertEquals(['id' => 123, 'callback_url' => 'https://example.com/webhook'], $output); 971 | } 972 | 973 | public function testCreateWebhookSubscriptionException() 974 | { 975 | $this->expectException('Strava\API\Exception'); 976 | 977 | $serviceMock = $this->getServiceMock(); 978 | $serviceMock->expects($this->once())->method('createWebhookSubscription') 979 | ->will($this->throwException(new ServiceException)); 980 | 981 | $client = new Strava\API\Client($serviceMock); 982 | $client->createWebhookSubscription(12345, 'secret', 'https://example.com/webhook', 'verify_token'); 983 | } 984 | 985 | public function testListWebhookSubscriptions() 986 | { 987 | $serviceMock = $this->getServiceMock(); 988 | $serviceMock->expects($this->once())->method('listWebhookSubscriptions') 989 | ->with(12345, 'secret') 990 | ->will($this->returnValue([['id' => 123, 'callback_url' => 'https://example.com/webhook']])); 991 | 992 | $client = new Strava\API\Client($serviceMock); 993 | $output = $client->listWebhookSubscriptions(12345, 'secret'); 994 | 995 | $this->assertEquals([['id' => 123, 'callback_url' => 'https://example.com/webhook']], $output); 996 | } 997 | 998 | public function testListWebhookSubscriptionsException() 999 | { 1000 | $this->expectException('Strava\API\Exception'); 1001 | 1002 | $serviceMock = $this->getServiceMock(); 1003 | $serviceMock->expects($this->once())->method('listWebhookSubscriptions') 1004 | ->will($this->throwException(new ServiceException)); 1005 | 1006 | $client = new Strava\API\Client($serviceMock); 1007 | $client->listWebhookSubscriptions(12345, 'secret'); 1008 | } 1009 | 1010 | public function testDeleteWebhookSubscription() 1011 | { 1012 | $serviceMock = $this->getServiceMock(); 1013 | $serviceMock->expects($this->once())->method('deleteWebhookSubscription') 1014 | ->with(12345, 'secret', 123) 1015 | ->will($this->returnValue(['success' => true])); 1016 | 1017 | $client = new Strava\API\Client($serviceMock); 1018 | $output = $client->deleteWebhookSubscription(12345, 'secret', 123); 1019 | 1020 | $this->assertEquals(['success' => true], $output); 1021 | } 1022 | 1023 | public function testDeleteWebhookSubscriptionException() 1024 | { 1025 | $this->expectException('Strava\API\Exception'); 1026 | 1027 | $serviceMock = $this->getServiceMock(); 1028 | $serviceMock->expects($this->once())->method('deleteWebhookSubscription') 1029 | ->will($this->throwException(new ServiceException)); 1030 | 1031 | $client = new Strava\API\Client($serviceMock); 1032 | $client->deleteWebhookSubscription(12345, 'secret', 123); 1033 | } 1034 | 1035 | } 1036 | --------------------------------------------------------------------------------