Completed
Push — ezp25533-http_cache_anonymous_... ( b956b0...914558 )
by
unknown
25:20
created

Content::loadContent()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 41
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 26
nc 12
nop 2
dl 0
loc 41
rs 8.439
c 0
b 0
f 0
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
 * @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\Values\Content\Relation;
18
use eZ\Publish\API\Repository\Values\Content\VersionInfo;
19
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
20
use eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException;
21
use eZ\Publish\API\Repository\Exceptions\ContentValidationException;
22
use eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException;
23
use eZ\Publish\Core\REST\Server\Exceptions\BadRequestException;
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(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \eZ\Publish\C...Info->mainLocationId)); (eZ\Publish\Core\REST\Server\Values\CachedValue) is incompatible with the return type documented by eZ\Publish\Core\REST\Ser...er\Content::loadContent of type eZ\Publish\Core\REST\Server\Values\RestContent.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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()
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...
122
            )
123
        );
124
125
        $contentInfo = $this->repository->getContentService()->loadContentInfo($contentId);
126
127
        // update section
128
        if ($updateStruct->sectionId !== null) {
0 ignored issues
show
Documentation introduced by
The property sectionId 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...
129
            $section = $this->repository->getSectionService()->loadSection($updateStruct->sectionId);
0 ignored issues
show
Documentation introduced by
The property sectionId 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...
130
            $this->repository->getSectionService()->assignSection($contentInfo, $section);
131
            $updateStruct->sectionId = null;
0 ignored issues
show
Documentation introduced by
The property sectionId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
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) {
0 ignored issues
show
Bug introduced by
The expression $updateStruct of type object<eZ\Publish\API\Re...ory\Values\ValueObject> is not traversable.
Loading history...
139
            if ($propertyName !== 'sectionId' && $propertyValue !== null) {
140
                // update content
141
                $this->repository->getContentService()->updateContentMetadata($contentInfo, $updateStruct);
0 ignored issues
show
Compatibility introduced by
$updateStruct of type object<eZ\Publish\API\Re...ory\Values\ValueObject> is not a sub-type of object<eZ\Publish\API\Re...ntMetadataUpdateStruct>. 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...
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) {
213
            return $versionValue;
214
        }
215
216
        return new Values\CachedValue(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \eZ\Publish\C...Info->mainLocationId)); (eZ\Publish\Core\REST\Server\Values\CachedValue) is incompatible with the return type documented by eZ\Publish\Core\REST\Ser...t::loadContentInVersion of type eZ\Publish\Core\REST\Server\Values\Version.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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()
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...
239
            )
240
        );
241
242
        try {
243
            $content = $this->repository->getContentService()->createContent(
244
                $contentCreate->contentCreateStruct,
0 ignored issues
show
Documentation introduced by
The property contentCreateStruct 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...
245
                array($contentCreate->locationCreateStruct)
0 ignored issues
show
Documentation introduced by
The property locationCreateStruct 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...
246
            );
247
        } catch (ContentValidationException $e) {
248
            throw new BadRequestException($e->getMessage());
249
        } catch (ContentFieldValidationException $e) {
250
            throw new BadRequestException($e->getMessage());
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
        $versionList = new Values\VersionList(
332
            $this->repository->getContentService()->loadVersions($contentInfo),
333
            $request->getPathInfo()
334
        );
335
336
        if ($contentInfo->mainLocationId === null) {
337
            return $versionList;
338
        }
339
340
        return new Values\CachedValue(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \eZ\Publish\C...Info->mainLocationId)); (eZ\Publish\Core\REST\Server\Values\CachedValue) is incompatible with the return type documented by eZ\Publish\Core\REST\Ser...nt::loadContentVersions of type eZ\Publish\Core\REST\Server\Values\VersionList.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
341
            $versionList,
342
            array('locationId' => $contentInfo->mainLocationId)
343
        );
344
    }
