Completed
Push — signal_search_issues ( 5556b2...f328ba )
by André
63:06 queued 07:22
created

Location::createLocation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 2
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the Role controller class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 *
9
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\REST\Server\Controller;
12
13
use eZ\Publish\Core\REST\Common\Message;
14
use eZ\Publish\Core\REST\Common\Exceptions;
15
use eZ\Publish\Core\REST\Server\Values;
16
use eZ\Publish\Core\REST\Server\Controller as RestController;
17
use eZ\Publish\API\Repository\LocationService;
18
use eZ\Publish\API\Repository\ContentService;
19
use eZ\Publish\API\Repository\TrashService;
20
use eZ\Publish\API\Repository\Exceptions\InvalidArgumentException;
21
use eZ\Publish\Core\REST\Server\Exceptions\BadRequestException;
22
use eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException;
23
use Symfony\Component\HttpFoundation\Request;
24
25
/**
26
 * Location controller.
27
 */
28
class Location extends RestController
29
{
30
    /**
31
     * Location service.
32
     *
33
     * @var \eZ\Publish\API\Repository\LocationService
34
     */
35
    protected $locationService;
36
37
    /**
38
     * Content service.
39
     *
40
     * @var \eZ\Publish\API\Repository\ContentService
41
     */
42
    protected $contentService;
43
44
    /**
45
     * Trash service.
46
     *
47
     * @var \eZ\Publish\API\Repository\TrashService
48
     */
49
    protected $trashService;
50
51
    /**
52
     * Construct controller.
53
     *
54
     * @param \eZ\Publish\API\Repository\LocationService $locationService
55
     * @param \eZ\Publish\API\Repository\ContentService $contentService
56
     * @param \eZ\Publish\API\Repository\TrashService $trashService
57
     */
58
    public function __construct(LocationService $locationService, ContentService $contentService, TrashService $trashService)
59
    {
60
        $this->locationService = $locationService;
61
        $this->contentService = $contentService;
62
        $this->trashService = $trashService;
63
    }
64
65
    /**
66
     * Loads the location for a given ID (x)or remote ID.
67
     *
68
     * @throws \eZ\Publish\Core\REST\Server\Exceptions\BadRequestException
69
     *
70
     * @return \eZ\Publish\Core\REST\Server\Values\TemporaryRedirect
71
     */
72
    public function redirectLocation(Request $request)
73
    {
74
        if (!$request->query->has('id') && !$request->query->has('remoteId')) {
75
            throw new BadRequestException("At least one of 'id' or 'remoteId' parameters is required.");
76
        }
77
78
        if ($request->query->has('id')) {
79
            $location = $this->locationService->loadLocation($request->query->get('id'));
80
        } else {
81
            $location = $this->locationService->loadLocationByRemoteId($request->query->get('remoteId'));
82
        }
83
84
        return new Values\TemporaryRedirect(
85
            $this->router->generate(
86
                'ezpublish_rest_loadLocation',
87
                array(
88
                    'locationPath' => trim($location->pathString, '/'),
89
                )
90
            )
91
        );
92
    }
93
94
    /**
95
     * Creates a new location for object with id $contentId.
96
     *
97
     * @param mixed $contentId
98
     *
99
     * @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException
100
     *
101
     * @return \eZ\Publish\Core\REST\Server\Values\CreatedLocation
102
     */
103
    public function createLocation($contentId, Request $request)
104
    {
105
        $locationCreateStruct = $this->inputDispatcher->parse(
106
            new Message(
107
                array('Content-Type' => $request->headers->get('Content-Type')),
108
                $request->getContent()
0 ignored issues
show
Bug introduced by
It seems like $request->getContent() targeting Symfony\Component\HttpFo...n\Request::getContent() can also be of type resource; however, eZ\Publish\Core\REST\Common\Message::__construct() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
109
            )
110
        );
111
112
        $contentInfo = $this->contentService->loadContentInfo($contentId);
113
114
        try {
115
            $createdLocation = $this->locationService->createLocation($contentInfo, $locationCreateStruct);
0 ignored issues
show
Compatibility introduced by
$locationCreateStruct of type object<eZ\Publish\API\Re...ory\Values\ValueObject> is not a sub-type of object<eZ\Publish\API\Re...t\LocationCreateStruct>. It seems like you assume a child class of the class eZ\Publish\API\Repository\Values\ValueObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
116
        } catch (InvalidArgumentException $e) {
117
            throw new ForbiddenException($e->getMessage());
118
        }
119
120
        return new Values\CreatedLocation(array('restLocation' => new Values\RestLocation($createdLocation, 0)));
121
    }
122
123
    /**
124
     * Loads a location.
125
     *
126
     * @param string $locationPath
127
     *
128
     * @return \eZ\Publish\Core\REST\Server\Values\RestLocation
129
     */
130
    public function loadLocation($locationPath)
131
    {
132
        $location = $this->locationService->loadLocation(
133
            $this->extractLocationIdFromPath($locationPath)
134
        );
135
136
        if (trim($location->pathString, '/') != $locationPath) {
137
            throw new Exceptions\NotFoundException(
138
                "Could not find location with path string $locationPath"
139
            );
140
        }
141
142
        return new Values\CachedValue(
143
            new Values\RestLocation(
144
                $location,
145
                $this->locationService->getLocationChildCount($location)
146
            ),
147
            array('locationId' => $location->id)
148
        );
149
    }
150
151
    /**
152
     * Deletes a location.
153
     *
154
     * @param string $locationPath
155
     *
156
     * @return \eZ\Publish\Core\REST\Server\Values\NoContent
157
     */
158
    public function deleteSubtree($locationPath)
159
    {
160
        $location = $this->locationService->loadLocation(
161
            $this->extractLocationIdFromPath($locationPath)
162
        );
163
        $this->locationService->deleteLocation($location);
164
165
        return new Values\NoContent();
166
    }
167
168
    /**
169
     * Copies a subtree to a new destination.
170
     *
171
     * @param string $locationPath
172
     *
173
     * @return \eZ\Publish\Core\REST\Server\Values\ResourceCreated
174
     */
175
    public function copySubtree($locationPath, Request $request)
176
    {
177
        $location = $this->locationService->loadLocation(
178
            $this->extractLocationIdFromPath($locationPath)
179
        );
180
181
        $destinationLocation = $this->locationService->loadLocation(
182
            $this->extractLocationIdFromPath(
183
                $this->requestParser->parseHref(
184
                    $request->headers->get('Destination'),
185
                    'locationPath'
186
                )
187
            )
188
        );
189
190
        $newLocation = $this->locationService->copySubtree($location, $destinationLocation);
191
192
        return new Values\ResourceCreated(
193
            $this->router->generate(
194
                'ezpublish_rest_loadLocation',
195
                array(
196
                    'locationPath' => trim($newLocation->pathString, '/'),
197
                )
198
            )
199
        );
200
    }
201
202
    /**
203
     * Moves a subtree to a new location.
204
     *
205
     * @param string $locationPath
206
     *
207
     * @throws \eZ\Publish\Core\REST\Server\Exceptions\BadRequestException if the Destination header cannot be parsed as location or trash
208
     *
209
     * @return \eZ\Publish\Core\REST\Server\Values\ResourceCreated
210
     */
211
    public function moveSubtree($locationPath, Request $request)
212
    {
213
        $locationToMove = $this->locationService->loadLocation(
214
            $this->extractLocationIdFromPath($locationPath)
215
        );
216
217
        $destinationLocationId = null;
218
        $destinationHref = $request->headers->get('Destination');
219
        try {
220
            // First check to see if the destination is for moving within another subtree
221
            $destinationLocationId = $this->extractLocationIdFromPath(
222
                $this->requestParser->parseHref($destinationHref, 'locationPath')
0 ignored issues
show
Bug introduced by
It seems like $destinationHref defined by $request->headers->get('Destination') on line 218 can also be of type array; however, eZ\Publish\Core\REST\Com...uestParser::parseHref() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
223
            );
224
225
            // We're moving the subtree
226
            $destinationLocation = $this->locationService->loadLocation($destinationLocationId);
227
            $this->locationService->moveSubtree($locationToMove, $destinationLocation);
228
229
            // Reload the location to get the new position is subtree
230
            $locationToMove = $this->locationService->loadLocation($locationToMove->id);
231
232
            return new Values\ResourceCreated(
233
                $this->router->generate(
234
                    'ezpublish_rest_loadLocation',
235
                    array(
236
                        'locationPath' => trim($locationToMove->pathString, '/'),
237
                    )
238
                )
239
            );
240
        } catch (Exceptions\InvalidArgumentException $e) {
241
            // If parsing of destination fails, let's try to see if destination is trash
242
            try {
243
                $route = $this->requestParser->parse($destinationHref);
0 ignored issues
show
Bug introduced by
It seems like $destinationHref defined by $request->headers->get('Destination') on line 218 can also be of type array; however, eZ\Publish\Core\REST\Common\RequestParser::parse() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
244
                if (!isset($route['_route']) || $route['_route'] !== 'ezpublish_rest_loadTrashItems') {
245
                    throw new Exceptions\InvalidArgumentException('');
246
                }
247
                // Trash the subtree
248
                $trashItem = $this->trashService->trash($locationToMove);
249
250
                return new Values\ResourceCreated(
251
                    $this->router->generate(
252
                        'ezpublish_rest_loadTrashItem',
253
                        array('trashItemId' => $trashItem->id)
254
                    )
255
                );
256
            } catch (Exceptions\InvalidArgumentException $e) {
257
                // If that fails, the Destination header is not formatted right
258
                // so just throw the BadRequestException
259
                throw new BadRequestException("{$destinationHref} is not an acceptable destination");
260
            }
261
        }
262
    }
263
264
    /**
265
     * Swaps a location with another one.
266
     *
267
     * @param string $locationPath
268
     *
269
     * @return \eZ\Publish\Core\REST\Server\Values\NoContent
270
     */
271
    public function swapLocation($locationPath, Request $request)
272
    {
273
        $locationId = $this->extractLocationIdFromPath($locationPath);
274
        $location = $this->locationService->loadLocation($locationId);
275
276
        $destinationLocation = $this->locationService->loadLocation(
277
            $this->extractLocationIdFromPath(
278
                $this->requestParser->parseHref(
279
                    $request->headers->get('Destination'),
280
                    'locationPath'
281
                )
282
            )
283
        );
284
285
        $this->locationService->swapLocation($location, $destinationLocation);
286
287
        return new Values\NoContent();
288
    }
289
290
    /**
291
     * Loads a location by remote ID.
292
     *
293
     * @todo remove, or use in loadLocation with filter
294
     *
295
     * @return \eZ\Publish\Core\REST\Server\Values\LocationList
296
     */
297
    public function loadLocationByRemoteId(Request $request)
298
    {
299
        return new Values\LocationList(
300
            array(
301
                new Values\RestLocation(
302
                    $location = $this->locationService->loadLocationByRemoteId(
303
                        $request->query->get('remoteId')
304
                    ),
305
                    $this->locationService->getLocationChildCount($location)
306
                ),
307
            ),
308
            $request->getPathInfo()
309
        );
310
    }
311
312
    /**
313
     * Loads all locations for content object.
314
     *
315
     * @param mixed $contentId
316
     *
317
     * @return \eZ\Publish\Core\REST\Server\Values\LocationList
318
     */
319
    public function loadLocationsForContent($contentId, Request $request)
320
    {
321
        $restLocations = array();
322
        $contentInfo = $this->contentService->loadContentInfo($contentId);
323
        foreach ($this->locationService->loadLocations($contentInfo) as $location) {
324
            $restLocations[] = new Values\RestLocation(
325
                $location,
326
                // @todo Remove, and make optional in VO. Not needed for a location list.
327
                $this->locationService->getLocationChildCount($location)
328
            );
329
        }
330
331
        return new Values\CachedValue(
332
            new Values\LocationList($restLocations, $request->getPathInfo()),
333
            array('locationId' => $contentInfo->mainLocationId)
334
        );
335
    }
336
337
    /**
338
     * Loads child locations of a location.
339
     *
340
     * @param string $locationPath
341
     *
342
     * @return \eZ\Publish\Core\REST\Server\Values\LocationList
343
     */
344
    public function loadLocationChildren($locationPath, Request $request)
345
    {
346
        $offset = $request->query->has('offset') ? (int)$request->query->get('offset') : 0;
347
        $limit = $request->query->has('limit') ? (int)$request->query->get('limit') : 10;
348
349
        $restLocations = array();
350
        $locationId = $this->extractLocationIdFromPath($locationPath);
351
        $children = $this->locationService->loadLocationChildren(
352
            $this->locationService->loadLocation($locationId),
353
            $offset >= 0 ? $offset : 0,
354
            $limit >= 0 ? $limit : 25
355
        )->locations;
356
        foreach ($children as $location) {
357
            $restLocations[] = new Values\RestLocation(
358
                $location,
359
                $this->locationService->getLocationChildCount($location)
360
            );
361
        }
362
363
        return new Values\CachedValue(
364
            new Values\LocationList($restLocations, $request->getPathInfo()),
365
            array('locationId' => $locationId)
366
        );
367
    }
368
369
    /**
370
     * Extracts and returns an item id from a path, e.g. /1/2/58 => 58.
371
     *
372
     * @param string $path
373
     *
374
     * @return mixed
375
     */
376
    private function extractLocationIdFromPath($path)
377
    {
378
        $pathParts = explode('/', $path);
379
380
        return array_pop($pathParts);
381
    }
382
383
    /**
384
     * Updates a location.
385
     *
386
     * @param string $locationPath
387
     *
388
     * @return \eZ\Publish\Core\REST\Server\Values\RestLocation
389
     */
390
    public function updateLocation($locationPath, Request $request)
391
    {
392
        $locationUpdate = $this->inputDispatcher->parse(
393
            new Message(
394
                array('Content-Type' => $request->headers->get('Content-Type')),
395
                $request->getContent()
0 ignored issues
show
Bug introduced by
It seems like $request->getContent() targeting Symfony\Component\HttpFo...n\Request::getContent() can also be of type resource; however, eZ\Publish\Core\REST\Common\Message::__construct() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
396
            )
397
        );
398
399
        $location = $this->locationService->loadLocation($this->extractLocationIdFromPath($locationPath));
400
401
        // First handle hiding/unhiding so that updating location afterwards
402
        // will return updated location with hidden/visible status correctly updated
403
        // Exact check for true/false is needed as null signals that no hiding/unhiding
404
        // is to be performed
405
        if ($locationUpdate->hidden === true) {
0 ignored issues
show
Documentation introduced by
The property hidden does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. 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...
406
            $this->locationService->hideLocation($location);
407
        } elseif ($locationUpdate->hidden === false) {
0 ignored issues
show
Documentation introduced by
The property hidden does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. 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...
408
            $this->locationService->unhideLocation($location);
409
        }
410
411
        return new Values\RestLocation(
412
            $location = $this->locationService->updateLocation($location, $locationUpdate->locationUpdateStruct),
0 ignored issues
show
Documentation introduced by
The property locationUpdateStruct does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. 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...
413
            $this->locationService->getLocationChildCount($location)
414
        );
415
    }
416
}
417