Completed
Push — master ( 37faaa...541bbf )
by Raffael
10:18 queued 06:30
created

File::putChunk()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 58
ccs 0
cts 49
cp 0
rs 7.983
c 0
b 0
f 0
cc 7
crap 56
nc 7
nop 10

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2019 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\App\Api\v1;
13
14
use Balloon\Filesystem\Acl\Exception\Forbidden as ForbiddenException;
15
use Balloon\Filesystem\Exception;
16
use Balloon\Filesystem\Node\Collection;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\App\Api\v1\Collection.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
17
use Balloon\Helper;
18
use Balloon\Session\SessionInterface;
19
use Micro\Http\Response;
20
21
class File extends Node
22
{
23
    /**
24
     * @api {get} /api/v1/file/history?id=:id Get history
25
     * @apiVersion 1.0.0
26
     * @apiName getHistory
27
     * @apiGroup Node\File
28
     * @apiPermission none
29
     * @apiDescription Get a full change history of a file
30
     * @apiUse _getNode
31
     *
32
     * @apiExample (cURL) example:
33
     * curl -XGET "https://SERVER/api/v1/file/history?id=544627ed3c58891f058b4686&pretty"
34
     * curl -XGET "https://SERVER/api/v1/file/544627ed3c58891f058b4686/history?pretty"
35
     * curl -XGET "https://SERVER/api/v1/file/history?p=/absolute/path/to/my/file&pretty"
36
     *
37
     * @apiSuccess (200 OK) {number} status Status Code
38
     * @apiSuccess (200 OK) {object[]} data History
39
     * @apiSuccess (200 OK) {number} data.version Version
40
     * @apiSuccess (200 OK) {object} data.changed Changed timestamp
41
     * @apiSuccess (200 OK) {number} data.changed.sec Changed timestamp in Unix time
42
     * @apiSuccess (200 OK) {number} data.changed.usec Additional microseconds to changed Unix timestamp
43
     * @apiSuccess (200 OK) {string} data.user User which changed the version
44
     * @apiSuccess (200 OK) {number} data.type Change type, there are five different change types including:</br>
45
     *  0 - Initially added</br>
46
     *  1 - Content modified</br>
47
     *  2 - Version rollback</br>
48
     *  3 - Deleted</br>
49
     *  4 - Undeleted
50
     * @apiSuccess (200 OK) {object} data.file Reference to the content
51
     * @apiSuccess (200 OK) {string} data.file.id Content reference ID
52
     * @apiSuccess (200 OK) {number} data.size Content size in bytes
53
     * @apiSuccess (200 OK) {string} data.mime Content mime type
54
     * @apiSuccessExample {json} Success-Response:
55
     * HTTP/1.1 200 OK
56
     * {
57
     *      "status": 200,
58
     *      "data": [
59
     *          {
60
     *              "version": 1,
61
     *              "changed": {
62
     *                  "sec": 1413883885,
63
     *                  "usec": 876000
64
     *              },
65
     *              "user": "peter.meier",
66
     *              "type": 0,
67
     *              "file": {
68
     *                  "$id": "544627ed3c58891f058b4688"
69
     *              },
70
     *              "size": 178,
71
     *              "mime": "text\/plain"
72
     *          }
73
     *      ]
74
     * }
75
     *
76
     * @param string $id
77
     * @param string $p
78
     */
79
    public function getHistory(?string $id = null, ?string $p = null): Response
80
    {
81
        $result = $this->_getNode($id, $p)->getHistory();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Balloon\Filesystem\Node\NodeInterface as the method getHistory() does only exist in the following implementations of said interface: Balloon\Filesystem\Node\File.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
82
        $body = [];
83
84
        foreach ($result as $version) {
85
            $v = (array) $version;
86
87
            $v['user'] = $this->server->getUserById($version['user'])->getUsername();
88
            $v['changed'] = Helper::DateTimeToUnix($version['changed']);
89
            $body[] = $v;
90
        }
91
92
        return (new Response())->setCode(200)->setBody([
93
            'status' => 200,
94
            'data' => $body,
95
        ]);
96
    }
97
98
    /**
99
     * @api {post} /api/v1/file/restore?id=:id Rollback version
100
     * @apiVersion 1.0.0
101
     * @apiName postRestore
102
     * @apiGroup Node\File
103
     * @apiPermission none
104
     * @apiDescription Rollback to a recent version from history. Use the version number from history.
105
     * @apiUse _getNode
106
     *
107
     * @apiExample (cURL) example:
108
     * curl -XPOST "https://SERVER/api/v1/file/restore?id=544627ed3c58891f058b4686&pretty&vesion=11"
109
     * curl -XPOST "https://SERVER/api/v1/file/544627ed3c58891f058b4686/restore?pretty&version=1"
110
     * curl -XPOST "https://SERVER/api/v1/file/restore?p=/absolute/path/to/my/file&pretty&version=3"
111
     *
112
     * @apiParam (GET Parameter) {number} version The version from history to rollback to
113
     *
114
     * @apiSuccessExample {json} Success-Response:
115
     * HTTP/1.1 204 No Content
116
     *
117
     * @param string $id
118
     * @param string $p
119
     * @param string $version
120
     */
121
    public function postRestore(int $version, ?string $id = null, ?string $p = null): Response
122
    {
123
        $result = $this->_getNode($id, $p)->restore($version);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Balloon\Filesystem\Node\NodeInterface as the method restore() does only exist in the following implementations of said interface: Balloon\Filesystem\Node\File.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
124
125
        return (new Response())->setCode(204);
126
    }
127
128
    /**
129
     * @api {put} /api/v1/file/chunk Upload file chunk
130
     * @apiVersion 1.0.0
131
     * @apiName putChunk
132
     * @apiGroup Node\File
133
     * @apiPermission none
134
     * @apiUse _getNode
135
     * @apuUse _conflictNode
136
     * @apiUse _writeAction
137
     * @apiDescription Upload a file chunk. Use this method if you have possible big files!
138
     * You have to manually splitt the binary data into
139
     * multiple chunks and upload them successively using this method. Once uploading the last chunk,
140
     * the server will automatically create or update the file node.
141
     * You may set the parent collection, name and or custom attributes only with the last request to save traffic.
142
     *
143
     * @apiExample (cURL) example:
144
     * # Upload a new file myfile.jpg into the collection 544627ed3c58891f058b4686.
145
     * 1. First splitt the file into multiple 8M (For example, you could also use a smaller or bigger size) chunks
146
     * 2. Create a unique name for the chunkgroup (Could also be the filename), best thing is to create a UUIDv4
147
     * 3. Upload each chunk successively (follow the binary order of your file!) using the chunk PUT method
148
     *   (The server identifies each chunk with the index parameter, beginning with #1).
149
     * 4. If chunk number 3 will be reached, the server automatically place all chunks to the new file node
150
     *
151
     * curl -XPUT "https://SERVER/api/v1/file/chunk?collection=544627ed3c58891f058b4686&name=myfile.jpg&index=1&chunks=3&chunkgroup=myuniquechunkgroup&size=12342442&pretty" --data-binary @chunk1.bin
152
     * curl -XPUT "https://SERVER/api/v1/file/chunk?collection=544627ed3c58891f058b4686&name=myfile.jpg&index=2&chunks=3&chunkgroup=myuniquechunkgroup&size=12342442&pretty" --data-binary @chunk2.bin
153
     * curl -XPUT "https://SERVER/api/v1/file/chunk?collection=544627ed3c58891f058b4686&name=myfile.jpg&index=3&chunks=3&chunkgroup=myuniquechunkgroup&size=12342442&pretty" --data-binary @chunk3.bin
154
     *
155
     * @apiParam (GET Parameter) {string} [id] Either id, p (path) of a file node or a parent collection id must be given
156
     * @apiParam (GET Parameter) {string} [p] Either id, p (path) of a file node or a parent collection id must be given
157
     * @apiParam (GET Parameter) {string} [collection] Either id, p (path) of a file node or a parent collection id must be given
158
     * (If none of them are given, the file will be placed to the root)
159
     * @apiParam (GET Parameter) {string} [name] Needs to be set if the chunk belongs to a new file
160
     * @apiParam (GET Parameter) {number} index Chunk ID (consider chunk order!)
161
     * @apiParam (GET Parameter) {number} chunks Total number of chunks
162
     * @apiParam (GET Parameter) {string} chunkgroup A unique name which identifes a group of chunks (One file)
163
     * @apiParam (GET Parameter) {number} size The total file size in bytes
164
     * @apiParam (GET Parameter) {object} [attributes] Overwrite some attributes which are usually generated on the server
165
     * @apiParam (GET Parameter) {number} [attributes.created] Set specific created timestamp (UNIX timestamp format)
166
     * @apiParam (GET Parameter) {number} [attributes.changed] Set specific changed timestamp (UNIX timestamp format)
167
     *
168
     * @apiSuccess (200 OK) {number} status Status Code
169
     * @apiSuccess (200 OK) {number} data Increased version number if the last chunk was uploaded and existing node was updated.
170
     * It will return the old version if the submited file content was equal to the existing one.
171
     *
172
     * @apiSuccess (201 Created) {number} status Status Code
173
     * @apiSuccess (201 Created) {string} data Node ID if the last chunk was uploaded and a new node was added
174
     *
175
     * @apiSuccess (206 Partial Content) {number} status Status Code
176
     * @apiSuccess (206 Partial Content) {string} data Chunk ID if it was not the last chunk
177
     * @apiSuccessExample {json} Success-Response (Not the last chunk yet):
178
     * HTTP/1.1 206 Partial Content
179
     * {
180
     *      "status": 206,
181
     *      "data": "1"
182
     * }
183
     *
184
     * @apiSuccessExample {json} Success-Response (New file created, Last chunk):
185
     * HTTP/1.1 201 Created
186
     * {
187
     *      "status": 201,
188
     *      "data": "78297329329389e332234342"
189
     * }
190
     *
191
     * @apiSuccessExample {json} Success-Response (File updated, Last chunk):
192
     * HTTP/1.1 200 OK
193
     * {
194
     *      "status": 200,
195
     *      "data": 2
196
     * }
197
     *
198
     * @apiErrorExample {json} Error-Response (quota full):
199
     * HTTP/1.1 507 Insufficient Storage
200
     * {
201
     *      "status": 507
202
     *      "data": {
203
     *          "error": "Balloon\Exception\InsufficientStorage",
204
     *          "message": "user quota is full",
205
     *          "code": 66
206
     *      }
207
     * }
208
     *
209
     * @apiErrorExample {json} Error-Response (Size limit exceeded):
210
     * HTTP/1.1 400 Bad Request
211
     * {
212
     *      "status": 400,
213
     *      "data": {
214
     *          "error": "Balloon\\Exception\\Conflict",
215
     *          "message": "file size exceeded limit",
216
     *          "code": 17
217
     *      }
218
     * }
219
     *
220
     * @apiErrorExample {json} Error-Response (Chunks lost):
221
     * HTTP/1.1 400 Bad Request
222
     * {
223
     *      "status": 400,
224
     *      "data": {
225
     *          "error": "Balloon\\Exception\\Conflict",
226
     *          "message": "chunks lost, reupload all chunks",
227
     *          "code": 275
228
     *      }
229
     * }
230
     *
231
     * @apiErrorExample {json} Error-Response (Chunks invalid size):
232
     * HTTP/1.1 400 Bad Request
233
     * {
234
     *      "status": 400,
235
     *      "data": {
236
     *          "error": "Balloon\\Exception\\Conflict",
237
     *          "message": "merged chunks temp file size is not as expected",
238
     *          "code": 276
239
     *      }
240
     * }
241
     *
242
     * @param string $id
243
     * @param string $p
244
     * @param string $collection
245
     * @param string $name
246
     *
247
     * @return Response
248
     */
249
    public function putChunk(
250
        string $chunkgroup,
251
        ?string $id = null,
252
        ?string $p = null,
253
        ?string $collection = null,
254
        ?string $name = null,
255
        int $index = 1,
256
        int $chunks = 0,
257
        int $size = 0,
0 ignored issues
show
Unused Code introduced by
The parameter $size is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
258
        array $attributes = [],
259
        int $conflict = 0
260
    ) {
261
        ini_set('auto_detect_line_endings', '1');
262
        $input = fopen('php://input', 'rb');
263
        if (!is_string($chunkgroup) || empty($chunkgroup)) {
264
            throw new Exception\InvalidArgument('chunkgroup must be valid unique string');
265
        }
266
267
        if ($index > $chunks) {
268
            throw new Exception\InvalidArgument('chunk index can not be greater than the total number of chunks');
269
        }
270
271
        if (!preg_match('#^([A-Za-z0-9\.\-_])+$#', $chunkgroup)) {
272
            throw new Exception\InvalidArgument('chunkgroup may only contain #^[(A-Za-z0-9\.\-_])+$#');
273
        }
274
275
        $session = $this->db->selectCollection('fs.files')->findOne([
276
            'metadata.chunkgroup' => $this->server->getIdentity()->getId().'_'.$chunkgroup,
277
            'metadata.temporary' => true,
278
        ]);
279
280
        $parent = $this->getParent($id, $p, $collection);
281
282
        if ($session === null) {
283
            $session = $this->session_factory->add($this->server->getIdentity(), $parent, $input);
284
            $this->db->selectCollection('fs.files')->updateOne(
285
                ['_id' => $session->getId()],
286
                ['$set' => [
287
                    'metadata.chunkgroup' => $this->server->getIdentity()->getId().'_'.$chunkgroup,
288
                ]]
289
            );
290
        } else {
291
            $session = $session['_id'];
292
            $session = $this->session_factory->getOne($this->server->getIdentity(), $session);
293
            $this->session_factory->update($this->server->getIdentity(), $session, $parent, $input);
294
        }
295
296
        if ($index === $chunks) {
297
            $attributes = $this->_verifyAttributes($attributes);
298
299
            return $this->_put($session, $id, $p, $collection, $name, $attributes, $conflict);
300
        }
301
302
        return (new Response())->setCode(206)->setBody([
303
                'status' => 206,
304
                'data' => $index,
305
            ]);
306
    }
307
308
    /**
309
     * @api {put} /api/v1/file Upload file
310
     * @apiVersion 1.0.0
311
     * @apiName put
312
     * @apiGroup Node\File
313
     * @apiPermission none
314
     * @apiUse _getNode
315
     * @apiUse _conflictNode
316
     * @apiUse _writeAction
317
     *
318
     * @apiDescription Upload an entire file in one-shot. Attention, there is file size limit,
319
     * if you have possible big files use the method PUT chunk!
320
     *
321
     * @apiExample (cURL) example:
322
     * #Update content of file 544627ed3c58891f058b4686
323
     * curl -XPUT "https://SERVER/api/v1/file?id=544627ed3c58891f058b4686" --data-binary myfile.txt
324
     * curl -XPUT "https://SERVER/api/v1/file/544627ed3c58891f058b4686" --data-binary myfile.txt
325
     *
326
     * #Upload new file under collection 544627ed3c58891f058b3333
327
     * curl -XPUT "https://SERVER/api/v1/file?collection=544627ed3c58891f058b3333&name=myfile.txt" --data-binary myfile.txt
328
     *
329
     * @apiParam (GET Parameter) {string} [id] Either id, p (path) of a file node or a parent collection id must be given
330
     *
331
     * @apiParam (GET Parameter) {string} [id] Either id, p (path) of a file node or a parent collection id must be given
332
     * @apiParam (GET Parameter) {string} [p] Either id, p (path) of a file node or a parent collection id must be given
333
     * @apiParam (GET Parameter) {string} [collection] Either id, p (path) of a file node or a parent collection id must be given
334
     * (If none of them are given, the file will be placed to the root)
335
     * @apiParam (GET Parameter) {string} [name] Needs to be set if the chunk belongs to a new file
336
     * or to identify an existing child file if a collection id was set
337
     * @apiParam (GET Parameter) {object} attributes Overwrite some attributes which are usually generated on the server
338
     * @apiParam (GET Parameter) {number} attributes.created Set specific created timestamp (UNIX timestamp format)
339
     * @apiParam (GET Parameter) {number} attributes.changed Set specific changed timestamp (UNIX timestamp format)
340
     *
341
     * @apiSuccess (200 OK) {number} status Status Code
342
     * @apiSuccess (200 OK) {number} data Increased version number if an existing file was updated. It will return
343
     * the old version if the submited file content was equal to the existing one.
344
     *
345
     * @apiSuccess (201 Created) {number} status Status Code
346
     * @apiSuccess (201 Created) {string} data Node ID
347
     * @apiSuccessExample {json} Success-Response (New file created):
348
     * HTTP/1.1 201 Created
349
     * {
350
     *      "status": 201,
351
     *      "data": "78297329329389e332234342"
352
     * }
353
     *
354
     * @apiSuccessExample {json} Success-Response (File updated):
355
     * HTTP/1.1 200 OK
356
     * {
357
     *      "status": 200,
358
     *      "data": 2
359
     * }
360
     *
361
     * @apiErrorExample {json} Error-Response (quota full):
362
     * HTTP/1.1 507 Insufficient Storage
363
     * {
364
     *      "status": 507
365
     *      "data": {
366
     *          "error": "Balloon\Exception\InsufficientStorage",
367
     *          "message": "user quota is full",
368
     *          "code": 65
369
     *      }
370
     * }
371
     *
372
     * @apiErrorExample {json} Error-Response (Size limit exceeded):
373
     * HTTP/1.1 400 Bad Request
374
     * {
375
     *      "status": 400,
376
     *      "data": {
377
     *          "error": "Balloon\\Exception\\Conflict",
378
     *          "message": "file size exceeded limit",
379
     *          "code": 17
380
     *      }
381
     * }
382
     *
383
     * @param string $id
384
     * @param string $p
385
     * @param string $collection
386
     * @param string $name
387
     */
388
    public function put(
389
        ?string $id = null,
390
        ?string $p = null,
391
        ?string $collection = null,
392
        ?string $name = null,
393
        array $attributes = [],
394
        int $conflict = 0
395
    ): Response {
396
        $attributes = $this->_verifyAttributes($attributes);
397
398
        ini_set('auto_detect_line_endings', '1');
399
        $input = fopen('php://input', 'rb');
400
401
        $parent = $this->getParent($id, $p, $collection);
402
        $session = $this->session_factory->add($this->server->getIdentity(), $parent, $input);
403
404
        return $this->_put($session, $id, $p, $collection, $name, $attributes, $conflict);
405
    }
406
407
    /**
408
     * Get Parent.
409
     */
410
    protected function getParent($id, $p, $collection): Collection
411
    {
412
        if ($id !== null) {
413
            return $this->_getNode($id, $p)->getParent();
414
        }
415
        if ($p !== null) {
416
            $path = '/'.ltrim(dirname('/'.$p), '/');
417
418
            return $this->_getNode($id, $path, Collection::class);
419
        }
420
        if ($id === null && $p === null && $collection === null) {
421
            return $this->server->getFilesystem()->getRoot();
422
        }
423
424
        return $this->_getNode($collection, null, Collection::class);
425
    }
426
427
    /**
428
     * Add or update file.
429
     *
430
     * @param ObjecId $session
431
     * @param string  $id
432
     * @param string  $p
433
     * @param string  $collection
434
     * @param string  $name
435
     */
436
    protected function _put(
437
        SessionInterface $session,
438
        ?string $id = null,
439
        ?string $p = null,
440
        ?string $collection = null,
441
        ?string $name = null,
442
        array $attributes = [],
443
        int $conflict = 0
0 ignored issues
show
Unused Code introduced by
The parameter $conflict is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
444
    ): Response {
445
        if (null === $id && null === $p && null === $name) {
446
            throw new Exception\InvalidArgument('neither id, p nor name was set');
447
        }
448
449
        if (null !== $p && null !== $name) {
450
            throw new Exception\InvalidArgument('p and name can not be used at the same time');
451
        }
452
453
        try {
454
            if (null !== $p) {
455
                $node = $this->_getNode(null, $p);
456
                $result = $node->setContent($session, $attributes);
457
458
                return (new Response())->setCode(200)->setBody([
459
                    'status' => 200,
460
                    'data' => $result,
461
                ]);
462
            }
463
            if (null !== $id && null === $collection) {
464
                $node = $this->_getNode($id);
465
                $result = $node->setContent($session, $attributes);
466
467
                return (new Response())->setCode(200)->setBody([
468
                    'status' => 200,
469
                    'data' => $result,
470
                ]);
471
            }
472
            if (null === $p && null === $id && null !== $name) {
473
                $collection = $this->_getNode($collection, null, Collection::class, false, true);
474
475
                if ($collection->childExists($name)) {
476
                    $child = $collection->getChild($name);
477
                    $result = $child->setContent($session, $attributes);
478
479
                    return (new Response())->setCode(200)->setBody([
480
                        'status' => 200,
481
                        'data' => $result,
482
                    ]);
483
                }
484
                if (!is_string($name) || empty($name)) {
485
                    throw new Exception\InvalidArgument('name must be a valid string');
486
                }
487
488
                $result = $collection->addFile($name, $session, $attributes)->getId();
489
490
                return (new Response())->setCode(201)->setBody([
491
                    'status' => 201,
492
                    'data' => (string) $result,
493
                ]);
494
            }
495
        } catch (ForbiddenException $e) {
496
            throw new Exception\Conflict('a node called '.$name.' does already exists in this collection', Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS);
497
        } catch (Exception\NotFound $e) {
498
            if (null !== $p && null === $id) {
499
                if (!is_string($p) || empty($p)) {
500
                    throw new Exception\InvalidArgument('path (p) must be a valid string');
501
                }
502
503
                $parent_path = '/'.ltrim(dirname('/'.$p), '/');
504
                $name = Helper::mb_basename($p);
505
506
                try {
507
                    $parent = $this->findNodeByPath($parent_path, Collection::class);
508
509
                    if (!is_string($name) || empty($name)) {
510
                        throw new Exception\InvalidArgument('name must be a valid string');
511
                    }
512
513
                    $result = $parent->addFile($name, $session, $attributes)->getId();
514
515
                    return (new Response())->setCode(201)->setBody([
516
                        'status' => 201,
517
                        'data' => (string) $result,
518
                    ]);
519
                } catch (Exception\NotFound $e) {
520
                    throw new Exception('parent collection '.$parent_path.' was not found');
521
                }
522
            } else {
523
                throw $e;
524
            }
525
        }
526
    }
527
}
528