Completed
Push — master ( e28f9a...ae4772 )
by Aske
12:03
created

appendDefaultDimensionPresetUriSegments()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
namespace MOC\NotFound\ViewHelpers;
3
4
use Neos\Flow\Annotations as Flow;
5
use Neos\Flow\Configuration\ConfigurationManager;
6
use Neos\Flow\Core\Bootstrap;
7
use Neos\Flow\Http\Request;
8
use Neos\Flow\Http\RequestHandler;
9
use Neos\Flow\Http\Response;
10
use Neos\Flow\Http\Uri;
11
use Neos\Flow\Mvc\ActionRequest;
12
use Neos\Flow\Mvc\Dispatcher;
13
use Neos\Flow\Mvc\Routing\Router;
14
use Neos\Flow\Security\Context as SecurityContext;
15
use Neos\FluidAdaptor\Core\ViewHelper\AbstractViewHelper;
16
use Neos\Neos\Domain\Service\ContentDimensionPresetSourceInterface;
17
use Neos\Neos\Routing\FrontendNodeRoutePartHandler;
18
19
/**
20
 * Loads the content of a given URL
21
 */
22
class RequestViewHelper extends AbstractViewHelper
23
{
24
    /**
25
     * @Flow\Inject
26
     * @var Dispatcher
27
     */
28
    protected $dispatcher;
29
30
    /**
31
     * @Flow\Inject(lazy=false)
32
     * @var Bootstrap
33
     */
34
    protected $bootstrap;
35
36
    /**
37
     * @Flow\Inject(lazy=false)
38
     * @var Router
39
     */
40
    protected $router;
41
42
    /**
43
     * @Flow\Inject(lazy=false)
44
     * @var SecurityContext
45
     */
46
    protected $securityContext;
47
48
    /**
49
     * @Flow\Inject
50
     * @var ConfigurationManager
51
     */
52
    protected $configurationManager;
53
54
    /**
55
     * @Flow\InjectConfiguration(path="routing.supportEmptySegmentForDimensions", package="Neos.Neos")
56
     * @var boolean
57
     */
58
    protected $supportEmptySegmentForDimensions;
59
60
    /**
61
     * @Flow\Inject
62
     * @var ContentDimensionPresetSourceInterface
63
     */
64
    protected $contentDimensionPresetSource;
65
66
    /**
67
     * Initialize this engine
68
     *
69
     * @return void
70
     */
71
    public function initializeObject()
72
    {
73
        $this->router->setRoutesConfiguration($this->configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_ROUTES));
74
    }
75
76
    /**
77
     * @param string $path
78
     * @return string
79
     * @throws \Exception
80
     */
81
    public function render($path = null)
82
    {
83
        $this->appendFirstUriPartIfValidDimension($path);
84
        /** @var RequestHandler $activeRequestHandler */
85
        $activeRequestHandler = $this->bootstrap->getActiveRequestHandler();
86
        $parentHttpRequest = $activeRequestHandler->getHttpRequest();
87
        $uri = rtrim($parentHttpRequest->getBaseUri(), '/') . '/' . $path;
88
        $httpRequest = Request::create(new Uri($uri));
89
        $matchingRoute = $this->router->route($httpRequest);
90
        if (!$matchingRoute) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matchingRoute of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
91
            $exception = new \Exception(sprintf('Uri with path "%s" could not be found.', $uri), 1426446160);
92
            $exceptionHandler = set_exception_handler(null)[0];
93
            $exceptionHandler->handleException($exception);
94
            exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method render() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
95
        }
96
        $request = new ActionRequest($parentHttpRequest);
97
        foreach ($matchingRoute as $argumentName => $argumentValue) {
98
            $request->setArgument($argumentName, $argumentValue);
99
        }
100
        $response = new Response($activeRequestHandler->getHttpResponse());
101
102
        $this->securityContext->withoutAuthorizationChecks(function () use ($request, $response) {
103
            $this->dispatcher->dispatch($request, $response);
104
        });
105
106
        return $response->getContent();
107
    }
108
109
    /**
110
     * @param string $path
111
     * @return void
112
     */
113
    protected function appendFirstUriPartIfValidDimension(&$path)
114
    {
115
        $requestPath = ltrim($this->controllerContext->getRequest()->getHttpRequest()->getUri()->getPath(), '/');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Neos\Flow\Mvc\RequestInterface as the method getHttpRequest() does only exist in the following implementations of said interface: Neos\Flow\Mvc\ActionRequest.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
116
        $matches = [];
117
        preg_match(FrontendNodeRoutePartHandler::DIMENSION_REQUEST_PATH_MATCHER, $requestPath, $matches);
118
        if (!isset($matches['firstUriPart']) && !isset($matches['dimensionPresetUriSegments'])) {
119
            return;
120
        }
121
122
        $dimensionPresets = $this->contentDimensionPresetSource->getAllPresets();
123
        if (count($dimensionPresets) === 0) {
124
            return;
125
        }
126
127
        $firstUriPartExploded = explode('_', $matches['firstUriPart'] ?: $matches['dimensionPresetUriSegments']);
128
        if ($this->supportEmptySegmentForDimensions) {
129
            foreach ($firstUriPartExploded as $uriSegment) {
130
                $uriSegmentIsValid = false;
131
                foreach ($dimensionPresets as $dimensionName => $dimensionPreset) {
132
                    $preset = $this->contentDimensionPresetSource->findPresetByUriSegment($dimensionName, $uriSegment);
133
                    if ($preset !== null) {
134
                        $uriSegmentIsValid = true;
135
                        break;
136
                    }
137
                }
138
                if (!$uriSegmentIsValid) {
139
                    return;
140
                }
141
            }
142
        } else {
143
            if (count($firstUriPartExploded) !== count($dimensionPresets)) {
144
                $this->appendDefaultDimensionPresetUriSegments($dimensionPresets, $path);
145
                return;
146
            }
147
            foreach ($dimensionPresets as $dimensionName => $dimensionPreset) {
148
                $uriSegment = array_shift($firstUriPartExploded);
149
                $preset = $this->contentDimensionPresetSource->findPresetByUriSegment($dimensionName, $uriSegment);
150
                if ($preset === null) {
151
                    $this->appendDefaultDimensionPresetUriSegments($dimensionPresets, $path);
152
                    return;
153
                }
154
            }
155
        }
156
157
        $path = $matches['firstUriPart'] . '/' . $path;
158
    }
159
160
    /**
161
     * @param array $dimensionPresets
162
     * @param string $path
163
     * @return void
164
     */
165
    protected function appendDefaultDimensionPresetUriSegments(array $dimensionPresets, &$path) {
166
        $defaultDimensionPresetUriSegments = [];
167
        foreach ($dimensionPresets as $dimensionName => $dimensionPreset) {
168
            $defaultDimensionPresetUriSegments[] = $dimensionPreset['presets'][$dimensionPreset['defaultPreset']]['uriSegment'];
169
        }
170
        $path = implode('_', $defaultDimensionPresetUriSegments) . '/' . $path;
171
    }
172
}
173