Completed
Push — apply-code-style ( 1a43bf...37cc85 )
by
unknown
45:48
created

ContentViewBuilder   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

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

8 Methods

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

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