Completed
Push — EZP-31490-multilingual-content... ( c5cda6 )
by
unknown
12:12
created

ContentViewBuilder::matches()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
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($contentId, $languageCode ? [$languageCode] : null);
172
    }
173
174
    /**
175
     * Loads the embedded content with id $contentId.
176
     * Will load the content with sudo(), and check if the user can view_embed this content, for the given location
177
     * if provided.
178
     *
179
     * @param mixed $contentId
180
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
181
     *
182
     * @return \eZ\Publish\API\Repository\Values\Content\Content
183
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
184
     */
185
    private function loadEmbeddedContent($contentId, Location $location = null, ?string $languageCode = null)
186
    {
187
        $content = $this->repository->sudo(
188
            function (Repository $repository) use ($contentId, $languageCode) {
189
                return $repository->getContentService()->loadContent($contentId, $languageCode ? [$languageCode] : null);
190
            }
191
        );
192
193
        if (!$this->canRead($content, $location)) {
194
            throw new UnauthorizedException(
195
                'content', 'read|view_embed',
196
                ['contentId' => $contentId, 'locationId' => $location !== null ? $location->id : 'n/a']
197
            );
198
        }
199
200
        // Check that Content is published, since sudo allows loading unpublished content.
201
        if (
202
            $content->getVersionInfo()->status !== VersionInfo::STATUS_PUBLISHED
203
            && !$this->permissionResolver->canUser('content', 'versionread', $content)
204
        ) {
205
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentId]);
206
        }
207
208
        return $content;
209
    }
210
211
    /**
212
     * Loads a visible Location.
213
     * @param $locationId
214
     *
215
     * @return \eZ\Publish\API\Repository\Values\Content\Location
216
     */
217
    private function loadLocation($locationId)
218
    {
219
        $location = $this->repository->sudo(
220
            function (Repository $repository) use ($locationId) {
221
                return $repository->getLocationService()->loadLocation($locationId);
222
            }
223
        );
224
225
        $request = $this->requestStack->getCurrentRequest();
226
        if (!$request || !$request->attributes->get(PreviewController::PREVIEW_PARAMETER_NAME, false)) {
227
            if ($location->invisible || $location->hidden) {
228
                throw new HiddenLocationException($location, 'Cannot display Location because it is flagged as invisible.');
229
            }
230
        }
231
232
        return $location;
233
    }
234
235
    /**
236
     * Checks if a user can read a content, or view it as an embed.
237
     *
238
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
239
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
240
     * @param bool $isEmbed
241
     *
242
     * @return bool
243
     */
244
    private function canRead(Content $content, Location $location = null, bool $isEmbed = true): bool
245
    {
246
        $targets = isset($location) ? [$location] : [];
247
248
        return
249
            $this->permissionResolver->canUser('content', 'read', $content->contentInfo, $targets) ||
250
            ($isEmbed && $this->permissionResolver->canUser('content', 'view_embed', $content->contentInfo, $targets));
251
    }
252
253
    /**
254
     * Checks if the view is an embed one.
255
     * Uses either the controller action (embedAction), or the viewType (embed/embed-inline).
256
     *
257
     * @param array $parameters The ViewBuilder parameters array.
258
     *
259
     * @return bool
260
     */
261
    private function isEmbed($parameters)
262
    {
263
        if ($parameters['_controller'] === 'ez_content:embedAction') {
264
            return true;
265
        }
266
        if (\in_array($parameters['viewType'], ['embed', 'embed-inline'])) {
267
            return true;
268
        }
269
270
        return false;
271
    }
272
}
273