Completed
Push — master ( 4ad6bd...3873e4 )
by Ingo
11:53
created

Controller   D

Complexity

Total Complexity 88

Size/Duplication

Total Lines 690
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
dl 0
loc 690
rs 4.4903
c 0
b 0
f 0
wmc 88
lcom 1
cbo 10

30 Methods

Rating   Name   Duplication   Size   Complexity  
A setRequest() 0 7 1
C handleAction() 0 37 8
A setURLParams() 0 5 1
A getURLParams() 0 4 1
A getResponse() 0 7 2
A setResponse() 0 5 1
A defaultAction() 0 4 1
A getAction() 0 4 1
A init() 0 9 2
A doInit() 0 19 2
A beforeHandleRequest() 0 10 1
A afterHandleRequest() 0 5 1
B handleRequest() 0 25 3
B prepareResponse() 0 34 5
D getViewer() 0 30 9
A hasAction() 0 4 2
A removeAction() 0 13 3
A definingClassForAction() 0 19 4
A hasActionTemplate() 0 16 3
A render() 0 13 3
A disableBasicAuth() 0 4 1
A curr() 0 8 2
A has_curr() 0 4 2
A can() 0 15 4
A pushCurrent() 0 6 1
A popCurrent() 0 12 2
A redirect() 0 11 3
A redirectedTo() 0 4 2
D join_links() 0 42 16
A get_template_global_variables() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Controller 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 Controller, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Control;
4
5
use SilverStripe\Core\ClassInfo;
6
use SilverStripe\Dev\Debug;
7
use SilverStripe\ORM\FieldType\DBHTMLText;
8
use SilverStripe\Security\BasicAuth;
9
use SilverStripe\Security\Member;
10
use SilverStripe\Security\Security;
11
use SilverStripe\View\SSViewer;
12
use SilverStripe\View\TemplateGlobalProvider;
13
14
/**
15
 * Controllers are the cornerstone of all site functionality in SilverStripe. The {@link Director}
16
 * selects a controller to pass control to, and then calls {@link handleRequest()}. This method will execute
17
 * the appropriate action - either by calling the action method, or displaying the action's template.
18
 *
19
 * See {@link getTemplate()} for information on how the template is chosen.
20
 */
