├── .github
├── FUNDING.yml
├── dependabot.yml
├── ISSUE_TEMPLATE
│ └── config.yml
└── workflows
│ ├── code-style.yml
│ ├── update-changelog.yml
│ ├── static-analysis.yml
│ ├── run-tests.yml
│ └── dependabot-auto-merge.yml
├── src
├── TokenProvider.php
├── InMemoryTokenProvider.php
├── RefreshableTokenProvider.php
├── UploadSessionCursor.php
├── Exceptions
│ └── BadRequest.php
└── Client.php
├── phpunit.xml
├── LICENSE.md
├── composer.json
├── README.md
└── CHANGELOG.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: spatie
2 | custom: https://spatie.be/open-source/support-us
3 |
--------------------------------------------------------------------------------
/src/TokenProvider.php:
--------------------------------------------------------------------------------
1 | token;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/RefreshableTokenProvider.php:
--------------------------------------------------------------------------------
1 | getBody(), true);
15 |
16 | if ($body !== null) {
17 | if (isset($body['error']['.tag'])) {
18 | $this->dropboxCode = $body['error']['.tag'];
19 | }
20 |
21 | parent::__construct($body['error_summary']);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/update-changelog.yml:
--------------------------------------------------------------------------------
1 | name: "Update Changelog"
2 |
3 | on:
4 | release:
5 | types: [released]
6 |
7 | jobs:
8 | update:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v4
14 | with:
15 | ref: main
16 |
17 | - name: Update Changelog
18 | uses: stefanzweifel/changelog-updater-action@v1
19 | with:
20 | latest-version: ${{ github.event.release.name }}
21 | release-notes: ${{ github.event.release.body }}
22 |
23 | - name: Commit updated CHANGELOG
24 | uses: stefanzweifel/git-auto-commit-action@v5
25 | with:
26 | branch: main
27 | commit_message: Update CHANGELOG
28 | file_pattern: CHANGELOG.md
29 |
--------------------------------------------------------------------------------
/.github/workflows/static-analysis.yml:
--------------------------------------------------------------------------------
1 | name: Static Analysis
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | phpstan:
7 | name: PHPStan (PHP ${{ matrix.php-version }})
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | php-version:
13 | - '8.2'
14 |
15 | steps:
16 | - name: Checkout code
17 | uses: actions/checkout@v4
18 |
19 | - name: Install PHP
20 | uses: shivammathur/setup-php@v2
21 | with:
22 | coverage: none
23 | php-version: ${{ matrix.php-version }}
24 | tools: cs2pr
25 |
26 | - name: Install Composer dependencies
27 | uses: ramsey/composer-install@v3
28 |
29 | - name: Run PHPStan
30 | run: 'vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr'
31 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | fail-fast: true
10 | matrix:
11 | php: [8.2, 8.1]
12 | stability: [lowest, highest]
13 |
14 | name: PHP ${{ matrix.php }} - ${{ matrix.stability }} deps
15 |
16 | steps:
17 | - name: Checkout code
18 | uses: actions/checkout@v4
19 |
20 | - name: Setup PHP
21 | uses: shivammathur/setup-php@v2
22 | with:
23 | php-version: ${{ matrix.php }}
24 | coverage: none
25 |
26 | - name: Setup problem matchers
27 | run: |
28 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
29 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
30 |
31 | - name: Install dependencies
32 | uses: ramsey/composer-install@v3
33 | with:
34 | dependency-versions: ${{ matrix.stability }}
35 |
36 | - name: Execute tests
37 | run: vendor/bin/pest
38 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | tests
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | src/
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Spatie bvba
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot-auto-merge.yml:
--------------------------------------------------------------------------------
1 | name: dependabot-auto-merge
2 | on: pull_request_target
3 |
4 | permissions:
5 | pull-requests: write
6 | contents: write
7 |
8 | jobs:
9 | dependabot:
10 | runs-on: ubuntu-latest
11 | if: ${{ github.actor == 'dependabot[bot]' }}
12 | steps:
13 |
14 | - name: Dependabot metadata
15 | id: metadata
16 | uses: dependabot/fetch-metadata@v2.4.0
17 | with:
18 | github-token: "${{ secrets.GITHUB_TOKEN }}"
19 | compat-lookup: true
20 |
21 | - name: Auto-merge Dependabot PRs for semver-minor updates
22 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
23 | run: gh pr merge --auto --merge "$PR_URL"
24 | env:
25 | PR_URL: ${{github.event.pull_request.html_url}}
26 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
27 |
28 | - name: Auto-merge Dependabot PRs for semver-patch updates
29 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
30 | run: gh pr merge --auto --merge "$PR_URL"
31 | env:
32 | PR_URL: ${{github.event.pull_request.html_url}}
33 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
34 |
35 | - name: Auto-merge Dependabot PRs for Action major versions when compatibility is higher than 90%
36 | if: ${{steps.metadata.outputs.package-ecosystem == 'github_actions' && steps.metadata.outputs.update-type == 'version-update:semver-major' && steps.metadata.outputs.compatibility-score >= 90}}
37 | run: gh pr merge --auto --merge "$PR_URL"
38 | env:
39 | PR_URL: ${{github.event.pull_request.html_url}}
40 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
41 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spatie/dropbox-api",
3 | "description": "A minimal implementation of Dropbox API v2",
4 | "keywords": [
5 | "spatie",
6 | "dropbox-api",
7 | "dropbox",
8 | "api",
9 | "v2"
10 | ],
11 | "homepage": "https://github.com/spatie/dropbox-api",
12 | "license": "MIT",
13 | "authors": [
14 | {
15 | "name": "Alex Vanderbist",
16 | "email": "alex.vanderbist@gmail.com",
17 | "homepage": "https://spatie.be",
18 | "role": "Developer"
19 | },
20 | {
21 | "name": "Freek Van der Herten",
22 | "email": "freek@spatie.be",
23 | "homepage": "https://spatie.be",
24 | "role": "Developer"
25 | }
26 | ],
27 | "require": {
28 | "php": "^8.1",
29 | "ext-json": "*",
30 | "graham-campbell/guzzle-factory": "^4.0.2|^5.0|^6.0|^7.0",
31 | "guzzlehttp/guzzle": "^6.2|^7.0"
32 | },
33 | "require-dev": {
34 | "laravel/pint": "^1.10.1",
35 | "pestphp/pest": "^2.6.3",
36 | "phpstan/phpstan": "^1.10.16"
37 | },
38 | "autoload": {
39 | "psr-4": {
40 | "Spatie\\Dropbox\\": "src"
41 | }
42 | },
43 | "autoload-dev": {
44 | "psr-4": {
45 | "Spatie\\Dropbox\\Tests\\": "tests"
46 | }
47 | },
48 | "scripts": {
49 | "test": "vendor/bin/pest",
50 | "pint": "vendor/bin/pint",
51 | "phpstan": "vendor/bin/phpstan analyze"
52 | },
53 | "config": {
54 | "sort-packages": true,
55 | "allow-plugins": {
56 | "pestphp/pest-plugin": true
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A minimal implementation of Dropbox API v2
2 |
3 | [](https://packagist.org/packages/spatie/dropbox-api)
4 | [](https://github.com/spatie/dropbox-api/actions?query=workflow%3ATests+branch%3Amaster)
5 | [](https://github.com/spatie/dropbox-api/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amaster)
6 | [](https://packagist.org/packages/spatie/dropbox-api)
7 |
8 |
9 | This is a minimal PHP implementation of the [Dropbox API v2](https://www.dropbox.com/developers/documentation/http/overview). It contains only the methods needed for [our flysystem-dropbox adapter](https://github.com/spatie/flysystem-dropbox). We are open however to PRs that add extra methods to the client.
10 |
11 | Here are a few examples on how you can use the package:
12 |
13 | ```php
14 | $client = new Spatie\Dropbox\Client($authorizationToken);
15 |
16 | //create a folder
17 | $client->createFolder($path);
18 |
19 | //list a folder
20 | $client->listFolder($path);
21 |
22 | //get a temporary link
23 | $client->getTemporaryLink($path);
24 | ```
25 |
26 | ## Support us
27 |
28 | [
](https://spatie.be/github-ad-click/dropbox-api)
29 |
30 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
31 |
32 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).
33 |
34 | ## Installation
35 |
36 | You can install the package via composer:
37 |
38 | ``` bash
39 | composer require spatie/dropbox-api
40 | ```
41 |
42 | ## Usage
43 |
44 | The first thing you need to do is get an authorization token at Dropbox. Unlike [other companies](https://google.com) Dropbox has made this very easy. You can just generate a token in the [App Console](https://www.dropbox.com/developers/apps) for any Dropbox API app. You'll find more info at [the Dropbox Developer Blog](https://blogs.dropbox.com/developers/2014/05/generate-an-access-token-for-your-own-account/).
45 |
46 | With an authorization token you can instantiate a `Spatie\Dropbox\Client`.
47 |
48 | ```php
49 | $client = new Spatie\Dropbox\Client($authorizationToken);
50 | ```
51 |
52 | or alternatively you can implement `Spatie\Dropbox\TokenProvider`
53 | which will provide the access-token from its
54 | `TokenProvider->getToken(): string` method.
55 |
56 | If you use oauth2 to authenticate and to acquire refresh-tokens and access-tokens,
57 | (like [thephpleague/oauth2-client](https://github.com/thephpleague/oauth2-client)),
58 | you can create an adapter that internally takes care of token-expiration and refreshing tokens,
59 | and at runtime will supply the access-token via the `TokenProvider->getToken(): string` method.
60 |
61 | *(Dropbox announced they will be moving to short-lived access_tokens mid 2021).*
62 |
63 |
64 | ```php
65 | // implements Spatie\Dropbox\TokenProvider
66 | $tokenProvider = new AutoRefreshingDropBoxTokenService($refreshToken);
67 | $client = new Spatie\Dropbox\Client($tokenProvider);
68 | ```
69 |
70 |
71 |
72 | or alternatively you can authenticate as an App using your App Key & Secret.
73 |
74 | ```php
75 | $client = new Spatie\Dropbox\Client([$appKey, $appSecret]);
76 | ```
77 |
78 | If you only need to access the public endpoints you can instantiate `Spatie\Dropbox\Client` without any arguments.
79 |
80 | ```php
81 | $client = new Spatie\Dropbox\Client();
82 | ```
83 |
84 | ## Dropbox Endpoints
85 |
86 | Look in [the source code of `Spatie\Dropbox\Client`](https://github.com/spatie/dropbox-api/blob/master/src/Client.php) to discover the methods you can use.
87 |
88 | Here's an example:
89 |
90 | ```php
91 | $content = 'hello, world';
92 | $client->upload('/dropboxpath/filename.txt', $content, $mode='add');
93 |
94 | $from = '/dropboxpath/somefile.txt';
95 | $to = '/dropboxpath/archive/somefile.txt';
96 | $client->move($from, $to);
97 | ```
98 |
99 | If the destination filename already exists, dropbox will throw an Exception with 'to/conflict/file/..'
100 |
101 | The ``upload()`` and ``move()`` methods have an optional extra 'autorename' argument
102 | to try and let dropbox automatically rename the file if there is such a conflict.
103 |
104 | Here's an example:
105 |
106 | ```php
107 | $from = '/dropboxpath/somefile.txt';
108 | $to = '/dropboxpath/archive/somefile.txt';
109 | $client->move($from, $to, $autorename=true);
110 | // with autorename results in 'somefile (1).txt'
111 | ```
112 |
113 |
114 | If you do not find your favorite method, you can directly use the `contentEndpointRequest` and `rpcEndpointRequest` functions.
115 |
116 | ```php
117 | public function contentEndpointRequest(string $endpoint, array $arguments, $body): ResponseInterface
118 |
119 | public function rpcEndpointRequest(string $endpoint, array $parameters): array
120 | ```
121 |
122 | Here's an example:
123 |
124 | ```php
125 | $client->rpcEndpointRequest('search', ['path' => '', 'query' => 'bat cave']);
126 | ```
127 |
128 | If you need to change the subdomain of the endpoint URL used in the API request, you can prefix the endpoint path with `subdomain::`.
129 |
130 | Here's an example:
131 |
132 | ```php
133 | $client->rpcEndpointRequest('content::files/get_thumbnail_batch', $parameters);
134 | ```
135 |
136 | ## Changelog
137 |
138 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
139 |
140 | ## Testing
141 |
142 | ``` bash
143 | composer test
144 | ```
145 |
146 | ## Contributing
147 |
148 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.
149 |
150 | You can run `composer pint` to run the code style fixer, and `composer phpstan` to run the static analysis.
151 |
152 | ## Security
153 |
154 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker.
155 |
156 | ## Postcardware
157 |
158 | You're free to use this package (it's [MIT-licensed](LICENSE.md)), but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using.
159 |
160 | Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium.
161 |
162 | We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards).
163 |
164 | ## Credits
165 |
166 | - [Alex Vanderbist](https://github.com/AlexVanderbist)
167 | - [Freek Van der Herten](https://github.com/freekmurze)
168 | - [All Contributors](../../contributors)
169 |
170 | ## License
171 |
172 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
173 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `dropbox-api` will be documented in this file
4 |
5 | ## 1.23.0 - 2025-01-06
6 |
7 | ### What's Changed
8 |
9 | * Run tests using Pest by @jmsche in https://github.com/spatie/dropbox-api/pull/124
10 | * Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/spatie/dropbox-api/pull/125
11 | * Bump actions/checkout from 2 to 4 by @dependabot in https://github.com/spatie/dropbox-api/pull/126
12 | * Bump aglipanci/laravel-pint-action from 2.3.0 to 2.3.1 by @dependabot in https://github.com/spatie/dropbox-api/pull/129
13 | * Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/spatie/dropbox-api/pull/128
14 | * Bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/spatie/dropbox-api/pull/131
15 | * Bump aglipanci/laravel-pint-action from 2.3.1 to 2.4 by @dependabot in https://github.com/spatie/dropbox-api/pull/133
16 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.2.0 by @dependabot in https://github.com/spatie/dropbox-api/pull/136
17 | * PHP 8.4 deprecations by @simoheinonen in https://github.com/spatie/dropbox-api/pull/138
18 |
19 | ### New Contributors
20 |
21 | * @simoheinonen made their first contribution in https://github.com/spatie/dropbox-api/pull/138
22 |
23 | **Full Changelog**: https://github.com/spatie/dropbox-api/compare/1.22.0...1.23.0
24 |
25 | ## 1.22.0 - 2023-06-08
26 |
27 | ### What's Changed
28 |
29 | - Cleanup and improvements by @jmsche in https://github.com/spatie/dropbox-api/pull/123
30 | - Drop support for old PHP versions
31 |
32 | **Full Changelog**: https://github.com/spatie/dropbox-api/compare/1.21.2...1.22.0
33 |
34 | ## 1.21.2 - 2023-06-07
35 |
36 | ### What's Changed
37 |
38 | - Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/dropbox-api/pull/115
39 | - Bump dependabot/fetch-metadata from 1.4.0 to 1.5.0 by @dependabot in https://github.com/spatie/dropbox-api/pull/117
40 | - Bump dependabot/fetch-metadata from 1.5.0 to 1.5.1 by @dependabot in https://github.com/spatie/dropbox-api/pull/118
41 | - Fix README badges by @jmsche in https://github.com/spatie/dropbox-api/pull/120
42 | - Allow graham-campbell/guzzle-factory v7 by @jmsche in https://github.com/spatie/dropbox-api/pull/119
43 | - Fix PHPUnit errors & failures by @jmsche in https://github.com/spatie/dropbox-api/pull/121
44 |
45 | **Full Changelog**: https://github.com/spatie/dropbox-api/compare/1.21.1...1.21.2
46 |
47 | ## 1.21.1 - 2023-03-17
48 |
49 | ### What's Changed
50 |
51 | - Add PHP 8.2 support by @patinthehat in https://github.com/spatie/dropbox-api/pull/110
52 | - Add dependabot automation by @patinthehat in https://github.com/spatie/dropbox-api/pull/109
53 | - Update Dependabot Automation by @patinthehat in https://github.com/spatie/dropbox-api/pull/112
54 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/dropbox-api/pull/113
55 | - Allow graham-campbell/guzzle-factory v6 by @jmsche in https://github.com/spatie/dropbox-api/pull/114
56 |
57 | ### New Contributors
58 |
59 | - @patinthehat made their first contribution in https://github.com/spatie/dropbox-api/pull/110
60 | - @dependabot made their first contribution in https://github.com/spatie/dropbox-api/pull/113
61 |
62 | **Full Changelog**: https://github.com/spatie/dropbox-api/compare/1.21.0...1.21.1
63 |
64 | ## 1.21.0 - 2022-09-27
65 |
66 | ### What's Changed
67 |
68 | - Add ability to set the namespace ID for requests by @rstefanic in https://github.com/spatie/dropbox-api/pull/105
69 |
70 | ### New Contributors
71 |
72 | - @rstefanic made their first contribution in https://github.com/spatie/dropbox-api/pull/105
73 |
74 | **Full Changelog**: https://github.com/spatie/dropbox-api/compare/1.20.2...1.21.0
75 |
76 | ## 1.20.2 - 2022-06-24
77 |
78 | ### What's Changed
79 |
80 | - uploadSessionStart and uploadSessionFinish can accept resource by @dmitryuk in https://github.com/spatie/dropbox-api/pull/102
81 |
82 | ### New Contributors
83 |
84 | - @dmitryuk made their first contribution in https://github.com/spatie/dropbox-api/pull/102
85 |
86 | **Full Changelog**: https://github.com/spatie/dropbox-api/compare/1.20.1...1.20.2
87 |
88 | ## 1.20.1 - 2022-03-29
89 |
90 | ## What's Changed
91 |
92 | - Fix refreshable token response by @einarsozols in https://github.com/spatie/dropbox-api/pull/100
93 |
94 | ## New Contributors
95 |
96 | - @einarsozols made their first contribution in https://github.com/spatie/dropbox-api/pull/100
97 |
98 | **Full Changelog**: https://github.com/spatie/dropbox-api/compare/1.20.0...1.20.1
99 |
100 | ## Unreleased
101 |
102 | - Added refreshable token provider interface.
103 |
104 | ## 1.19.1 - 2021-07-04
105 |
106 | - fix compability with guzzlehttp/psr7 2.0 (#91)
107 |
108 | ## 1.19.0 - 2021-06-18
109 |
110 | - add autoRename parameter for move() method (#89)
111 |
112 | ## 1.18.0 - 2021-05-27
113 |
114 | - add autorename option to upload method (#86)
115 |
116 | ## 1.17.1 - 2021-03-01
117 |
118 | - allow graham-campbell/guzzle-factory v5 (#79)
119 |
120 | ## 1.17.0 - 2020-12-08
121 |
122 | - `TokenProvider` interface for accesstokens (#76)
123 |
124 | ## 1.16.1 - 2020-11-27
125 |
126 | - allow PHP 8
127 |
128 | ## 1.16.0 - 2020-09-25
129 |
130 | - allow the Client to work with Dropbox business accounts
131 |
132 | ## 1.15.0 - 2020-07-09
133 |
134 | - allow Guzzle 7 (#70)
135 |
136 | ## 1.14.0 - 2020-05-11
137 |
138 | - add support for app authentication and no authentication
139 |
140 | ## 1.13.0 - 2020-05-03
141 |
142 | - added `downloadZip` (#66)
143 |
144 | ## 1.12.0 - 2020-02-04
145 |
146 | - add `search` method
147 |
148 | ## 1.11.1 - 2019-12-12
149 |
150 | - make compatible with PHP 7.4
151 |
152 | ## 1.11.0 - 2019-07-04
153 |
154 | - add `$response` to `BadRequest`
155 |
156 | ## 1.10.0 - 2019-07-01
157 |
158 | - move retry stuff to package
159 |
160 | ## 1.9.0 - 2019-05-21
161 |
162 | - make guzzle retry 5xx and 429 responses
163 |
164 | ## 1.8.0 - 2019-04-13
165 |
166 | - add `getEndpointUrl`
167 | - drop support for PHP 7.0
168 |
169 | ## 1.7.1 - 2019-02-13
170 |
171 | - fix for `createSharedLinkWithSettings` with empty settings
172 |
173 | ## 1.7.0 - 2019-02-06
174 |
175 | - add getter and setter for the access token
176 |
177 | ## 1.6.6 - 2018-07-19
178 |
179 | - fix for piped streams
180 |
181 | ## 1.6.5 - 2018-01-15
182 |
183 | - adjust `normalizePath` to allow id/rev/ns to be queried
184 |
185 | ## 1.6.4 - 2017-12-05
186 |
187 | - fix max chunk size
188 |
189 | ## 1.6.1 - 2017-07-28
190 |
191 | - fix for finishing upload session
192 |
193 | ## 1.6.0 - 2017-07-28
194 |
195 | - add various new methods to enable chuncked uploads
196 |
197 | ## 1.5.3 - 2017-07-28
198 |
199 | - use recommended `move_v2` method to move files
200 |
201 | ## 1.5.2 - 2017-07-17
202 |
203 | - add missing parameters to `listSharedLinks` method
204 |
205 | ## 1.5.1 - 2017-07-17
206 |
207 | - fix broken `revokeToken` and `getAccountInfo`
208 |
209 | ## 1.5.0 - 2017-07-11
210 |
211 | - add `revokeToken` and `getAccountInfo`
212 |
213 | ## 1.4.0 - 2017-07-11
214 |
215 | - add `listSharedLinks`
216 |
217 | ## 1.3.0 - 2017-07-04
218 |
219 | - add error code to thrown exception
220 |
221 | ## 1.2.0 - 2017-04-29
222 |
223 | - added `createSharedLinkWithSettings`
224 |
225 | ## 1.1.0 - 2017-04-22
226 |
227 | - added `listFolderContinue`
228 |
229 | ## 1.0.1 - 2017-04-19
230 |
231 | - Bugfix: set default value for request body
232 |
233 | ## 1.0.0 - 2017-04-19
234 |
235 | - initial release
236 |
--------------------------------------------------------------------------------
/src/Client.php:
--------------------------------------------------------------------------------
1 | |TokenProvider|null $accessTokenOrAppCredentials
54 | * @param int $maxChunkSize Set max chunk size per request (determines when to switch from "one shot upload" to upload session and defines chunk size for uploads via session).
55 | * @param int $maxUploadChunkRetries How many times to retry an upload session start or append after RequestException.
56 | * @param ?string $teamMemberId The team member ID to be specified for Dropbox business accounts
57 | */
58 | public function __construct(
59 | string|array|TokenProvider|null $accessTokenOrAppCredentials = null,
60 | ?ClientInterface $client = null,
61 | int $maxChunkSize = self::MAX_CHUNK_SIZE,
62 | protected int $maxUploadChunkRetries = 0,
63 | protected ?string $teamMemberId = null,
64 | ) {
65 | if (is_array($accessTokenOrAppCredentials)) {
66 | [$this->appKey, $this->appSecret] = $accessTokenOrAppCredentials;
67 | }
68 | if ($accessTokenOrAppCredentials instanceof TokenProvider) {
69 | $this->tokenProvider = $accessTokenOrAppCredentials;
70 | }
71 | if (is_string($accessTokenOrAppCredentials)) {
72 | $this->tokenProvider = new InMemoryTokenProvider($accessTokenOrAppCredentials);
73 | }
74 |
75 | $this->client = $client ?? new GuzzleClient(['handler' => GuzzleFactory::handler()]);
76 |
77 | $this->maxChunkSize = ($maxChunkSize < self::MAX_CHUNK_SIZE ? max($maxChunkSize, 1) : self::MAX_CHUNK_SIZE);
78 | }
79 |
80 | /**
81 | * Copy a file or folder to a different location in the user's Dropbox.
82 | *
83 | * If the source path is a folder all its contents will be copied.
84 | *
85 | * @return array
86 | *
87 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-copy_v2
88 | */
89 | public function copy(string $fromPath, string $toPath): array
90 | {
91 | $parameters = [
92 | 'from_path' => $this->normalizePath($fromPath),
93 | 'to_path' => $this->normalizePath($toPath),
94 | ];
95 |
96 | return $this->rpcEndpointRequest('files/copy_v2', $parameters);
97 | }
98 |
99 | /**
100 | * Create a folder at a given path.
101 | *
102 | * @return array
103 | *
104 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-create_folder
105 | */
106 | public function createFolder(string $path): array
107 | {
108 | $parameters = [
109 | 'path' => $this->normalizePath($path),
110 | ];
111 |
112 | $object = $this->rpcEndpointRequest('files/create_folder', $parameters);
113 |
114 | $object['.tag'] = 'folder';
115 |
116 | return $object;
117 | }
118 |
119 | /**
120 | * Create a shared link with custom settings.
121 | *
122 | * If no settings are given then the default visibility is RequestedVisibility.public.
123 | * The resolved visibility, though, may depend on other aspects such as team and
124 | * shared folder settings). Only for paid users.
125 | *
126 | * @param array $settings
127 | * @return array
128 | *
129 | * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-create_shared_link_with_settings
130 | */
131 | public function createSharedLinkWithSettings(string $path, array $settings = []): array
132 | {
133 | $parameters = [
134 | 'path' => $this->normalizePath($path),
135 | ];
136 |
137 | if (count($settings)) {
138 | $parameters = array_merge(compact('settings'), $parameters);
139 | }
140 |
141 | return $this->rpcEndpointRequest('sharing/create_shared_link_with_settings', $parameters);
142 | }
143 |
144 | /**
145 | * Search a file or folder in the user's Dropbox.
146 | *
147 | * @return array
148 | *
149 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-search
150 | */
151 | public function search(string $query, bool $includeHighlights = false): array
152 | {
153 | $parameters = [
154 | 'query' => $query,
155 | 'include_highlights' => $includeHighlights,
156 | ];
157 |
158 | return $this->rpcEndpointRequest('files/search_v2', $parameters);
159 | }
160 |
161 | /**
162 | * List shared links.
163 | *
164 | * For empty path returns a list of all shared links. For non-empty path
165 | * returns a list of all shared links with access to the given path.
166 | *
167 | * If direct_only is set true, only direct links to the path will be returned, otherwise
168 | * it may return link to the path itself and parent folders as described on docs.
169 | *
170 | * @return array
171 | *
172 | * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-list_shared_links
173 | */
174 | public function listSharedLinks(?string $path = null, bool $direct_only = false, ?string $cursor = null): array
175 | {
176 | $parameters = [
177 | 'path' => $path ? $this->normalizePath($path) : null,
178 | 'cursor' => $cursor,
179 | 'direct_only' => $direct_only,
180 | ];
181 |
182 | $body = $this->rpcEndpointRequest('sharing/list_shared_links', $parameters);
183 |
184 | return $body['links'];
185 | }
186 |
187 | /**
188 | * Delete the file or folder at a given path.
189 | *
190 | * If the path is a folder, all its contents will be deleted too.
191 | * A successful response indicates that the file or folder was deleted.
192 | *
193 | * @return array
194 | *
195 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-delete
196 | */
197 | public function delete(string $path): array
198 | {
199 | $parameters = [
200 | 'path' => $this->normalizePath($path),
201 | ];
202 |
203 | return $this->rpcEndpointRequest('files/delete', $parameters);
204 | }
205 |
206 | /**
207 | * Download a file from a user's Dropbox.
208 | *
209 | *
210 | * @return resource
211 | *
212 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download
213 | */
214 | public function download(string $path)
215 | {
216 | $arguments = [
217 | 'path' => $this->normalizePath($path),
218 | ];
219 |
220 | $response = $this->contentEndpointRequest('files/download', $arguments);
221 |
222 | return StreamWrapper::getResource($response->getBody());
223 | }
224 |
225 | /**
226 | * Download a folder from the user's Dropbox, as a zip file.
227 | * The folder must be less than 20 GB in size and have fewer than 10,000 total files.
228 | * The input cannot be a single file. Any single file must be less than 4GB in size.
229 | *
230 | *
231 | * @return resource
232 | *
233 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download_zip
234 | */
235 | public function downloadZip(string $path)
236 | {
237 | $arguments = [
238 | 'path' => $this->normalizePath($path),
239 | ];
240 |
241 | $response = $this->contentEndpointRequest('files/download_zip', $arguments);
242 |
243 | return StreamWrapper::getResource($response->getBody());
244 | }
245 |
246 | /**
247 | * Returns the metadata for a file or folder.
248 | *
249 | * Note: Metadata for the root folder is unsupported.
250 | *
251 | * @return array
252 | *
253 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_metadata
254 | */
255 | public function getMetadata(string $path): array
256 | {
257 | $parameters = [
258 | 'path' => $this->normalizePath($path),
259 | ];
260 |
261 | return $this->rpcEndpointRequest('files/get_metadata', $parameters);
262 | }
263 |
264 | /**
265 | * Get a temporary link to stream content of a file.
266 | *
267 | * This link will expire in four hours and afterwards you will get 410 Gone.
268 | * Content-Type of the link is determined automatically by the file's mime type.
269 | *
270 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_temporary_link
271 | */
272 | public function getTemporaryLink(string $path): string
273 | {
274 | $parameters = [
275 | 'path' => $this->normalizePath($path),
276 | ];
277 |
278 | $body = $this->rpcEndpointRequest('files/get_temporary_link', $parameters);
279 |
280 | return $body['link'];
281 | }
282 |
283 | /**
284 | * Get a thumbnail for an image.
285 | *
286 | * This method currently supports files with the following file extensions:
287 | * jpg, jpeg, png, tiff, tif, gif and bmp.
288 | *
289 | * Photos that are larger than 20MB in size won't be converted to a thumbnail.
290 | *
291 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail
292 | */
293 | public function getThumbnail(string $path, string $format = 'jpeg', string $size = 'w64h64'): string
294 | {
295 | $arguments = [
296 | 'path' => $this->normalizePath($path),
297 | 'format' => $format,
298 | 'size' => $size,
299 | ];
300 |
301 | $response = $this->contentEndpointRequest('files/get_thumbnail', $arguments);
302 |
303 | return (string) $response->getBody();
304 | }
305 |
306 | /**
307 | * Starts returning the contents of a folder.
308 | *
309 | * If the result's ListFolderResult.has_more field is true, call
310 | * list_folder/continue with the returned ListFolderResult.cursor to retrieve more entries.
311 | *
312 | * Note: auth.RateLimitError may be returned if multiple list_folder or list_folder/continue calls
313 | * with same parameters are made simultaneously by same API app for same user. If your app implements
314 | * retry logic, please hold off the retry until the previous request finishes.
315 | *
316 | * @return array
317 | *
318 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
319 | */
320 | public function listFolder(string $path = '', bool $recursive = false): array
321 | {
322 | $parameters = [
323 | 'path' => $this->normalizePath($path),
324 | 'recursive' => $recursive,
325 | ];
326 |
327 | return $this->rpcEndpointRequest('files/list_folder', $parameters);
328 | }
329 |
330 | /**
331 | * Once a cursor has been retrieved from list_folder, use this to paginate through all files and
332 | * retrieve updates to the folder, following the same rules as documented for list_folder.
333 | *
334 | * @return array
335 | *
336 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-continue
337 | */
338 | public function listFolderContinue(string $cursor = ''): array
339 | {
340 | return $this->rpcEndpointRequest('files/list_folder/continue', compact('cursor'));
341 | }
342 |
343 | /**
344 | * Move a file or folder to a different location in the user's Dropbox.
345 | *
346 | * If the source path is a folder all its contents will be moved.
347 | *
348 | * @return array
349 | *
350 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-move_v2
351 | */
352 | public function move(string $fromPath, string $toPath, bool $autorename = false): array
353 | {
354 | $parameters = [
355 | 'from_path' => $this->normalizePath($fromPath),
356 | 'to_path' => $this->normalizePath($toPath),
357 | 'autorename' => $autorename,
358 | ];
359 |
360 | return $this->rpcEndpointRequest('files/move_v2', $parameters);
361 | }
362 |
363 | /**
364 | * The file should be uploaded in chunks if its size exceeds the 150 MB threshold
365 | * or if the resource size could not be determined (eg. a popen() stream).
366 | *
367 | * @param string|resource $contents
368 | */
369 | protected function shouldUploadChunked(mixed $contents): bool
370 | {
371 | $size = is_string($contents) ? strlen($contents) : fstat($contents)['size'];
372 |
373 | if ($this->isPipe($contents)) {
374 | return true;
375 | }
376 |
377 | return $size > $this->maxChunkSize;
378 | }
379 |
380 | /**
381 | * Check if the contents is a pipe stream (not seekable, no size defined).
382 | *
383 | * @param string|resource $contents
384 | */
385 | protected function isPipe(mixed $contents): bool
386 | {
387 | return is_resource($contents) && (fstat($contents)['mode'] & 010000) != 0;
388 | }
389 |
390 | /**
391 | * Create a new file with the contents provided in the request.
392 | *
393 | * Do not use this to upload a file larger than 150 MB. Instead, create an upload session with upload_session/start.
394 | *
395 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload
396 | *
397 | * @param string|resource $contents
398 | * @return array
399 | */
400 | public function upload(string $path, mixed $contents, string $mode = 'add', bool $autorename = false): array
401 | {
402 | if ($this->shouldUploadChunked($contents)) {
403 | return $this->uploadChunked($path, $contents, $mode);
404 | }
405 |
406 | $arguments = [
407 | 'path' => $this->normalizePath($path),
408 | 'mode' => $mode,
409 | 'autorename' => $autorename,
410 | ];
411 |
412 | $response = $this->contentEndpointRequest('files/upload', $arguments, $contents);
413 |
414 | $metadata = json_decode($response->getBody(), true);
415 |
416 | $metadata['.tag'] = 'file';
417 |
418 | return $metadata;
419 | }
420 |
421 | /**
422 | * Upload file split in chunks. This allows uploading large files, since
423 | * Dropbox API v2 limits the content size to 150MB.
424 | *
425 | * The chunk size will affect directly the memory usage, so be careful.
426 | * Large chunks tends to speed up the upload, while smaller optimizes memory usage.
427 | *
428 | * @param string|resource $contents
429 | * @return array
430 | */
431 | public function uploadChunked(string $path, mixed $contents, string $mode = 'add', ?int $chunkSize = null): array
432 | {
433 | if ($chunkSize === null || $chunkSize > $this->maxChunkSize) {
434 | $chunkSize = $this->maxChunkSize;
435 | }
436 |
437 | $stream = $this->getStream($contents);
438 |
439 | $cursor = $this->uploadChunk(self::UPLOAD_SESSION_START, $stream, $chunkSize, null);
440 |
441 | while (! $stream->eof()) {
442 | $cursor = $this->uploadChunk(self::UPLOAD_SESSION_APPEND, $stream, $chunkSize, $cursor);
443 | }
444 |
445 | return $this->uploadSessionFinish('', $cursor, $path, $mode);
446 | }
447 |
448 | /**
449 | * @throws Exception
450 | */
451 | protected function uploadChunk(int $type, StreamInterface &$stream, int $chunkSize, ?UploadSessionCursor $cursor = null): UploadSessionCursor
452 | {
453 | $maximumTries = $stream->isSeekable() ? $this->maxUploadChunkRetries : 0;
454 | $pos = $stream->tell();
455 |
456 | $tries = 0;
457 |
458 | tryUpload:
459 | try {
460 | $tries++;
461 |
462 | $chunkStream = new Psr7\LimitStream($stream, $chunkSize, $stream->tell());
463 |
464 | if ($type === self::UPLOAD_SESSION_START) {
465 | return $this->uploadSessionStart($chunkStream);
466 | }
467 |
468 | if ($type === self::UPLOAD_SESSION_APPEND && $cursor !== null) {
469 | return $this->uploadSessionAppend($chunkStream, $cursor);
470 | }
471 |
472 | throw new Exception('Invalid type');
473 | } catch (RequestException $exception) {
474 | if ($tries < $maximumTries) {
475 | // rewind
476 | $stream->seek($pos, SEEK_SET);
477 | goto tryUpload;
478 | }
479 |
480 | throw $exception;
481 | }
482 | }
483 |
484 | /**
485 | * Upload sessions allow you to upload a single file in one or more requests,
486 | * for example where the size of the file is greater than 150 MB.
487 | * This call starts a new upload session with the given data.
488 | *
489 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
490 | *
491 | * @param string|resource|StreamInterface $contents
492 | */
493 | public function uploadSessionStart(mixed $contents, bool $close = false): UploadSessionCursor
494 | {
495 | $arguments = compact('close');
496 |
497 | $response = json_decode(
498 | $this->contentEndpointRequest('files/upload_session/start', $arguments, $contents)->getBody(),
499 | true
500 | );
501 |
502 | return new UploadSessionCursor($response['session_id'], ($contents instanceof StreamInterface ? $contents->tell() : strlen($contents)));
503 | }
504 |
505 | /**
506 | * Append more data to an upload session.
507 | * When the parameter close is set, this call will close the session.
508 | * A single request should not upload more than 150 MB.
509 | *
510 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
511 | */
512 | public function uploadSessionAppend(string|StreamInterface $contents, UploadSessionCursor $cursor, bool $close = false): UploadSessionCursor
513 | {
514 | $arguments = compact('cursor', 'close');
515 |
516 | $pos = $contents instanceof StreamInterface ? $contents->tell() : 0;
517 | $this->contentEndpointRequest('files/upload_session/append_v2', $arguments, $contents);
518 |
519 | $cursor->offset += $contents instanceof StreamInterface ? ($contents->tell() - $pos) : strlen($contents);
520 |
521 | return $cursor;
522 | }
523 |
524 | /**
525 | * Finish an upload session and save the uploaded data to the given file path.
526 | * A single request should not upload more than 150 MB.
527 | *
528 | * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
529 | *
530 | * @param string|resource|StreamInterface $contents
531 | * @return array
532 | */
533 | public function uploadSessionFinish(mixed $contents, UploadSessionCursor $cursor, string $path, string $mode = 'add', bool $autorename = false, bool $mute = false): array
534 | {
535 | $arguments = compact('cursor');
536 | $arguments['commit'] = compact('path', 'mode', 'autorename', 'mute');
537 |
538 | $response = $this->contentEndpointRequest(
539 | 'files/upload_session/finish',
540 | $arguments,
541 | ($contents == '') ? null : $contents
542 | );
543 |
544 | $metadata = json_decode($response->getBody(), true);
545 |
546 | $metadata['.tag'] = 'file';
547 |
548 | return $metadata;
549 | }
550 |
551 | /**
552 | * Get Account Info for current authenticated user.
553 | *
554 | * @return array
555 | *
556 | * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
557 | */
558 | public function getAccountInfo(): array
559 | {
560 | return $this->rpcEndpointRequest('users/get_current_account');
561 | }
562 |
563 | /**
564 | * Revoke current access token.
565 | *
566 | * @link https://www.dropbox.com/developers/documentation/http/documentation#auth-token-revoke
567 | */
568 | public function revokeToken(): void
569 | {
570 | $this->rpcEndpointRequest('auth/token/revoke');
571 | }
572 |
573 | protected function normalizePath(string $path): string
574 | {
575 | if (preg_match("/^id:.*|^rev:.*|^(ns:[0-9]+(\/.*)?)/", $path) === 1) {
576 | return $path;
577 | }
578 |
579 | $path = trim($path, '/');
580 |
581 | return ($path === '') ? '' : '/'.$path;
582 | }
583 |
584 | protected function getEndpointUrl(string $subdomain, string $endpoint): string
585 | {
586 | if (count($parts = explode('::', $endpoint)) === 2) {
587 | [$subdomain, $endpoint] = $parts;
588 | }
589 |
590 | return "https://{$subdomain}.dropboxapi.com/2/{$endpoint}";
591 | }
592 |
593 | /**
594 | * @param array $arguments
595 | * @param string|resource|StreamInterface $body
596 | *
597 | * @throws \Exception
598 | */
599 | public function contentEndpointRequest(string $endpoint, array $arguments, mixed $body = '', bool $isRefreshed = false): ResponseInterface
600 | {
601 | $headers = ['Dropbox-API-Arg' => json_encode($arguments)];
602 |
603 | if ($body !== '') {
604 | $headers['Content-Type'] = 'application/octet-stream';
605 | }
606 |
607 | try {
608 | $response = $this->client->request('POST', $this->getEndpointUrl('content', $endpoint), [
609 | 'headers' => $this->getHeaders($headers),
610 | 'body' => $body,
611 | ]);
612 | } catch (ClientException $exception) {
613 | if (
614 | $isRefreshed
615 | || ! $this->tokenProvider instanceof RefreshableTokenProvider
616 | || ! $this->tokenProvider->refresh($exception)
617 | ) {
618 | throw $this->determineException($exception);
619 | }
620 | $response = $this->contentEndpointRequest($endpoint, $arguments, $body, true);
621 | }
622 |
623 | return $response;
624 | }
625 |
626 | /**
627 | * @param array>|null $parameters
628 | * @return array
629 | */
630 | public function rpcEndpointRequest(string $endpoint, ?array $parameters = null, bool $isRefreshed = false): array
631 | {
632 | try {
633 | $options = ['headers' => $this->getHeaders()];
634 |
635 | if ($parameters) {
636 | $options['json'] = $parameters;
637 | }
638 |
639 | $response = $this->client->request('POST', $this->getEndpointUrl('api', $endpoint), $options);
640 | } catch (ClientException $exception) {
641 | if (
642 | $isRefreshed
643 | || ! $this->tokenProvider instanceof RefreshableTokenProvider
644 | || ! $this->tokenProvider->refresh($exception)
645 | ) {
646 | throw $this->determineException($exception);
647 | }
648 |
649 | return $this->rpcEndpointRequest($endpoint, $parameters, true);
650 | }
651 |
652 | return json_decode($response->getBody(), true) ?? [];
653 | }
654 |
655 | protected function determineException(ClientException $exception): Exception
656 | {
657 | if (in_array($exception->getResponse()->getStatusCode(), [400, 409])) {
658 | return new BadRequest($exception->getResponse());
659 | }
660 |
661 | return $exception;
662 | }
663 |
664 | /**
665 | * @param string|resource $contents
666 | */
667 | protected function getStream(mixed $contents): StreamInterface
668 | {
669 | if ($this->isPipe($contents)) {
670 | /* @var resource $contents */
671 | return new PumpStream(function ($length) use ($contents) {
672 | $data = fread($contents, $length);
673 | if (strlen($data) === 0) {
674 | return false;
675 | }
676 |
677 | return $data;
678 | });
679 | }
680 |
681 | return Psr7\Utils::streamFor($contents);
682 | }
683 |
684 | /**
685 | * Get the access token.
686 | */
687 | public function getAccessToken(): string
688 | {
689 | return $this->tokenProvider->getToken();
690 | }
691 |
692 | /**
693 | * Set the access token.
694 | */
695 | public function setAccessToken(string $accessToken): self
696 | {
697 | $this->tokenProvider = new InMemoryTokenProvider($accessToken);
698 |
699 | return $this;
700 | }
701 |
702 | /**
703 | * Set the namespace ID.
704 | */
705 | public function setNamespaceId(string $namespaceId): self
706 | {
707 | $this->namespaceId = $namespaceId;
708 |
709 | return $this;
710 | }
711 |
712 | /**
713 | * Get the HTTP headers.
714 | *
715 | * @param array $headers
716 | * @return array
717 | */
718 | protected function getHeaders(array $headers = []): array
719 | {
720 | $auth = [];
721 | if ($this->tokenProvider || ($this->appKey && $this->appSecret)) {
722 | $auth = $this->tokenProvider
723 | ? $this->getHeadersForBearerToken($this->tokenProvider->getToken())
724 | : $this->getHeadersForCredentials();
725 | }
726 |
727 | if ($this->teamMemberId) {
728 | $auth = array_merge(
729 | $auth,
730 | [
731 | 'Dropbox-API-Select-User' => $this->teamMemberId,
732 | ]
733 | );
734 | }
735 |
736 | if ($this->namespaceId) {
737 | $headers = array_merge(
738 | $headers,
739 | [
740 | 'Dropbox-API-Path-Root' => json_encode([
741 | '.tag' => 'namespace_id',
742 | 'namespace_id' => $this->namespaceId,
743 | ]),
744 | ]
745 | );
746 | }
747 |
748 | return array_merge($auth, $headers);
749 | }
750 |
751 | /**
752 | * @return array{Authorization: string}
753 | */
754 | protected function getHeadersForBearerToken(string $token): array
755 | {
756 | return [
757 | 'Authorization' => "Bearer {$token}",
758 | ];
759 | }
760 |
761 | /**
762 | * @return array{Authorization: string}
763 | */
764 | protected function getHeadersForCredentials(): array
765 | {
766 | return [
767 | 'Authorization' => 'Basic '.base64_encode("{$this->appKey}:{$this->appSecret}"),
768 | ];
769 | }
770 | }
771 |
--------------------------------------------------------------------------------