Completed
Push — 3 ( 1d0cff...2b4954 )
by Damian
13:47
created

Controller::handleRequest()   C

Complexity

Conditions 9
Paths 24

Size

Total Lines 53
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 35
nc 24
nop 2
dl 0
loc 53
rs 6.8963
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Base controller class.
4
 * Controllers are the cornerstone of all site functionality in SilverStripe.  The {@link Director}
5
 * selects a controller to pass control to, and then calls {@link run()}.  This method will execute
6
 * the appropriate action - either by calling the action method, or displaying the action's template.
7
 *
8
 * See {@link getTemplate()} for information on how the template is chosen.
9
 * @package framework
10
 * @subpackage control
11
 */
12
class Controller extends RequestHandler implements TemplateGlobalProvider {
13
14
	/**
15
	 * @var array $urlParams An array of arguments extracted from the URL
16
	 */
17
	protected $urlParams;
18
19
	/**
20
	 * @var array $requestParams Contains all GET and POST parameters
21
	 * passed to the current {@link SS_HTTPRequest}.
22
	 * @uses SS_HTTPRequest->requestVars()
23
	 */
24
	protected $requestParams;
25
26
	/**
27
	 * @var string $action The URL part matched on the current controller as
28
	 * determined by the "$Action" part of the {@link $url_handlers} definition.
29
	 * Should correlate to a public method on this controller.
30
	 * Used in {@link render()} and {@link getViewer()} to determine
31
	 * action-specific templates.
32
	 */
33
	protected $action;
34
35
	/**
36
	 * The {@link Session} object for this controller
37
	 */
38
	protected $session;
39
40
	/**
41
	 * Stack of current controllers.
42
	 * Controller::$controller_stack[0] is the current controller.
43
	 */
44
	protected static $controller_stack = array();
45
46
	protected $basicAuthEnabled = true;
47
48
	/**
49
	 * @var SS_HTTPResponse $response The response object that the controller returns.
50
	 * Set in {@link handleRequest()}.
51
	 */
52
	protected $response;
53
54
	/**
55
	 * Default URL handlers - (Action)/(ID)/(OtherID)
56
	 */
57
	private static $url_handlers = array(
58
		'$Action//$ID/$OtherID' => 'handleAction',
59
	);
60
61
	private static $allowed_actions = array(
62
		'handleAction',
63
		'handleIndex',
64
	);
65
66
	/**
67
	 * Initialisation function that is run before any action on the controller is called.
68
	 *
69
	 * @uses BasicAuth::requireLogin()
70
	 */
71
	public function init() {
72
		if($this->basicAuthEnabled) BasicAuth::protect_site_if_necessary();
73
74
		// Directly access the session variable just in case the Group or Member tables don't yet exist
75
		if(Member::config()->log_last_visited) {
76
			Deprecation::notice(
77
				'4.0',
78
				'Member::$LastVisited is deprecated. From 4.0 onwards you should implement this as a custom extension'
79
			);
80
			if(Session::get('loggedInAs') && Security::database_is_ready() && ($member = Member::currentUser())) {
81
				DB::prepared_query(
82
					sprintf('UPDATE "Member" SET "LastVisited" = %s WHERE "ID" = ?', DB::get_conn()->now()),
83
					array($member->ID)
84
				);
85
			}
86
		}
87
88
		// This is used to test that subordinate controllers are actually calling parent::init() - a common bug
89
		$this->baseInitCalled = true;
90
	}
91
92
	/**
93
	 * Executes this controller, and return an {@link SS_HTTPResponse} object with the result.
94
	 *
95
	 * This method first does a few set-up activities:
96
	 *  - Push this controller ont to the controller stack -
97
	 *    see {@link Controller::curr()} for information about this.
98
	 *  - Call {@link init()}
99
	 *  - Defer to {@link RequestHandler->handleRequest()} to determine which action
100
	 *    should be executed
101
	 *
102
	 * Note: $requestParams['executeForm'] support was removed,
103
	 * make the following change in your URLs:
104
	 * "/?executeForm=FooBar" -> "/FooBar"
105
	 * Also make sure "FooBar" is in the $allowed_actions of your controller class.
106
	 *
107
	 * Note: You should rarely need to overload run() -
108
	 * this kind of change is only really appropriate for things like nested
109
	 * controllers - {@link ModelAsController} and {@link RootURLController}
110
	 * are two examples here.  If you want to make more
111
	 * orthodox functionality, it's better to overload {@link init()} or {@link index()}.
112
	 *
113
	 * Important: If you are going to overload handleRequest,
114
	 * make sure that you start the method with $this->pushCurrent()
115
	 * and end the method with $this->popCurrent().
116
	 * Failure to do this will create weird session errors.
117
	 *
118
	 * @param $request The {@link SS_HTTPRequest} object that is responsible
119
	 *  for distributing request parsing.
120
	 * @return SS_HTTPResponse The response that this controller produces,
121
	 *  including HTTP headers such as redirection info
122
	 */
123
	public function handleRequest(SS_HTTPRequest $request, DataModel $model) {
0 ignored issues
show
Coding Style introduced by
handleRequest 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...
124
		if(!$request) user_error("Controller::handleRequest() not passed a request!", E_USER_ERROR);
125
126
		$this->pushCurrent();
127
		$this->urlParams = $request->allParams();
128
		$this->setRequest($request);
129
		$this->getResponse();
130
		$this->setDataModel($model);
131
132
		$this->extend('onBeforeInit');
133
134
		// Init
135
		$this->baseInitCalled = false;
136
		$this->init();
137
		if(!$this->baseInitCalled) {
138
			user_error("init() method on class '$this->class' doesn't call Controller::init()."
139
				. "Make sure that you have parent::init() included.", E_USER_WARNING);
140
		}
141
142
		$this->extend('onAfterInit');
143
144
		$response = $this->getResponse();
145
		// If we had a redirection or something, halt processing.
146
		if($response->isFinished()) {
147
			$this->popCurrent();
148
			return $response;
149
		}
150
151
		$body = parent::handleRequest($request, $model);
152
		if($body instanceof SS_HTTPResponse) {
153
			if(isset($_REQUEST['debug_request'])) {
154
				Debug::message("Request handler returned SS_HTTPResponse object to $this->class controller;"
155
					. "returning it without modification.");
156
			}
157
			$response = $body;
158
			$this->setResponse($response);
159
160
		} else {
161
			if($body instanceof SS_Object && $body->hasMethod('getViewer')) {
162
				if(isset($_REQUEST['debug_request'])) {
163
					Debug::message("Request handler $body->class object to $this->class controller;"
164
						. "rendering with template returned by $body->class::getViewer()");
165
				}
166
				$body = $body->getViewer($this->getAction())->process($body);
167
			}
168
169
			$response->setBody($body);
170
		}
171
172
		ContentNegotiator::process($response);
173
		$this->popCurrent();
174
		return $response;
175
	}
176
177
	/**
178
	 * Controller's default action handler.  It will call the method named in $Action, if that method exists.
179
	 * If $Action isn't given, it will use "index" as a default.
180
	 */
181
	protected function handleAction($request, $action) {
182
		$this->extend('beforeCallActionHandler', $request, $action);
183
184
		foreach($request->latestParams() as $k => $v) {
185
			if($v || !isset($this->urlParams[$k])) $this->urlParams[$k] = $v;
186
		}
187
188
		$this->action = $action;
189
		$this->requestParams = $request->requestVars();
190
191
		if($this->hasMethod($action)) {
192
			$result = parent::handleAction($request, $action);
193
194
			// If the action returns an array, customise with it before rendering the template.
195
			if(is_array($result)) {
196
				return $this->getViewer($action)->process($this->customise($result));
197
			} else {
198
				return $result;
199
			}
200
		} else {
201
			return $this->getViewer($action)->process($this);
202
		}
203
	}
204
205
	public function setURLParams($urlParams) {
206
		$this->urlParams = $urlParams;
207
	}
208
209
	/**
210
	 * @return array The parameters extracted from the URL by the {@link Director}.
211
	 */
212
	public function getURLParams() {
213
		return $this->urlParams;
214
	}
215
216
	/**
217
	 * Returns the SS_HTTPResponse object that this controller is building up.
218
	 * Can be used to set the status code and headers
219
	 */
220
	public function getResponse() {
221
		if (!$this->response) {
222
			$this->setResponse(new SS_HTTPResponse());
223
		}
224
		return $this->response;
225
	}
226
227
	/**
228
	 * Sets the SS_HTTPResponse object that this controller is building up.
229
	 *
230
	 * @param SS_HTTPResponse $response
231
	 * @return Controller
232
	 */
233
	public function setResponse(SS_HTTPResponse $response) {
234
		$this->response = $response;
235
		return $this;
236
	}
237
238
	protected $baseInitCalled = false;
239
240
	/**
241
	 * Return the object that is going to own a form that's being processed, and handle its execution.
242
	 * Note that the result needn't be an actual controller object.
243
	 */
244
	public function getFormOwner() {
245
		// Get the appropraite ocntroller: sometimes we want to get a form from another controller
246
		if(isset($this->requestParams['formController'])) {
247
			$formController = Director::getControllerForURL($this->requestParams['formController']);
248
249
			while(is_a($formController, 'NestedController')) {
250
				$formController = $formController->getNestedController();
251
			}
252
			return $formController;
253
254
		} else {
255
			return $this;
256
		}
257
	}
258
259
	/**
260
	 * This is the default action handler used if a method doesn't exist.
261
	 * It will process the controller object with the template returned by {@link getViewer()}
262
	 */
263
	public function defaultAction($action) {
264
		return $this->getViewer($action)->process($this);
265
	}
266
267
	/**
268
	 * Returns the action that is being executed on this controller.
269
	 */
270
	public function getAction() {
271
		return $this->action;
272
	}
273
274
	/**
275
	 * Return an SSViewer object to process the data
276
	 * @return SSViewer The viewer identified being the default handler for this Controller/Action combination
277
	 */
278
	public function getViewer($action) {
279
		// Hard-coded templates
280
		if(isset($this->templates[$action]) && $this->templates[$action]) {
281
			$templates = $this->templates[$action];
282
283
		}	else if(isset($this->templates['index']) && $this->templates['index']) {
284
			$templates = $this->templates['index'];
285
286
		}	else if($this->template) {
287
			$templates = $this->template;
0 ignored issues
show
Documentation introduced by
The property template does not exist on object<Controller>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
288
		} else {
289
			// Add action-specific templates for inheritance chain
290
			$templates = array();
291
			$parentClass = $this->class;
292
			if($action && $action != 'index') {
293
				$parentClass = $this->class;
294
				while($parentClass != "Controller") {
295
					$templates[] = strtok($parentClass,'_') . '_' . $action;
296
					$parentClass = get_parent_class($parentClass);
297
				}
298
			}
299
			// Add controller templates for inheritance chain
300
			$parentClass = $this->class;
301
			while($parentClass != "Controller") {
302
				$templates[] = strtok($parentClass,'_');
303
				$parentClass = get_parent_class($parentClass);
304
			}
305
306
			$templates[] = 'Controller';
307
308
			// remove duplicates
309
			$templates = array_unique($templates);
310
		}
311
312
		return new SSViewer($templates);
313
	}
314
315
	public function hasAction($action) {
316
		return parent::hasAction($action) || $this->hasActionTemplate($action);
317
	}
318
319
	/**
320
	 * Removes all the "action" part of the current URL and returns the result.
321
	 * If no action parameter is present, returns the full URL
322
	 * @static
323
	 * @return String
324
	 */
325
	public function removeAction($fullURL, $action = null) {
326
		if (!$action) $action = $this->getAction();    //default to current action
327
		$returnURL = $fullURL;
328
329
		if (($pos = strpos($fullURL, $action)) !== false) {
330
			$returnURL = substr($fullURL,0,$pos);
331
		}
332
333
		return $returnURL;
334
	}
335
336
	/**
337
	 * Return the class that defines the given action, so that we know where to check allowed_actions.
338
	 * Overrides RequestHandler to also look at defined templates
339
	 */
340
	protected function definingClassForAction($action) {
341
		$definingClass = parent::definingClassForAction($action);
342
		if($definingClass) return $definingClass;
343
344
		$class = get_class($this);
345
		while($class != 'RequestHandler') {
346
			$templateName = strtok($class, '_') . '_' . $action;
347
			if(SSViewer::hasTemplate($templateName)) return $class;
348
349
			$class = get_parent_class($class);
350
		}
351
	}
352
353
	/**
354
	 * Returns TRUE if this controller has a template that is specifically designed to handle a specific action.
355
	 *
356
	 * @param string $action
357
	 * @return bool
358
	 */
359
	public function hasActionTemplate($action) {
360
		if(isset($this->templates[$action])) return true;
361
362
		$parentClass = $this->class;
363
		$templates   = array();
364
365
		while($parentClass != 'Controller') {
366
			$templates[] = strtok($parentClass, '_') . '_' . $action;
367
			$parentClass = get_parent_class($parentClass);
368
		}
369
370
		return SSViewer::hasTemplate($templates);
371
	}
372
373
	/**
374
	 * Render the current controller with the templates determined
375
	 * by {@link getViewer()}.
376
	 *
377
	 * @param array $params Key-value array for custom template variables (Optional)
378
	 * @return string Parsed template content
379
	 */
380
	public function render($params = null) {
381
		$template = $this->getViewer($this->getAction());
382
383
		// if the object is already customised (e.g. through Controller->run()), use it
384
		$obj = ($this->customisedObj) ? $this->customisedObj : $this;
0 ignored issues
show
Bug introduced by
The property customisedObj does not seem to exist. Did you mean customisedObject?

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...
385
386
		if($params) $obj = $this->customise($params);
387
388
		return $template->process($obj);
389
	}
390
391
	/**
392
	 * Call this to disable site-wide basic authentication for a specific contoller.
393
	 * This must be called before Controller::init().  That is, you must call it in your controller's
394
	 * init method before it calls parent::init().
395
	 */
396
	public function disableBasicAuth() {
397
		$this->basicAuthEnabled = false;
398
	}
399
400
	/**
401
	 * Returns the current controller
402
	 * @return Controller
403
	 */
404
	public static function curr() {
405
		if(Controller::$controller_stack) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \Controller::$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...
406
			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...
407
		} else {
408
			user_error("No current controller available", E_USER_WARNING);
409
		}
410
	}