21
class Controller extends RequestHandler implements TemplateGlobalProvider
22
{
23
24
    /**
25
     * An array of arguments extracted from the URL.
26
     *
27
     * @var array
28
     */
29
    protected $urlParams;
30
31
    /**
32
     * Contains all GET and POST parameters passed to the current {@link HTTPRequest}.
33
     *
34
     * @var array
35
     */
36
    protected $requestParams;
37
38
    /**
39
     * The URL part matched on the current controller as determined by the "$Action" part of the
40
     * {@link $url_handlers} definition. Should correlate to a public method on this controller.
41
     *
42
     * Used in {@link render()} and {@link getViewer()} to determine action-specific templates.
43
     *
44
     * @var string
45
     */
46
    protected $action;
47
48
    /**
49
     * Stack of current controllers. Controller::$controller_stack[0] is the current controller.
50
     *
51
     * @var array
52
     */
53
    protected static $controller_stack = array();
54
55
    /**
56
     * Assign templates for this controller.
57
     * Map of action => template name
58
     *
59
     * @var array
60
     */
61
    protected $templates = [];
62
63
    /**
64
     * @var bool
65
     */
66
    protected $basicAuthEnabled = true;
67
68
    /**
69
     * The response object that the controller returns.
70
     *
71
     * Set in {@link handleRequest()}.
72
     *
73
     * @var HTTPResponse
74
     */
75
    protected $response;
76
77
    /**
78
     * Default URL handlers.
79
     *
80
     * @var array
81
     */
82
    private static $url_handlers = array(
83
        '$Action//$ID/$OtherID' => 'handleAction',
84
    );
85
86
    /**
87
     * @var array
88
     */
89
    private static $allowed_actions = array(
90
        'handleAction',
91
        'handleIndex',
92
    );
93
94
    /**
95
     * Initialisation function that is run before any action on the controller is called.
96
     *
97
     * @uses BasicAuth::requireLogin()
98
     */
99
    protected function init()
100
    {
101
        if ($this->basicAuthEnabled) {
102
            BasicAuth::protect_site_if_necessary();
103
        }
104
105
        // This is used to test that subordinate controllers are actually calling parent::init() - a common bug
106
        $this->baseInitCalled = true;
107
    }
108
109
    /**
110
     * A stand in function to protect the init function from failing to be called as well as providing before and
111
     * after hooks for the init function itself
112
     *
113
     * This should be called on all controllers before handling requests
114
     */
115
    public function doInit()
116
    {
117
        //extension hook
118
        $this->extend('onBeforeInit');
119
120
        // Safety call
121
        $this->baseInitCalled = false;
122
        $this->init();
123
        if (!$this->baseInitCalled) {
124
            $class = static::class;
125
            user_error(
126
                "init() method on class '{$class}' doesn't call Controller::init()."
127
                . "Make sure that you have parent::init() included.",
128
                E_USER_WARNING
129
            );
130
        }
131
132
        $this->extend('onAfterInit');
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     *
138
     * Also set the URLParams
139
     */
140
    public function setRequest($request)
141
    {
142
        $return = parent::setRequest($request);
143
        $this->setURLParams($this->getRequest()->allParams());
144
145
        return $return;
146
    }
147
148
    /**
149
     * A bootstrap for the handleRequest method
150
     *
151
     * @todo setDataModel and setRequest are redundantly called in parent::handleRequest() - sort this out
152
     *
153
     * @param HTTPRequest $request
154
     */
155
    protected function beforeHandleRequest(HTTPRequest $request)
156
    {
157
        //Set up the internal dependencies (request, response)
158
        $this->setRequest($request);
159
        //Push the current controller to protect against weird session issues
160
        $this->pushCurrent();
161
        $this->setResponse(new HTTPResponse());
162
        //kick off the init functionality
163
        $this->doInit();
164
    }
165
166
    /**
167
     * Cleanup for the handleRequest method
168
     */
169
    protected function afterHandleRequest()
170
    {
171
        //Pop the current controller from the stack
172
        $this->popCurrent();
173
    }
174
175
    /**
176
     * Executes this controller, and return an {@link HTTPResponse} object with the result.
177
     *
178
     * This method defers to {@link RequestHandler->handleRequest()} to determine which action
179
     *    should be executed
180
     *
181
     * Note: You should rarely need to overload handleRequest() -
182
     * this kind of change is only really appropriate for things like nested
183
     * controllers - {@link ModelAsController} and {@link RootURLController}
184
     * are two examples here.  If you want to make more
185
     * orthodox functionality, it's better to overload {@link init()} or {@link index()}.
186
     *
187
     * Important: If you are going to overload handleRequest,
188
     * make sure that you start the method with $this->beforeHandleRequest()
189
     * and end the method with $this->afterHandleRequest()
190
     *
191
     * @param HTTPRequest $request
192
     * @return HTTPResponse
193
     */
194
    public function handleRequest(HTTPRequest $request)
195
    {
196
        if (!$request) {
197
            user_error("Controller::handleRequest() not passed a request!", E_USER_ERROR);
198
        }
199
200
        //set up the controller for the incoming request
201
        $this->beforeHandleRequest($request);
202
203
        //if the before handler manipulated the response in a way that we shouldn't proceed, then skip our request
204
        // handling
205
        if (!$this->getResponse()->isFinished()) {
206
            //retrieve the response for the request
207
            $response = parent::handleRequest($request);
208
209
            //prepare the response (we can receive an assortment of response types (strings/objects/HTTPResponses)
210
            $this->prepareResponse($response);
211
        }
212
213
        //after request work
214
        $this->afterHandleRequest();
215
216
        //return the response
217
        return $this->getResponse();
218
    }
219
220
    /**
221
     * Prepare the response (we can receive an assortment of response types (strings/objects/HTTPResponses) and
222
     * changes the controller response object appropriately
223
     *
224
     * @param HTTPResponse|Object $response
225
     */
226
    protected function prepareResponse($response)
0 ignored issues
show
Coding Style introduced by
prepareResponse uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
227
    {
228
        if ($response instanceof HTTPResponse) {
229
            if (isset($_REQUEST['debug_request'])) {
230
                $class = static::class;
231
                Debug::message(
232
                    "Request handler returned HTTPResponse object to {$class} controller;"
233
                    . "returning it without modification."
234
                );
235
            }
236
            $this->setResponse($response);
237
        } else {
238
            // Could be Controller, or ViewableData_Customised controller wrapper
239
            if (ClassInfo::hasMethod($response, 'getViewer')) {
240
                if (isset($_REQUEST['debug_request'])) {
241
                    $class = static::class;
242
                    $responseClass = get_class($response);
243
                    Debug::message(
244
                        "Request handler {$responseClass} object to {$class} controller;"
245
                        . "rendering with template returned by {$responseClass}::getViewer()"
246
                    );
247
                }
248
                $response = $response->getViewer($this->getAction())->process($response);
249
            }
250
251
            $this->getResponse()->setBody($response);
252
        }
253
254
        //deal with content if appropriate
255
        ContentNegotiator::process($this->getResponse());
256
257
        //add cache headers
258
        HTTP::add_cache_headers($this->getResponse());
259
    }
260
261
    /**
262
     * Controller's default action handler.  It will call the method named in "$Action", if that method
263
     * exists. If "$Action" isn't given, it will use "index" as a default.
264
     *
265
     * @param HTTPRequest $request
266
     * @param string $action
267
     *
268
     * @return DBHTMLText|HTTPResponse
269
     */
270
    protected function handleAction($request, $action)
271
    {
272
        foreach ($request->latestParams() as $k => $v) {
273
            if ($v || !isset($this->urlParams[$k])) {
274
                $this->urlParams[$k] = $v;
275
            }
276
        }
277
278
        $this->action = $action;
279
        $this->requestParams = $request->requestVars();
280
281
        if ($this->hasMethod($action)) {
282
            $result = parent::handleAction($request, $action);
283
284
            // If the action returns an array, customise with it before rendering the template.
285
            if (is_array($result)) {
286
                return $this->getViewer($action)->process($this->customise($result));
287
            } else {
288
                return $result;
289
            }
290
        }
291
292
        // Fall back to index action with before/after handlers
293
        $beforeResult = $this->extend('beforeCallActionHandler', $request, $action);
294
        if ($beforeResult) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $beforeResult 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...
295
            return reset($beforeResult);
296
        }
297
298
        $result = $this->getViewer($action)->process($this);
299
300
        $afterResult = $this->extend('afterCallActionHandler', $request, $action, $result);
301
        if ($afterResult) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $afterResult 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...
302
            return reset($afterResult);
303
        }
304
305
        return $result;
306
    }