345
346
    /**
347
     * The version is deleted.
348
     *
349
     * @param mixed $contentId
350
     * @param mixed $versionNumber
351
     *
352
     * @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException
353
     *
354
     * @return \eZ\Publish\Core\REST\Server\Values\NoContent
355
     */
356 View Code Duplication
    public function deleteContentVersion($contentId, $versionNumber)
357
    {
358
        $versionInfo = $this->repository->getContentService()->loadVersionInfo(
359
            $this->repository->getContentService()->loadContentInfo($contentId),
360
            $versionNumber
361
        );
362
363
        if ($versionInfo->status === VersionInfo::STATUS_PUBLISHED) {
364
            throw new ForbiddenException('Version in status PUBLISHED cannot be deleted');
365
        }
366
367
        $this->repository->getContentService()->deleteVersion(
368
            $versionInfo
369
        );
370
371
        return new Values\NoContent();
372
    }
373
374
    /**
375
     * The system creates a new draft version as a copy from the given version.
376
     *
377
     * @param mixed $contentId
378
     * @param mixed $versionNumber
379
     *
380
     * @return \eZ\Publish\Core\REST\Server\Values\CreatedVersion
381
     */
382
    public function createDraftFromVersion($contentId, $versionNumber)
383
    {
384
        $contentInfo = $this->repository->getContentService()->loadContentInfo($contentId);
385
        $contentType = $this->repository->getContentTypeService()->loadContentType($contentInfo->contentTypeId);
386
        $contentDraft = $this->repository->getContentService()->createContentDraft(
387
            $contentInfo,
388
            $this->repository->getContentService()->loadVersionInfo($contentInfo, $versionNumber)
389
        );
390
391
        return new Values\CreatedVersion(
392
            array(
393
                'version' => new Values\Version(
394
                    $contentDraft,
395
                    $contentType,
396
                    $this->repository->getContentService()->loadRelations($contentDraft->getVersionInfo())
397
                ),
398
            )
399
        );
400
    }
401
402
    /**
403
     * The system creates a new draft version as a copy from the current version.
404
     *
405
     * @param mixed $contentId
406
     *
407
     * @throws ForbiddenException if the current version is already a draft
408
     *
409
     * @return \eZ\Publish\Core\REST\Server\Values\CreatedVersion
410
     */
411
    public function createDraftFromCurrentVersion($contentId)
412
    {
413
        $contentInfo = $this->repository->getContentService()->loadContentInfo($contentId);
414
        $contentType = $this->repository->getContentTypeService()->loadContentType($contentInfo->contentTypeId);
415
        $versionInfo = $this->repository->getContentService()->loadVersionInfo(
416
            $contentInfo
417
        );
418
419
        if ($versionInfo->status === VersionInfo::STATUS_DRAFT) {
420
            throw new ForbiddenException('Current version is already in status DRAFT');
421
        }
422
423
        $contentDraft = $this->repository->getContentService()->createContentDraft($contentInfo);
424
425
        return new Values\CreatedVersion(
426
            array(
427
                'version' => new Values\Version(
428
                    $contentDraft,
429
                    $contentType,
430
                    $this->repository->getContentService()->loadRelations($contentDraft->getVersionInfo())
431
                ),
432
            )
433
        );
434
    }
435
436
    /**
437
     * A specific draft is updated.
438
     *
439
     * @param mixed $contentId
440
     * @param mixed $versionNumber
441
     *
442
     * @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException
443
     * @throws \eZ\Publish\Core\REST\Server\Exceptions\BadRequestException
444
     *
445
     * @return \eZ\Publish\Core\REST\Server\Values\Version
446
     */
447
    public function updateVersion($contentId, $versionNumber, Request $request)
