|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* File containing the Content 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
|
|
|
namespace eZ\Publish\Core\REST\Server\Controller; |
|
10
|
|
|
|
|
11
|
|
|
use eZ\Publish\Core\REST\Client\Exceptions\BadStateException; |
|
12
|
|
|
use eZ\Publish\Core\REST\Common\Message; |
|
13
|
|
|
use eZ\Publish\Core\REST\Common\Exceptions; |
|
14
|
|
|
use eZ\Publish\Core\REST\Server\Values; |
|
15
|
|
|
use eZ\Publish\Core\REST\Server\Controller as RestController; |
|
16
|
|
|
use eZ\Publish\API\Repository\Values\Content\Relation; |
|
17
|
|
|
use eZ\Publish\API\Repository\Values\Content\VersionInfo; |
|
18
|
|
|
use eZ\Publish\API\Repository\Exceptions\NotFoundException; |
|
19
|
|
|
use eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException; |
|
20
|
|
|
use eZ\Publish\API\Repository\Exceptions\ContentValidationException; |
|
21
|
|
|
use eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException; |
|
22
|
|
|
use eZ\Publish\Core\REST\Server\Exceptions\BadRequestException; |
|
23
|
|
|
use eZ\Publish\Core\REST\Server\Exceptions\ContentFieldValidationException as RESTContentFieldValidationException; |
|
24
|
|
|
use Symfony\Component\HttpFoundation\Request; |
|
25
|
|
|
use Symfony\Component\HttpKernel\HttpKernelInterface; |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* Content controller. |
|
29
|
|
|
*/ |
|
30
|
|
|
class Content extends RestController |
|
31
|
|
|
{ |
|
32
|
|
|
/** |
|
33
|
|
|
* Loads a content info by remote ID. |
|
34
|
|
|
* |
|
35
|
|
|
* @throws \eZ\Publish\Core\REST\Server\Exceptions\BadRequestException |
|
36
|
|
|
* |
|
37
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\TemporaryRedirect |
|
38
|
|
|
*/ |
|
39
|
|
View Code Duplication |
public function redirectContent(Request $request) |
|
40
|
|
|
{ |
|
41
|
|
|
if (!$request->query->has('remoteId')) { |
|
42
|
|
|
throw new BadRequestException("'remoteId' parameter is required."); |
|
43
|
|
|
} |
|
44
|
|
|
|
|
45
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfoByRemoteId( |
|
46
|
|
|
$request->query->get('remoteId') |
|
47
|
|
|
); |
|
48
|
|
|
|
|
49
|
|
|
return new Values\TemporaryRedirect( |
|
50
|
|
|
$this->router->generate( |
|
51
|
|
|
'ezpublish_rest_loadContent', |
|
52
|
|
|
array( |
|
53
|
|
|
'contentId' => $contentInfo->id, |
|
54
|
|
|
) |
|
55
|
|
|
) |
|
56
|
|
|
); |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* Loads a content info, potentially with the current version embedded. |
|
61
|
|
|
* |
|
62
|
|
|
* @param mixed $contentId |
|
63
|
|
|
* @param \Symfony\Component\HttpFoundation\Request $request |
|
64
|
|
|
* |
|
65
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\RestContent |
|
66
|
|
|
*/ |
|
67
|
|
|
public function loadContent($contentId, Request $request) |
|
68
|
|
|
{ |
|
69
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
70
|
|
|
|
|
71
|
|
|
$mainLocation = null; |
|
72
|
|
|
if (!empty($contentInfo->mainLocationId)) { |
|
73
|
|
|
$mainLocation = $this->repository->getLocationService()->loadLocation($contentInfo->mainLocationId); |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
$contentType = $this->repository->getContentTypeService()->loadContentType($contentInfo->contentTypeId); |
|
77
|
|
|
|
|
78
|
|
|
$contentVersion = null; |
|
79
|
|
|
$relations = null; |
|
80
|
|
|
if ($this->getMediaType($request) === 'application/vnd.ez.api.content') { |
|
81
|
|
|
$languages = null; |
|
82
|
|
|
if ($request->query->has('languages')) { |
|
83
|
|
|
$languages = explode(',', $request->query->get('languages')); |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
$contentVersion = $this->repository->getContentService()->loadContent($contentId, $languages); |
|
87
|
|
|
$relations = $this->repository->getContentService()->loadRelations($contentVersion->getVersionInfo()); |
|
88
|
|
|
} |
|
89
|
|
|
|
|
90
|
|
|
$restContent = new Values\RestContent( |
|
91
|
|
|
$contentInfo, |
|
92
|
|
|
$mainLocation, |
|
93
|
|
|
$contentVersion, |
|
94
|
|
|
$contentType, |
|
95
|
|
|
$relations, |
|
96
|
|
|
$request->getPathInfo() |
|
97
|
|
|
); |
|
98
|
|
|
|
|
99
|
|
|
if ($contentInfo->mainLocationId === null) { |
|
100
|
|
|
return $restContent; |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
return new Values\CachedValue( |
|
|
|
|
|
|
104
|
|
|
$restContent, |
|
105
|
|
|
array('locationId' => $contentInfo->mainLocationId) |
|
106
|
|
|
); |
|
107
|
|
|
} |
|
108
|
|
|
|
|
109
|
|
|
/** |
|
110
|
|
|
* Updates a content's metadata. |
|
111
|
|
|
* |
|
112
|
|
|
* @param mixed $contentId |
|
113
|
|
|
* |
|
114
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\RestContent |
|
115
|
|
|
*/ |
|
116
|
|
|
public function updateContentMetadata($contentId, Request $request) |
|
117
|
|
|
{ |
|
118
|
|
|
$updateStruct = $this->inputDispatcher->parse( |
|
119
|
|
|
new Message( |
|
120
|
|
|
array('Content-Type' => $request->headers->get('Content-Type')), |
|
121
|
|
|
$request->getContent() |
|
|
|
|
|
|
122
|
|
|
) |
|
123
|
|
|
); |
|
124
|
|
|
|
|
125
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
126
|
|
|
|
|
127
|
|
|
// update section |
|
128
|
|
|
if ($updateStruct->sectionId !== null) { |
|
|
|
|
|
|
129
|
|
|
$section = $this->repository->getSectionService()->loadSection($updateStruct->sectionId); |
|
|
|
|
|
|
130
|
|
|
$this->repository->getSectionService()->assignSection($contentInfo, $section); |
|
131
|
|
|
$updateStruct->sectionId = null; |
|
|
|
|
|
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
// @todo Consider refactoring! ContentService::updateContentMetadata throws the same exception |
|
135
|
|
|
// in case the updateStruct is empty and if remoteId already exists. Since REST version of update struct |
|
136
|
|
|
// includes section ID in addition to other fields, we cannot throw exception if only sectionId property |
|
137
|
|
|
// is set, so we must skip updating content in that case instead of allowing propagation of the exception. |
|
138
|
|
|
foreach ($updateStruct as $propertyName => $propertyValue) { |
|
|
|
|
|
|
139
|
|
|
if ($propertyName !== 'sectionId' && $propertyValue !== null) { |
|
140
|
|
|
// update content |
|
141
|
|
|
$this->repository->getContentService()->updateContentMetadata($contentInfo, $updateStruct); |
|
|
|
|
|
|
142
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
143
|
|
|
break; |
|
144
|
|
|
} |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
try { |
|
148
|
|
|
$locationInfo = $this->repository->getLocationService()->loadLocation($contentInfo->mainLocationId); |
|
149
|
|
|
} catch (NotFoundException $e) { |
|
150
|
|
|
$locationInfo = null; |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
return new Values\RestContent( |
|
154
|
|
|
$contentInfo, |
|
155
|
|
|
$locationInfo |
|
156
|
|
|
); |
|
157
|
|
|
} |
|
158
|
|
|
|
|
159
|
|
|
/** |
|
160
|
|
|
* Loads a specific version of a given content object. |
|
161
|
|
|
* |
|
162
|
|
|
* @param mixed $contentId |
|
163
|
|
|
* |
|
164
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\TemporaryRedirect |
|
165
|
|
|
*/ |
|
166
|
|
View Code Duplication |
public function redirectCurrentVersion($contentId) |
|
167
|
|
|
{ |
|
168
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
169
|
|
|
|
|
170
|
|
|
return new Values\TemporaryRedirect( |
|
171
|
|
|
$this->router->generate( |
|
172
|
|
|
'ezpublish_rest_loadContentInVersion', |
|
173
|
|
|
array( |
|
174
|
|
|
'contentId' => $contentId, |
|
175
|
|
|
'versionNumber' => $contentInfo->currentVersionNo, |
|
176
|
|
|
) |
|
177
|
|
|
) |
|
178
|
|
|
); |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
/** |
|
182
|
|
|
* Loads a specific version of a given content object. |
|
183
|
|
|
* |
|
184
|
|
|
* @param mixed $contentId |
|
185
|
|
|
* @param int $versionNumber |
|
186
|
|
|
* |
|
187
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\Version |
|
188
|
|
|
*/ |
|
189
|
|
|
public function loadContentInVersion($contentId, $versionNumber, Request $request) |
|
190
|
|
|
{ |
|
191
|
|
|
$languages = null; |
|
192
|
|
|
if ($request->query->has('languages')) { |
|
193
|
|
|
$languages = explode(',', $request->query->get('languages')); |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
$content = $this->repository->getContentService()->loadContent( |
|
197
|
|
|
$contentId, |
|
198
|
|
|
$languages, |
|
199
|
|
|
$versionNumber |
|
200
|
|
|
); |
|
201
|
|
|
$contentType = $this->repository->getContentTypeService()->loadContentType( |
|
202
|
|
|
$content->getVersionInfo()->getContentInfo()->contentTypeId |
|
203
|
|
|
); |
|
204
|
|
|
|
|
205
|
|
|
$versionValue = new Values\Version( |
|
206
|
|
|
$content, |
|
207
|
|
|
$contentType, |
|
208
|
|
|
$this->repository->getContentService()->loadRelations($content->getVersionInfo()), |
|
209
|
|
|
$request->getPathInfo() |
|
210
|
|
|
); |
|
211
|
|
|
|
|
212
|
|
|
if ($content->contentInfo->mainLocationId === null || $content->versionInfo->status === VersionInfo::STATUS_DRAFT) { |
|
213
|
|
|
return $versionValue; |
|
214
|
|
|
} |
|
215
|
|
|
|
|
216
|
|
|
return new Values\CachedValue( |
|
|
|
|
|
|
217
|
|
|
$versionValue, |
|
218
|
|
|
array('locationId' => $content->contentInfo->mainLocationId) |
|
219
|
|
|
); |
|
220
|
|
|
} |
|
221
|
|
|
|
|
222
|
|
|
/** |
|
223
|
|
|
* Creates a new content draft assigned to the authenticated user. |
|
224
|
|
|
* If a different userId is given in the input it is assigned to the |
|
225
|
|
|
* given user but this required special rights for the authenticated |
|
226
|
|
|
* user (this is useful for content staging where the transfer process |
|
227
|
|
|
* does not have to authenticate with the user which created the content |
|
228
|
|
|
* object in the source server). The user has to publish the content if |
|
229
|
|
|
* it should be visible. |
|
230
|
|
|
* |
|
231
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\CreatedContent |
|
232
|
|
|
*/ |
|
233
|
|
|
public function createContent(Request $request) |
|
234
|
|
|
{ |
|
235
|
|
|
$contentCreate = $this->inputDispatcher->parse( |
|
236
|
|
|
new Message( |
|
237
|
|
|
array('Content-Type' => $request->headers->get('Content-Type')), |
|
238
|
|
|
$request->getContent() |
|
|
|
|
|
|
239
|
|
|
) |
|
240
|
|
|
); |
|
241
|
|
|
|
|
242
|
|
|
try { |
|
243
|
|
|
$content = $this->repository->getContentService()->createContent( |
|
244
|
|
|
$contentCreate->contentCreateStruct, |
|
|
|
|
|
|
245
|
|
|
array($contentCreate->locationCreateStruct) |
|
|
|
|
|
|
246
|
|
|
); |
|
247
|
|
|
} catch (ContentValidationException $e) { |
|
248
|
|
|
throw new BadRequestException($e->getMessage()); |
|
249
|
|
|
} catch (ContentFieldValidationException $e) { |
|
250
|
|
|
throw new RESTContentFieldValidationException($e); |
|
251
|
|
|
} |
|
252
|
|
|
|
|
253
|
|
|
$contentValue = null; |
|
254
|
|
|
$contentType = null; |
|
255
|
|
|
$relations = null; |
|
256
|
|
|
if ($this->getMediaType($request) === 'application/vnd.ez.api.content') { |
|
257
|
|
|
$contentValue = $content; |
|
258
|
|
|
$contentType = $this->repository->getContentTypeService()->loadContentType( |
|
259
|
|
|
$content->getVersionInfo()->getContentInfo()->contentTypeId |
|
260
|
|
|
); |
|
261
|
|
|
$relations = $this->repository->getContentService()->loadRelations($contentValue->getVersionInfo()); |
|
262
|
|
|
} |
|
263
|
|
|
|
|
264
|
|
|
return new Values\CreatedContent( |
|
265
|
|
|
array( |
|
266
|
|
|
'content' => new Values\RestContent( |
|
267
|
|
|
$content->contentInfo, |
|
268
|
|
|
null, |
|
269
|
|
|
$contentValue, |
|
270
|
|
|
$contentType, |
|
271
|
|
|
$relations |
|
272
|
|
|
), |
|
273
|
|
|
) |
|
274
|
|
|
); |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
|
|
/** |
|
278
|
|
|
* The content is deleted. If the content has locations (which is required in 4.x) |
|
279
|
|
|
* on delete all locations assigned the content object are deleted via delete subtree. |
|
280
|
|
|
* |
|
281
|
|
|
* @param mixed $contentId |
|
282
|
|
|
* |
|
283
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\NoContent |
|
284
|
|
|
*/ |
|
285
|
|
|
public function deleteContent($contentId) |
|
286
|
|
|
{ |
|
287
|
|
|
$this->repository->getContentService()->deleteContent( |
|
288
|
|
|
$this->repository->getContentService()->loadContentInfo($contentId) |
|
289
|
|
|
); |
|
290
|
|
|
|
|
291
|
|
|
return new Values\NoContent(); |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
/** |
|
295
|
|
|
* Creates a new content object as copy under the given parent location given in the destination header. |
|
296
|
|
|
* |
|
297
|
|
|
* @param mixed $contentId |
|
298
|
|
|
* |
|
299
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\ResourceCreated |
|
300
|
|
|
*/ |
|
301
|
|
|
public function copyContent($contentId, Request $request) |
|
302
|
|
|
{ |
|
303
|
|
|
$destination = $request->headers->get('Destination'); |
|
304
|
|
|
|
|
305
|
|
|
$parentLocationParts = explode('/', $destination); |
|
306
|
|
|
$copiedContent = $this->repository->getContentService()->copyContent( |
|
307
|
|
|
$this->repository->getContentService()->loadContentInfo($contentId), |
|
308
|
|
|
$this->repository->getLocationService()->newLocationCreateStruct(array_pop($parentLocationParts)) |
|
309
|
|
|
); |
|
310
|
|
|
|
|
311
|
|
|
return new Values\ResourceCreated( |
|
312
|
|
|
$this->router->generate( |
|
313
|
|
|
'ezpublish_rest_loadContent', |
|
314
|
|
|
array('contentId' => $copiedContent->id) |
|
315
|
|
|
) |
|
316
|
|
|
); |
|
317
|
|
|
} |
|
318
|
|
|
|
|
319
|
|
|
/** |
|
320
|
|
|
* Returns a list of all versions of the content. This method does not |
|
321
|
|
|
* include fields and relations in the Version elements of the response. |
|
322
|
|
|
* |
|
323
|
|
|
* @param mixed $contentId |
|
324
|
|
|
* |
|
325
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\VersionList |
|
326
|
|
|
*/ |
|
327
|
|
|
public function loadContentVersions($contentId, Request $request) |
|
328
|
|
|
{ |
|
329
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
330
|
|
|
|
|
331
|
|
|
return new Values\VersionList( |
|
332
|
|
|
$this->repository->getContentService()->loadVersions($contentInfo), |
|
333
|
|
|
$request->getPathInfo() |
|
334
|
|
|
); |
|
335
|
|
|
} |
|
336
|
|
|
|
|
337
|
|
|
/** |
|
338
|
|
|
* The version is deleted. |
|
339
|
|
|
* |
|
340
|
|
|
* @param mixed $contentId |
|
341
|
|
|
* @param mixed $versionNumber |
|
342
|
|
|
* |
|
343
|
|
|
* @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException |
|
344
|
|
|
* |
|
345
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\NoContent |
|
346
|
|
|
*/ |
|
347
|
|
View Code Duplication |
public function deleteContentVersion($contentId, $versionNumber) |
|
348
|
|
|
{ |
|
349
|
|
|
$versionInfo = $this->repository->getContentService()->loadVersionInfo( |
|
350
|
|
|
$this->repository->getContentService()->loadContentInfo($contentId), |
|
351
|
|
|
$versionNumber |
|
352
|
|
|
); |
|
353
|
|
|
|
|
354
|
|
|
if ($versionInfo->isPublished()) { |
|
355
|
|
|
throw new ForbiddenException('Version in status PUBLISHED cannot be deleted'); |
|
356
|
|
|
} |
|
357
|
|
|
|
|
358
|
|
|
$this->repository->getContentService()->deleteVersion( |
|
359
|
|
|
$versionInfo |
|
360
|
|
|
); |
|
361
|
|
|
|
|
362
|
|
|
return new Values\NoContent(); |
|
363
|
|
|
} |
|
364
|
|
|
|
|
365
|
|
|
/** |
|
366
|
|
|
* Remove the given Translation of the given published Version and publish new one. |
|
367
|
|
|
* |
|
368
|
|
|
* @param int $contentId |
|
369
|
|
|
* @param string $languageCode |
|
370
|
|
|
* |
|
371
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\NoContent |
|
372
|
|
|
* |
|
373
|
|
|
* @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException |
|
374
|
|
|
* @throws \Exception |
|
375
|
|
|
*/ |
|
376
|
|
|
public function deletePublishedVersionTranslation($contentId, $languageCode) |
|
377
|
|
|
{ |
|
378
|
|
|
$contentService = $this->repository->getContentService(); |
|
379
|
|
|
$versionInfo = $this->repository->getContentService()->loadVersionInfoById($contentId); |
|
380
|
|
|
|
|
381
|
|
|
if (!$versionInfo->isPublished()) { |
|
382
|
|
|
throw new ForbiddenException('Translation can be deleted from PUBLISHED Version only'); |
|
383
|
|
|
} |
|
384
|
|
|
|
|
385
|
|
|
$this->repository->beginTransaction(); |
|
386
|
|
|
try { |
|
387
|
|
|
if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) { |
|
388
|
|
|
$contentMetadataUpdateStruct = $contentService->newContentMetadataUpdateStruct(); |
|
389
|
|
|
$contentMetadataUpdateStruct->mainLanguageCode = $this->getNewMainLanguageCode( |
|
390
|
|
|
$versionInfo, |
|
391
|
|
|
$languageCode |
|
392
|
|
|
); |
|
393
|
|
|
$content = $contentService->updateContentMetadata( |
|
394
|
|
|
$versionInfo->contentInfo, |
|
395
|
|
|
$contentMetadataUpdateStruct |
|
396
|
|
|
); |
|
397
|
|
|
$versionInfo = $content->versionInfo; |
|
398
|
|
|
} |
|
399
|
|
|
$draft = $contentService->createContentDraft($versionInfo->contentInfo); |
|
400
|
|
|
|
|
401
|
|
|
$draft = $contentService->deleteTranslation($draft->versionInfo, $languageCode); |
|
402
|
|
|
|
|
403
|
|
|
$contentService->publishVersion($draft->versionInfo); |
|
404
|
|
|
|
|
405
|
|
|
$this->repository->commit(); |
|
406
|
|
|
} catch (\Exception $e) { |
|
407
|
|
|
$this->repository->rollback(); |
|
408
|
|
|
throw $e; |
|
409
|
|
|
} |
|
410
|
|
|
|
|
411
|
|
|
return new Values\NoContent(); |
|
412
|
|
|
} |
|
413
|
|
|
|
|
414
|
|
|
/** |
|
415
|
|
|
* The system creates a new draft version as a copy from the given version. |
|
416
|
|
|
* |
|
417
|
|
|
* @param mixed $contentId |
|
418
|
|
|
* @param mixed $versionNumber |
|
419
|
|
|
* |
|
420
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\CreatedVersion |
|
421
|
|
|
*/ |
|
422
|
|
View Code Duplication |
public function createDraftFromVersion($contentId, $versionNumber) |
|
423
|
|
|
{ |
|
424
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
425
|
|
|
$contentType = $this->repository->getContentTypeService()->loadContentType($contentInfo->contentTypeId); |
|
426
|
|
|
$contentDraft = $this->repository->getContentService()->createContentDraft( |
|
427
|
|
|
$contentInfo, |
|
428
|
|
|
$this->repository->getContentService()->loadVersionInfo($contentInfo, $versionNumber) |
|
429
|
|
|
); |
|
430
|
|
|
|
|
431
|
|
|
return new Values\CreatedVersion( |
|
432
|
|
|
array( |
|
433
|
|
|
'version' => new Values\Version( |
|
434
|
|
|
$contentDraft, |
|
435
|
|
|
$contentType, |
|
436
|
|
|
$this->repository->getContentService()->loadRelations($contentDraft->getVersionInfo()) |
|
437
|
|
|
), |
|
438
|
|
|
) |
|
439
|
|
|
); |
|
440
|
|
|
} |
|
441
|
|
|
|
|
442
|
|
|
/** |
|
443
|
|
|
* The system creates a new draft version as a copy from the current version. |
|
444
|
|
|
* |
|
445
|
|
|
* @param mixed $contentId |
|
446
|
|
|
* |
|
447
|
|
|
* @throws ForbiddenException if the current version is already a draft |
|
448
|
|
|
* |
|
449
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\CreatedVersion |
|
450
|
|
|
*/ |
|
451
|
|
View Code Duplication |
public function createDraftFromCurrentVersion($contentId) |
|
452
|
|
|
{ |
|
453
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
454
|
|
|
$contentType = $this->repository->getContentTypeService()->loadContentType($contentInfo->contentTypeId); |
|
455
|
|
|
$versionInfo = $this->repository->getContentService()->loadVersionInfo( |
|
456
|
|
|
$contentInfo |
|
457
|
|
|
); |
|
458
|
|
|
|
|
459
|
|
|
if ($versionInfo->isDraft()) { |
|
460
|
|
|
throw new ForbiddenException('Current version is already in status DRAFT'); |
|
461
|
|
|
} |
|
462
|
|
|
|
|
463
|
|
|
$contentDraft = $this->repository->getContentService()->createContentDraft($contentInfo); |
|
464
|
|
|
|
|
465
|
|
|
return new Values\CreatedVersion( |
|
466
|
|
|
array( |
|
467
|
|
|
'version' => new Values\Version( |
|
468
|
|
|
$contentDraft, |
|
469
|
|
|
$contentType, |
|
470
|
|
|
$this->repository->getContentService()->loadRelations($contentDraft->getVersionInfo()) |
|
471
|
|
|
), |
|
472
|
|
|
) |
|
473
|
|
|
); |
|
474
|
|
|
} |
|
475
|
|
|
|
|
476
|
|
|
/** |
|
477
|
|
|
* A specific draft is updated. |
|
478
|
|
|
* |
|
479
|
|
|
* @param mixed $contentId |
|
480
|
|
|
* @param mixed $versionNumber |
|
481
|
|
|
* |
|
482
|
|
|
* @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException |
|
483
|
|
|
* @throws \eZ\Publish\Core\REST\Server\Exceptions\BadRequestException |
|
484
|
|
|
* |
|
485
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\Version |
|
486
|
|
|
*/ |
|
487
|
|
|
public function updateVersion($contentId, $versionNumber, Request $request) |
|
488
|
|
|
{ |
|
489
|
|
|
$contentUpdateStruct = $this->inputDispatcher->parse( |
|
490
|
|
|
new Message( |
|
491
|
|
|
array( |
|
492
|
|
|
'Content-Type' => $request->headers->get('Content-Type'), |
|
493
|
|
|
'Url' => $this->router->generate( |
|
494
|
|
|
'ezpublish_rest_updateVersion', |
|
495
|
|
|
array( |
|
496
|
|
|
'contentId' => $contentId, |
|
497
|
|
|
'versionNumber' => $versionNumber, |
|
498
|
|
|
) |
|
499
|
|
|
), |
|
500
|
|
|
), |
|
501
|
|
|
$request->getContent() |
|
|
|
|
|
|
502
|
|
|
) |
|
503
|
|
|
); |
|
504
|
|
|
|
|
505
|
|
|
$versionInfo = $this->repository->getContentService()->loadVersionInfo( |
|
506
|
|
|
$this->repository->getContentService()->loadContentInfo($contentId), |
|
507
|
|
|
$versionNumber |
|
508
|
|
|
); |
|
509
|
|
|
|
|
510
|
|
|
if (!$versionInfo->isDraft()) { |
|
511
|
|
|
throw new ForbiddenException('Only version in status DRAFT can be updated'); |
|
512
|
|
|
} |
|
513
|
|
|
|
|
514
|
|
|
try { |
|
515
|
|
|
$this->repository->getContentService()->updateContent($versionInfo, $contentUpdateStruct); |
|
|
|
|
|
|
516
|
|
|
} catch (ContentValidationException $e) { |
|
517
|
|
|
throw new BadRequestException($e->getMessage()); |
|
518
|
|
|
} catch (ContentFieldValidationException $e) { |
|
519
|
|
|
throw new RESTContentFieldValidationException($e); |
|
520
|
|
|
} |
|
521
|
|
|
|
|
522
|
|
|
$languages = null; |
|
523
|
|
|
if ($request->query->has('languages')) { |
|
524
|
|
|
$languages = explode(',', $request->query->get('languages')); |
|
525
|
|
|
} |
|
526
|
|
|
|
|
527
|
|
|
// Reload the content to handle languages GET parameter |
|
528
|
|
|
$content = $this->repository->getContentService()->loadContent( |
|
529
|
|
|
$contentId, |
|
530
|
|
|
$languages, |
|
531
|
|
|
$versionInfo->versionNo |
|
532
|
|
|
); |
|
533
|
|
|
$contentType = $this->repository->getContentTypeService()->loadContentType( |
|
534
|
|
|
$content->getVersionInfo()->getContentInfo()->contentTypeId |
|
535
|
|
|
); |
|
536
|
|
|
|
|
537
|
|
|
return new Values\Version( |
|
538
|
|
|
$content, |
|
539
|
|
|
$contentType, |
|
540
|
|
|
$this->repository->getContentService()->loadRelations($content->getVersionInfo()), |
|
541
|
|
|
$request->getPathInfo() |
|
542
|
|
|
); |
|
543
|
|
|
} |
|
544
|
|
|
|
|
545
|
|
|
/** |
|
546
|
|
|
* The content version is published. |
|
547
|
|
|
* |
|
548
|
|
|
* @param mixed $contentId |
|
549
|
|
|
* @param mixed $versionNumber |
|
550
|
|
|
* |
|
551
|
|
|
* @throws ForbiddenException if version $versionNumber isn't a draft |
|
552
|
|
|
* |
|
553
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\NoContent |
|
554
|
|
|
*/ |
|
555
|
|
View Code Duplication |
public function publishVersion($contentId, $versionNumber) |
|
556
|
|
|
{ |
|
557
|
|
|
$versionInfo = $this->repository->getContentService()->loadVersionInfo( |
|
558
|
|
|
$this->repository->getContentService()->loadContentInfo($contentId), |
|
559
|
|
|
$versionNumber |
|
560
|
|
|
); |
|
561
|
|
|
|
|
562
|
|
|
if (!$versionInfo->isDraft()) { |
|
563
|
|
|
throw new ForbiddenException('Only version in status DRAFT can be published'); |
|
564
|
|
|
} |
|
565
|
|
|
|
|
566
|
|
|
$this->repository->getContentService()->publishVersion( |
|
567
|
|
|
$versionInfo |
|
568
|
|
|
); |
|
569
|
|
|
|
|
570
|
|
|
return new Values\NoContent(); |
|
571
|
|
|
} |
|
572
|
|
|
|
|
573
|
|
|
/** |
|
574
|
|
|
* Redirects to the relations of the current version. |
|
575
|
|
|
* |
|
576
|
|
|
* @param mixed $contentId |
|
577
|
|
|
* |
|
578
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\TemporaryRedirect |
|
579
|
|
|
*/ |
|
580
|
|
View Code Duplication |
public function redirectCurrentVersionRelations($contentId) |
|
581
|
|
|
{ |
|
582
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
583
|
|
|
|
|
584
|
|
|
return new Values\TemporaryRedirect( |
|
585
|
|
|
$this->router->generate( |
|
586
|
|
|
'ezpublish_rest_redirectCurrentVersionRelations', |
|
587
|
|
|
array( |
|
588
|
|
|
'contentId' => $contentId, |
|
589
|
|
|
'versionNumber' => $contentInfo->currentVersionNo, |
|
590
|
|
|
) |
|
591
|
|
|
) |
|
592
|
|
|
); |
|
593
|
|
|
} |
|
594
|
|
|
|
|
595
|
|
|
/** |
|
596
|
|
|
* Loads the relations of the given version. |
|
597
|
|
|
* |
|
598
|
|
|
* @param mixed $contentId |
|
599
|
|
|
* @param mixed $versionNumber |
|
600
|
|
|
* |
|
601
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\RelationList |
|
602
|
|
|
*/ |
|
603
|
|
|
public function loadVersionRelations($contentId, $versionNumber, Request $request) |
|
604
|
|
|
{ |
|
605
|
|
|
$offset = $request->query->has('offset') ? (int)$request->query->get('offset') : 0; |
|
606
|
|
|
$limit = $request->query->has('limit') ? (int)$request->query->get('limit') : -1; |
|
607
|
|
|
|
|
608
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
609
|
|
|
$relationList = $this->repository->getContentService()->loadRelations( |
|
610
|
|
|
$this->repository->getContentService()->loadVersionInfo($contentInfo, $versionNumber) |
|
611
|
|
|
); |
|
612
|
|
|
|
|
613
|
|
|
$relationList = array_slice( |
|
614
|
|
|
$relationList, |
|
615
|
|
|
$offset >= 0 ? $offset : 0, |
|
616
|
|
|
$limit >= 0 ? $limit : null |
|
617
|
|
|
); |
|
618
|
|
|
|
|
619
|
|
|
$relationListValue = new Values\RelationList( |
|
620
|
|
|
$relationList, |
|
621
|
|
|
$contentId, |
|
622
|
|
|
$versionNumber, |
|
623
|
|
|
$request->getPathInfo() |
|
624
|
|
|
); |
|
625
|
|
|
|
|
626
|
|
|
if ($contentInfo->mainLocationId === null) { |
|
627
|
|
|
return $relationListValue; |
|
628
|
|
|
} |
|
629
|
|
|
|
|
630
|
|
|
return new Values\CachedValue( |
|
|
|
|
|
|
631
|
|
|
$relationListValue, |
|
632
|
|
|
array('locationId' => $contentInfo->mainLocationId) |
|
633
|
|
|
); |
|
634
|
|
|
} |
|
635
|
|
|
|
|
636
|
|
|
/** |
|
637
|
|
|
* Loads a relation for the given content object and version. |
|
638
|
|
|
* |
|
639
|
|
|
* @param mixed $contentId |
|
640
|
|
|
* @param int $versionNumber |
|
641
|
|
|
* @param mixed $relationId |
|
642
|
|
|
* |
|
643
|
|
|
* @throws \eZ\Publish\Core\REST\Common\Exceptions\NotFoundException |
|
644
|
|
|
* |
|
645
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\RestRelation |
|
646
|
|
|
*/ |
|
647
|
|
|
public function loadVersionRelation($contentId, $versionNumber, $relationId, Request $request) |
|
648
|
|
|
{ |
|
649
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
650
|
|
|
$relationList = $this->repository->getContentService()->loadRelations( |
|
651
|
|
|
$this->repository->getContentService()->loadVersionInfo($contentInfo, $versionNumber) |
|
652
|
|
|
); |
|
653
|
|
|
|
|
654
|
|
|
foreach ($relationList as $relation) { |
|
655
|
|
|
if ($relation->id == $relationId) { |
|
656
|
|
|
$relation = new Values\RestRelation($relation, $contentId, $versionNumber); |
|
657
|
|
|
|
|
658
|
|
|
if ($contentInfo->mainLocationId === null) { |
|
659
|
|
|
return $relation; |
|
660
|
|
|
} |
|
661
|
|
|
|
|
662
|
|
|
return new Values\CachedValue( |
|
|
|
|
|
|
663
|
|
|
$relation, |
|
664
|
|
|
array('locationId' => $contentInfo->mainLocationId) |
|
665
|
|
|
); |
|
666
|
|
|
} |
|
667
|
|
|
} |
|
668
|
|
|
|
|
669
|
|
|
throw new Exceptions\NotFoundException("Relation not found: '{$request->getPathInfo()}'."); |
|
670
|
|
|
} |
|
671
|
|
|
|
|
672
|
|
|
/** |
|
673
|
|
|
* Deletes a relation of the given draft. |
|
674
|
|
|
* |
|
675
|
|
|
* @param mixed $contentId |
|
676
|
|
|
* @param int $versionNumber |
|
677
|
|
|
* @param mixed $relationId |
|
678
|
|
|
* |
|
679
|
|
|
* @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException |
|
680
|
|
|
* @throws \eZ\Publish\Core\REST\Common\Exceptions\NotFoundException |
|
681
|
|
|
* |
|
682
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\NoContent |
|
683
|
|
|
*/ |
|
684
|
|
|
public function removeRelation($contentId, $versionNumber, $relationId, Request $request) |
|
685
|
|
|
{ |
|
686
|
|
|
$versionInfo = $this->repository->getContentService()->loadVersionInfo( |
|
687
|
|
|
$this->repository->getContentService()->loadContentInfo($contentId), |
|
688
|
|
|
$versionNumber |
|
689
|
|
|
); |
|
690
|
|
|
|
|
691
|
|
|
$versionRelations = $this->repository->getContentService()->loadRelations($versionInfo); |
|
692
|
|
|
foreach ($versionRelations as $relation) { |
|
693
|
|
|
if ($relation->id == $relationId) { |
|
694
|
|
|
if ($relation->type !== Relation::COMMON) { |
|
695
|
|
|
throw new ForbiddenException('Relation is not of type COMMON'); |
|
696
|
|
|
} |
|
697
|
|
|
|
|
698
|
|
|
if (!$versionInfo->isDraft()) { |
|
699
|
|
|
throw new ForbiddenException('Relation of type COMMON can only be removed from drafts'); |
|
700
|
|
|
} |
|
701
|
|
|
|
|
702
|
|
|
$this->repository->getContentService()->deleteRelation($versionInfo, $relation->getDestinationContentInfo()); |
|
703
|
|
|
|
|
704
|
|
|
return new Values\NoContent(); |
|
705
|
|
|
} |
|
706
|
|
|
} |
|
707
|
|
|
|
|
708
|
|
|
throw new Exceptions\NotFoundException("Relation not found: '{$request->getPathInfo()}'."); |
|
709
|
|
|
} |
|
710
|
|
|
|
|
711
|
|
|
/** |
|
712
|
|
|
* Creates a new relation of type COMMON for the given draft. |
|
713
|
|
|
* |
|
714
|
|
|
* @param mixed $contentId |
|
715
|
|
|
* @param int $versionNumber |
|
716
|
|
|
* |
|
717
|
|
|
* @throws ForbiddenException if version $versionNumber isn't a draft |
|
718
|
|
|
* @throws ForbiddenException if a relation to the same content already exists |
|
719
|
|
|
* |
|
720
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\CreatedRelation |
|
721
|
|
|
*/ |
|
722
|
|
|
public function createRelation($contentId, $versionNumber, Request $request) |
|
723
|
|
|
{ |
|
724
|
|
|
$destinationContentId = $this->inputDispatcher->parse( |
|
725
|
|
|
new Message( |
|
726
|
|
|
array('Content-Type' => $request->headers->get('Content-Type')), |
|
727
|
|
|
$request->getContent() |
|
|
|
|
|
|
728
|
|
|
) |
|
729
|
|
|
); |
|
730
|
|
|
|
|
731
|
|
|
$contentInfo = $this->repository->getContentService()->loadContentInfo($contentId); |
|
732
|
|
|
$versionInfo = $this->repository->getContentService()->loadVersionInfo($contentInfo, $versionNumber); |
|
733
|
|
|
if (!$versionInfo->isDraft()) { |
|
734
|
|
|
throw new ForbiddenException('Relation of type COMMON can only be added to drafts'); |
|
735
|
|
|
} |
|
736
|
|
|
|
|
737
|
|
|
try { |
|
738
|
|
|
$destinationContentInfo = $this->repository->getContentService()->loadContentInfo($destinationContentId); |
|
|
|
|
|
|
739
|
|
|
} catch (NotFoundException $e) { |
|
740
|
|
|
throw new ForbiddenException($e->getMessage()); |
|
741
|
|
|
} |
|
742
|
|
|
|
|
743
|
|
|
$existingRelations = $this->repository->getContentService()->loadRelations($versionInfo); |
|
744
|
|
|
foreach ($existingRelations as $existingRelation) { |
|
745
|
|
|
if ($existingRelation->getDestinationContentInfo()->id == $destinationContentId) { |
|
746
|
|
|
throw new ForbiddenException('Relation of type COMMON to selected destination content ID already exists'); |
|
747
|
|
|
} |
|
748
|
|
|
} |
|
749
|
|
|
|
|
750
|
|
|
$relation = $this->repository->getContentService()->addRelation($versionInfo, $destinationContentInfo); |
|
751
|
|
|
|
|
752
|
|
|
return new Values\CreatedRelation( |
|
753
|
|
|
array( |
|
754
|
|
|
'relation' => new Values\RestRelation($relation, $contentId, $versionNumber), |
|
755
|
|
|
) |
|
756
|
|
|
); |
|
757
|
|
|
} |
|
758
|
|
|
|
|
759
|
|
|
/** |
|
760
|
|
|
* Creates and executes a content view. |
|
761
|
|
|
* |
|
762
|
|
|
* @deprecated Since platform 1.0. Forwards the request to the new /views location, but returns a 301. |
|
763
|
|
|
* |
|
764
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\RestExecutedView |
|
765
|
|
|
*/ |
|
766
|
|
|
public function createView() |
|
767
|
|
|
{ |
|
768
|
|
|
$response = $this->forward('ezpublish_rest.controller.views:createView'); |
|
769
|
|
|
|
|
770
|
|
|
// Add 301 status code and location href |
|
771
|
|
|
$response->setStatusCode(301); |
|
772
|
|
|
$response->headers->set('Location', $this->router->generate('ezpublish_rest_views_create')); |
|
773
|
|
|
|
|
774
|
|
|
return $response; |
|
775
|
|
|
} |
|
776
|
|
|
|
|
777
|
|
|
/** |
|
778
|
|
|
* @param string $controller |
|
779
|
|
|
* |
|
780
|
|
|
* @return \Symfony\Component\HttpFoundation\Response |
|
781
|
|
|
*/ |
|
782
|
|
|
protected function forward($controller) |
|
783
|
|
|
{ |
|
784
|
|
|
$path['_controller'] = $controller; |
|
|
|
|
|
|
785
|
|
|
$subRequest = $this->container->get('request_stack')->getCurrentRequest()->duplicate(null, null, $path); |
|
786
|
|
|
|
|
787
|
|
|
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST); |
|
788
|
|
|
} |
|
789
|
|
|
|
|
790
|
|
|
/** |
|
791
|
|
|
* Get new main language code traversing through all Versions to look for the oldest one. |
|
792
|
|
|
* |
|
793
|
|
|
* @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo |
|
794
|
|
|
* @param string $removedLanguageCode |
|
795
|
|
|
* |
|
796
|
|
|
* @return string |
|
797
|
|
|
* |
|
798
|
|
|
* @throws \eZ\Publish\Core\REST\Client\Exceptions\BadStateException |
|
799
|
|
|
*/ |
|
800
|
|
|
private function getNewMainLanguageCode(VersionInfo $versionInfo, $removedLanguageCode) |
|
801
|
|
|
{ |
|
802
|
|
|
$newLanguageCodeFilter = function ($languageCode) use ($removedLanguageCode) { |
|
803
|
|
|
return $languageCode !== $removedLanguageCode; |
|
804
|
|
|
}; |
|
805
|
|
|
|
|
806
|
|
|
$newMainLanguageCandidates = array_values( |
|
807
|
|
|
array_filter( |
|
808
|
|
|
$versionInfo->languageCodes, |
|
809
|
|
|
$newLanguageCodeFilter |
|
810
|
|
|
) |
|
811
|
|
|
); |
|
812
|
|
|
if (empty($newMainLanguageCandidates)) { |
|
813
|
|
|
throw new BadStateException( |
|
814
|
|
|
sprintf( |
|
815
|
|
|
'%s Translation is the only one the Version (ContentId=%d, VersionNo=%d) has.', |
|
816
|
|
|
$removedLanguageCode, |
|
817
|
|
|
$versionInfo->contentInfo->id, |
|
818
|
|
|
$versionInfo->versionNo |
|
819
|
|
|
) |
|
820
|
|
|
); |
|
821
|
|
|
} |
|
822
|
|
|
|
|
823
|
|
|
if (count($newMainLanguageCandidates) === 1) { |
|
824
|
|
|
// no need to choose, only one option |
|
825
|
|
|
return $newMainLanguageCandidates[0]; |
|
826
|
|
|
} |
|
827
|
|
|
|
|
828
|
|
|
// starting from the oldest Version, look for a first language code not matching removed one |
|
829
|
|
|
$versions = $this->repository->getContentService()->loadVersions($versionInfo->contentInfo->id); |
|
830
|
|
|
foreach ($versions as $version) { |
|
831
|
|
|
$candidates = array_values( |
|
832
|
|
|
array_filter( |
|
833
|
|
|
$version->languageCodes, |
|
834
|
|
|
$newLanguageCodeFilter |
|
835
|
|
|
) |
|
836
|
|
|
); |
|
837
|
|
|
// if there is a language and also exists in current version draft, it's the right choice |
|
838
|
|
|
if (!empty($candidates) && in_array($candidates[0], $newMainLanguageCandidates)) { |
|
839
|
|
|
return $candidates[0]; |
|
840
|
|
|
} |
|
841
|
|
|
} |
|
842
|
|
|
|
|
843
|
|
|
throw new BadStateException( |
|
844
|
|
|
sprintf( |
|
845
|
|
|
'Unable to change main Translation of the ContentId=%d before removing %s Translation.', |
|
846
|
|
|
$versionInfo->contentInfo->id, |
|
847
|
|
|
$removedLanguageCode |
|
848
|
|
|
) |
|
849
|
|
|
); |
|
850
|
|
|
} |
|
851
|
|
|
} |
|
852
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.