307
308
    /**
309
     * @param array $urlParams
310
     * @return $this
311
     */
312
    public function setURLParams($urlParams)
313
    {
314
        $this->urlParams = $urlParams;
315
        return $this;
316
    }
317
318
    /**
319
     * Returns the parameters extracted from the URL by the {@link Director}.
320
     *
321
     * @return array
322
     */
323
    public function getURLParams()
324
    {
325
        return $this->urlParams;
326
    }
327
328
    /**
329
     * Returns the HTTPResponse object that this controller is building up. Can be used to set the
330
     * status code and headers.
331
     *
332
     * @return HTTPResponse
333
     */
334
    public function getResponse()
335
    {
336
        if (!$this->response) {
337
            $this->setResponse(new HTTPResponse());
338
        }
339
        return $this->response;
340
    }
341
342
    /**
343
     * Sets the HTTPResponse object that this controller is building up.
344
     *
345
     * @param HTTPResponse $response
346
     *
347
     * @return $this
348
     */
349
    public function setResponse(HTTPResponse $response)
350
    {
351
        $this->response = $response;
352
        return $this;
353
    }
354
355
    /**
356
     * @var bool
357
     */
358
    protected $baseInitCalled = false;
359
360
    /**
361
     * This is the default action handler used if a method doesn't exist. It will process the
362
     * controller object with the template returned by {@link getViewer()}.
363
     *
364
     * @param string $action
365
     * @return DBHTMLText
366
     */
367
    public function defaultAction($action)
368
    {
369
        return $this->getViewer($action)->process($this);
370
    }
371
372
    /**
373
     * Returns the action that is being executed on this controller.
374
     *
375
     * @return string
376
     */
377
    public function getAction()
378
    {
379
        return $this->action;
380
    }
381
382
    /**
383
     * Return the viewer identified being the default handler for this Controller/Action combination.
384
     *
385
     * @param string $action
386
     *
387
     * @return SSViewer
388
     */
389
    public function getViewer($action)
390
    {
391
        // Hard-coded templates
392
        if (isset($this->templates[$action]) && $this->templates[$action]) {
393
            $templates = $this->templates[$action];
394
        } elseif (isset($this->templates['index']) && $this->templates['index']) {
395
            $templates = $this->templates['index'];
396
        } elseif ($this->template) {
0 ignored issues
show
Bug introduced by
The property template does not seem to exist. Did you mean templates?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
397
            $templates = $this->template;
0 ignored issues
show
Bug introduced by
The property template does not seem to exist. Did you mean templates?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
398
        } else {
399
            // Build templates based on class hierarchy
400
            $actionTemplates = [];
401
            $classTemplates = [];
402
            $parentClass = static::class;
403
            while ($parentClass !== parent::class) {
404
                // _action templates have higher priority
405
                if ($action && $action != 'index') {
406
                    $actionTemplates[] = strtok($parentClass, '_') . '_' . $action;
407
                }
408
                // class templates have lower priority
409
                $classTemplates[] = strtok($parentClass, '_');
410
                $parentClass = get_parent_class($parentClass);
411
            }
412
413
            // Add controller templates for inheritance chain
414
            $templates = array_unique(array_merge($actionTemplates, $classTemplates));
415
        }
416
417
        return new SSViewer($templates);
418
    }