448
    {
449
        $contentUpdateStruct = $this->inputDispatcher->parse(
450
            new Message(
451
                array(
452
                    'Content-Type' => $request->headers->get('Content-Type'),
453
                    'Url' => $this->router->generate(
454
                        'ezpublish_rest_updateVersion',
455
                        array(
456
                            'contentId' => $contentId,
457
                            'versionNumber' => $versionNumber,
458
                        )
459
                    ),
460
                ),
461
                $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...
462
            )
463
        );
464
465
        $versionInfo = $this->repository->getContentService()->loadVersionInfo(
466
            $this->repository->getContentService()->loadContentInfo($contentId),
467
            $versionNumber
468
        );
469
470
        if ($versionInfo->status !== VersionInfo::STATUS_DRAFT) {
471
            throw new ForbiddenException('Only version in status DRAFT can be updated');
472
        }
473
474
        try {
475
            $this->repository->getContentService()->updateContent($versionInfo, $contentUpdateStruct);
0 ignored issues
show
Compatibility introduced by
$contentUpdateStruct of type object<eZ\Publish\API\Re...ory\Values\ValueObject> is not a sub-type of object<eZ\Publish\API\Re...nt\ContentUpdateStruct>. 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...
476
        } catch (ContentValidationException $e) {
477
            throw new BadRequestException($e->getMessage());
478
        } catch (ContentFieldValidationException $e) {
479
            throw new BadRequestException($e->getMessage());
480
        }
481
482
        $languages = null;
483
        if ($request->query->has('languages')) {
484
            $languages = explode(',', $request->query->get('languages'));
485
        }
486
487
        // Reload the content to handle languages GET parameter
488
        $content = $this->repository->getContentService()->loadContent(
489
            $contentId,
490
            $languages,
491
            $versionInfo->versionNo
492
        );
493
        $contentType = $this->repository->getContentTypeService()->loadContentType(
494
            $content->getVersionInfo()->getContentInfo()->contentTypeId
495
        );
496
497
        return new Values\Version(
498
            $content,
499
            $contentType,
500
            $this->repository->getContentService()->loadRelations($content->getVersionInfo()),
501
            $request->getPathInfo()
502
        );
503
    }
504
505
    /**
506
     * The content version is published.
507
     *
508
     * @param mixed $contentId
509
     * @param mixed $versionNumber
510
     *
511
     * @throws ForbiddenException if version $versionNumber isn't a draft
512
     *
513
     * @return \eZ\Publish\Core\REST\Server\Values\NoContent
514
     */
515 View Code Duplication
    public function publishVersion($contentId, $versionNumber)
516
    {
517
        $versionInfo = $this->repository->getContentService()->loadVersionInfo(
518
            $this->repository->getContentService()->loadContentInfo($contentId),
519
            $versionNumber
520
        );
521
522
        if ($versionInfo->status !== VersionInfo::STATUS_DRAFT) {
523
            throw new ForbiddenException('Only version in status DRAFT can be published');
524
        }
525
526
        $this->repository->getContentService()->publishVersion(
527
            $versionInfo
528
        );
529
530
        return new Values\NoContent();
531
    }
532
533
    /**
534
     * Redirects to the relations of the current version.
535
     *
536
     * @param mixed $contentId
537
     *
538
     * @return \eZ\Publish\Core\REST\Server\Values\TemporaryRedirect
539
     */
540 View Code Duplication
    public function redirectCurrentVersionRelations($contentId)
541
    {
542
        $contentInfo = $this->repository->getContentService()->loadContentInfo($contentId);
543
544
        return new Values\TemporaryRedirect(
545
            $this->router->generate(
546
                'ezpublish_rest_redirectCurrentVersionRelations',
547
                array(
548
                    'contentId' => $contentId,
549
                    'versionNumber' => $contentInfo->currentVersionNo,
550
                )
551
            )
552
        );
553
    }
554
555
    /**
556
     * Loads the relations of the given version.
557
     *
558
     * @param mixed $contentId
559
     * @param mixed $versionNumber
560
     *
561
     * @return \eZ\Publish\Core\REST\Server\Values\RelationList
562
     */
