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

Collections::getShare()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
nc 3
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\v2;
13
14
use Balloon\AttributeDecorator\Pager;
15
use Balloon\Filesystem\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\App\Api\v2\Exception.

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...
16
use Balloon\Filesystem\Node\Collection as NodeCollection;
17
use Balloon\Server\AttributeDecorator as RoleAttributeDecorator;
18
use Micro\Http\Response;
19
20
class Collections extends Nodes
21
{
22
    /**
23
     * @api {head} /api/v2/collections/:id/children children exists?
24
     * @apiVersion 2.0.0
25
     * @apiName head
26
     * @apiGroup Node\Collection
27
     * @apiPermission none
28
     * @apiDescription Check if collection has any children
29
     * @apiUse _getNode
30
     *
31
     * @apiExample (cURL) example:
32
     * curl -XHEAD "https://SERVER/api/v2/collections/children?id=544627ed3c58891f058b4686"
33
     * curl -XHEAD "https://SERVER/api/v2/collections/544627ed3c58891f058b4686/children"
34
     * curl -XHEAD "https://SERVER/api/v2/collections/children?p=/absolute/path/to/my/collection"
35
     *
36
     * @apiSuccessExample {json} Success-Response (Children exists):
37
     * HTTP/1.1 204 Not Content
38
     *
39
     * @apiErrorExample {json} Error-Response (No children exists):
40
     * HTTP/1.1 404 Not Found
41
     *
42
     * @param string $id
43
     * @param string $p
44
     *
45
     * @return Response
46
     */
47
    public function headChildren(?string $id = null, ?string $p = null): Response
48
    {
49
        $result = $this->fs->getNode($id, $p, null, false, true);
50
        $children = $result->getSize();
51
52
        $response = (new Response())
53
            ->setHeader('Content-Length', $children);
54
55
        if ($children > 0) {
56
            $response->setCode(204);
57
        } else {
58
            $response->setCode(404);
59
        }
60
61
        return $response;
62
    }
63
64
    /**
65
     * @api {get} /api/v2/collections/:id/children Get children
66
     * @apiVersion 2.0.0
67
     * @apiName getChildren
68
     * @apiGroup Node\Collection
69
     * @apiPermission none
70
     * @apiDescription Find all children of a collection
71
     * @apiUse _getNode
72
     * @apiUse _nodeAttributes
73
     *
74
     * @apiExample (cURL) example:
75
     * curl -XGET "https://SERVER/api/v2/collections/children?id=212323eeffe2322344224452&pretty"
76
     * curl -XGET "https://SERVER/api/v2/collections/212323eeffe2322344224452/children?pretty&deleted=0"
77
     * curl -XGET "https://SERVER/api/v2/collections/children?p=/absolute/path/to/my/collection&deleted=1"
78
     *
79
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
80
     * @apiParam (GET Parameter) {string[]} [filter] Filter nodes
81
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted nodes or not, possible values:</br>
82
     * - 0 Exclude deleted</br>
83
     * - 1 Only deleted</br>
84
     * - 2 Include deleted</br>
85
     *
86
     * @apiSuccess (200 OK) {object[]} data Children
87
     * @apiSuccessExample {json} Success-Response:
88
     * HTTP/1.1 200 OK
89
     * [
90
     *  {..}
91
     * ]
92
     *
93
     * @param string $id
94
     * @param string $p
95
     * @param int    $deleted
96
     * @param array  $filter
97
     * @param array  $attributes
98
     * @param int    $offset
99
     * @param int    $limit
100
     *
101
     * @return Response
102
     */
103
    public function getChildren(
104
        ?string $id = null,
105
        ?string $p = null,
106
        int $deleted = 0,
107
        array $filter = [],
108
        array $attributes = [],
109
        ?int $offset = 0,
110
        ?int $limit = 20
111
    ): Response {
112
        $children = [];
0 ignored issues
show
Unused Code introduced by
$children 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...
113
114
        $node = $this->fs->getNode($id, $p, null, false, true);
115
        if ($node->isRoot()) {
116
            $uri = '/api/v2/collections/children';
117
        } else {
118
            $uri = '/api/v2/collections/'.$node->getId().'/children';
119
        }
120
121
        $nodes = $this->fs->getNode($id, $p, null, false, true)->getChildNodes($deleted, $filter, $offset, $limit);
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...
122
        $pager = new Pager($this->node_decorator, $nodes, $attributes, $offset, $limit, $uri);
123
        $result = $pager->paging();
124
125
        return (new Response())->setCode(200)->setBody($result);
126
    }
127
128
    /**
129
     * @api {get} /api/v2/collections/:id/share Get Share ACL
130
     * @apiVersion 2.0.0
131
     * @apiName getShare
132
     * @apiGroup Node\Collection
133
     * @apiPermission none
134
     * @apiDescription Get share acl and share name
135
     * @apiUse _getNode
136
     *
137
     * @apiExample (cURL) example:
138
     * curl -XGET "https://SERVER/api/v2/collections/share?id=212323eeffe2322344224452&pretty"
139
     * curl -XGET "https://SERVER/api/v2/collections/212323eeffe2322344224452/share?pretty"
140
     * curl -XGET "https://SERVER/api/v2/collections/share?p=/absolute/path/to/my/collection&pretty"
141
     *
142
     * @apiSuccess (200 OK) {string} name Share name
143
     * @apiSuccess (200 OK) {object[]} acl ACL rules
144
     * @apiSuccess (200 OK) {string} acl.type Either group or user
145
     * @apiSuccess (200 OK) {object} acl.role Role attributes
146
     * @apiSuccess (200 OK) {string} acl.priv Permission to access share
147
     * @apiSuccessExample {json} Success-Response:
148
     * HTTP/1.1 200 OK
149
     * {
150
     *      "name": "my share",
151
     *      "acl": [
152
     *          {
153
     *              "type":"user",
154
     *              "role": {
155
     *                  "id": "212323eeffe2322355224411",
156
     *                  "name": "example"
157
     *              },
158
     *              "privilege":"rw"
159
     *          }
160
     *      ]
161
     *}
162
     *
163
     * @param RoleAttributeDecorator $role_decorator
164
     * @param string                 $id
165
     * @param string                 $p
166
     * @param array                  $attributes
167
     *
168
     * @return Response
169
     */
170
    public function getShare(RoleAttributeDecorator $role_decorator, ?string $id = null, ?string $p = null, array $attributes = []): Response
171
    {
172
        $node = $this->fs->getNode($id, $p);
173
174
        if (!$node->isShared()) {
175
            throw new Exception\Conflict('node is not a share', Exception\Conflict::NOT_SHARED);
176
        }
177
178
        $acl = $node->getAcl();
179
180
        foreach ($acl as &$rule) {
181
            $rule['role'] = $role_decorator->decorate($rule['role'], $attributes);
182
        }
183
184
        return (new Response())->setCode(200)->setBody([
185
            'name' => $node->getShareName(),
186
            'acl' => $acl,
187
        ]);
188
    }
189
190
    /**
191
     * @api {post} /api/v2/collections/:id/share Create share
192
     * @apiVersion 2.0.0
193
     * @apiGroup Node\Collection
194
     * @apiPermission none
195
     * @apiDescription Create a new share from an existing collection
196
     * @apiUse _getNode
197
     * @apiUse _writeAction
198
     *
199
     * @apiExample (cURL) example:
200
     * curl -XPOST "https://SERVER/api/v2/collections/share?id=212323eeffe2322344224452&pretty"
201
     *
202
     * @apiParam (POST Parameter) {object[]} - ACL rules
203
     * @apiParam (POST Parameter) {string} -.type user or group
204
     * @apiParam (POST Parameter) {string} -.role Role id (user or group id)
205
     * @apiParam (POST Parameter) {string} -.privilege Permission to access share, could be on of the following:</br>
206
     *  rw - READ/WRITE </br>
207
     *  r - READONLY </br>
208
     *  w+ - INBOX (Only Access to owned nodes) </br>
209
     *  m - Manage </br>
210
     *  d - DENY </br>
211
     *
212
     * @apiSuccess (201 Created) {string} id Node ID
213
     * @apiSuccessExample {json} Success-Response (Created or Modified Share):
214
     * HTTP/1.1 200 OK
215
     * {
216
     *      "id": "212323eeffe2322344224452"
217
     * }
218
     *
219
     * @param string $id
220
     * @param string $p
221
     * @param array  $acl
222
     * @param string $name
223
     *
224
     * @return Response
225
     */
226
    public function postShare(array $acl, string $name, ?string $id = null, ?string $p = null): Response
227
    {
228
        $node = $this->fs->getNode($id, $p);
229
        $node->share($acl, $name);
230
        $result = $this->node_decorator->decorate($node);
231
232
        return (new Response())->setCode(200)->setBody($result);
233
    }
234
235
    /**
236
     * @api {delete} /api/v2/collections/:id/share Delete share
237
     * @apiVersion 2.0.0
238
     * @apiName deleteShare
239
     * @apiGroup Node\Collection
240
     * @apiPermission none
241
     * @apiDescription Does only remove sharing options and transform a share back into a normal collection.
242
     * There won't be any data loss after this action. All existing references would be removed automatically.
243
     * @apiUse _getNode
244
     * @apiUse _writeAction
245
     *
246
     * @apiExample (cURL) example:
247
     * curl -XDELETE "https://SERVER/api/v2/collections/share?id=212323eeffe2322344224452"
248
     * curl -XDELETE "https://SERVER/api/v2/collections/212323eeffe2322344224452/share"
249
     * curl -XDELETE "https://SERVER/api/v2/collections/share?p=/absolute/path/to/my/collection"
250
     *
251
     * @apiSuccessExample {json} Success-Response:
252
     * HTTP/1.1 204 No Content
253
     *
254
     * @param string $id
255
     * @param string $p
256
     *
257
     * @return Response
258
     */
259
    public function deleteShare(?string $id = null, ?string $p = null): Response
260
    {
261
        $node = $this->fs->getNode($id, $p);
262
        $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...
263
264
        return (new Response())->setCode(204);
265
    }
266
267
    /**
268
     * @api {post} /api/v2/collections/:id Create collection
269
     * @apiVersion 2.0.0
270
     * @apiName post
271
     * @apiGroup Node\Collection
272
     * @apiPermission none
273
     * @apiDescription Create a new collection. You can create a new collection combining a parent collection (id) and a name (name)
274
     * or set an absolute path (p) to the new collection. Additionally it is possible to overwrite server generated timestamps like created or changed (attributes).
275
     * Via the more advanced option filter (attributes.filter) you can create a special collection which can contain any nodes based on the given filter.
276
     * For example a filter could be {mime: application/pdf}, therefore the collection would contain all files with mimetype application/pdf accessible by you.
277
     * (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)
278
     * @apiUse _getNode
279
     * @apiUse _conflictNode
280
     * @apiUse _writeAction
281
     *
282
     * @apiExample (cURL) example:
283
     * curl -XGET "https://SERVER/api/v2/collections?id=544627ef3c58891f058b468f&name=MyNewFolder&pretty"
284
     * curl -XGET "https://SERVER/api/v2/collections/544627ef3c58891f058b468f?name=MyNewFolder&pretty"
285
     * curl -XGET "https://SERVER/api/v2/collections/?p=/absolute/path/to/my/collection&name=MyNewFolder&pretty&conflict=2"
286
     *
287
     * @apiParam (GET Parameter) {string} id Either id or p (path) of a node must be given.
288
     * @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,
289
     * the path must contain the name of the new collection.
290
     * @apiParam (GET Parameter) {string} name A collection name must be set in conjuction with id, don't need to set with a path
291
     * @apiParam (GET Parameter) {object} attributes Overwrite some attributes which are usually generated on the server
292
     * @apiParam (GET Parameter) {number} attributes.created Set specific created timestamp (UNIX timestamp format)
293
     * @apiParam (GET Parameter) {number} attributes.changed Set specific changed timestamp (UNIX timestamp format)
294
     * @apiParam (GET Parameter) {number} attributes.destroy Set specific self-destroy timestamp (UNIX timestamp format)
295
     * @apiParam (GET Parameter) {array} attributes.filter Set specific set of children instead just parent=this
296
     *
297
     * @apiSuccess (201 Created) {string} id Node ID
298
     * @apiSuccessExample {json} Success-Response:
299
     * HTTP/1.1 201 Created
300
     * {
301
     *      "id": "544627ed3c58891f058b4682"
302
     * }
303
     *
304
     * @param string $id
305
     * @param string $p
306
     * @param array  $attributes
307
     * @param int    $conflict
308
     *
309
     * @return Response
310
     */
311
    public function post(
312
        ?string $id = null,
313
        ?string $p = null,
314
        ?string $name = null,
315
        array $attributes = [],
316
        int $conflict = 0
317
    ): Response {
318
        if (null !== $p && null !== $name) {
319
            throw new Exception\InvalidArgument('p and name can not be used at the same time');
320
        }
321
322
        $attributes = $this->_verifyAttributes($attributes);
323
324
        if (null === $id && null !== $p) {
325
            if (!is_string($p) || empty($p)) {
326
                throw new Exception\InvalidArgument('name must be a valid string');
327
            }
328
329
            $parent_path = dirname($p);
330
            $name = basename($p);
331
            $parent = $this->fs->findNodeByPath($parent_path, NodeCollection::class);
332
            $result = $parent->addDirectory($name, $attributes, $conflict);
333
            $result = $this->node_decorator->decorate($result);
334
335
            return (new Response())->setCode(201)->setBody($result);
336
        }
337
        if (null !== $id && null === $name) {
338
            throw new Exception\InvalidArgument('name must be set with id');
339
        }
340
        $parent = $this->fs->getNode($id, null, null, false, true);
341
342
        if (!is_string($name) || empty($name)) {
343
            throw new Exception\InvalidArgument('name must be a valid string');
344
        }
345
346
        $result = $parent->addDirectory($name, $attributes, $conflict);
347
        $result = $this->node_decorator->decorate($result);
348
349
        return (new Response())->setCode(201)->setBody($result);
350
    }
351
}
352