Completed
Push — master ( 256b3e...67d26f )
by Łukasz
13:35
created

ContentViewBuilder::loadEmbeddedContent()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 3
nop 3
dl 0
loc 25
rs 8.8977
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license For full copyright and license information view LICENSE file distributed with this source code.
5
 */
6
namespace eZ\Publish\Core\MVC\Symfony\View\Builder;
7
8
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
9
use eZ\Publish\API\Repository\Repository;
10
use eZ\Publish\API\Repository\Values\Content\Content;
11
use eZ\Publish\API\Repository\Values\Content\Location;
12
use eZ\Publish\API\Repository\Values\Content\VersionInfo;
13
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
14
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
15
use eZ\Publish\Core\Helper\ContentInfoLocationLoader;
16
use eZ\Publish\Core\MVC\Exception\HiddenLocationException;
17
use eZ\Publish\Core\MVC\Symfony\Controller\Content\PreviewController;
18
use eZ\Publish\Core\MVC\Symfony\View\Configurator;
19
use eZ\Publish\Core\MVC\Symfony\View\ContentView;
20
use eZ\Publish\Core\MVC\Symfony\View\EmbedView;
21
use eZ\Publish\Core\MVC\Symfony\View\ParametersInjector;
22
use Symfony\Component\HttpFoundation\RequestStack;
23
24
/**
25
 * Builds ContentView objects.
26
 */
27
class ContentViewBuilder implements ViewBuilder
28
{
29
    /** @var \eZ\Publish\API\Repository\Repository */
30
    private $repository;
31
32
    /** @var \eZ\Publish\API\Repository\PermissionResolver */
33
    private $permissionResolver;
34
35
    /** @var \eZ\Publish\Core\MVC\Symfony\View\Configurator */
36
    private $viewConfigurator;
37
38
    /** @var \eZ\Publish\Core\MVC\Symfony\View\ParametersInjector */
39
    private $viewParametersInjector;
40
41
    /** @var \Symfony\Component\HttpFoundation\RequestStack */
42
    private $requestStack;
43
44
    /**
45
     * Default templates, indexed per viewType (full, line, ...).
46
     * @var array
47
     */
48
    private $defaultTemplates;
49
50
    /** @var \eZ\Publish\Core\Helper\ContentInfoLocationLoader */
51
    private $locationLoader;
52
53
    public function __construct(
54
        Repository $repository,
55
        Configurator $viewConfigurator,
56
        ParametersInjector $viewParametersInjector,
57
        RequestStack $requestStack,
58
        ContentInfoLocationLoader $locationLoader = null
59
    ) {
60
        $this->repository = $repository;
61
        $this->viewConfigurator = $viewConfigurator;
62
        $this->viewParametersInjector = $viewParametersInjector;
63
        $this->locationLoader = $locationLoader;
64
        $this->permissionResolver = $this->repository->getPermissionResolver();
65
        $this->requestStack = $requestStack;
66
    }
67
68
    public function matches($argument)
69
    {
70
        return strpos($argument, 'ez_content:') !== false;
71
    }
72
73
    /**
74
     * @param array $parameters
75
     *
76
     * @return \eZ\Publish\Core\MVC\Symfony\View\ContentView|\eZ\Publish\Core\MVC\Symfony\View\View
77
     *         If both contentId and locationId parameters are missing
78
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
79
     *         If both contentId and locationId parameters are missing
80
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
81
     */
82
    public function buildView(array $parameters)
83
    {
84
        $view = new ContentView(null, [], $parameters['viewType']);
85
        $view->setIsEmbed($this->isEmbed($parameters));
86
87
        if ($view->isEmbed() && $parameters['viewType'] === null) {
88
            $view->setViewType(EmbedView::DEFAULT_VIEW_TYPE);
89
        }
90
91
        if (isset($parameters['location']) && $parameters['location'] instanceof Location) {
92
            $location = $parameters['location'];
93
        } elseif (isset($parameters['locationId'])) {
94
            $location = $this->loadLocation($parameters['locationId']);
95
        } else {
96
            $location = null;
97
        }
98
99
        if (isset($parameters['content'])) {
100
            $content = $parameters['content'];
101
        } elseif ($location instanceof Location) {
102
            // if we already have location load content true it so we avoid dual loading in case user does that in view
103
            $content = $location->getContent();
104
            if (!$this->canRead($content, $location, $view->isEmbed())) {
105
                $missingPermission = 'read' . ($view->isEmbed() ? '|view_embed' : '');
106
                throw new UnauthorizedException(
107
                    'content',
108
                    $missingPermission,
109
                    [
110
                        'contentId' => $content->id,
111
                        'locationId' => $location->id,
112
                    ]
113
                );
114
            }
115
        } else {
116
            if (isset($parameters['contentId'])) {
117
                $contentId = $parameters['contentId'];
118
            } elseif (isset($location)) {
119
                $contentId = $location->contentId;
120
            } else {
121
                throw new InvalidArgumentException('Content', 'Could not load any content from the parameters');
122
            }
123
124
            $languageCode = $parameters['languageCode'] ?? null;
125
126
            $content = $view->isEmbed() ? $this->loadEmbeddedContent($contentId, $location, $languageCode) : $this->loadContent($contentId, $languageCode);
127
        }
128
129
        $view->setContent($content);
0 ignored issues
show
Bug introduced by
It seems like $content defined by $parameters['content'] on line 100 can also be of type object<eZ\Publish\API\Re...alues\Content\Location>; however, eZ\Publish\Core\MVC\Symf...ntentView::setContent() does only seem to accept object<eZ\Publish\API\Re...Values\Content\Content>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
130
131
        if (isset($location)) {
132
            if ($location->contentId !== $content->id) {
133
                throw new InvalidArgumentException('Location', 'Provided Location does not belong to the selected Content item');
134
            }
135
136
            if (isset($parameters['contentId']) && $location->contentId !== (int)$parameters['contentId']) {
137
                throw new InvalidArgumentException(
138
                    'Location',
139
                    'Provided Location does not belong to the Content item requested via the contentId parameter'
140
                );
141
            }
142
        } elseif (isset($this->locationLoader)) {
143
            try {
144
                $location = $this->locationLoader->loadLocation($content->contentInfo);
145
            } catch (NotFoundException $e) {
146
                // nothing else to do
147
            }
148
        }
149
150
        if (isset($location)) {
151
            $view->setLocation($location);
152
        }
153
154
        $this->viewParametersInjector->injectViewParameters($view, $parameters);
155
        $this->viewConfigurator->configure($view);
156
157
        return $view;
158
    }
159
160
    /**
161
     * Loads Content with id $contentId.
162
     *
163
     * @param mixed $contentId
164
     *
165
     * @return \eZ\Publish\API\Repository\Values\Content\Content
166
     *
167
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
168
     */
169
    private function loadContent($contentId, ?string $languageCode = null)
170
    {
171
        return $this->repository->getContentService()->loadContent(
172
            $contentId,
173
            $languageCode ? [$languageCode] : null
174
        );
175
    }
176
177
    /**
178
     * Loads the embedded content with id $contentId.
179
     * Will load the content with sudo(), and check if the user can view_embed this content, for the given location
180
     * if provided.
181
     *
182
     * @param mixed $contentId
183
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
184
     *
185
     * @return \eZ\Publish\API\Repository\Values\Content\Content
186
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
187
     */
188
    private function loadEmbeddedContent($contentId, Location $location = null, ?string $languageCode = null)
189
    {
190
        $content = $this->repository->sudo(
191
            function (Repository $repository) use ($contentId, $languageCode) {
192
                return $repository->getContentService()->loadContent($contentId, $languageCode ? [$languageCode] : null);
193
            }
194
        );
195
196
        if (!$this->canRead($content, $location)) {
197
            throw new UnauthorizedException(
198
                'content', 'read|view_embed',
199
                ['contentId' => $contentId, 'locationId' => $location !== null ? $location->id : 'n/a']
200
            );
201
        }
202
203
        // Check that Content is published, since sudo allows loading unpublished content.
204
        if (
205
            $content->getVersionInfo()->status !== VersionInfo::STATUS_PUBLISHED
206
            && !$this->permissionResolver->canUser('content', 'versionread', $content)
207
        ) {
208
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentId]);
209
        }
