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() |
|
|
|
|
109
|
|
|
) |
110
|
|
|
); |
111
|
|
|
|
112
|
|
|
$contentInfo = $this->contentService->loadContentInfo($contentId); |
113
|
|
|
|
114
|
|
|
try { |
115
|
|
|
$createdLocation = $this->locationService->createLocation($contentInfo, $locationCreateStruct); |
|
|
|
|
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 | \eZ\Publish\Core\REST\Server\Values\NoContent |
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') |
|
|
|
|
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); |
|
|
|
|
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
|
|
|
if (isset($trashItem)) { |
251
|
|
|
return new Values\ResourceCreated( |
252
|
|
|
$this->router->generate( |
253
|
|
|
'ezpublish_rest_loadTrashItem', |
254
|
|
|
array('trashItemId' => $trashItem->id) |
|
|
|
|
255
|
|
|
) |
256
|
|
|
); |
257
|
|
|
} else { |
258
|
|
|
// Only a location has been trashed and not the object |
259
|
|
|
return new Values\NoContent(); |
|
|
|
|
260
|
|
|
} |
261
|
|
|
} catch (Exceptions\InvalidArgumentException $e) { |
262
|
|
|
// If that fails, the Destination header is not formatted right |
263
|
|
|
// so just throw the BadRequestException |
264
|
|
|
throw new BadRequestException("{$destinationHref} is not an acceptable destination"); |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Swaps a location with another one. |
271
|
|
|
* |
272
|
|
|
* @param string $locationPath |
273
|
|
|
* |
274
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\NoContent |
275
|
|
|
*/ |
276
|
|
|
public function swapLocation($locationPath, Request $request) |
277
|
|
|
{ |
278
|
|
|
$locationId = $this->extractLocationIdFromPath($locationPath); |
279
|
|
|
$location = $this->locationService->loadLocation($locationId); |
280
|
|
|
|
281
|
|
|
$destinationLocation = $this->locationService->loadLocation( |
282
|
|
|
$this->extractLocationIdFromPath( |
283
|
|
|
$this->requestParser->parseHref( |
284
|
|
|
$request->headers->get('Destination'), |
285
|
|
|
'locationPath' |
286
|
|
|
) |
287
|
|
|
) |
288
|
|
|
); |
289
|
|
|
|
290
|
|
|
$this->locationService->swapLocation($location, $destinationLocation); |
291
|
|
|
|
292
|
|
|
return new Values\NoContent(); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* Loads a location by remote ID. |
297
|
|
|
* |
298
|
|
|
* @todo remove, or use in loadLocation with filter |
299
|
|
|
* |
300
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\LocationList |
301
|
|
|
*/ |
302
|
|
|
public function loadLocationByRemoteId(Request $request) |
303
|
|
|
{ |
304
|
|
|
return new Values\LocationList( |
305
|
|
|
array( |
306
|
|
|
new Values\RestLocation( |
307
|
|
|
$location = $this->locationService->loadLocationByRemoteId( |
308
|
|
|
$request->query->get('remoteId') |
309
|
|
|
), |
310
|
|
|
$this->locationService->getLocationChildCount($location) |
311
|
|
|
), |
312
|
|
|
), |
313
|
|
|
$request->getPathInfo() |
314
|
|
|
); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Loads all locations for content object. |
319
|
|
|
* |
320
|
|
|
* @param mixed $contentId |
321
|
|
|
* |
322
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\LocationList |
323
|
|
|
*/ |
324
|
|
|
public function loadLocationsForContent($contentId, Request $request) |
325
|
|
|
{ |
326
|
|
|
$restLocations = array(); |
327
|
|
|
$contentInfo = $this->contentService->loadContentInfo($contentId); |
328
|
|
|
foreach ($this->locationService->loadLocations($contentInfo) as $location) { |
329
|
|
|
$restLocations[] = new Values\RestLocation( |
330
|
|
|
$location, |
331
|
|
|
// @todo Remove, and make optional in VO. Not needed for a location list. |
332
|
|
|
$this->locationService->getLocationChildCount($location) |
333
|
|
|
); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
return new Values\CachedValue( |
337
|
|
|
new Values\LocationList($restLocations, $request->getPathInfo()), |
338
|
|
|
array('locationId' => $contentInfo->mainLocationId) |
339
|
|
|
); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* Loads child locations of a location. |
344
|
|
|
* |
345
|
|
|
* @param string $locationPath |
346
|
|
|
* |
347
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\LocationList |
348
|
|
|
*/ |
349
|
|
|
public function loadLocationChildren($locationPath, Request $request) |
350
|
|
|
{ |
351
|
|
|
$offset = $request->query->has('offset') ? (int)$request->query->get('offset') : 0; |
352
|
|
|
$limit = $request->query->has('limit') ? (int)$request->query->get('limit') : 10; |
353
|
|
|
|
354
|
|
|
$restLocations = array(); |
355
|
|
|
$locationId = $this->extractLocationIdFromPath($locationPath); |
356
|
|
|
$children = $this->locationService->loadLocationChildren( |
357
|
|
|
$this->locationService->loadLocation($locationId), |
358
|
|
|
$offset >= 0 ? $offset : 0, |
359
|
|
|
$limit >= 0 ? $limit : 25 |
360
|
|
|
)->locations; |
361
|
|
|
foreach ($children as $location) { |
362
|
|
|
$restLocations[] = new Values\RestLocation( |
363
|
|
|
$location, |
364
|
|
|
$this->locationService->getLocationChildCount($location) |
365
|
|
|
); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
return new Values\CachedValue( |
369
|
|
|
new Values\LocationList($restLocations, $request->getPathInfo()), |
370
|
|
|
array('locationId' => $locationId) |
371
|
|
|
); |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* Extracts and returns an item id from a path, e.g. /1/2/58 => 58. |
376
|
|
|
* |
377
|
|
|
* @param string $path |
378
|
|
|
* |
379
|
|
|
* @return mixed |
380
|
|
|
*/ |
381
|
|
|
private function extractLocationIdFromPath($path) |
382
|
|
|
{ |
383
|
|
|
$pathParts = explode('/', $path); |
384
|
|
|
|
385
|
|
|
return array_pop($pathParts); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Updates a location. |
390
|
|
|
* |
391
|
|
|
* @param string $locationPath |
392
|
|
|
* |
393
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\RestLocation |
394
|
|
|
*/ |
395
|
|
|
public function updateLocation($locationPath, Request $request) |
396
|
|
|
{ |
397
|
|
|
$locationUpdate = $this->inputDispatcher->parse( |
398
|
|
|
new Message( |
399
|
|
|
array('Content-Type' => $request->headers->get('Content-Type')), |
400
|
|
|
$request->getContent() |
|
|
|
|
401
|
|
|
) |
402
|
|
|
); |
403
|
|
|
|
404
|
|
|
$location = $this->locationService->loadLocation($this->extractLocationIdFromPath($locationPath)); |
405
|
|
|
|
406
|
|
|
// First handle hiding/unhiding so that updating location afterwards |
407
|
|
|
// will return updated location with hidden/visible status correctly updated |
408
|
|
|
// Exact check for true/false is needed as null signals that no hiding/unhiding |
409
|
|
|
// is to be performed |
410
|
|
|
if ($locationUpdate->hidden === true) { |
|
|
|
|
411
|
|
|
$this->locationService->hideLocation($location); |
412
|
|
|
} elseif ($locationUpdate->hidden === false) { |
|
|
|
|
413
|
|
|
$this->locationService->unhideLocation($location); |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
return new Values\RestLocation( |
417
|
|
|
$location = $this->locationService->updateLocation($location, $locationUpdate->locationUpdateStruct), |
|
|
|
|
418
|
|
|
$this->locationService->getLocationChildCount($location) |
419
|
|
|
); |
420
|
|
|
} |
421
|
|
|
} |
422
|
|
|
|
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.