Completed
Push — 4.0 ( b59aea...80f83b )
by Loz
52s queued 21s
created

CanonicalURLMiddleware   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 78
dl 0
loc 323
rs 9.0399
c 0
b 0
f 0
wmc 42

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setRedirectType() 0 4 1
A getForceSSL() 0 3 1
A setForceSSLPatterns() 0 4 1
A setForceSSLDomain() 0 4 1
A getForceSSLPatterns() 0 3 2
A setEnabledEnvs() 0 4 1
A getRedirectType() 0 3 1
A getForceWWW() 0 3 1
A setForceSSL() 0 4 1
A setForceWWW() 0 4 1
A getEnabledEnvs() 0 3 1
A getForceSSLDomain() 0 3 1
A throwRedirectIfNeeded() 0 9 3
B getRedirect() 0 37 8
A process() 0 9 2
B isEnabled() 0 20 7
A requiresSSL() 0 28 6
A getOrValidateRequest() 0 9 3

How to fix   Complexity   

Complex Class

Complex classes like CanonicalURLMiddleware 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.

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 CanonicalURLMiddleware, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Control\Middleware;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Control\HTTP;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Control\HTTPResponse;
10
use SilverStripe\Control\HTTPResponse_Exception;
11
use SilverStripe\Core\CoreKernel;
12
use SilverStripe\Core\Injector\Injectable;
13
use SilverStripe\Core\Injector\Injector;
14
15
/**
16
 * Allows events to be registered and passed through middleware.
17
 * Useful for event registered prior to the beginning of a middleware chain.
18
 */