210
211
        return $content;
212
    }
213
214
    /**
215
     * Loads a visible Location.
216
     * @param $locationId
217
     *
218
     * @return \eZ\Publish\API\Repository\Values\Content\Location
219
     */
220
    private function loadLocation($locationId)
221
    {
222
        $location = $this->repository->sudo(
223
            function (Repository $repository) use ($locationId) {
224
                return $repository->getLocationService()->loadLocation($locationId);
225
            }
226
        );
227
228
        $request = $this->requestStack->getCurrentRequest();
229
        if (!$request || !$request->attributes->get(PreviewController::PREVIEW_PARAMETER_NAME, false)) {
230
            if ($location->invisible || $location->hidden) {
231
                throw new HiddenLocationException($location, 'Cannot display Location because it is flagged as invisible.');
232
            }
233
        }
234
235
        return $location;
236
    }
237
238
    /**
239
     * Checks if a user can read a content, or view it as an embed.
240
     *
241
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
242
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
243
     * @param bool $isEmbed
244
     *
245
     * @return bool
246
     */
247
    private function canRead(Content $content, Location $location = null, bool $isEmbed = true): bool
248
    {
249
        $targets = isset($location) ? [$location] : [];
250
251
        return
252
            $this->permissionResolver->canUser('content', 'read', $content->contentInfo, $targets) ||
253
            ($isEmbed && $this->permissionResolver->canUser('content', 'view_embed', $content->contentInfo, $targets));
254
    }
255
256
    /**
257
     * Checks if the view is an embed one.
258
     * Uses either the controller action (embedAction), or the viewType (embed/embed-inline).
259
     *
260
     * @param array $parameters The ViewBuilder parameters array.
261
     *
262
     * @return bool
263
     */
264
    private function isEmbed($parameters)
265
    {
266
        if ($parameters['_controller'] === 'ez_content:embedAction') {
267
            return true;
268
        }
269
        if (\in_array($parameters['viewType'], ['embed', 'embed-inline'])) {
270
            return true;
271
        }
272
273
        return false;
274
    }
275
}
276