[PHP] Dropbox API でファイル情報を取得

## PHPでDropboxAPIを使う
[Dropbox API v2](https://www.dropbox.com/developers/documentation/http/overview) は公式のPHP用sdkが用意されていないので、直接HTTPリクエストを利用します。
⇒[cURL関数](http://php.net/manual/ja/ref.curl.php)を使うのがよいでしょう。

### 事前準備：API用の登録を行う
https://www.dropbox.com/developers
↑ここから「Create your app」→「Dropbox API」で登録します。
登録したら、「generate access token」でアクセストークンを生成してメモしておきます。

#### 【追記】2021/09/30 以降の認証方式
従来の永続的なアクセストークンは廃止されるとのこと。
代わりに、リフレッシュトークンを利用して短期的なアクセストークンを取得して各APIを呼び出す必要があるようです。
（参考）
[Migrating App Permissions and Access Tokens](https://dropbox.tech/developers/migrating-app-permissions-and-access-tokens)
[Dropbox API: アプリの権限とアクセス トークンの移行](https://navi.dropbox.jp/dropbox-api-migrating-app-permissions-and-access-tokens)

- アクセストークン取得方法

(1) ブラウザでAUTHORIZATION_CODEを取得する

```
https://api.dropbox.com/oauth2/authorize?token_access_type=offline&response_type=code&client_id=<APP_KEY>
```

(2) コマンドでリフレッシュトークンを取得

```
curl https://api.dropbox.com/oauth2/token \
    -d code=<AUTHORIZATION_CODE> \
    -d grant_type=authorization_code \
    -u <APP_KEY>:<APP_SECRET>
```

(3) リフレッシュトークンでアクセストークンを取得

```
curl https://api.dropbox.com/oauth2/token \
    -d grant_type=refresh_token \
    -d refresh_token=<REFRESH_TOKEN> \
    -u <APP_KEY>:<APP_SECRET>
```

これをコード化すると…
```php:アクセストークン取得.php
	private function getAccessToken()
	{
		$url = 'https://api.dropboxapi.com/oauth2/token';
		$data = [
			'grant_type' => 'refresh_token',
			'refresh_token' => self::REFRESH_TOKEN,
		];

		$ch = curl_init();

		$options = array(
			CURLOPT_URL => $url,
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_USERPWD => self::APP_KEY . ":" . self::APP_SECRET,
			CURLOPT_POSTFIELDS => http_build_query($data)
		);

		curl_setopt_array($ch, $options);

		$res = curl_exec($ch);
		$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

		$token = '';
		if (!curl_errno($ch) && $http_code == "200") {
			$res = json_decode($res, true); //連想配列で返す
			$token = $res['access_token'];
		} else {
			print_r("ERROR: Failed to access DropBox via API" . PHP_EOL);
			print_r(curl_error($ch) . PHP_EOL);
		}

		curl_close($ch);

		return $token;
	}
```

(4) 取得したアクセストークンを利用
```php
    $this->access_token = $this->getAccessToken();
```
#### 【追記2】チーム向けDropboxの仕様変更
Dropbox Businessのチームプランを利用している場合に限りますが、フォルダツリーが「チームフォルダ」形式から「チームスペース」形式へと順次変更されるようです。
（参考）
[DBX Team Files Guide](https://developers.dropbox.com/ja-jp/dbx-team-files-guide)
[チーム スペースを使用するチームへの対応ガイド](https://navi.dropbox.jp/guide-to-adding-support-for-team-space)

端的にいうと、チームフォルダへアクセスするにはヘッダーで```Dropbox-API-Path-Root```を指定する必要があるようです。
- [Path Root Header Modes](https://www.dropbox.com/developers/reference/path-root-header-modes)

指定する値は、アカウント情報から取得できるnamespace_idです。
- [users/get_current_account](https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account)
```:例）
    "root_info": {
        ".tag": "user",
        "home_namespace_id": "3235641",
        "root_namespace_id": "3235641"
    },
```
従来どおりチームフォルダへアクセスする場合は、以下のようにヘッダーに追記すればよさそうです。
```
  --header 'Dropbox-API-Path-Root: {".tag": "root", "root": "<root_namespace_id>"}
```
または
```
  --header 'Dropbox-API-Path-Root: {".tag": "namespace_id", "namespace_id": "<root_namespace_id>"}
```

これをコード化すると…
```php:ルートネームスペース取得.php
	private function getRootNameSpace()
	{
		$url = "https://api.dropboxapi.com/2/users/get_current_account";

		$ch = curl_init();

		$headers = array(
			'Authorization: Bearer ' . $this->accessToken, //取得したアクセストークン
			'Content-Type: application/json',
		);

		$options = array(
			CURLOPT_URL => $url,
			CURLOPT_HTTPHEADER => $headers,
			CURLOPT_POST => true,
			CURLOPT_POSTFIELDS => null, //POSTパラメータ不要
			CURLOPT_RETURNTRANSFER => true,
		);

		curl_setopt_array($ch, $options);

		$res = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

		$root_id = '';
		if (!curl_errno($ch) && $http_code == "200") {
			$res = json_decode($res, true);
			$root_id = $res['root_info']['root_namespace_id'];
		} else {
			print_r("ERROR: Failed to access DropBox via API" . PHP_EOL);
			print_r(curl_error($ch) . PHP_EOL);
		}

		curl_close($ch);

		return $root_id;
	}
```
これを以降のAPIコール時のヘッダーに追加してあげればOKです。
```php
    $this->root_namespace_id = $this->getRootNameSpace();
```

### 確認：Dropbox API Explorerでテスト
公式に[HTTPリクエストのテスト画面](https://dropbox.github.io/dropbox-api-v2-explorer/)があるので、ここで用意されているAPIの一覧やURL、パラメータを確認しておきましょう。

## ファイル一覧を取得する
特定フォルダ下のファイル一覧を取得する場合は、

- [files/list_folder](https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder)
- [files/list_folder/continue](https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-continue)

を使います。
オプションでサブフォルダ下も見るかどうかなど指定可能です。

`list_folder`で取得し、さらに続きがある場合は`list_folder/continue`で続きを取得することになります。

```php:ファイル一覧取得.php
	function getFileList($dirname)
	{
		$url = "https://api.dropboxapi.com/2/files/list_folder";

		$ch = curl_init();

		$headers = array(
			'Authorization: Bearer ' . $this->access_token, //取得したアクセストークン
			'Dropbox-API-Path-Root: {".tag": "root", "root": "' . $this->root_namespace_id . '"}', //チームフォルダへアクセスする場合
			'Content-Type: application/json',
		);

		$post = array(
			"path" => "{$dirname}", //対象のディレクトリ
			"recursive" => true, //サブフォルダ下も見る
		);

		$options = array(
			CURLOPT_URL => $url,
			CURLOPT_HTTPHEADER => $headers,
			CURLOPT_POST => true,
			CURLOPT_POSTFIELDS => json_encode($post),
			CURLOPT_RETURNTRANSFER => true,
		);

		curl_setopt_array($ch, $options);

		$res = curl_exec($ch);
		$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

		$files = array();
		if (!curl_errno($ch) && $http_code == "200") {
			$res = json_decode($res, true); //連想配列で返す
			if ($res["entries"]) {
				foreach ($res["entries"] as $index => $content) {
					if ($content[".tag"] == "file") {
						$files[] = $content;
					}
				}
			}
			if ($res["has_more"]) {
				//続き読み込み
				$morefiles = $this->getFileListRecursive($res["cursor"]);
				$files = array_merge($files, $morefiles);
			}
		} else {
			print_r("ERROR: Failed to access DropBox via API" . PHP_EOL);
			print_r(curl_error($ch) . PHP_EOL);
		}

		curl_close($ch);

		var_export($files);
	}
```

```php:ファイル一覧取得（続き）.php
	function getFileListRecursive($cursor)
	{
		$url = "https://api.dropboxapi.com/2/files/list_folder/continue";

		$ch = curl_init();

		$headers = array(
			'Authorization: Bearer ' . $this->access_token,
			'Dropbox-API-Path-Root: {".tag": "root", "root": "' . $this->root_namespace_id . '"}', //チームフォルダへアクセスする場合
			'Content-Type: application/json',
		);

		$post = array(
			"cursor" => "{$cursor}", //ファイル取得レスポンスに含まれるカーソルがパラメータ
		);

		$options = array(
			CURLOPT_URL => $url,
			CURLOPT_HTTPHEADER => $headers,
			CURLOPT_POST => true,
			CURLOPT_POSTFIELDS => json_encode($post),
			CURLOPT_RETURNTRANSFER => true,
		);

		curl_setopt_array($ch, $options);

		$res = curl_exec($ch);
		$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

		$files = array();
		if (!curl_errno($ch) && $http_code == "200") {
			$res = json_decode($res, true);
			if ($res["entries"]) {
				foreach ($res["entries"] as $content) {
					if ($content[".tag"] == "file") {
						$files[] = $content;
					}
				}
			}
			if ($res["has_more"]) {
				//再帰的に読み込み
				$morefiles = $this->getFileListRecursive($res["cursor"]);
				$files = array_merge($files, $morefiles);
			}
		} else {
			print_r("ERROR: Failed to access DropBox via API" . PHP_EOL);
			print_r(curl_error($ch) . PHP_EOL);
		}

		curl_close($ch);

		return $files;
	}
```

## 指定ファイルの共有リンクを取得する
取得したファイルのダウンロード用のリンクが欲しい場合は共有リンクを取得します。

- [sharing/list_shared_links](https://www.dropbox.com/developers/documentation/http/documentation#sharing-list_shared_links)

また、共有リンクを発行することもできます。

- [sharing/create_shared_link_with_settings](https://www.dropbox.com/developers/documentation/http/documentation#sharing-create_shared_link_with_settings)

```php:共有リンク取得.php
	function getSharedLink($path)
	{
		$url = "https://api.dropboxapi.com/2/sharing/list_shared_links";

		$ch = curl_init();

		$headers = array(
			'Authorization: Bearer ' . $this->access_token,
			'Dropbox-API-Path-Root: {".tag": "root", "root": "' . $this->root_namespace_id . '"}', //チームフォルダへアクセスする場合
			'Content-Type: application/json',
		);

		$post = array(
			"path" => "{$path}", //ファイルパス
			"direct_only" => true, //ファイルへのアクセスのみ許可
		);

		$options = array(
			CURLOPT_URL => $url,
			CURLOPT_HTTPHEADER => $headers,
			CURLOPT_POST => true,
			CURLOPT_POSTFIELDS => json_encode($post),
			CURLOPT_RETURNTRANSFER => true,
		);

		curl_setopt_array($ch, $options);

		$res = curl_exec($ch);
		$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

		$link = "";
		if (!curl_errno($ch) && $http_code == "200") {
			$res = json_decode($res, true);
			if ($res["links"]) {
				$links = $res["links"];
				$firstlink = $links[0];
				$link = $firstlink["url"];
			} else {
				//なければ共有リンク発行
				$link = $this->createSharedLink($path);
			}
		} else {
			print_r("ERROR: Failed to access DropBox via API" . PHP_EOL);
			print_r(curl_error($ch) . PHP_EOL);
		}

		curl_close($ch);

		return $link;
	}
```

```php:共有リンク発行.php
	function createSharedLink($path)
	{
		$url = "https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings";

		$ch = curl_init();

		$headers = array(
			'Authorization: Bearer ' . $this->access_token,
			'Dropbox-API-Path-Root: {".tag": "root", "root": "' . $this->root_namespace_id . '"}', //チームフォルダへアクセスする場合
			'Content-Type: application/json',
		);

		$post = array(
			"path" => "{$path}", //ファイルパス
			"settings" => array(
				"requested_visibility" => array(
					".tag" => "public" //公開
				),
			),
		);

		$options = array(
			CURLOPT_URL => $url,
			CURLOPT_HTTPHEADER => $headers,
			CURLOPT_POST => true,
			CURLOPT_POSTFIELDS => json_encode($post),
			CURLOPT_RETURNTRANSFER => true,
		);

		curl_setopt_array($ch, $options);

		$res = curl_exec($ch);
		$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

		$link = "";
		if (!curl_errno($ch) && $http_code == "200") {
			$res = json_decode($res, true);
			if ($res["url"]) {
				$link = $res["url"];
			} elseif ($res["error"]) {
				//既に設定済みなど
				$error = $res["error"];
				print_r("WARNING: Failed to create shared link [{$path}] - {$error['.tag']}" . PHP_EOL);
			}
		} else {
			print_r("ERROR: Failed to access DropBox via API" . PHP_EOL);
			print_r(curl_error($ch) . PHP_EOL);
		}

		curl_close($ch);

		return $link;
	}
```

## まとめ
このあたりを組み合わせて、Dropbox上のファイルのダウンロードページをつくったりしました。

ファイルアップロードもできるようにすればストレージをDropboxにしたWEBサービスもつくれそうです。
⇒2019/6/29[アップロード編](https://qiita.com/tksnino/items/bcbde6eddcb553446f63)書きました。