411
412
	/**
413
	 * Tests whether we have a currently active controller or not
414
	 * @return boolean True if there is at least 1 controller in the stack.
415
	 */
416
	public static function has_curr() {
417
		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...
418
	}
419
420
	/**
421
	 * Returns true if the member is allowed to do the given action.
422
	 * @param perm The permission to be checked, such as 'View'.
423
	 * @param member The member whose permissions need checking.  Defaults to the currently logged
424
	 * in user.
425
	 * @return boolean
426
	 */
427
	public function can($perm, $member = null) {
428
		if(!$member) $member = Member::currentUser();
429
		if(is_array($perm)) {
430
			$perm = array_map(array($this, 'can'), $perm, array_fill(0, count($perm), $member));
431
			return min($perm);
432
		}
433
		if($this->hasMethod($methodName = 'can' . $perm)) {
434
			return $this->$methodName($member);
435
		} else {
436
			return true;
437
		}
438
	}
439
440
	//---------------------------------------------------------------------------------------------------------------
441
442
	/**
443
	 * Pushes this controller onto the stack of current controllers.
444
	 * This means that any redirection, session setting, or other things that rely on Controller::curr() will now
445
	 * write to this controller object.
446
	 */
447
	public function pushCurrent() {
448
		array_unshift(self::$controller_stack, $this);
449
		// Create a new session object
450
		if(!$this->session) {
451
			if(isset(self::$controller_stack[1])) {
452
				$this->session = self::$controller_stack[1]->getSession();
453
			} else {
454
				$this->session = Injector::inst()->create('Session', array());
455
			}
456
		}
457
	}