19
class CanonicalURLMiddleware implements HTTPMiddleware
20
{
21
    use Injectable;
22
23
    /**
24
     * Set if we should redirect to WWW
25
     *
26
     * @var bool
27
     */
28
    protected $forceWWW = false;
29
30
    /**
31
     * Set if we should force SSL
32
     *
33
     * @var bool
34
     */
35
    protected $forceSSL = false;
36
37
    /**
38
     * Redirect type
39
     *
40
     * @var int
41
     */
42
    protected $redirectType = 301;
43
44
    /**
45
     * Environment variables this middleware is enabled in, or a fixed boolean flag to
46
     * apply to all environments. cli is disabled unless present here as `cli`, or set to true
47
     * to force enabled.
48
     *
49
     * @var array|bool
50
     */
51
    protected $enabledEnvs = [
52
        CoreKernel::LIVE
53
    ];
54
55
    /**
56
     * If forceSSL is enabled, this is the list of patterns that the url must match (at least one)
57
     *
58
     * @var array Array of regexps to match against relative url
59
     */
60
    protected $forceSSLPatterns = [];
61
62
    /**
63
     * SSL Domain to use
64
     *
65
     * @var string
66
     */
67
    protected $forceSSLDomain = null;
68
69
    /**
70
     * @return array
71
     */
72
    public function getForceSSLPatterns()
73
    {
74
        return $this->forceSSLPatterns ?: [];
75
    }
76
77
    /**
78
     * @param array $forceSSLPatterns
79
     * @return $this
80
     */
81
    public function setForceSSLPatterns($forceSSLPatterns)
82
    {
83
        $this->forceSSLPatterns = $forceSSLPatterns;
84
        return $this;
85
    }
86
87
    /**
88
     * @return string
89
     */
90
    public function getForceSSLDomain()
91
    {
92
        return $this->forceSSLDomain;
93
    }
94
95
    /**
96
     * @param string $forceSSLDomain
97
     * @return $this
98
     */
99
    public function setForceSSLDomain($forceSSLDomain)
100
    {
101
        $this->forceSSLDomain = $forceSSLDomain;
102
        return $this;
103
    }
104
105
    /**
106
     * @return bool
107
     */
108
    public function getForceWWW()
109
    {
110
        return $this->forceWWW;
111
    }
112
113
    /**
114
     * @param bool $forceWWW
115
     * @return $this
116
     */
117
    public function setForceWWW($forceWWW)
118
    {
119
        $this->forceWWW = $forceWWW;
120
        return $this;
121
    }
122
123
    /**
124
     * @return bool
125
     */
126
    public function getForceSSL()
127
    {
128
        return $this->forceSSL;
129
    }
130
131
    /**
132
     * @param bool $forceSSL
133
     * @return $this
134
     */
135
    public function setForceSSL($forceSSL)
136
    {
137
        $this->forceSSL = $forceSSL;
138
        return $this;
139
    }
140
141
    /**
142
     * Generate response for the given request
143
     *
144
     * @param HTTPRequest $request
145
     * @param callable $delegate
146
     * @return HTTPResponse
147
     */
148
    public function process(HTTPRequest $request, callable $delegate)
149
    {
150
        // Handle any redirects
151
        $redirect = $this->getRedirect($request);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $redirect is correct as $this->getRedirect($request) targeting SilverStripe\Control\Mid...ddleware::getRedirect() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
152
        if ($redirect) {
0 ignored issues
show
introduced by
$redirect is of type null, thus it always evaluated to false.
Loading history...
153
            return $redirect;
154
        }
155
156
        return $delegate($request);
157
    }
158
159
    /**
160
     * Given request object determine if we should redirect.
161
     *
162
     * @param HTTPRequest $request Pre-validated request object
163
     * @return HTTPResponse|null If a redirect is needed return the response
164
     */
165
    protected function getRedirect(HTTPRequest $request)
166
    {
167
        // Check global disable
168
        if (!$this->isEnabled()) {
169
            return null;
170
        }
171
172
        // Get properties of current request
173
        $host = $request->getHost();
174
        $scheme = $request->getScheme();
175
176
        // Check https
177
        if ($this->requiresSSL($request)) {
178
            $scheme = 'https';
179
180
            // Promote ssl domain if configured
181
            $host = $this->getForceSSLDomain() ?: $host;
182
        }
183
184
        // Check www.
185
        if ($this->getForceWWW() && strpos($host, 'www.') !== 0) {
186
            $host = "www.{$host}";
187
        }
188
189
        // No-op if no changes
190
        if ($request->getScheme() === $scheme && $request->getHost() === $host) {
191
            return null;
192
        }
193
194
        // Rebuild url for request
195
        $url = Controller::join_links("{$scheme}://{$host}", Director::baseURL(), $request->getURL(true));
196
197
        // Force redirect
198
        $response = new HTTPResponse();
199
        $response->redirect($url, $this->getRedirectType());
200
        HTTP::add_cache_headers($response);
201
        return $response;
202
    }
203
204
    /**
205
     * Handles redirection to canonical urls outside of the main middleware chain
206
     * using HTTPResponseException.
207
     * Will not do anything if a current HTTPRequest isn't available
208
     *
209
     * @param HTTPRequest|null $request Allow HTTPRequest to be used for the base comparison
210
     * @throws HTTPResponse_Exception
211
     */
212
    public function throwRedirectIfNeeded(HTTPRequest $request = null)
213
    {
214
        $request = $this->getOrValidateRequest($request);
215
        if (!$request) {
216
            return;
217
        }
218
        $response = $this->getRedirect($request);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $response is correct as $this->getRedirect($request) targeting SilverStripe\Control\Mid...ddleware::getRedirect() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
219
        if ($response) {
0 ignored issues
show
introduced by
$response is of type null, thus it always evaluated to false.
Loading history...
220
            throw new HTTPResponse_Exception($response);
221
        }
222
    }
223
224
    /**
225
     * Return a valid request, if one is available, or null if none is available
226
     *
227
     * @param HTTPRequest $request
228
     * @return mixed|null
229
     */
230
    protected function getOrValidateRequest(HTTPRequest $request = null)
231
    {
232
        if ($request instanceof HTTPRequest) {
233
            return $request;
234
        }
235
        if (Injector::inst()->has(HTTPRequest::class)) {
236
            return Injector::inst()->get(HTTPRequest::class);
237
        }
238
        return null;
239
    }
240
241
    /**
242
     * Check if a redirect for SSL is necessary
243
     *
244
     * @param HTTPRequest $request
245
     * @return bool
246
     */
247
    protected function requiresSSL(HTTPRequest $request)
248
    {
249
        // Check if force SSL is enabled
250
        if (!$this->getForceSSL()) {
251
            return false;
252
        }
253
254
        // Already on SSL
255
        if ($request->getScheme() === 'https') {
256
            return false;
257
        }
258
259
        // Veto if any existing patterns fail
260
        $patterns = $this->getForceSSLPatterns();
261
        if (!$patterns) {
262
            return true;
263
        }
264
265
        // Filter redirect based on url
266
        $relativeURL = $request->getURL(true);
267
        foreach ($patterns as $pattern) {
268
            if (preg_match($pattern, $relativeURL)) {
269
                return true;
270
            }
271
        }
272
273
        // No patterns match
274
        return false;
275
    }
276
277
    /**
278
     * @return int
279
     */
280
    public function getRedirectType()
281
    {
282
        return $this->redirectType;
283
    }
284
285
    /**
286
     * @param int $redirectType
287
     * @return $this
288
     */
289
    public function setRedirectType($redirectType)
290
    {
291
        $this->redirectType = $redirectType;
292
        return $this;
293
    }
294
295
    /**
296
     * Get enabled flag, or list of environments to enable in.
297
     *
298
     * @return array|bool
299
     */
300
    public function getEnabledEnvs()
301
    {
302
        return $this->enabledEnvs;
303
    }
304
305
    /**
306
     * Set enabled flag, or list of environments to enable in.
307
     * Note: CLI is disabled by default, so `"cli"(string)` or `true(bool)` should be specified if you wish to
308
     * enable for testing.
309
     *
310
     * @param array|bool $enabledEnvs
311
     * @return $this
312
     */
313
    public function setEnabledEnvs($enabledEnvs)
314
    {
315
        $this->enabledEnvs = $enabledEnvs;
316
        return $this;
317
    }
318
319
    /**
320
     * Ensure this middleware is enabled
321
     */
322
    protected function isEnabled()
323
    {
324
        // At least one redirect must be enabled
325
        if (!$this->getForceWWW() && !$this->getForceSSL()) {
326
            return false;
327
        }
328
329
        // Filter by env vars
330
        $enabledEnvs = $this->getEnabledEnvs();
331
        if (is_bool($enabledEnvs)) {
332
            return $enabledEnvs;
333
        }
334
335
        // If CLI, EnabledEnvs must contain CLI
336
        if (Director::is_cli() && !in_array('cli', $enabledEnvs)) {
337
            return false;
338
        }
339
340
        // Check other envs
341
        return empty($enabledEnvs) || in_array(Director::get_environment_type(), $enabledEnvs);
342
    }
343
}
344