Completed
Branch dev (276354)
by Raffael
15:43
created

Collection::getShare()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 16
nc 4
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2018 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\App\Api\v1\AttributeDecorator\RoleDecorator;
15
use Balloon\Filesystem\Exception;
16
use Balloon\Filesystem\Node\Collection as NodeCollection;
17
use Micro\Http\Response;
18
19
class Collection extends Node
20
{
21
    /**
22
     * @api {head} /api/v1/collection/children?id=:id children exists?
23
     * @apiVersion 1.0.0
24
     * @apiName head
25
     * @apiGroup Node\Collection
26
     * @apiPermission none
27
     * @apiDescription Check if collection has any children
28
     * @apiUse _getNode
29
     *
30
     * @apiExample (cURL) example:
31
     * curl -XHEAD "https://SERVER/api/v1/collection/children?id=544627ed3c58891f058b4686"
32
     * curl -XHEAD "https://SERVER/api/v1/collection/544627ed3c58891f058b4686/children"
33
     * curl -XHEAD "https://SERVER/api/v1/collection/children?p=/absolute/path/to/my/collection"
34
     *
35
     * @apiSuccessExample {json} Success-Response (Children exists):
36
     * HTTP/1.1 204 Not Content
37
     *
38
     * @apiErrorExample {json} Error-Response (No children exists):
39
     * HTTP/1.1 404 Not Found
40
     *
41
     * @param string $id
42
     * @param string $p
43
     *
44
     * @return Response
45
     */
46
    public function headChildren(?string $id = null, ?string $p = null): Response
47
    {
48
        $result = $this->fs->getNode($id, $p, null, false, true);
49
        $children = $result->getSize();
50
51
        $response = (new Response())
52
            ->setHeader('Content-Length', $children);
53
54
        if ($children > 0) {
55
            $response->setCode(204);
56
        } else {
57
            $response->setCode(404);
58
        }
59
60
        return $response;
61
    }
62
63
    /**
64
     * @api {get} /api/v1/collection/children Get children
65
     * @apiVersion 1.0.0
66
     * @apiName getChildren
67
     * @apiGroup Node\Collection
68
     * @apiPermission none
69
     * @apiDescription Find all children of a collection
70
     * @apiUse _getNode
71
     * @apiUse _nodeAttributes
72
     *
73
     * @apiExample (cURL) example:
74
     * curl -XGET "https://SERVER/api/v1/collection/children?id=212323eeffe2322344224452&pretty"
75
     * curl -XGET "https://SERVER/api/v1/collection/212323eeffe2322344224452/children?pretty&deleted=0"
76
     * curl -XGET "https://SERVER/api/v1/collection/children?p=/absolute/path/to/my/collection&deleted=1"
77
     *
78
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
79
     * @apiParam (GET Parameter) {string[]} [filter] Filter nodes
80
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted nodes or not, possible values:</br>
81
     * - 0 Exclude deleted</br>
82
     * - 1 Only deleted</br>
83
     * - 2 Include deleted</br>
84
     *
85
     * @apiSuccess (200 OK) {number} status Status Code
86
     * @apiSuccess (200 OK) {object[]} data Children
87
     * @apiSuccessExample {json} Success-Response:
88
     * HTTP/1.1 200 OK
89
     * {
90
     *      "status":200,
91
     *      "data": [{..}, {...}] //Shorted
92
     * }
93
     *
94
     * @param string $id
95
     * @param string $p
96
     * @param int    $deleted
97
     * @param array  $filter
98
     * @param array  $attributes
99
     *
100
     * @return Response
101
     */
102
    public function getChildren(
103
        ?string $id = null,
104
        ?string $p = null,
105
        int $deleted = 0,
106
        array $filter = [],
107
        array $attributes = []
108
    ): Response {
109
        $children = [];
110
        $nodes = $this->fs->getNode($id, $p, null, false, true)->getChildNodes($deleted, $filter);
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 getChildNodes() does only exist in the following implementations of said interface: Balloon\Filesystem\Node\Collection.

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...
111
112
        foreach ($nodes as $node) {
113
            $children[] = $this->node_decorator->decorate($node, $attributes);
114
        }
115
116
        return (new Response())->setCode(200)->setBody([
117
            'code' => 200,
118
            'data' => $children,
119
        ]);
120
    }
121
122
    /**
123
     * @api {get} /api/v1/collection/share?id=:id Get Share settings
124
     * @apiVersion 1.0.0
125
     * @apiName getShare
126
     * @apiGroup Node\Collection
127
     * @apiPermission none
128
     * @apiDescription Get share acl and share name
129
     * @apiUse _getNode
130
     *
131
     * @apiExample (cURL) example:
132
     * curl -XGET "https://SERVER/api/v1/collection/share?id=212323eeffe2322344224452&pretty"
133
     * curl -XGET "https://SERVER/api/v1/collection/212323eeffe2322344224452/share?pretty"
134
     * curl -XGET "https://SERVER/api/v1/collection/share?p=/absolute/path/to/my/collection&pretty"
135
     *
136
     * @apiSuccess (200 OK) {number} status Status Code
137
     * @apiSuccess (200 OK) {string} data.name Share name
138
     * @apiSuccess (200 OK) {object[]} data.acl ACL rules
139
     * @apiSuccess (200 OK) {string} data.acl.type Either group or user
140
     * @apiSuccess (200 OK) {object} data.acl.role Role attributes
141
     * @apiSuccess (200 OK) {string} data.acl.priv Permission to access share
142
     * @apiSuccessExample {json} Success-Response:
143
     * HTTP/1.1 200 OK
144
     * {
145
     *      "status":200,
146
     *      "data":{
147
     *          "name": "my share",
148
     *          "acl": [
149
     *              {
150
     *                  "type":"user",
151
     *                  "role": {
152
     *                      "id": "212323eeffe2322355224411",
153
     *                  },
154
     *                  "privilege":"rw"
155
     *              }
156
     *          ]
157
     *      }
158
     *}
159
     *
160
     * @param RoleDecorator $role_decorator
161
     * @param string        $id
162
     * @param string        $p
163
     * @param array         $attributes
164
     *
165
     * @return Response
166
     */
167
    public function getShare(RoleDecorator $role_decorator, ?string $id = null, ?string $p = null, array $attributes = []): Response
168
    {
169
        $node = $this->fs->getNode($id, $p);
170
171
        if (!$node->isShared()) {
172
            throw new Exception\Conflict('node is not a share', Exception\Conflict::NOT_SHARED);
173
        }
174
175
        $acl = $node->getAcl();
176
        $rules = [];
177
178
        foreach ($acl as &$rule) {
179
            $role = $role_decorator->decorate($rule['role'], $attributes);
180
            $rules[] = [
181
                'type' => $rule['role'] instanceof User ? 'user' : 'group',
182
                'priv' => $rule['privilege'],
183
                'name' => $role['name'],
184
                'id' => (string) $rule['id'],
185
            ];
186
        }
187
188
        return (new Response())->setCode(200)->setBody([
189
            'code' => 200,
190
            'data' => $rules,
191
        ]);
192
    }
193
194
    /**
195
     * @api {post} /api/v1/collection/share?id=:id Create share
196
     * @apiVersion 1.0.0
197
     * @apiGroup Node\Collection
198
     * @apiPermission none
199
     * @apiDescription Create a new share from an existing collection
200
     * @apiUse _getNode
201
     * @apiUse _writeAction
202
     *
203
     * @apiExample (cURL) example:
204
     * curl -XPOST "https://SERVER/api/v1/collection/share?id=212323eeffe2322344224452&pretty"
205
     *
206
     * @apiParam (POST Parameter) {object[]} acl ACL rules
207
     * @apiParam (POST Parameter) {string} acl.type user or group
208
     * @apiParam (POST Parameter) {string} acl.role Role id (user or group id)
209
     * @apiParam (POST Parameter) {string} acl.privilege Permission to access share, could be on of the following:</br>
210
     *  rw - READ/WRITE </br>
211
     *  r - READONLY </br>
212
     *  w+ - INBOX (Only Access to owned nodes) </br>
213
     *  m - Manage </br>
214
     *  d - DENY </br>
215
     *
216
     * @apiSuccess (201 Created) {number} status Status code
217
     * @apiSuccess (201 Created) {boolean} data
218
     * @apiSuccessExample {json} Success-Response (Created or Modified Share):
219
     * HTTP/1.1 201 Created
220
     * {
221
     *      "status":201,
222
     *      "data": true
223
     * }
224
     *
225
     * @apiSuccessExample {json} Success-Response (Removed share):
226
     * HTTP/1.1 204 No Content
227
     *
228
     * @param string $id
229
     * @param string $p
230
     * @param array  $acl
231
     * @param string $name
232
     *
233
     * @return Response
234
     */
235
    public function postShare(array $acl, string $name, ?string $id = null, ?string $p = null): Response
236
    {
237
        $node = $this->fs->getNode($id, $p);
238
        $result = $node->share($acl, $name);
239
240
        if (null === $result) {
241
            return (new Response())->setCode(204);
242
        }
243
244
        return (new Response())->setCode(201)->setBody([
245
            'code' => 201,
246
            'data' => $result,
247
        ]);
248
    }
249
250
    /**
251
     * @api {delete} /api/v1/collection/share?id=:id Delete share
252
     * @apiVersion 1.0.0
253
     * @apiName deleteShare
254
     * @apiGroup Node\Collection
255
     * @apiPermission none
256
     * @apiDescription Does only remove sharing options and transform a share back into a normal collection.
257
     * There won't be any data loss after this action. All existing references would be removed automatically.
258
     * @apiUse _getNode
259
     * @apiUse _writeAction
260
     *
261
     * @apiExample (cURL) example:
262
     * curl -XDELETE "https://SERVER/api/v1/collection/share?id=212323eeffe2322344224452"
263
     * curl -XDELETE "https://SERVER/api/v1/collection/212323eeffe2322344224452/share"
264
     * curl -XDELETE "https://SERVER/api/v1/collection/share?p=/absolute/path/to/my/collection"
265
     *
266
     * @apiSuccessExample {json} Success-Response:
267
     * HTTP/1.1 204 No Content
268
     *
269
     * @param string $id
270
     * @param string $p
271
     *
272
     * @return Response
273
     */
274
    public function deleteShare(?string $id = null, ?string $p = null): Response
275
    {
276
        $node = $this->fs->getNode($id, $p);
277
        $result = $node->unshare();
0 ignored issues
show
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...
278
279
        return (new Response())->setCode(204);
280
    }
281
282
    /**
283
     * @api {post} /api/v1/collection?id=:id Create collection
284
     * @apiVersion 1.0.0
285
     * @apiName post
286
     * @apiGroup Node\Collection
287
     * @apiPermission none
288
     * @apiDescription Create a new collection. You can create a new collection combining a parent collection (id) and a name (name)
289
     * or set an absolute path (p) to the new collection. Additionally it is possible to overwrite server generated timestamps like created or changed (attributes).
290
     * Via the more advanced option filter (attributes.filter) you can create a special collection which can contain any nodes based on the given filter.
291
     * For example a filter could be {mime: application/pdf}, therefore the collection would contain all files with mimetype application/pdf accessible by you.
292
     * (Attention this can result in a slow server response since you could create a filter where no indexes exists, therefore the database engine needs to search the entire database)
293
     * @apiUse _getNode
294
     * @apiUse _conflictNode
295
     * @apiUse _writeAction
296
     *
297
     * @apiExample (cURL) example:
298
     * curl -XGET "https://SERVER/api/v1/collection?id=544627ef3c58891f058b468f&name=MyNewFolder&pretty"
299
     * curl -XGET "https://SERVER/api/v1/collection/544627ef3c58891f058b468f?name=MyNewFolder&pretty"
300
     * curl -XGET "https://SERVER/api/v1/collection/?p=/absolute/path/to/my/collection&name=MyNewFolder&pretty&conflict=2"
301
     *
302
     * @apiParam (GET Parameter) {string} id Either id or p (path) of a node must be given.
303
     * @apiParam (GET Parameter) {string} p Either id or p (path) of a node must be given. If a path is given, no name must be set,
304
     * the path must contain the name of the new collection.
305
     * @apiParam (GET Parameter) {string} name A collection name must be set in conjuction with id, don't need to set with a path
306
     * @apiParam (GET Parameter) {object} attributes Overwrite some attributes which are usually generated on the server
307
     * @apiParam (GET Parameter) {number} attributes.created Set specific created timestamp (UNIX timestamp format)
308
     * @apiParam (GET Parameter) {number} attributes.changed Set specific changed timestamp (UNIX timestamp format)
309
     * @apiParam (GET Parameter) {number} attributes.destroy Set specific self-destroy timestamp (UNIX timestamp format)
310
     * @apiParam (GET Parameter) {array} attributes.filter Set specific set of children instead just parent=this
311
     *
312
     * @apiSuccess (201 Created) {number} status Status Code
313
     * @apiSuccess (201 Created) {string} data Node ID
314
     * @apiSuccessExample {json} Success-Response:
315
     * HTTP/1.1 201 Created
316
     * {
317
     *      "status":201,
318
     *      "data": "544627ed3c58891f058b4682"
319
     * }
320
     *
321
     * @param string $id
322
     * @param string $p
323
     * @param array  $attributes
324
     * @param int    $conflict
325
     *
326
     * @return Response
327
     */
328
    public function post(
329
        ?string $id = null,
330
        ?string $p = null,
331
        ?string $name = null,
332
        array $attributes = [],
333
        int $conflict = 0
334
    ): Response {
335
        if (null !== $p && null !== $name) {
336
            throw new Exception\InvalidArgument('p and name can not be used at the same time');
337
        }
338
339
        $attributes = $this->_verifyAttributes($attributes);
340
341
        if (null === $id && null !== $p) {
342
            if (!is_string($p) || empty($p)) {
343
                throw new Exception\InvalidArgument('name must be a valid string');
344
            }
345
346
            $parent_path = dirname($p);
347
            $name = basename($p);
348
            $parent = $this->fs->findNodeByPath($parent_path, NodeCollection::class);
349
            $result = $parent->addDirectory($name, $attributes, $conflict)->getId();
350
351
            return (new Response())->setCode(201)->setBody([
352
                'code' => 201,
353
                'data' => (string) $result,
354
            ]);
355
        }
356
        if (null !== $id && null === $name) {
357
            throw new Exception\InvalidArgument('name must be set with id');
358
        }
359
        $parent = $this->fs->getNode($id, null, null, false, true);
360
361
        if (!is_string($name) || empty($name)) {
362
            throw new Exception\InvalidArgument('name must be a valid string');
363
        }
364
365
        $result = $parent->addDirectory($name, $attributes, $conflict)->getId();
366
367
        return (new Response())->setCode(201)->setBody([
368
            'code' => 201,
369
            'data' => (string) $result,
370
        ]);
371
    }
372
}
373