Completed
Push — master ( 4cc32c...1e5b02 )
by Arman
18s queued 15s
created

GoogleDriveApp::getAuthUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
nc 1
nop 2
dl 0
loc 12
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Quantum\Libraries\Storage\Adapters\GoogleDrive;
4
5
use Quantum\Exceptions\AppException;
6
use Quantum\Exceptions\CryptorException;
7
use Quantum\Exceptions\DatabaseException;
8
use Quantum\Libraries\Curl\HttpClient;
9
use Exception;
10
11
class GoogleDriveApp
12
{
13
    /**
14
     * Authorization URL
15
     */
16
    const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
17
18
    /**
19
     * Authorization scope
20
     */
21
    const AUTH_SCOPE = 'https://www.googleapis.com/auth/drive';
22
23
    /**
24
     * Token URL
25
     */
26
    const AUTH_TOKEN_URL = 'https://oauth2.googleapis.com/token';
27
28
    /**
29
     * URL for file metadata operations
30
     */
31
    const FILE_METADATA_URL = 'https://www.googleapis.com/drive/v3/files';
32
33
    /**
34
     * URL for file media operations
35
     */
36
    const FILE_MEDIA_URL = 'https://www.googleapis.com/upload/drive/v3/files';
37
38
    /**
39
     * Folder mimetype
40
     */
41
    const FOLDER_MIMETYPE = 'application/vnd.google-apps.folder';
42
43
    /**
44
     * Kind/Type  of drive file
45
     */
46
    const DRIVE_FILE_KIND = 'drive#file';
47
48
    /**
49
     * Error code for invalid token
50
     */
51
    const INVALID_TOKEN_ERROR_CODE = 401;
52
53
    /**
54
     * @var HttpClient
55
     */
56
    private $httpClient;
57
58
    /**
59
     * @var string
60
     */
61
    private $appKey;
62
63
    /**
64
     * @var string
65
     */
66
    private $appSecret;
67
68
    /**
69
     * @var TokenServiceInterface
70
     */
71
    private $tokenService;
72
73
    /**
74
     * GoogleDriveApp constructor
75
     * @param string $appKey
76
     * @param string $appSecret
77
     * @param TokenServiceInterface $tokenService
78
     * @param HttpClient $httpClient
79
     */
80
    public function __construct(string $appKey, string $appSecret, TokenServiceInterface $tokenService, HttpClient $httpClient)
81
    {
82
        $this->appKey = $appKey;
83
        $this->appSecret = $appSecret;
84
        $this->tokenService = $tokenService;
85
        $this->httpClient = $httpClient;
86
    }
87
88
    /**
89
     * Gets Auth URL
90
     * @param string $redirectUrl
91
     * @param string $accessType
92
     * @return string
93
     * @throws AppException
94
     * @throws CryptorException
95
     * @throws DatabaseException
96
     */
97
    public function getAuthUrl(string $redirectUrl, string $accessType = "offline"): string
98
    {
99
        $params = [
100
            'client_id' => $this->appKey,
101
            'response_type' => 'code',
102
            'state' => csrf_token(),
103
            'scope' => self::AUTH_SCOPE,
104
            'redirect_uri' => $redirectUrl,
105
            'access_type' => $accessType,
106
        ];
107
108
        return self::AUTH_URL . '?' . http_build_query($params, '', '&');
109
    }
110
111
    /**
112
     * Fetch tokens
113
     * @param string $code
114
     * @param string $redirectUrl
115
     * @param bool $byRefresh
116
     * @return object|null
117
     * @throws Exception
118
     */
119
    public function fetchTokens(string $code, string $redirectUrl = ''): ?object
120
    {
121
122
        $params = [
123
            'code' => $code,
124
            'grant_type' => 'authorization_code',
125
            'client_id' => $this->appKey,
126
            'client_secret' => $this->appSecret,
127
            'redirect_uri' => $redirectUrl
128
        ];
129
130
        $response = $this->sendRequest(self::AUTH_TOKEN_URL, $params);
131
132
        $this->tokenService->saveTokens($response->access_token, $response->refresh_token);
133
134
        return $response;
135
    }
136
137
    /**
138
     * Fetches the access token by refresh token
139
     * @param string $refreshToken
140
     * @return string
141
     * @throws Exception
142
     */
143
    private function fetchAccessTokenByRefreshToken(string $refreshToken): string
144
    {
145
        $params = [
146
            'refresh_token' => $refreshToken,
147
            'grant_type' => 'refresh_token',
148
            'client_id' => $this->appKey,
149
            'client_secret' => $this->appSecret
150
        ];
151
152
        $response = $this->sendRequest(self::AUTH_TOKEN_URL, $params);
153
154
        $this->tokenService->saveTokens($response->access_token);
155
156
        return $response->access_token;
157
    }
158
159
    /**
160
     * Sends rpc request
161
     * @param string $uri
162
     * @param mixed|null $data
163
     * @param array $headers
164
     * @param string $method
165
     * @return mixed|null
166
     * @throws Exception
167
     */
168
    public function sendRequest(string $uri, $data = null, array $headers = [], string $method = 'POST')
169
    {
170
        $this->httpClient
171
            ->createRequest($uri)
172
            ->setMethod($method)
173
            ->setData($data)
174
            ->setHeaders($headers)
175
            ->start();
176
177
        $responseBody = $this->httpClient->getResponseBody();
178
179
        if ($errors = $this->httpClient->getErrors()) {
180
            $code = $errors['code'];
181
182
            if ($this->accessTokenNeedsRefresh($code)) {
183
                $prevUrl = $this->httpClient->url();
184
                $prevData = $this->httpClient->getData();
185
                $prevHeaders = $this->httpClient->getRequestHeaders();
186
187
                $refreshToken = $this->tokenService->getRefreshToken();
188
189
                $accessToken = $this->fetchAccessTokenByRefreshToken($refreshToken);
190
191
                $prevHeaders['Authorization'] = 'Bearer ' . $accessToken;
192
193
                $responseBody = $this->sendRequest($prevUrl, $prevData, $prevHeaders);
194
195
            } else {
196
                throw new Exception(json_encode($responseBody ?? $errors), E_ERROR);
197
            }
198
        }
199
200
        return $responseBody;
201
    }
202
203
    /**
204
     * Sends rpc request
205
     * @param string $url
206
     * @param mixed $params
207
     * @param string $method
208
     * @param string $contentType
209
     * @return mixed|null
210
     * @throws Exception
211
     */
212
    public function rpcRequest(string $url, $params = [], string $method = 'POST', string $contentType = 'application/json')
213
    {
214
        try {
215
            $headers = [
216
                'Authorization' => 'Bearer ' . $this->tokenService->getAccessToken(),
217
                'Content-Type' => $contentType
218
            ];
219
            return $this->sendRequest($url, $params, $headers, $method);
220
        }catch (Exception $e){
221
            throw new Exception($e->getMessage(), (int)$e->getCode());
222
        }
223
    }
224
225
    /**
226
     * Gets file information
227
     * @param string $fileId
228
     * @param bool $media
229
     * @param array $params
230
     * @return mixed|null
231
     * @throws Exception
232
     */
233
    public function getFileInfo(string $fileId, bool $media = false, array $params = []){
234
        $queryParam = $media ? '?alt=media' : '?fields=*';
235
        return $this->rpcRequest(GoogleDriveApp::FILE_METADATA_URL . '/' . $fileId . $queryParam, $params, 'GET');
236
    }
237
238
    /**
239
     * Checks if the access token need refresh
240
     * @param int $code
241
     * @return bool
242
     */
243
    private function accessTokenNeedsRefresh(int $code): bool
244
    {
245
        if ($code != 401) {
246
            return false;
247
        }
248
249
        return true;
250
    }
251
}