ContentViewBuilder::buildView()   F
last analyzed

Complexity

Conditions 20
Paths 378

Size

Total Lines 77

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
nc 378
nop 1
dl 0
loc 77
rs 1.1083
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
namespace eZ\Publish\Core\MVC\Symfony\View\Builder;
8
9
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
10
use eZ\Publish\API\Repository\Repository;
11
use eZ\Publish\API\Repository\Values\Content\Content;
12
use eZ\Publish\API\Repository\Values\Content\Location;
13
use eZ\Publish\API\Repository\Values\Content\VersionInfo;
14
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
15
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
16
use eZ\Publish\Core\Helper\ContentInfoLocationLoader;
17
use eZ\Publish\Core\MVC\Exception\HiddenLocationException;
18
use eZ\Publish\Core\MVC\Symfony\Controller\Content\PreviewController;
19
use eZ\Publish\Core\MVC\Symfony\View\Configurator;
20
use eZ\Publish\Core\MVC\Symfony\View\ContentView;
21
use eZ\Publish\Core\MVC\Symfony\View\EmbedView;
22
use eZ\Publish\Core\MVC\Symfony\View\ParametersInjector;
23
use Symfony\Component\HttpFoundation\RequestStack;
24
25
/**
26
 * Builds ContentView objects.
27
 */
28
class ContentViewBuilder implements ViewBuilder
29
{
30
    /** @var \eZ\Publish\API\Repository\Repository */
31
    private $repository;
32
33
    /** @var \eZ\Publish\API\Repository\PermissionResolver */
34
    private $permissionResolver;
35
36
    /** @var \eZ\Publish\Core\MVC\Symfony\View\Configurator */
37
    private $viewConfigurator;
38
39
    /** @var \eZ\Publish\Core\MVC\Symfony\View\ParametersInjector */
40
    private $viewParametersInjector;
41
42
    /** @var \Symfony\Component\HttpFoundation\RequestStack */
43
    private $requestStack;
44
45
    /**
46
     * Default templates, indexed per viewType (full, line, ...).
47
     *
48
     * @var array
49
     */
50
    private $defaultTemplates;
51
52
    /** @var \eZ\Publish\Core\Helper\ContentInfoLocationLoader */
53
    private $locationLoader;
54
55
    public function __construct(
56
        Repository $repository,
57
        Configurator $viewConfigurator,
58
        ParametersInjector $viewParametersInjector,
59
        RequestStack $requestStack,
60
        ContentInfoLocationLoader $locationLoader = null
61
    ) {
62
        $this->repository = $repository;
63
        $this->viewConfigurator = $viewConfigurator;
64
        $this->viewParametersInjector = $viewParametersInjector;
65
        $this->locationLoader = $locationLoader;
66
        $this->permissionResolver = $this->repository->getPermissionResolver();
67
        $this->requestStack = $requestStack;
68
    }
69
70
    public function matches($argument)
71
    {
72
        return strpos($argument, 'ez_content:') !== false;
73
    }
74
75
    /**
76
     * @param array $parameters
77
     *
78
     * @return \eZ\Publish\Core\MVC\Symfony\View\ContentView|\eZ\Publish\Core\MVC\Symfony\View\View
79
     *         If both contentId and locationId parameters are missing
80
     *
81
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
82
     *         If both contentId and locationId parameters are missing
83
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
84
     */
85
    public function buildView(array $parameters)
86
    {
87
        $view = new ContentView(null, [], $parameters['viewType']);
88
        $view->setIsEmbed($this->isEmbed($parameters));
89
90
        if ($view->isEmbed() && $parameters['viewType'] === null) {
91
            $view->setViewType(EmbedView::DEFAULT_VIEW_TYPE);
92
        }
93
94
        if (isset($parameters['location']) && $parameters['location'] instanceof Location) {
95
            $location = $parameters['location'];
96
        } elseif (isset($parameters['locationId'])) {
97
            $location = $this->loadLocation($parameters['locationId']);
98
        } else {
99
            $location = null;
100
        }
101
102
        if (isset($parameters['content'])) {
103
            $content = $parameters['content'];
104
        } elseif ($location instanceof Location) {
105
            // if we already have location load content true it so we avoid dual loading in case user does that in view
106
            $content = $location->getContent();
107
            if (!$this->canRead($content, $location, $view->isEmbed())) {
108
                $missingPermission = 'read' . ($view->isEmbed() ? '|view_embed' : '');
109
                throw new UnauthorizedException(
110
                    'content',
111
                    $missingPermission,
112
                    [
113
                        'contentId' => $content->id,
114
                        'locationId' => $location->id,
115
                    ]
116
                );
117
            }
118
        } else {
119
            if (isset($parameters['contentId'])) {
120
                $contentId = $parameters['contentId'];
121
            } elseif (isset($location)) {
122
                $contentId = $location->contentId;
123
            } else {
124
                throw new InvalidArgumentException('Content', 'Could not load any content from the parameters');
125
            }
126
127
            $languageCode = $parameters['languageCode'] ?? null;
128
129
            $content = $view->isEmbed() ? $this->loadEmbeddedContent($contentId, $location, $languageCode) : $this->loadContent($contentId, $languageCode);
130
        }
131
132
        $view->setContent($content);
0 ignored issues
show
Bug introduced by
It seems like $content defined by $parameters['content'] on line 103 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...
133
134
        if (isset($location)) {
135
            if ($location->contentId !== $content->id) {
136
                throw new InvalidArgumentException('Location', 'Provided Location does not belong to the selected Content item');
137
            }
138
139
            if (isset($parameters['contentId']) && $location->contentId !== (int)$parameters['contentId']) {
140
                throw new InvalidArgumentException(
141
                    'Location',
142
                    'Provided Location does not belong to the Content item requested via the contentId parameter'
143
                );
144
            }
145
        } elseif (isset($this->locationLoader)) {
146
            try {
147
                $location = $this->locationLoader->loadLocation($content->contentInfo);
148
            } catch (NotFoundException $e) {
149
                // nothing else to do
150
            }
151
        }
152
153
        if (isset($location)) {
154
            $view->setLocation($location);
155
        }
156
157
        $this->viewParametersInjector->injectViewParameters($view, $parameters);
158
        $this->viewConfigurator->configure($view);
159
160
        return $view;
161
    }
162
163
    /**
164
     * Loads Content with id $contentId.
165
     *
166
     * @param mixed $contentId
167
     *
168
     * @return \eZ\Publish\API\Repository\Values\Content\Content
169
     *
170
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
171
     */
172
    private function loadContent($contentId, ?string $languageCode = null)
173
    {
174
        return $this->repository->getContentService()->loadContent(
175
            $contentId,
176
            $languageCode ? [$languageCode] : null
177
        );
178
    }
179
180
    /**
181
     * Loads the embedded content with id $contentId.
182
     * Will load the content with sudo(), and check if the user can view_embed this content, for the given location
183
     * if provided.
184
     *
185
     * @param mixed $contentId
186
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
187
     *
188
     * @return \eZ\Publish\API\Repository\Values\Content\Content
189
     *
190
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
191
     */
192
    private function loadEmbeddedContent($contentId, Location $location = null, ?string $languageCode = null)
193
    {
194
        $content = $this->repository->sudo(
195
            function (Repository $repository) use ($contentId, $languageCode) {
196
                return $repository->getContentService()->loadContent($contentId, $languageCode ? [$languageCode] : null);
197
            }
198
        );
199
200
        if (!$this->canRead($content, $location)) {
201
            throw new UnauthorizedException(
202
                'content', 'read|view_embed',
203
                ['contentId' => $contentId, 'locationId' => $location !== null ? $location->id : 'n/a']
204
            );
205
        }
206
207
        // Check that Content is published, since sudo allows loading unpublished content.
208
        if (
209
            $content->getVersionInfo()->status !== VersionInfo::STATUS_PUBLISHED
210
            && !$this->permissionResolver->canUser('content', 'versionread', $content)
211
        ) {
212
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentId]);
213
        }