419
420
    /**
421
     * @param string $action
422
     *
423
     * @return bool
424
     */
425
    public function hasAction($action)
426
    {
427
        return parent::hasAction($action) || $this->hasActionTemplate($action);
428
    }
429
430
    /**
431
     * Removes all the "action" part of the current URL and returns the result. If no action parameter
432
     * is present, returns the full URL.
433
     *
434
     * @param string $fullURL
435
     * @param null|string $action
436
     *
437
     * @return string
438
     */
439
    public function removeAction($fullURL, $action = null)
440
    {
441
        if (!$action) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $action of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
442
            $action = $this->getAction();    //default to current action
443
        }
444
        $returnURL = $fullURL;
445
446
        if (($pos = strpos($fullURL, $action)) !== false) {
447
            $returnURL = substr($fullURL, 0, $pos);
448
        }
449
450
        return $returnURL;
451
    }
452
453
    /**
454
     * Return the class that defines the given action, so that we know where to check allowed_actions.
455
     * Overrides RequestHandler to also look at defined templates.
456
     *
457
     * @param string $action
458
     *
459
     * @return string
460
     */
461
    protected function definingClassForAction($action)
462
    {
463
        $definingClass = parent::definingClassForAction($action);
464
        if ($definingClass) {
465
            return $definingClass;
466
        }
467
468
        $class = static::class;
469
        while ($class != 'SilverStripe\\Control\\RequestHandler') {
470
            $templateName = strtok($class, '_') . '_' . $action;
471
            if (SSViewer::hasTemplate($templateName)) {
472
                return $class;
473
            }
474
475
            $class = get_parent_class($class);
476
        }
477
478
        return null;
479
    }
480
481
    /**
482
     * Returns TRUE if this controller has a template that is specifically designed to handle a
483
     * specific action.
484
     *
485
     * @param string $action
486
     *
487
     * @return bool
488
     */
489
    public function hasActionTemplate($action)
490
    {
491
        if (isset($this->templates[$action])) {
492
            return true;
493
        }
494
495
        $parentClass = static::class;
496
        $templates   = array();
497
498
        while ($parentClass != __CLASS__) {
499
            $templates[] = strtok($parentClass, '_') . '_' . $action;
500
            $parentClass = get_parent_class($parentClass);
501
        }
502
503
        return SSViewer::hasTemplate($templates);
504
    }
505
506
    /**
507
     * Render the current controller with the templates determined by {@link getViewer()}.
508
     *
509
     * @param array $params
510
     *
511
     * @return string
512
     */
513
    public function render($params = null)
514
    {
515
        $template = $this->getViewer($this->getAction());
516
517
        // if the object is already customised (e.g. through Controller->run()), use it
518
        $obj = $this->getCustomisedObj() ?: $this;
519
520
        if ($params) {
521
            $obj = $this->customise($params);
522
        }
523
524
        return $template->process($obj);
525
    }
526
527
    /**
528
     * Call this to disable site-wide basic authentication for a specific controller. This must be
529
     * called before Controller::init(). That is, you must call it in your controller's init method
530
     * before it calls parent::init().
531
     */
532
    public function disableBasicAuth()
533
    {
534
        $this->basicAuthEnabled = false;
535
    }
536
537
    /**
538
     * Returns the current controller.
539
     *
540
     * @return Controller
541
     */
542
    public static function curr()
543
    {
544
        if (Controller::$controller_stack) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \SilverStripe\Control\Co...ller::$controller_stack 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...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
545
            return Controller::$controller_stack[0];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
546
        }
547
        user_error("No current controller available", E_USER_WARNING);
548
        return null;
549
    }
550
551
    /**
552
     * Tests whether we have a currently active controller or not. True if there is at least 1
553
     * controller in the stack.
554
     *
555
     * @return bool
556
     */
557
    public static function has_curr()
558
    {
559
        return Controller::$controller_stack ? true : false;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
560
    }
561
562
    /**
563
     * Returns true if the member is allowed to do the given action. Defaults to the currently logged
564
     * in user.
565
     *
566
     * @param string $perm
567
     * @param null|member $member
568
     *
569
     * @return bool
570
     */
571
    public function can($perm, $member = null)