563
    public function loadVersionRelations($contentId, $versionNumber, Request $request)
564
    {
565
        $offset = $request->query->has('offset') ? (int)$request->query->get('offset') : 0;
566
        $limit = $request->query->has('limit') ? (int)$request->query->get('limit') : -1;
567
568
        $contentInfo = $this->repository->getContentService()->loadContentInfo($contentId);
569
        $relationList = $this->repository->getContentService()->loadRelations(
570
            $this->repository->getContentService()->loadVersionInfo($contentInfo, $versionNumber)
571
        );
572
573
        $relationList = array_slice(
574
            $relationList,
575
            $offset >= 0 ? $offset : 0,
576
            $limit >= 0 ? $limit : null
577
        );
578
579
        $relationListValue = new Values\RelationList(
580
            $relationList,
581
            $contentId,
582
            $versionNumber,
583
            $request->getPathInfo()
584
        );
585
586
        if ($contentInfo->mainLocationId === null) {
587
            return $relationListValue;
588
        }
589
590
        return new Values\CachedValue(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \eZ\Publish\C...Info->mainLocationId)); (eZ\Publish\Core\REST\Server\Values\CachedValue) is incompatible with the return type documented by eZ\Publish\Core\REST\Ser...t::loadVersionRelations of type eZ\Publish\Core\REST\Server\Values\RelationList.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
591
            $relationListValue,
592
            array('locationId' => $contentInfo->mainLocationId)
593
        );
594
    }
595
596
    /**
597
     * Loads a relation for the given content object and version.
598
     *
599
     * @param mixed $contentId
600
     * @param int $versionNumber
601
     * @param mixed $relationId
602
     *
603
     * @throws \eZ\Publish\Core\REST\Common\Exceptions\NotFoundException
604
     *
605
     * @return \eZ\Publish\Core\REST\Server\Values\RestRelation
606
     */
607
    public function loadVersionRelation($contentId, $versionNumber, $relationId, Request $request)
608
    {
609
        $contentInfo = $this->repository->getContentService()->loadContentInfo($contentId);
610
        $relationList = $this->repository->getContentService()->loadRelations(
611
            $this->repository->getContentService()->loadVersionInfo($contentInfo, $versionNumber)
612
        );
613
614
        foreach ($relationList as $relation) {
615
            if ($relation->id == $relationId) {
616
                $relation = new Values\RestRelation($relation, $contentId, $versionNumber);
617
618
                if ($contentInfo->mainLocationId === null) {
619
                    return $relation;
620
                }
621
622
                return new Values\CachedValue(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \eZ\Publish\C...Info->mainLocationId)); (eZ\Publish\Core\REST\Server\Values\CachedValue) is incompatible with the return type documented by eZ\Publish\Core\REST\Ser...nt::loadVersionRelation of type eZ\Publish\Core\REST\Server\Values\RestRelation.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
623
                    $relation,
624
                    array('locationId' => $contentInfo->mainLocationId)
625
                );
626
            }
627
        }
628
629
        throw new Exceptions\NotFoundException("Relation not found: '{$request->getPathInfo()}'.");
630
    }
631
632
    /**
633
     * Deletes a relation of the given draft.
634
     *
635
     * @param mixed $contentId
636
     * @param int   $versionNumber
637
     * @param mixed $relationId
638
     *
639
     * @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException
640
     * @throws \eZ\Publish\Core\REST\Common\Exceptions\NotFoundException
641
     *
642
     * @return \eZ\Publish\Core\REST\Server\Values\NoContent
643
     */
644
    public function removeRelation($contentId, $versionNumber, $relationId, Request $request)