214
215
        return $content;
216
    }
217
218
    /**
219
     * Loads a visible Location.
220
     *
221
     * @param $locationId
222
     *
223
     * @return \eZ\Publish\API\Repository\Values\Content\Location
224
     */
225
    private function loadLocation($locationId)
226
    {
227
        $location = $this->repository->sudo(
228
            function (Repository $repository) use ($locationId) {
229
                return $repository->getLocationService()->loadLocation($locationId);
230
            }
231
        );
232
233
        $request = $this->requestStack->getCurrentRequest();
234
        if (!$request || !$request->attributes->get(PreviewController::PREVIEW_PARAMETER_NAME, false)) {
235
            if ($location->invisible || $location->hidden) {
236
                throw new HiddenLocationException($location, 'Cannot display Location because it is flagged as invisible.');
237
            }
238
        }
239
240
        return $location;
241
    }
242
243
    /**
244
     * Checks if a user can read a content, or view it as an embed.
245
     *
246
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
247
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
248
     * @param bool $isEmbed
249
     *
250
     * @return bool
251
     */
252
    private function canRead(Content $content, Location $location = null, bool $isEmbed = true): bool
253
    {
254
        $targets = isset($location) ? [$location] : [];
255
256
        return
257
            $this->permissionResolver->canUser('content', 'read', $content->contentInfo, $targets) ||
258
            ($isEmbed && $this->permissionResolver->canUser('content', 'view_embed', $content->contentInfo, $targets));
259
    }
260
261
    /**
262
     * Checks if the view is an embed one.
263
     * Uses either the controller action (embedAction), or the viewType (embed/embed-inline).
264
     *
265
     * @param array $parameters The ViewBuilder parameters array.
266
     *
267
     * @return bool
268
     */
269
    private function isEmbed($parameters)
270
    {
271
        if ($parameters['_controller'] === 'ez_content:embedAction') {
272
            return true;
273
        }
274
        if (\in_array($parameters['viewType'], ['embed', 'embed-inline'])) {
275
            return true;
276
        }
277
278
        return false;
279
    }
280
}
281