1 | <?php |
||||
2 | |||||
3 | namespace Kapersoft\ShareFile; |
||||
4 | |||||
5 | use Exception; |
||||
6 | use GuzzleHttp\Psr7; |
||||
7 | use GuzzleHttp\HandlerStack; |
||||
8 | use GuzzleHttp\Handler\MockHandler; |
||||
9 | use GuzzleHttp\Client as GuzzleClient; |
||||
10 | use GuzzleHttp\Exception\ClientException; |
||||
11 | use Kapersoft\Sharefile\Exceptions\BadRequest; |
||||
12 | |||||
13 | /** |
||||
14 | * Class Client. |
||||
15 | * |
||||
16 | * @author Jan Willem Kaper <[email protected]> |
||||
17 | * @license MIT (see License.txt) |
||||
18 | * |
||||
19 | * @link http://github.com/kapersoft/sharefile-api |
||||
20 | */ |
||||
21 | class Client |
||||
22 | { |
||||
23 | /** |
||||
24 | * ShareFile token. |
||||
25 | * |
||||
26 | * @var array |
||||
27 | */ |
||||
28 | public $token; |
||||
29 | |||||
30 | /** |
||||
31 | * Guzzle Client. |
||||
32 | * |
||||
33 | * @var \GuzzleHttp\Client |
||||
34 | */ |
||||
35 | public $client; |
||||
36 | |||||
37 | /** |
||||
38 | * Thumbnail size. |
||||
39 | */ |
||||
40 | const THUMBNAIL_SIZE_M = 75; |
||||
41 | const THUMBNAIL_SIZE_L = 600; |
||||
42 | |||||
43 | /* |
||||
44 | * ShareFile Folder |
||||
45 | */ |
||||
46 | const FOLDER_TOP = 'top'; |
||||
47 | const FOLDER_HOME = 'home'; |
||||
48 | const FOLDER_FAVORITES = 'favorites'; |
||||
49 | const FOLDER_ALLSHARED = 'allshared'; |
||||
50 | |||||
51 | /* |
||||
52 | * Default Chunk Size for uploading files |
||||
53 | */ |
||||
54 | const DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024; // 8 megabytes |
||||
55 | |||||
56 | /** |
||||
57 | * Client constructor. |
||||
58 | * |
||||
59 | * @param string $hostname ShareFile hostname |
||||
60 | * @param string $client_id OAuth2 client_id |
||||
61 | * @param string $client_secret OAuth2 client_secret |
||||
62 | * @param string $username ShareFile username |
||||
63 | * @param string $password ShareFile password |
||||
64 | * @param MockHandler|HandlerStack $handler Guzzle Handler |
||||
65 | * |
||||
66 | * @throws Exception |
||||
67 | */ |
||||
68 | 75 | public function __construct(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null) |
|||
69 | { |
||||
70 | 75 | $response = $this->authenticate($hostname, $client_id, $client_secret, $username, $password, $handler); |
|||
71 | |||||
72 | 69 | if (! isset($response['access_token']) || ! isset($response['subdomain'])) { |
|||
73 | 3 | throw new Exception("Incorrect response from Authentication: 'access_token' or 'subdomain' is missing."); |
|||
74 | } |
||||
75 | |||||
76 | 66 | $this->token = $response; |
|||
77 | 66 | $this->client = new GuzzleClient( |
|||
78 | [ |
||||
79 | 66 | 'handler' => $handler, |
|||
80 | 'headers' => [ |
||||
81 | 66 | 'Authorization' => "Bearer {$this->token['access_token']}", |
|||
82 | ], |
||||
83 | ] |
||||
84 | ); |
||||
85 | 66 | } |
|||
86 | |||||
87 | /** |
||||
88 | * ShareFile authentication using username/password. |
||||
89 | * |
||||
90 | * @param string $hostname ShareFile hostname |
||||
91 | * @param string $client_id OAuth2 client_id |
||||
92 | * @param string $client_secret OAuth2 client_secret |
||||
93 | * @param string $username ShareFile username |
||||
94 | * @param string $password ShareFile password |
||||
95 | * @param MockHandler|HandlerStack $handler Guzzle Handler |
||||
96 | * |
||||
97 | * @throws Exception |
||||
98 | * |
||||
99 | * @return array |
||||
100 | */ |
||||
101 | 75 | protected function authenticate(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null):array |
|||
102 | { |
||||
103 | 75 | $uri = "https://{$hostname}/oauth/token"; |
|||
104 | |||||
105 | $parameters = [ |
||||
106 | 75 | 'grant_type' => 'password', |
|||
107 | 75 | 'client_id' => $client_id, |
|||
108 | 75 | 'client_secret' => $client_secret, |
|||
109 | 75 | 'username' => $username, |
|||
110 | 75 | 'password' => $password, |
|||
111 | ]; |
||||
112 | |||||
113 | try { |
||||
114 | 75 | $client = new GuzzleClient(['handler' => $handler]); |
|||
115 | 75 | $response = $client->post( |
|||
116 | 75 | $uri, |
|||
117 | 75 | ['form_params' => $parameters] |
|||
118 | ); |
||||
119 | 3 | } catch (ClientException $exception) { |
|||
120 | 3 | throw $exception; |
|||
121 | } |
||||
122 | |||||
123 | 72 | if ($response->getStatusCode() == '200') { |
|||
124 | 69 | return json_decode($response->getBody(), true); |
|||
125 | } else { |
||||
126 | 3 | throw new Exception('Authentication error', $response->getStatusCode()); |
|||
127 | } |
||||
128 | } |
||||
129 | |||||
130 | /** |
||||
131 | * Get user details. |
||||
132 | * |
||||
133 | * @param string $userId ShareFile user id (optional) |
||||
134 | * |
||||
135 | * @return array |
||||
136 | */ |
||||
137 | 3 | public function getUser(string $userId = ''):array |
|||
138 | { |
||||
139 | 3 | return $this->get("Users({$userId})"); |
|||
140 | } |
||||
141 | |||||
142 | /** |
||||
143 | * Create a folder. |
||||
144 | * |
||||
145 | * @param string $parentId Id of the parent folder |
||||
146 | * @param string $name Name |
||||
147 | * @param string $description Description |
||||
148 | * @param bool $overwrite Overwrite folder |
||||
149 | * |
||||
150 | * @return array |
||||
151 | */ |
||||
152 | 6 | public function createFolder(string $parentId, string $name, string $description = '', bool $overwrite = false):array |
|||
153 | { |
||||
154 | 6 | $parameters = $this->buildHttpQuery( |
|||
155 | [ |
||||
156 | 6 | 'overwrite' => $overwrite, |
|||
157 | 'passthrough' => false, |
||||
158 | ] |
||||
159 | ); |
||||
160 | |||||
161 | $data = [ |
||||
162 | 6 | 'name' => $name, |
|||
163 | 6 | 'description' => $description, |
|||
164 | ]; |
||||
165 | |||||
166 | 6 | return $this->post("Items({$parentId})/Folder?{$parameters}", $data); |
|||
167 | } |
||||
168 | |||||
169 | /** |
||||
170 | * Get Folder/File using Id. |
||||
171 | * |
||||
172 | * @param string $itemId Item id |
||||
173 | * @param bool $getChildren Include children |
||||
174 | * |
||||
175 | * @return array |
||||
176 | */ |
||||
177 | 6 | public function getItemById(string $itemId, bool $getChildren = false):array |
|||
178 | { |
||||
179 | 6 | $parameters = $getChildren === true ? '$expand=Children' : ''; |
|||
180 | |||||
181 | 6 | return $this->get("Items({$itemId})?{$parameters}"); |
|||
182 | } |
||||
183 | |||||
184 | /** |
||||
185 | * Get Folder/File using path. |
||||
186 | * |
||||
187 | * @param string $path Path |
||||
188 | * @param string $itemId Id of the root folder (optional) |
||||
189 | * |
||||
190 | * @return array |
||||
191 | */ |
||||
192 | 3 | View Code Duplication | public function getItemByPath(string $path, string $itemId = ''):array |
||
0 ignored issues
–
show
|
|||||
193 | { |
||||
194 | 3 | if (empty($itemId)) { |
|||
195 | 3 | return $this->get("Items/ByPath?Path={$path}"); |
|||
196 | } else { |
||||
197 | return $this->get("Items({$itemId})/ByPath?Path={$path}"); |
||||
198 | } |
||||
199 | } |
||||
200 | |||||
201 | /** |
||||
202 | * Get breadcrumps of an item. |
||||
203 | * |
||||
204 | * @param string $itemId Item Id |
||||
205 | * |
||||
206 | * @return array |
||||
207 | */ |
||||
208 | 3 | public function getItemBreadcrumps(string $itemId):array |
|||
209 | { |
||||
210 | 3 | return $this->get("Items({$itemId})/Breadcrumbs"); |
|||
211 | } |
||||
212 | |||||
213 | /** |
||||
214 | * Copy an item. |
||||
215 | * |
||||
216 | * @param string $targetId Id of the target folder |
||||
217 | * @param string $itemId Id of the copied item |
||||
218 | * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) |
||||
219 | * |
||||
220 | * @return array |
||||
221 | */ |
||||
222 | 6 | public function copyItem(string $targetId, string $itemId, bool $overwrite = false):array |
|||
223 | { |
||||
224 | 6 | $parameters = $this->buildHttpQuery( |
|||
225 | [ |
||||
226 | 6 | 'targetid' => $targetId, |
|||
227 | 6 | 'overwrite' => $overwrite, |
|||
228 | ] |
||||
229 | ); |
||||
230 | |||||
231 | 6 | return $this->post("Items({$itemId})/Copy?{$parameters}"); |
|||
232 | } |
||||
233 | |||||
234 | /** |
||||
235 | * Update an item. |
||||
236 | * |
||||
237 | * @param string $itemId Id of the item |
||||
238 | * @param array $data New data |
||||
239 | * @param bool $forceSync Indicates whether operation is to be executed synchronously (optional) |
||||
240 | * @param bool $notify Indicates whether an email should be sent to users subscribed to Upload Notifications (optional) |
||||
241 | * |
||||
242 | * @return array |
||||
243 | */ |
||||
244 | 3 | View Code Duplication | public function updateItem(string $itemId, array $data, bool $forceSync = true, bool $notify = true):array |
||
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||||
245 | { |
||||
246 | 3 | $parameters = $this->buildHttpQuery( |
|||
247 | [ |
||||
248 | 3 | 'forceSync' => $forceSync, |
|||
249 | 3 | 'notify' => $notify, |
|||
250 | ] |
||||
251 | ); |
||||
252 | |||||
253 | 3 | return $this->patch("Items({$itemId})?{$parameters}", $data); |
|||
254 | } |
||||
255 | |||||
256 | /** |
||||
257 | * Delete an item. |
||||
258 | * |
||||
259 | * @param string $itemId Item id |
||||
260 | * @param bool $singleversion True it will delete only the specified version rather than all sibling files with the same filename (optional) |
||||
261 | * @param bool $forceSync True will block the operation from taking place asynchronously (optional) |
||||
262 | * |
||||
263 | * @return string |
||||
264 | */ |
||||
265 | 3 | View Code Duplication | public function deleteItem(string $itemId, bool $singleversion = false, bool $forceSync = false):string |
||
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||||
266 | { |
||||
267 | 3 | $parameters = $this->buildHttpQuery( |
|||
268 | [ |
||||
269 | 3 | 'singleversion' => $singleversion, |
|||
270 | 3 | 'forceSync' => $forceSync, |
|||
271 | ] |
||||
272 | ); |
||||
273 | |||||
274 | 3 | return $this->delete("Items({$itemId})?{$parameters}"); |
|||
0 ignored issues
–
show
|
|||||
275 | } |
||||
276 | |||||
277 | /** |
||||
278 | * Get temporary download URL for an item. |
||||
279 | * |
||||
280 | * @param string $itemId Item id |
||||
281 | * @param bool $includeallversions For folder downloads only, includes old versions of files in the folder in the zip when true, current versions only when false (default) |
||||
282 | * |
||||
283 | * @return array |
||||
284 | */ |
||||
285 | 3 | View Code Duplication | public function getItemDownloadUrl(string $itemId, bool $includeallversions = false):array |
||
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||||
286 | { |
||||
287 | 3 | $parameters = $this->buildHttpQuery( |
|||
288 | [ |
||||
289 | 3 | 'includeallversions' => $includeallversions, |
|||
290 | 'redirect' => false, |
||||
291 | ] |
||||
292 | ); |
||||
293 | |||||
294 | 3 | return $this->get("Items({$itemId})/Download?{$parameters}"); |
|||
295 | } |
||||
296 | |||||
297 | /** |
||||
298 | * Get contents of and item. |
||||
299 | * |
||||
300 | * @param string $itemId Item id |
||||
301 | * @param bool $includeallversions $includeallversions For folder downloads only, includes old versions of files in the folder in the zip when true, current versions only when false (default) |
||||
302 | * |
||||
303 | * @return mixed |
||||
304 | */ |
||||
305 | 3 | View Code Duplication | public function getItemContents(string $itemId, bool $includeallversions = false) |
||
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||||
306 | { |
||||
307 | 3 | $parameters = $this->buildHttpQuery( |
|||
308 | [ |
||||
309 | 3 | 'includeallversions' => $includeallversions, |
|||
310 | 'redirect' => true, |
||||
311 | ] |
||||
312 | ); |
||||
313 | |||||
314 | 3 | return $this->get("Items({$itemId})/Download?{$parameters}"); |
|||
315 | } |
||||
316 | |||||
317 | /** |
||||
318 | * Get the Chunk Uri to start a file-upload. |
||||
319 | * |
||||
320 | * @param string $method Upload method (Standard or Streamed) |
||||
321 | * @param string $filename Name of file |
||||
322 | * @param string $folderId Id of the parent folder |
||||
323 | * @param bool $unzip Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional) |
||||
324 | * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) |
||||
325 | * @param bool $notify Indicates whether users will be notified of this upload - based on folder preferences (optional) |
||||
326 | * @param bool $raw Send contents contents directly in the POST body (=true) or send contents in MIME format (=false) (optional) |
||||
327 | * @param resource $stream Resource stream of the contents (optional) |
||||
328 | * |
||||
329 | * @return array |
||||
330 | */ |
||||
331 | 9 | public function getChunkUri(string $method, string $filename, string $folderId, bool $unzip = false, $overwrite = true, bool $notify = true, bool $raw = false, $stream = null):array |
|||
332 | { |
||||
333 | 9 | $parameters = $this->buildHttpQuery( |
|||
334 | [ |
||||
335 | 9 | 'method' => $method, |
|||
336 | 9 | 'raw' => $raw, |
|||
337 | 9 | 'fileName' => basename($filename), |
|||
338 | 9 | 'fileSize' => $stream == null ? filesize($filename) : fstat($stream)['size'], |
|||
339 | 'canResume' => false, |
||||
340 | 'startOver' => false, |
||||
341 | 9 | 'unzip' => $unzip, |
|||
342 | 9 | 'tool' => 'apiv3', |
|||
343 | 9 | 'overwrite' => $overwrite, |
|||
344 | 9 | 'title' => basename($filename), |
|||
345 | 'isSend' => false, |
||||
346 | 9 | 'responseFormat' => 'json', |
|||
347 | 9 | 'notify' => $notify, |
|||
348 | 9 | 'clientCreatedDateUTC' => $stream == null ? filectime($filename) : fstat($stream)['ctime'], |
|||
349 | 9 | 'clientModifiedDateUTC' => $stream == null ? filemtime($filename) : fstat($stream)['mtime'], |
|||
350 | ] |
||||
351 | ); |
||||
352 | |||||
353 | 9 | return $this->post("Items({$folderId})/Upload?{$parameters}"); |
|||
354 | } |
||||
355 | |||||
356 | /** |
||||
357 | * Upload a file using a single HTTP POST. |
||||
358 | * |
||||
359 | * @param string $filename Name of file |
||||
360 | * @param string $folderId Id of the parent folder |
||||
361 | * @param bool $unzip Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional) |
||||
362 | * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) |
||||
363 | * @param bool $notify Indicates whether users will be notified of this upload - based on folder preferences (optional) |
||||
364 | * |
||||
365 | * @return string |
||||
366 | */ |
||||
367 | 3 | public function uploadFileStandard(string $filename, string $folderId, bool $unzip = false, bool $overwrite = true, bool $notify = true):string |
|||
368 | { |
||||
369 | 3 | $chunkUri = $this->getChunkUri('standard', $filename, $folderId, $unzip, $overwrite, $notify); |
|||
370 | |||||
371 | 3 | $response = $this->client->request( |
|||
372 | 3 | 'POST', |
|||
373 | 3 | $chunkUri['ChunkUri'], |
|||
374 | [ |
||||
375 | 'multipart' => [ |
||||
376 | [ |
||||
377 | 3 | 'name' => 'File1', |
|||
378 | 3 | 'contents' => fopen($filename, 'r'), |
|||
379 | ], |
||||
380 | ], |
||||
381 | ] |
||||
382 | ); |
||||
383 | |||||
384 | 3 | return (string) $response->getBody(); |
|||
385 | } |
||||
386 | |||||
387 | /** |
||||
388 | * Upload a file using multiple HTTP POSTs. |
||||
389 | * |
||||
390 | * @param mixed $stream Stream resource |
||||
391 | * @param string $folderId Id of the parent folder |
||||
392 | * @param string $filename Filename (optional) |
||||
393 | * @param bool $unzip Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional) |
||||
394 | * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) |
||||
395 | * @param bool $notify Indicates whether users will be notified of this upload - based on folder preferences (optional) |
||||
396 | * @param int $chunkSize Maximum size of the individual HTTP posts in bytes |
||||
397 | * |
||||
398 | * @return string |
||||
399 | */ |
||||
400 | 3 | public function uploadFileStreamed($stream, string $folderId, string $filename = null, bool $unzip = false, bool $overwrite = true, bool $notify = true, int $chunkSize = null):string |
|||
401 | { |
||||
402 | 3 | $filename = $filename ?? stream_get_meta_data($stream)['uri']; |
|||
403 | 3 | if (empty($filename)) { |
|||
404 | return 'Error: no filename'; |
||||
405 | } |
||||
406 | |||||
407 | 3 | $chunkUri = $this->getChunkUri('streamed', $filename, $folderId, $unzip, $overwrite, $notify, true, $stream); |
|||
408 | 3 | $chunkSize = $chunkSize ?? SELF::DEFAULT_CHUNK_SIZE; |
|||
409 | 3 | $index = 0; |
|||
410 | |||||
411 | // First Chunk |
||||
412 | 3 | $data = $this->readChunk($stream, $chunkSize); |
|||
413 | 3 | while (! ((strlen($data) < $chunkSize) || feof($stream))) { |
|||
414 | 3 | $parameters = $this->buildHttpQuery( |
|||
415 | [ |
||||
416 | 3 | 'index' => $index, |
|||
417 | 3 | 'byteOffset' => $index * $chunkSize, |
|||
418 | 3 | 'hash' => md5($data), |
|||
419 | ] |
||||
420 | ); |
||||
421 | |||||
422 | 3 | $response = $this->uploadChunk("{$chunkUri['ChunkUri']}&{$parameters}", $data); |
|||
423 | |||||
424 | 3 | if ($response != 'true') { |
|||
425 | return $response; |
||||
426 | } |
||||
427 | |||||
428 | // Next chunk |
||||
429 | 3 | $index++; |
|||
430 | 3 | $data = $this->readChunk($stream, $chunkSize); |
|||
431 | } |
||||
432 | |||||
433 | // Final chunk |
||||
434 | 3 | $parameters = $this->buildHttpQuery( |
|||
435 | [ |
||||
436 | 3 | 'index' => $index, |
|||
437 | 3 | 'byteOffset' => $index * $chunkSize, |
|||
438 | 3 | 'hash' => md5($data), |
|||
439 | 3 | 'filehash' => Psr7\hash(Psr7\stream_for($stream), 'md5'), |
|||
440 | 'finish' => true, |
||||
441 | ] |
||||
442 | ); |
||||
443 | |||||
444 | 3 | return $this->uploadChunk("{$chunkUri['ChunkUri']}&{$parameters}", $data); |
|||
445 | } |
||||
446 | |||||
447 | /** |
||||
448 | * Get Thumbnail of an item. |
||||
449 | * |
||||
450 | * @param string $itemId Item id |
||||
451 | * @param int $size Thumbnail size: THUMBNAIL_SIZE_M or THUMBNAIL_SIZE_L (optional) |
||||
452 | * |
||||
453 | * @return array |
||||
454 | */ |
||||
455 | 3 | View Code Duplication | public function getThumbnailUrl(string $itemId, int $size = 75):array |
||
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||||
456 | { |
||||
457 | 3 | $parameters = $this->buildHttpQuery( |
|||
458 | [ |
||||
459 | 3 | 'size' => $size, |
|||
460 | 'redirect' => false, |
||||
461 | ] |
||||
462 | ); |
||||
463 | |||||
464 | 3 | return $this->get("Items({$itemId})/Thumbnail?{$parameters}"); |
|||
465 | } |
||||
466 | |||||
467 | /** |
||||
468 | * Get browser link for an item. |
||||
469 | * |
||||
470 | * @param string $itemId Item id |
||||
471 | * |
||||
472 | * @return array |
||||
473 | */ |
||||
474 | 3 | public function getWebAppLink(string $itemId):array |
|||
475 | { |
||||
476 | 3 | return $this->post("Items({$itemId})/WebAppLink"); |
|||
477 | } |
||||
478 | |||||
479 | /** |
||||
480 | * Share Share for external user. |
||||
481 | * |
||||
482 | * @param array $options Share options |
||||
483 | * @param bool $notify Indicates whether user will be notified if item is downloaded (optional) |
||||
484 | * |
||||
485 | * @return array |
||||
486 | */ |
||||
487 | 3 | public function createShare(array $options, $notify = false):array |
|||
488 | { |
||||
489 | 3 | $parameters = $this->buildHttpQuery( |
|||
490 | [ |
||||
491 | 3 | 'notify' => $notify, |
|||
492 | 'direct' => true, |
||||
493 | ] |
||||
494 | ); |
||||
495 | |||||
496 | 3 | return $this->post("Shares?{$parameters}", $options); |
|||
497 | } |
||||
498 | |||||
499 | /** |
||||
500 | * Get AccessControl List for an item. |
||||
501 | * |
||||
502 | * @param string $itemId Id of an item |
||||
503 | * @param string $userId Id of an user |
||||
504 | * |
||||
505 | * @return array |
||||
506 | */ |
||||
507 | 6 | View Code Duplication | public function getItemAccessControls(string $itemId, string $userId = ''):array |
||
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||||
508 | { |
||||
509 | 6 | if (! empty($userId)) { |
|||
510 | 3 | return $this->get("AccessControls(principalid={$userId},itemid={$itemId})"); |
|||
511 | } else { |
||||
512 | 3 | return $this->get("Items({$itemId})/AccessControls"); |
|||
513 | } |
||||
514 | } |
||||
515 | |||||
516 | /** |
||||
517 | * Build API uri. |
||||
518 | * |
||||
519 | * @param string $endpoint API endpoint |
||||
520 | * |
||||
521 | * @return string |
||||
522 | */ |
||||
523 | 63 | protected function buildUri(string $endpoint): string |
|||
524 | { |
||||
525 | 63 | return "https://{$this->token['subdomain']}.sf-api.com/sf/v3/{$endpoint}"; |
|||
526 | } |
||||
527 | |||||
528 | /** |
||||
529 | * Make a request to the API. |
||||
530 | * |
||||
531 | * @param string $method HTTP Method |
||||
532 | * @param string $endpoint API endpoint |
||||
533 | * @param mixed|string|array $json POST body (optional) |
||||
534 | * |
||||
535 | * @throws Exception |
||||
536 | * |
||||
537 | * @return mixed |
||||
538 | */ |
||||
539 | 63 | protected function request(string $method, string $endpoint, $json = null) |
|||
540 | { |
||||
541 | 63 | $uri = $this->buildUri($endpoint); |
|||
542 | 63 | $options = $json != null ? ['json' => $json] : []; |
|||
543 | |||||
544 | try { |
||||
545 | 63 | $response = $this->client->request($method, $uri, $options); |
|||
546 | } catch (ClientException $exception) { |
||||
547 | throw $this->determineException($exception); |
||||
548 | } |
||||
549 | |||||
550 | 63 | $body = (string) $response->getBody(); |
|||
551 | |||||
552 | 63 | return $this->jsonValidator($body) ? json_decode($body, true) : $body; |
|||
553 | } |
||||
554 | |||||
555 | /** |
||||
556 | * Shorthand for GET-request. |
||||
557 | * |
||||
558 | * @param string $endpoint API endpoint |
||||
559 | * |
||||
560 | * @return mixed |
||||
561 | */ |
||||
562 | 30 | protected function get(string $endpoint) |
|||
563 | { |
||||
564 | 30 | return $this->request('GET', $endpoint); |
|||
565 | } |
||||
566 | |||||
567 | /** |
||||
568 | * Shorthand for POST-request. |
||||
569 | * |
||||
570 | * @param string $endpoint API endpoint |
||||
571 | * @param mixed|string|array $json POST body (optional) |
||||
572 | * |
||||
573 | * @return mixed |
||||
574 | */ |
||||
575 | 27 | protected function post(string $endpoint, $json = null) |
|||
576 | { |
||||
577 | 27 | return $this->request('POST', $endpoint, $json); |
|||
578 | } |
||||
579 | |||||
580 | /** |
||||
581 | * Shorthand for PATCH-request. |
||||
582 | * |
||||
583 | * @param string $endpoint API endpoint |
||||
584 | * @param mixed|string|array $json POST body (optional) |
||||
585 | * |
||||
586 | * @return mixed |
||||
587 | */ |
||||
588 | 3 | protected function patch(string $endpoint, $json = null) |
|||
589 | { |
||||
590 | 3 | return $this->request('PATCH', $endpoint, $json); |
|||
591 | } |
||||
592 | |||||
593 | /** |
||||
594 | * Shorthand for DELETE-request. |
||||
595 | * |
||||
596 | * @param string $endpoint API endpoint |
||||
597 | * |
||||
598 | * @return string|array |
||||
599 | */ |
||||
600 | 3 | protected function delete(string $endpoint) |
|||
601 | { |
||||
602 | 3 | return $this->request('DELETE', $endpoint); |
|||
603 | } |
||||
604 | |||||
605 | /** |
||||
606 | * Upload a chunk of data using HTTP POST body. |
||||
607 | * |
||||
608 | * @param string $uri Upload URI |
||||
609 | * @param string $data Contents to upload |
||||
610 | * |
||||
611 | * @return string|array |
||||
612 | */ |
||||
613 | 3 | protected function uploadChunk($uri, $data) |
|||
614 | { |
||||
615 | 3 | $response = $this->client->request( |
|||
616 | 3 | 'POST', |
|||
617 | 3 | $uri, |
|||
618 | [ |
||||
619 | 'headers' => [ |
||||
620 | 3 | 'Content-Length' => strlen($data), |
|||
621 | 3 | 'Content-Type' => 'application/octet-stream', |
|||
622 | ], |
||||
623 | 3 | 'body' => $data, |
|||
624 | ] |
||||
625 | ); |
||||
626 | |||||
627 | 3 | return (string) $response->getBody(); |
|||
628 | } |
||||
629 | |||||
630 | /** |
||||
631 | * Sometimes fread() returns less than the request number of bytes (for example, when reading |
||||
632 | * from network streams). This function repeatedly calls fread until the requested number of |
||||
633 | * bytes have been read or we've reached EOF. |
||||
634 | * |
||||
635 | * @param resource $stream |
||||
636 | * @param int $chunkSize |
||||
637 | * |
||||
638 | * @throws Exception |
||||
639 | * @return string |
||||
640 | */ |
||||
641 | 3 | protected function readChunk($stream, int $chunkSize) |
|||
642 | { |
||||
643 | 3 | $chunk = ''; |
|||
644 | 3 | while (! feof($stream) && $chunkSize > 0) { |
|||
645 | 3 | $part = fread($stream, $chunkSize); |
|||
646 | 3 | if ($part === false) { |
|||
647 | throw new Exception('Error reading from $stream.'); |
||||
648 | } |
||||
649 | 3 | $chunk .= $part; |
|||
650 | 3 | $chunkSize -= strlen($part); |
|||
651 | } |
||||
652 | |||||
653 | 3 | return $chunk; |
|||
654 | } |
||||
655 | |||||
656 | /** |
||||
657 | * Handle ClientException. |
||||
658 | * |
||||
659 | * @param ClientException $exception ClientException |
||||
660 | * |
||||
661 | * @return Exception |
||||
662 | */ |
||||
663 | protected function determineException(ClientException $exception): Exception |
||||
664 | { |
||||
665 | if (in_array($exception->getResponse()->getStatusCode(), [400, 403, 404, 409])) { |
||||
666 | return new BadRequest($exception->getResponse()); |
||||
667 | } |
||||
668 | |||||
669 | return $exception; |
||||
670 | } |
||||
671 | |||||
672 | /** |
||||
673 | * Build HTTP query. |
||||
674 | * |
||||
675 | * @param array $parameters Query parameters |
||||
676 | * |
||||
677 | * @return string |
||||
678 | */ |
||||
679 | 39 | protected function buildHttpQuery(array $parameters):string |
|||
680 | { |
||||
681 | 39 | return http_build_query( |
|||
682 | 39 | array_map( |
|||
683 | 39 | function ($parameter) { |
|||
684 | 39 | if (! is_bool($parameter)) { |
|||
685 | 18 | return $parameter; |
|||
686 | } |
||||
687 | |||||
688 | 39 | return $parameter ? 'true' : 'false'; |
|||
689 | 39 | }, |
|||
690 | 39 | $parameters |
|||
691 | ) |
||||
692 | ); |
||||
693 | } |
||||
694 | |||||
695 | /** |
||||
696 | * Validate JSON. |
||||
697 | * |
||||
698 | * @param mixed $data JSON variable |
||||
699 | * |
||||
700 | * @return bool |
||||
701 | */ |
||||
702 | 63 | protected function jsonValidator($data = null):bool |
|||
703 | { |
||||
704 | 63 | if (! empty($data)) { |
|||
705 | 60 | @json_decode($data); |
|||
0 ignored issues
–
show
It seems like you do not handle an error condition for
json_decode() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||
706 | |||||
707 | 60 | return json_last_error() === JSON_ERROR_NONE; |
|||
708 | } |
||||
709 | |||||
710 | 3 | return false; |
|||
711 | } |
||||
712 | } |
||||
713 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.