Passed
Push — 4.1 ( 6d2665...02ad0f )
by
unknown
32:36 queued 24:09
created

safeReloadWithTokens()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 15
nc 2
nop 2
dl 0
loc 26
rs 9.7666
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core\Startup;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\Control\HTTPResponse;
8
use SilverStripe\Control\HTTPResponse_Exception;
9
use SilverStripe\Control\Middleware\HTTPMiddleware;
10
use SilverStripe\Core\Application;
11
use SilverStripe\Security\Security;
12
13
/**
14
 * Decorates application bootstrapping with errorcontrolchain
15
 *
16
 * @internal This class is designed specifically for use pre-startup and may change without warning
17
 */
18
class ErrorControlChainMiddleware implements HTTPMiddleware
19
{
20
    /**
21
     * @var Application
22
     */
23
    protected $application = null;
24
25
    /**
26
     * Build error control chain for an application
27
     *
28
     * @param Application $application
29
     */
30
    public function __construct(Application $application)
31
    {
32
        $this->application = $application;
33
    }
34
35
    /**
36
     * @param HTTPRequest $request
37
     * @return ConfirmationTokenChain
38
     */
39
    protected function prepareConfirmationTokenChain(HTTPRequest $request)
40
    {
41
        $chain = new ConfirmationTokenChain();
42
        $chain->pushToken(new URLConfirmationToken('dev/build', $request));
43
44
        foreach (['isTest', 'isDev', 'flush'] as $parameter) {
45
            $chain->pushToken(new ParameterConfirmationToken($parameter, $request));
46
        }
47
48
        return $chain;
49
    }
50
51
    public function process(HTTPRequest $request, callable $next)
52
    {
53
        $result = null;
54
55
        // Prepare tokens and execute chain
56
        $confirmationTokenChain = $this->prepareConfirmationTokenChain($request);
57
        $errorControlChain = new ErrorControlChain();
58
        $errorControlChain
59
            ->then(function () use ($request, $errorControlChain, $confirmationTokenChain, $next, &$result) {
60
                if ($confirmationTokenChain->suppressionRequired()) {
61
                    $confirmationTokenChain->suppressTokens();
62
                } else {
63
                    // If no redirection is necessary then we can disable error supression
64
                    $errorControlChain->setSuppression(false);
65
                }
66
67
                try {
68
                    // Check if a token is requesting a redirect
69
                    if ($confirmationTokenChain && $confirmationTokenChain->reloadRequired()) {
70
                        $result = $this->safeReloadWithTokens($request, $confirmationTokenChain);
71
                    } else {
72
                        // If no reload necessary, process application
73
                        $result = call_user_func($next, $request);
74
                    }
75
                } catch (HTTPResponse_Exception $exception) {
76
                    $result = $exception->getResponse();
77
                }
78
            })
79
            // Finally if a token was requested but there was an error while figuring out if it's allowed, do it anyway
80
            ->thenIfErrored(function () use ($confirmationTokenChain) {
81
                if ($confirmationTokenChain && $confirmationTokenChain->reloadRequiredIfError()) {
82
                    try {
83
                        // Reload requires manual boot
84
                        $this->getApplication()->getKernel()->boot(false);
85
                    } finally {
86
                        // Given we're in an error state here, try to continue even if the kernel boot fails
87
                        $result = $confirmationTokenChain->reloadWithTokens();
88
                        $result->output();
89
                    }
90
                }
91
            })
92
            ->execute();
93
        return $result;
94
    }
95
96
    /**
97
     * Reload application with the given token, but only if either the user is authenticated,
98
     * or authentication is impossible.
99
     *
100
     * @param HTTPRequest $request
101
     * @param ConfirmationTokenChain $confirmationTokenChain
102
     * @return HTTPResponse
103
     */
104
    protected function safeReloadWithTokens(HTTPRequest $request, ConfirmationTokenChain $confirmationTokenChain)
105
    {
106
        // Safe reload requires manual boot
107
        $this->getApplication()->getKernel()->boot(false);
108
109
        // Ensure session is started
110
        $request->getSession()->init($request);
111
        
112
        // Request with ErrorDirector
113
        $result = ErrorDirector::singleton()->handleRequestWithTokenChain(
114
            $request,
115
            $confirmationTokenChain,
116
            $this->getApplication()->getKernel()
117
        );
118
        if ($result) {
119
            return $result;
120
        }
121
122
        // Fail and redirect the user to the login page
123
        $params = array_merge($request->getVars(), $confirmationTokenChain->params(false));
124
        $backURL = $confirmationTokenChain->getRedirectUrlBase() . '?' . http_build_query($params);
125
        $loginPage = Director::absoluteURL(Security::config()->get('login_url'));
126
        $loginPage .= "?BackURL=" . urlencode($backURL);
127
        $result = new HTTPResponse();
128
        $result->redirect($loginPage);
129
        return $result;
130
    }
131
132
    /**
133
     * @return Application
134
     */
135
    public function getApplication()
136
    {
137
        return $this->application;
138
    }
139
140
    /**
141
     * @param Application $application
142
     * @return $this
143
     */
144
    public function setApplication(Application $application)
145
    {
146
        $this->application = $application;
147
        return $this;
148
    }
149
}
150