Completed
Push — 7.5 ( 3cbdfb...483473 )
by Łukasz
22:00
created

ContentViewBuilder   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 17
dl 0
loc 253
rs 8.96
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 1
A matches() 0 4 1
F buildView() 0 84 23
A loadContent() 0 4 1
A loadEmbeddedContent() 0 25 5
A loadLocation() 0 17 5
A canRead() 0 8 4
A isEmbed() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like ContentViewBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ContentViewBuilder, and based on these observations, apply Extract Interface, too.

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
use Symfony\Component\HttpKernel\Controller\ControllerReference;
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
     * @var array
48
     */
49
    private $defaultTemplates;
50
51
    /** @var \eZ\Publish\Core\Helper\ContentInfoLocationLoader */
52
    private $locationLoader;
53
54
    public function __construct(
55
        Repository $repository,
56
        Configurator $viewConfigurator,
57
        ParametersInjector $viewParametersInjector,
58
        RequestStack $requestStack,
59
        ContentInfoLocationLoader $locationLoader = null
60
    ) {
61
        $this->repository = $repository;
62
        $this->viewConfigurator = $viewConfigurator;
63
        $this->viewParametersInjector = $viewParametersInjector;
64
        $this->locationLoader = $locationLoader;
65
        $this->permissionResolver = $this->repository->getPermissionResolver();
66
        $this->requestStack = $requestStack;
67
    }
68
69
    public function matches($argument)
70
    {
71
        return strpos($argument, 'ez_content:') !== false;
72
    }
73
74
    /**
75
     * @param array $parameters
76
     *
77
     * @return \eZ\Publish\Core\MVC\Symfony\View\ContentView|\eZ\Publish\Core\MVC\Symfony\View\View
78
     *         If both contentId and locationId parameters are missing
79
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
80
     *         If both contentId and locationId parameters are missing
81
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
82
     */
83
    public function buildView(array $parameters)
84
    {
85
        $view = new ContentView(null, [], $parameters['viewType']);
86
        $view->setIsEmbed($this->isEmbed($parameters));
87
88
        if ($view->isEmbed() && $parameters['viewType'] === null) {
89
            $view->setViewType(EmbedView::DEFAULT_VIEW_TYPE);
90
        }
91
92
        if (isset($parameters['location']) && $parameters['location'] instanceof Location) {
93
            $location = $parameters['location'];
94
        } elseif (isset($parameters['locationId'])) {
95
            $location = $this->loadLocation($parameters['locationId']);
96
        } else {
97
            $location = null;
98
        }
99
100
        if (isset($parameters['content'])) {
101
            $content = $parameters['content'];
102
        } elseif ($location instanceof Location) {
103
            // if we already have location load content true it so we avoid dual loading in case user does that in view
104
            $content = $location->getContent();
105
            if (!$this->canRead($content, $location, $view->isEmbed())) {
106
                $missingPermission = 'read' . ($view->isEmbed() ? '|view_embed' : '');
107
                throw new UnauthorizedException(
108
                    'content',
109
                    $missingPermission,
110
                    [
111
                        'contentId' => $content->id,
112
                        'locationId' => $location->id,
113
                    ]
114
                );
115
            }
116
        } else {
117
            if (isset($parameters['contentId'])) {
118
                $contentId = $parameters['contentId'];
119
            } elseif (isset($location)) {
120
                $contentId = $location->contentId;
121
            } else {
122
                throw new InvalidArgumentException('Content', 'No content could be loaded from parameters');
123
            }
124
125
            $content = $view->isEmbed() ? $this->loadEmbeddedContent($contentId, $location) : $this->loadContent($contentId);
126
        }
127
128
        $view->setContent($content);
0 ignored issues
show
Bug introduced by
It seems like $content defined by $parameters['content'] on line 101 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...
129
130
        if (isset($location)) {
131
            if ($location->contentId !== $content->id) {
132
                throw new InvalidArgumentException('Location', 'Provided location does not belong to selected content');
133
            }
134
135
            if (isset($parameters['contentId']) && $location->contentId !== (int)$parameters['contentId']) {
136
                throw new InvalidArgumentException(
137
                    'Location',
138
                    'Provided location does not belong to selected content as requested via contentId parameter'
139
                );
140
            }
141
        } elseif (isset($this->locationLoader)) {
142
            try {
143
                $location = $this->locationLoader->loadLocation($content->contentInfo);
144
            } catch (NotFoundException $e) {
145
                // nothing else to do
146
            }
147
        }
148
149
        if (isset($location)) {
150
            $view->setLocation($location);
151
        }
152
153
        $this->viewParametersInjector->injectViewParameters($view, $parameters);
154
        $this->viewConfigurator->configure($view);
155
156
        // deprecated controller actions are replaced with their new equivalent, viewAction and embedAction
157
        if (!$view->getControllerReference() instanceof ControllerReference) {
158
            if (\in_array($parameters['_controller'], ['ez_content:viewLocation', 'ez_content:viewContent'])) {
159
                $view->setControllerReference(new ControllerReference('ez_content:viewAction'));
160
            } elseif (\in_array($parameters['_controller'], ['ez_content:embedLocation', 'ez_content:embedContent'])) {
161
                $view->setControllerReference(new ControllerReference('ez_content:embedAction'));
162
            }
163
        }
164
165
        return $view;
166
    }
167
168
    /**
169
     * Loads Content with id $contentId.
170
     *
171
     * @param mixed $contentId
172
     *
173
     * @return \eZ\Publish\API\Repository\Values\Content\Content
174
     *
175
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
176
     */
177
    private function loadContent($contentId)
178
    {
179
        return $this->repository->getContentService()->loadContent($contentId);
180
    }
181
182
    /**
183
     * Loads the embedded content with id $contentId.
184
     * Will load the content with sudo(), and check if the user can view_embed this content, for the given location
185
     * if provided.
186
     *
187
     * @param mixed $contentId
188
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
189
     *
190
     * @return \eZ\Publish\API\Repository\Values\Content\Content
191
     * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException
192
     */
193
    private function loadEmbeddedContent($contentId, Location $location = null)
194
    {
195
        $content = $this->repository->sudo(
196
            function (Repository $repository) use ($contentId) {
197
                return $repository->getContentService()->loadContent($contentId);
198
            }
199
        );
200
201
        if (!$this->canRead($content, $location)) {
202
            throw new UnauthorizedException(
203
                'content', 'read|view_embed',
204
                ['contentId' => $contentId, 'locationId' => $location !== null ? $location->id : 'n/a']
205
            );
206
        }
207
208
        // Check that Content is published, since sudo allows loading unpublished content.
209
        if (
210
            $content->getVersionInfo()->status !== VersionInfo::STATUS_PUBLISHED
211
            && !$this->permissionResolver->canUser('content', 'versionread', $content)
212
        ) {
213
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentId]);
214
        }
215
216
        return $content;
217
    }
218
219
    /**
220
     * Loads a visible Location.
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, 'Location cannot be displayed as 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