Completed
Branch dev (d5d70c)
by Raffael
11:00
created

Collections::deleteShare()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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