458
459
	/**
460
	 * Pop this controller off the top of the stack.
461
	 */
462
	public function popCurrent() {
463
		if($this === self::$controller_stack[0]) {
464
			array_shift(self::$controller_stack);
465
		} else {
466
			user_error("popCurrent called on $this->class controller, but it wasn't at the top of the stack",
467
				E_USER_WARNING);
468
		}
469
	}
470
471
	/**
472
	 * Redirect to the given URL.
473
	 *
474
	 * @return SS_HTTPResponse
475
	 */
476
	public function redirect($url, $code=302) {
477
478
		if($this->getResponse()->getHeader('Location') && $this->getResponse()->getHeader('Location') != $url) {
479
			user_error("Already directed to " . $this->getResponse()->getHeader('Location')
480
				. "; now trying to direct to $url", E_USER_WARNING);
481
			return;
482
		}
483
484
		// Attach site-root to relative links, if they have a slash in them
485
		if($url=="" || $url[0]=='?' || (substr($url,0,4) != "http" && $url[0] != "/" && strpos($url,'/') !== false)) {
486
			$url = Director::baseURL() . $url;
487
		}
488
489
		return $this->getResponse()->redirect($url, $code);
490
	}
491
492
	/**
493
	 * Redirect back. Uses either the HTTP_REFERER or a manually set request-variable called "BackURL".
494
	 * This variable is needed in scenarios where not HTTP-Referer is sent (
495
	 * e.g when calling a page by location.href in IE).
496
	 * If none of the two variables is available, it will redirect to the base
497
	 * URL (see {@link Director::baseURL()}).
498
	 * @uses redirect()
499
	 */
