Completed
Push — master ( c91c6f...b018d3 )
by Raffael
07:39
created

File::putChunk()   D

Complexity

Conditions 13
Paths 56

Size

Total Lines 83
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 83
rs 4.9922
c 0
b 0
f 0
cc 13
eloc 57
nc 56
nop 10

How to fix   Long Method    Complexity    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
declare(strict_types=1);
3
4
/**
5
 * Balloon
6
 *
7
 * @category    Balloon
8
 * @author      Raffael Sahli <[email protected]>
9
 * @copyright   copryright (c) 2012-2016 gyselroth GmbH
10
 */
11
12
namespace Balloon\Rest\v1;
13
14
use Balloon\Exception;
15
use Balloon\Helper;
16
use Balloon\Http\Response;
17
use Balloon\Filesystem\Node\Collection;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\Rest\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...
18
19
class File extends Node
20
{
21
    /**
22
     * @api {get} /api/v1/file/preview?id=:id Get Preview
23
     * @apiVersion 1.0.6
24
     * @apiName getPreview
25
     * @apiGroup Node\File
26
     * @apiPermission none
27
     * @apiDescription Get a preview of the files content. The body either contains an encoded string or a jpeg binary
28
     * @apiUse _getNode
29
     *
30
     * @apiExample (cURL) exmaple:
31
     * curl -XGET "https://SERVER/api/v1/file/preview?id=544627ed3c58891f058b4686 > preview.jpg"
32
     * curl -XGET "https://SERVER/api/v1/file/544627ed3c58891f058b4686/preview > preview.jpg"
33
     * curl -XGET "https://SERVER/api/v1/file/preview?p=/absolute/path/to/my/file > preview.jpg"
34
     *
35
     * @apiParam (GET Parameter) {string} [encode=false] Set to base64 to return a jpeg encoded preview as base64, else return it as jpeg binary
36
     *
37
     * @apiSuccessExample {string} Success-Response:
38
     * HTTP/1.1 200 OK
39
     *
40
     * @apiSuccessExample {binary} Success-Response:
41
     * HTTP/1.1 200 OK
42
     *
43
     * @apiErrorExample {json} Error-Response (thumbnail not found):
44
     * HTTP/1.1 404 Not Found
45
     * {
46
     *      "status": 404,
47
     *      "data": {
48
     *          "error": "Balloon\\Exception\\NotFound",
49
     *          "message": "no preview exists"
50
     *      }
51
     * }
52
     *
53
     * @param  string $id
54
     * @param  string $p
55
     * @param  string $encode
56
     * @return void
57
     */
58
    public function getPreview(?string $id=null, ?string $p=null, ?string $encode=null): void
59
    {
60
        $node = $this->_getNode($id, $p);
61
        $data = $node->getPreview();
62
        
63
        header('Content-Type: image/jpeg');
64
        if ($encode == 'base64') {
65
            echo base64_encode($data);
66
        } else {
67
            echo $data;
68
        }
69
70
        exit();
71
    }
72
    
73
    
74
    /**
75
     * @api {get} /api/v1/file/history?id=:id Get history
76
     * @apiVersion 1.0.6
77
     * @apiName getHistory
78
     * @apiGroup Node\File
79
     * @apiPermission none
80
     * @apiDescription Get a full change history of a file
81
     * @apiUse _getNode
82
     *
83
     * @apiExample (cURL) example:
84
     * curl -XGET "https://SERVER/api/v1/file/history?id=544627ed3c58891f058b4686&pretty"
85
     * curl -XGET "https://SERVER/api/v1/file/544627ed3c58891f058b4686/history?pretty"
86
     * curl -XGET "https://SERVER/api/v1/file/history?p=/absolute/path/to/my/file&pretty"
87
     *
88
     * @apiSuccess (200 OK) {number} status Status Code
89
     * @apiSuccess (200 OK) {object[]} data History
90
     * @apiSuccess (200 OK) {number} data.version Version
91
     * @apiSuccess (200 OK) {object} data.changed Changed timestamp
92
     * @apiSuccess (200 OK) {number} data.changed.sec Changed timestamp in Unix time
93
     * @apiSuccess (200 OK) {number} data.changed.usec Additional microseconds to changed Unix timestamp
94
     * @apiSuccess (200 OK) {string} data.user User which changed the version
95
     * @apiSuccess (200 OK) {number} data.type Change type, there are five different change types including:</br>
96
     *  0 - Initially added</br>
97
     *  1 - Content modified</br>
98
     *  2 - Version rollback</br>
99
     *  3 - Deleted</br>
100
     *  4 - Undeleted
101
     * @apiSuccess (200 OK) {object} data.file Reference to the content
102
     * @apiSuccess (200 OK) {string} data.file.id Content reference ID
103
     * @apiSuccess (200 OK) {number} data.size Content size in bytes
104
     * @apiSuccess (200 OK) {string} data.mime Content mime type
105
     * @apiSuccessExample {json} Success-Response:
106
     * HTTP/1.1 200 OK
107
     * {
108
     *      "status": 200,
109
     *      "data": [
110
     *          {
111
     *              "version": 1,
112
     *              "changed": {
113
     *                  "sec": 1413883885,
114
     *                  "usec": 876000
115
     *              },
116
     *              "user": "peter.meier",
117
     *              "type": 0,
118
     *              "file": {
119
     *                  "$id": "544627ed3c58891f058b4688"
120
     *              },
121
     *              "size": 178,
122
     *              "mime": "text\/plain"
123
     *          }
124
     *      ]
125
     * }
126
     *
127
     * @param  string $id
128
     * @param  string $p
129
     * @return Response
130
     */
131
    public function getHistory(?string $id=null, ?string $p=null): Response
132
    {
133
        $result = Helper::escape(
134
            $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\INode 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...
135
        );
136
        return (new Response())->setCode(200)->setBody($result);
137
    }
138
139
140
    /**
141
     * @api {post} /api/v1/file/restore?id=:id Rollback version
142
     * @apiVersion 1.0.6
143
     * @apiName postRestore
144
     * @apiGroup Node\File
145
     * @apiPermission none
146
     * @apiDescription Rollback to a recent version from history. Use the version number from history.
147
     * @apiUse _getNode
148
     *
149
     * @apiExample (cURL) example:
150
     * curl -XPOST "https://SERVER/api/v1/file/restore?id=544627ed3c58891f058b4686&pretty&vesion=11"
151
     * curl -XPOST "https://SERVER/api/v1/file/544627ed3c58891f058b4686/restore?pretty&version=1"
152
     * curl -XPOST "https://SERVER/api/v1/file/restore?p=/absolute/path/to/my/file&pretty&version=3"
153
     *
154
     * @apiParam (GET Parameter) {number} version The version from history to rollback to
155
     *
156
     * @apiSuccessExample {json} Success-Response:
157
     * HTTP/1.1 204 No Content
158
     *
159
     * @param   string $id
160
     * @param   string $p
161
     * @param   string $version
162
     * @return  Response
163
     */
164
    public function postRestore(int $version, ?string $id=null, ?string $p=null): Response
165
    {
166
        $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\INode 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...
167
        return (new Response())->setCode(204);
168
    }
169
170
171
    /**
172
     * @api {put} /api/v1/file/chunk Upload file chunk
173
     * @apiVersion 1.0.6
174
     * @apiName putChunk
175
     * @apiGroup Node\File
176
     * @apiPermission none
177
     * @apiUse _getNode
178
     * @apuUse _conflictNode
179
     * @apiUse _writeAction
180
     * @apiDescription Upload a file chunk. Use this method if you have possible big files!
181
     * You have to manually splitt the binary data into
182
     * multiple chunks and upload them successively using this method. Once uploading the last chunk,
183
     * the server will automatically create or update the file node.
184
     * You may set the parent collection, name and or custom attributes only with the last request to save traffic.
185
     *
186
     * @apiExample (cURL) example:
187
     * # Upload a new file myfile.jpg into the collection 544627ed3c58891f058b4686.
188
     * 1. First splitt the file into multiple 8M (For example, you could also use a smaller or bigger size) chunks
189
     * 2. Create a unique name for the chunkgroup (Could also be the filename), best thing is to create a UUIDv4
190
     * 3. Upload each chunk successively (follow the binary order of your file!) using the chunk PUT method
191
     *   (The server identifies each chunk with the index parameter, beginning with #1).
192
     * 4. If chunk number 3 will be reached, the server automatically place all chunks to the new file node
193
     *
194
     * 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
195
     * 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
196
     * 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
197
     *
198
     * @apiParam (GET Parameter) {string} [id] Either id, p (path) of a file node or a parent collection id must be given
199
     * @apiParam (GET Parameter) {string} [p] Either id, p (path) of a file node or a parent collection id must be given
200
     * @apiParam (GET Parameter) {string} [collection] Either id, p (path) of a file node or a parent collection id must be given
201
     * (If none of them are given, the file will be placed to the root)
202
     * @apiParam (GET Parameter) {string} [name] Needs to be set if the chunk belongs to a new file
203
     * @apiParam (GET Parameter) {number} index Chunk ID (consider chunk order!)
204
     * @apiParam (GET Parameter) {number} chunks Total number of chunks
205
     * @apiParam (GET Parameter) {string} chunkgroup A unique name which identifes a group of chunks (One file)
206
     * @apiParam (GET Parameter) {number} size The total file size in bytes
207
     * @apiParam (GET Parameter) {object} [attributes] Overwrite some attributes which are usually generated on the server
208
     * @apiParam (GET Parameter) {number} [attributes.created] Set specific created timestamp (UNIX timestamp format)
209
     * @apiParam (GET Parameter) {number} [attributes.changed] Set specific changed timestamp (UNIX timestamp format)
210
     *
211
     *
212
     * @apiSuccess (200 OK) {number} status Status Code
213
     * @apiSuccess (200 OK) {number} data Increased version number if the last chunk was uploaded and existing node was updated.
214
     * It will return the old version if the submited file content was equal to the existing one.
215
     *
216
     * @apiSuccess (201 Created) {number} status Status Code
217
     * @apiSuccess (201 Created) {string} data Node ID if the last chunk was uploaded and a new node was added
218
     *
219
     * @apiSuccess (206 Partial Content) {number} status Status Code
220
     * @apiSuccess (206 Partial Content) {string} data Chunk ID if it was not the last chunk
221
     * @apiSuccessExample {json} Success-Response (Not the last chunk yet):
222
     * HTTP/1.1 206 Partial Content
223
     * {
224
     *      "status": 206,
225
     *      "data": "1"
226
     * }
227
     *
228
     * @apiSuccessExample {json} Success-Response (New file created, Last chunk):
229
     * HTTP/1.1 201 Created
230
     * {
231
     *      "status": 201,
232
     *      "data": "78297329329389e332234342"
233
     * }
234
     *
235
     * @apiSuccessExample {json} Success-Response (File updated, Last chunk):
236
     * HTTP/1.1 200 OK
237
     * {
238
     *      "status": 200,
239
     *      "data": 2
240
     * }
241
     *
242
     * @apiErrorExample {json} Error-Response (quota full):
243
     * HTTP/1.1 507 Insufficient Storage
244
     * {
245
     *      "status": 507
246
     *      "data": {
247
     *          "error": "Balloon\Exception\InsufficientStorage",
248
     *          "message": "user quota is full",
249
     *          "code": 66
250
     *      }
251
     * }
252
     *
253
     * @apiErrorExample {json} Error-Response (Size limit exceeded):
254
     * HTTP/1.1 400 Bad Request
255
     * {
256
     *      "status": 400,
257
     *      "data": {
258
     *          "error": "Balloon\\Exception\\Conflict",
259
     *          "message": "file size exceeded limit",
260
     *          "code": 17
261
     *      }
262
     * }
263
     *
264
     * @apiErrorExample {json} Error-Response (Chunks lost):
265
     * HTTP/1.1 400 Bad Request
266
     * {
267
     *      "status": 400,
268
     *      "data": {
269
     *          "error": "Balloon\\Exception\\Conflict",
270
     *          "message": "chunks lost, reupload all chunks",
271
     *          "code": 275
272
     *      }
273
     * }
274
     *
275
     * @apiErrorExample {json} Error-Response (Chunks invalid size):
276
     * HTTP/1.1 400 Bad Request
277
     * {
278
     *      "status": 400,
279
     *      "data": {
280
     *          "error": "Balloon\\Exception\\Conflict",
281
     *          "message": "merged chunks temp file size is not as expected",
282
     *          "code": 276
283
     *      }
284
     * }
285
     *
286
     * @param  string $id
287
     * @param  string $p
288
     * @param  string $collection
289
     * @param  string $name
290
     * @param  int $index
291
     * @param  int $chunks
292
     * @param  string $chunkgroup
293
     * @param  int $size
294
     * @param  array $attributes
295
     * @param  int $conflict
296
     * @return Response
297
     */
298
    public function putChunk(
299
        string $chunkgroup,
300
        ?string $id=null,
301
        ?string $p=null,
302
        ?string $collection=null,
303
        ?string $name=null,
304
        int $index=1,
305
        int $chunks=0,
306
        int $size=0,
307
        array $attributes=[],
308
        int $conflict=0)
309
    {
310
        ini_set('auto_detect_line_endings', '1');
311
        $input_handler = fopen('php://input', 'rb');
312
        if (!is_string($chunkgroup) || empty($chunkgroup)) {
313
            throw new Exception\InvalidArgument('chunkgroup must be valid unique string');
314
        }
315
        
316
        if ($index > $chunks) {
317
            throw new Exception\InvalidArgument('chunk index can not be greater than the total number of chunks');
318
        }
319
320
        $chunkgroup = Helper::filter($chunkgroup);
321
        $folder     = $this->config->dir->temp.DIRECTORY_SEPARATOR.'upload'.DIRECTORY_SEPARATOR.$this->user->getId();
0 ignored issues
show
Documentation introduced by
The property dir does not exist on object<Balloon\Config>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
322
323
        if (!file_exists($folder)) {
324
            mkdir($folder, 0700, true);
325
        }
326
327
        $file = $folder.DIRECTORY_SEPARATOR.$chunkgroup;
328
329
        $tmp_size = 0;
330
        if (file_exists($file)) {
331
            $tmp_size = filesize($file);
332
        } elseif ($index > 1) {
333
            throw new Exception\Conflict('chunks lost, reupload all chunks',
334
                Exception\Conflict::CHUNKS_LOST
335
            );
336
        }
337
        
338
        $chunkgroup_handler = fopen($file, 'a+');
339
        while (!feof($input_handler)) {
340
            $data  = fread($input_handler, 1024);
341
            $wrote = fwrite($chunkgroup_handler, $data);
342
            $tmp_size += $wrote;
343
344
            if ($tmp_size > (int)$this->config->file->max_size) {
0 ignored issues
show
Documentation introduced by
The property file does not exist on object<Balloon\Config>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
345
                fclose($input_handler);
346
                fclose($chunkgroup_handler);
347
                unlink($file);
348
                throw new Exception\InsufficientStorage('file size exceeded limit',
349
                    Exception\InsufficientStorage::FILE_SIZE_LIMIT
350
                );
351
            }
352
        }
353
    
354
        if ($index == $chunks) {
355
            clearstatcache();
356
            if (!is_readable($file)) {
357
                throw new Exception\Conflict('chunks lost, reupload all chunks',
358
                    Exception\Conflict::CHUNKS_LOST
359
                );
360
            }
361
            
362
            if ($tmp_size != $size) {
363
                fclose($chunkgroup_handler);
364
                unlink($file);
365
                throw new Exception\Conflict('merged chunks temp file size is not as expected',
366
                    Exception\Conflict::CHUNKS_INVALID_SIZE
367
                );
368
            }
369
            
370
            try {
371
                $attributes = $this->_verifyAttributes($attributes);
372
                return $this->_put($file, $id, $p, $collection, $name, $attributes, $conflict);
373
            } catch (\Exception $e) {
374
                unlink($file);
375
                throw $e;
376
            }
377
        } else {
378
            return (new Response())->setCode(206)->setBody($index);
379
        }
380
    }
381
382
383
    /**
384
     * @api {put} /api/v1/file Upload file
385
     * @apiVersion 1.0.6
386
     * @apiName put
387
     * @apiGroup Node\File
388
     * @apiPermission none
389
     * @apiUse _getNode
390
     * @apiUse _conflictNode
391
     * @apiUse _writeAction
392
     *
393
     * @apiDescription Upload an entire file in one-shot. Attention, there is file size limit,
394
     * if you have possible big files use the method PUT chunk!
395
     *
396
     * @apiExample (cURL) example:
397
     * #Update content of file 544627ed3c58891f058b4686
398
     * curl -XPUT "https://SERVER/api/v1/file?id=544627ed3c58891f058b4686" --data-binary myfile.txt
399
     * curl -XPUT "https://SERVER/api/v1/file/544627ed3c58891f058b4686" --data-binary myfile.txt
400
     *
401
     * #Upload new file under collection 544627ed3c58891f058b3333
402
     * curl -XPUT "https://SERVER/api/v1/file?collection=544627ed3c58891f058b3333&name=myfile.txt" --data-binary myfile.txt
403
     *
404
     * @apiParam (GET Parameter) {string} [id] Either id, p (path) of a file node or a parent collection id must be given
405
     *
406
     * @apiParam (GET Parameter) {string} [id] Either id, p (path) of a file node or a parent collection id must be given
407
     * @apiParam (GET Parameter) {string} [p] Either id, p (path) of a file node or a parent collection id must be given
408
     * @apiParam (GET Parameter) {string} [collection] Either id, p (path) of a file node or a parent collection id must be given
409
     * (If none of them are given, the file will be placed to the root)
410
     * @apiParam (GET Parameter) {string} [name] Needs to be set if the chunk belongs to a new file
411
     * or to identify an existing child file if a collection id was set
412
     * @apiParam (GET Parameter) {object} attributes Overwrite some attributes which are usually generated on the server
413
     * @apiParam (GET Parameter) {number} attributes.created Set specific created timestamp (UNIX timestamp format)
414
     * @apiParam (GET Parameter) {number} attributes.changed Set specific changed timestamp (UNIX timestamp format)
415
     *
416
     * @apiSuccess (200 OK) {number} status Status Code
417
     * @apiSuccess (200 OK) {number} data Increased version number if an existing file was updated. It will return
418
     * the old version if the submited file content was equal to the existing one.
419
     *
420
     * @apiSuccess (201 Created) {number} status Status Code
421
     * @apiSuccess (201 Created) {string} data Node ID
422
     * @apiSuccessExample {json} Success-Response (New file created):
423
     * HTTP/1.1 201 Created
424
     * {
425
     *      "status": 201,
426
     *      "data": "78297329329389e332234342"
427
     * }
428
     *
429
     * @apiSuccessExample {json} Success-Response (File updated):
430
     * HTTP/1.1 200 OK
431
     * {
432
     *      "status": 200,
433
     *      "data": 2
434
     * }
435
     *
436
     * @apiErrorExample {json} Error-Response (quota full):
437
     * HTTP/1.1 507 Insufficient Storage
438
     * {
439
     *      "status": 507
440
     *      "data": {
441
     *          "error": "Balloon\Exception\InsufficientStorage",
442
     *          "message": "user quota is full",
443
     *          "code": 65
444
     *      }
445
     * }
446
     *
447
     * @apiErrorExample {json} Error-Response (Size limit exceeded):
448
     * HTTP/1.1 400 Bad Request
449
     * {
450
     *      "status": 400,
451
     *      "data": {
452
     *          "error": "Balloon\\Exception\\Conflict",
453
     *          "message": "file size exceeded limit",
454
     *          "code": 17
455
     *      }
456
     * }
457
     *
458
     * @param  string $id
459
     * @param  string $p
460
     * @param  string $collection
461
     * @param  string $name
462
     * @param  array $attributes
463
     * @param  int $conflict
464
     * @return Response
465
     */
466
    public function put(
467
        ?string $id=null,
468
        ?string $p=null,
469
        ?string $collection=null,
470
        ?string $name=null,
471
        array $attributes=[],
472
        int $conflict=0): Response
473
    {
474
        $attributes = $this->_verifyAttributes($attributes);
475
476
        ini_set('auto_detect_line_endings', '1');
477
        $content = fopen('php://input', 'rb');
478
        return $this->_put($content, $id, $p, $collection, $name, $attributes, $conflict);
479
    }
480
481
482
    /**
483
     * Add or update file
484
     *
485
     * @param  string|resource $content
486
     * @param  string $id
487
     * @param  string $p
488
     * @param  string $collection
489
     * @param  string $name
490
     * @param  array $attributes
491
     * @param  int $conflict
492
     * @return Response
493
     */
494
    protected function _put(
495
        $content,
496
        ?string $id=null,
497
        ?string $p=null,
498
        ?string $collection=null,
499
        ?string $name=null,
500
        array $attributes=[],
501
        int $conflict=0): Response
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...
502
    {
503
        if ($id === null && $p === null && $name === null) {
504
            throw new Exception\InvalidArgument('neither id, p nor name was set');
505
        }
506
507
        if ($p !== null && $name !== null) {
508
            throw new Exception\InvalidArgument('p and name can not be used at the same time');
509
        }
510
511
        try {
512
            if ($p !== null) {
513
                $node = $this->_getNode(null, $p);
514
                $result = $node->put($content, false, $attributes);
515
                return (new Response())->setCode(200)->setBody($result);
516
            } elseif ($id !== null && $collection === null) {
517
                $node = $this->_getNode($id);
518
                $result = $node->put($content, false, $attributes);
519
                return (new Response())->setCode(200)->setBody($result);
520
            } elseif ($p === null && $id === null && $name !== null) {
521
                $collection =  $this->_getNode($collection, null, 'Collection', false, true);
522
523
                if ($collection->childExists($name)) {
524
                    $child = $collection->getChild($name);
525
                    $result = $child->put($content, false, $attributes);
526
                    return (new Response())->setCode(200)->setBody($result);
527
                } else {
528
                    if (!is_string($name) || empty($name)) {
529
                        throw new Exception\InvalidArgument('name must be a valid string');
530
                    }
531
532
                    $name = Helper::filter($name);
533
                    $result = $collection->addFile($name, $content, $attributes)->getId(true);
534
                    return (new Response())->setCode(201)->setBody($result);
535
                }
536
            }
537
        } catch(Exception\Forbidden $e) {
538
            throw new Exception\Conflict('a node called '.$name.' does already exists in this collection',
539
                Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
540
            );
541
        } catch (Exception\NotFound $e) {
542
            if ($p !== null && $id === null) {
543
                if (!is_string($p) || empty($p)) {
544
                    throw new Exception\InvalidArgument('path (p) must be a valid string');
545
                }
546
547
                $p = Helper::filter($p);
548
                $parent_path = dirname($p);
549
                $name = basename($p);
550
                try {
551
                    $parent = $this->fs->findNodeWithPath($parent_path, 'Collection');
552
                    
553
                    if (!is_string($name) || empty($name)) {
554
                        throw new Exception\InvalidArgument('name must be a valid string');
555
                    }
556
557
                    $result = $parent->addFile($name, $content, $attributes)->getId(true);
558
                    return (new Response())->setCode(201)->setBody($result);
559
                } catch (Exception\NotFound $e) {
560
                    throw new Exception('parent collection '.$parent_path.' was not found');
561
                }
562
            } else {
563
                throw $e;
564
            }
565
        }
566
    }
567
}
568