Completed
Push — ezp-31088-refactor-content-mod... ( ab3ba3...3726c8 )
by
unknown
14:09
created

ContentViewBuilder   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
dl 0
loc 244
rs 9.2
c 0
b 0
f 0
wmc 40
lcom 1
cbo 16

8 Methods

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