500
	public function redirectBack() {
501
		// Don't cache the redirect back ever
502
		HTTPCacheControl::singleton()->disableCache(true);
503
504
		$url = null;
505
506
		// In edge-cases, this will be called outside of a handleRequest() context; in that case,
507
		// redirect to the homepage - don't break into the global state at this stage because we'll
508
		// be calling from a test context or something else where the global state is inappropraite
509
		if($this->getRequest()) {
510
			if($this->getRequest()->requestVar('BackURL')) {
511
				$url = $this->getRequest()->requestVar('BackURL');
512
			} else if($this->getRequest()->isAjax() && $this->getRequest()->getHeader('X-Backurl')) {
513
				$url = $this->getRequest()->getHeader('X-Backurl');
514
			} else if($this->getRequest()->getHeader('Referer')) {
515
				$url = $this->getRequest()->getHeader('Referer');
516
			}
517
		}
518
519
		if(!$url) $url = Director::baseURL();
520
521
		// absolute redirection URLs not located on this site may cause phishing
522
		if(Director::is_site_url($url)) {
523
			$url = Director::absoluteURL($url, true);
524
			return $this->redirect($url);
525
		} else {
526
			return false;
527
		}
528
529
	}
530
531
	/**
532
	 * Tests whether a redirection has been requested.
533
	 * @return string If redirect() has been called, it will return the URL redirected to.  Otherwise, it will
534
	 * return null;
535
	 */