572
    {
573
        if (!$member) {
574
            $member = Security::getCurrentUser();
575
        }
576
        if (is_array($perm)) {
577
            $perm = array_map(array($this, 'can'), $perm, array_fill(0, count($perm), $member));
578
            return min($perm);
579
        }
580
        if ($this->hasMethod($methodName = 'can' . $perm)) {
581
            return $this->$methodName($member);
582
        } else {
583
            return true;
584
        }
585
    }
586
587
    /**
588
     * Pushes this controller onto the stack of current controllers. This means that any redirection,
589
     * session setting, or other things that rely on Controller::curr() will now write to this
590
     * controller object.
591
     *
592
     * Note: Ensure this controller is assigned a request with a valid session before pushing
593
     * it to the stack.
594
     */
595
    public function pushCurrent()
596
    {
597
        // Ensure this controller has a valid session
598
        $this->getRequest()->getSession();
599
        array_unshift(self::$controller_stack, $this);
600
    }
601
602
    /**
603
     * Pop this controller off the top of the stack.
604
     */
605
    public function popCurrent()
606
    {
607
        if ($this === self::$controller_stack[0]) {
608
            array_shift(self::$controller_stack);
609
        } else {
610
            $class = static::class;
611
            user_error(
612
                "popCurrent called on {$class} controller, but it wasn't at the top of the stack",
613
                E_USER_WARNING
614
            );
615
        }
616
    }
617
618
    /**
619
     * Redirect to the given URL.
620
     *
621
     * @param string $url
622
     * @param int $code
623
     * @return HTTPResponse
624
     */
625
    public function redirect($url, $code = 302)
626
    {
627
        if ($this->getResponse()->getHeader('Location') && $this->getResponse()->getHeader('Location') != $url) {
628
            user_error("Already directed to " . $this->getResponse()->getHeader('Location')
629
                . "; now trying to direct to $url", E_USER_WARNING);
630
            return null;
631
        }
632
        $response = parent::redirect($url, $code);
633
        $this->setResponse($response);
634
        return $response;
635
    }
636
637
    /**
638
     * Tests whether a redirection has been requested. If redirect() has been called, it will return
639
     * the URL redirected to. Otherwise, it will return null.
640
     *
641
     * @return null|string
642
     */
643
    public function redirectedTo()
644
    {
645
        return $this->getResponse() && $this->getResponse()->getHeader('Location');
646
    }
647
648
    /**
649
     * Joins two or more link segments together, putting a slash between them if necessary. Use this
650
     * for building the results of {@link Link()} methods. If either of the links have query strings,
651
     * then they will be combined and put at the end of the resulting url.
652
     *
653
     * Caution: All parameters are expected to be URI-encoded already.
654
     *
655
     * @param string|array $arg,.. One or more link segments, or list of link segments as an array
0 ignored issues
show
Bug introduced by
There is no parameter named $arg,... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
656
     * @return string
657
     */
658
    public static function join_links($arg = null)
659
    {
660
        if (func_num_args() === 1 && is_array($arg)) {
661
            $args = $arg;
662
        } else {
663
            $args = func_get_args();
664
        }
665
        $result = "";
666
        $queryargs = array();
667
        $fragmentIdentifier = null;
668
669
        foreach ($args as $arg) {
670
            // Find fragment identifier - keep the last one
671
            if (strpos($arg, '#') !== false) {
672
                list($arg, $fragmentIdentifier) = explode('#', $arg, 2);
673
            }
674
            // Find querystrings
675
            if (strpos($arg, '?') !== false) {
676
                list($arg, $suffix) = explode('?', $arg, 2);
677
                parse_str($suffix, $localargs);
678
                $queryargs = array_merge($queryargs, $localargs);
679
            }
680
            if ((is_string($arg) && $arg) || is_numeric($arg)) {
681
                $arg = (string) $arg;
682
                if ($result && substr($result, -1) != '/' && $arg[0] != '/') {
683
                    $result .= "/$arg";
684
                } else {
685
                    $result .= (substr($result, -1) == '/' && $arg[0] == '/') ? ltrim($arg, '/') : $arg;
686
                }
687
            }
688
        }
689
690
        if ($queryargs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $queryargs 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...
691
            $result .= '?' . http_build_query($queryargs);
692
        }
693
694
        if ($fragmentIdentifier) {
695
            $result .= "#$fragmentIdentifier";
696
        }
697
698
        return $result;
699
    }
700
701
    /**
702
     * @return array
703
     */
704
    public static function get_template_global_variables()
705
    {
706
        return array(
707
            'CurrentPage' => 'curr',
708
        );
709
    }
710
}
711