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

File::_put()   D

Complexity

Conditions 24
Paths 151

Size

Total Lines 73
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 73
rs 4.9645
c 0
b 0
f 0
cc 24
eloc 53
nc 151
nop 7

How to fix   Long Method    Complexity   

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:

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