536
	public function redirectedTo() {
537
		return $this->getResponse() && $this->getResponse()->getHeader('Location');
538
	}
539
540
	/**
541
	 * Get the Session object representing this Controller's session
542
	 * @return Session
543
	 */
544
	public function getSession() {
545
		return $this->session;
546
	}
547
548
	/**
549
	 * Set the Session object.
550
	 */
551
	public function setSession(Session $session) {
552
		$this->session = $session;
553
	}
554
555
	/**
556
	 * Joins two or more link segments together, putting a slash between them if necessary.
557
	 * Use this for building the results of {@link Link()} methods.
558
	 * If either of the links have query strings,
559
	 * then they will be combined and put at the end of the resulting url.
560
	 *
561
	 * Caution: All parameters are expected to be URI-encoded already.
562
	 *
563
	 * @param String
564
	 * @return String
565
	 */
566
	public static function join_links() {
567
		$args = func_get_args();
568
		$result = "";
569
		$queryargs = array();
570
		$fragmentIdentifier = null;
571
572
		foreach($args as $arg) {
573
			// Find fragment identifier - keep the last one
574
			if(strpos($arg,'#') !== false) {
575
				list($arg, $fragmentIdentifier) = explode('#',$arg,2);
576
			}
577
			// Find querystrings
578
			if(strpos($arg,'?') !== false) {
579
				list($arg, $suffix) = explode('?',$arg,2);
580
				parse_str($suffix, $localargs);
581
				$queryargs = array_merge($queryargs, $localargs);
582
			}
583
			if((is_string($arg) && $arg) || is_numeric($arg)) {
584
				$arg = (string) $arg;
585
				if($result && substr($result,-1) != '/' && $arg[0] != '/') $result .= "/$arg";
586
				else $result .= (substr($result, -1) == '/' && $arg[0] == '/') ? ltrim($arg, '/') : $arg;
587
			}
588
		}
589
590
		if($queryargs) $result .= '?' . http_build_query($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...
591
592
		if($fragmentIdentifier) $result .= "#$fragmentIdentifier";
593
594
		return $result;
595
	}
596
597
	public static function get_template_global_variables() {
598
		return array(
599
			'CurrentPage' => 'curr',
600
		);
601
	}
602
}
603
604
605