645
    {
646
        $versionInfo = $this->repository->getContentService()->loadVersionInfo(
647
            $this->repository->getContentService()->loadContentInfo($contentId),
648
            $versionNumber
649
        );
650
651
        $versionRelations = $this->repository->getContentService()->loadRelations($versionInfo);
652
        foreach ($versionRelations as $relation) {
653
            if ($relation->id == $relationId) {
654
                if ($relation->type !== Relation::COMMON) {
655
                    throw new ForbiddenException('Relation is not of type COMMON');
656
                }
657
658
                if ($versionInfo->status !== VersionInfo::STATUS_DRAFT) {
659
                    throw new ForbiddenException('Relation of type COMMON can only be removed from drafts');
660
                }
661
662
                $this->repository->getContentService()->deleteRelation($versionInfo, $relation->getDestinationContentInfo());
663
664
                return new Values\NoContent();
665
            }
666
        }
667
668
        throw new Exceptions\NotFoundException("Relation not found: '{$request->getPathInfo()}'.");
669
    }
670
671
    /**
672
     * Creates a new relation of type COMMON for the given draft.
673
     *
674
     * @param mixed $contentId
675
     * @param int $versionNumber
676
     *
677
     * @throws ForbiddenException if version $versionNumber isn't a draft
678
     * @throws ForbiddenException if a relation to the same content already exists
679
     *
680
     * @return \eZ\Publish\Core\REST\Server\Values\CreatedRelation
681
     */
682
    public function createRelation($contentId, $versionNumber, Request $request)
683
    {
684
        $destinationContentId = $this->inputDispatcher->parse(
685
            new Message(
686
                array('Content-Type' => $request->headers->get('Content-Type')),
687
                $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...
688
            )
689
        );
690
691
        $contentInfo = $this->repository->getContentService()->loadContentInfo($contentId);
692
        $versionInfo = $this->repository->getContentService()->loadVersionInfo($contentInfo, $versionNumber);
693
        if ($versionInfo->status !== VersionInfo::STATUS_DRAFT) {
694
            throw new ForbiddenException('Relation of type COMMON can only be added to drafts');
695
        }
696
697
        try {
698
            $destinationContentInfo = $this->repository->getContentService()->loadContentInfo($destinationContentId);
0 ignored issues
show
Documentation introduced by
$destinationContentId is of type object<eZ\Publish\API\Re...ory\Values\ValueObject>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
699
        } catch (NotFoundException $e) {
700
            throw new ForbiddenException($e->getMessage());
701
        }
702
703
        $existingRelations = $this->repository->getContentService()->loadRelations($versionInfo);
704
        foreach ($existingRelations as $existingRelation) {
705
            if ($existingRelation->getDestinationContentInfo()->id == $destinationContentId) {
706
                throw new ForbiddenException('Relation of type COMMON to selected destination content ID already exists');
707
            }
708
        }
709
710
        $relation = $this->repository->getContentService()->addRelation($versionInfo, $destinationContentInfo);
711
712
        return new Values\CreatedRelation(
713
            array(
714
                'relation' => new Values\RestRelation($relation, $contentId, $versionNumber),
715
            )
716
        );
717
    }
718
719
    /**
720
     * Creates and executes a content view.
721
     *
722
     * @deprecated Since platform 1.0. Forwards the request to the new /views location, but returns a 301.
723
     *
724
     * @return \eZ\Publish\Core\REST\Server\Values\RestExecutedView
725
     */
726
    public function createView()
727
    {
728
        $response = $this->forward('ezpublish_rest.controller.views:createView');
729
730
        // Add 301 status code and location href
731
        $response->setStatusCode(301);
732
        $response->headers->set('Location', $this->router->generate('ezpublish_rest_views_create'));
733
734
        return $response;
735
    }
736
737
    /**
738
     * @param string $controller
739
     *
740
     * @return \Symfony\Component\HttpFoundation\Response
741
     */
742
    protected function forward($controller)
743
    {
744
        $path['_controller'] = $controller;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$path was never initialized. Although not strictly required by PHP, it is generally a good practice to add $path = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
745
        $subRequest = $this->container->get('request_stack')->getCurrentRequest()->duplicate(null, null, $path);
746
747
        return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
748
    }
749
}
750