Passed
Push — 3.x ( 589a0f...691809 )
by Jerome
65:54 queued 11s
created

ResponseFactory::closeSession()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 4
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Elgg\Http;
4
5
use Elgg\Ajax\Service as AjaxService;
6
use Elgg\EventsService;
7
use Elgg\PluginHooksService;
8
use ElggEntity;
9
use InvalidArgumentException;
10
use InvalidParameterException;
11
use Symfony\Component\HttpFoundation\Cookie;
12
use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirectResponse;
13
use Symfony\Component\HttpFoundation\Response;
14
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
15
16
/**
17
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
18
 *
19
 * @since 2.3
20
 * @access private
21
 */
22
class ResponseFactory {
23
24
	/**
25
	 * @var Request
26
	 */
27
	private $request;
28
29
	/**
30
	 * @var AjaxService
31
	 */
32
	private $ajax;
33
34
	/**
35
	 * @var PluginHooksService
36
	 */
37
	private $hooks;
38
39
	/**
40
	 * @var ResponseTransport
41
	 */
42
	private $transport;
43
44
	/**
45
	 * @var Response|false
46
	 */
47
	private $response_sent = false;
48
49
	/**
50
	 * @var ResponseHeaderBag
51
	 */
52
	private $headers;
53
	
54
	/**
55
	 * @var EventsService
56
	 */
57
	private $events;
58
59
	/**
60
	 * Constructor
61
	 *
62
	 * @param Request            $request   HTTP request
63
	 * @param PluginHooksService $hooks     Plugin hooks service
64
	 * @param AjaxService        $ajax      AJAX service
65
	 * @param ResponseTransport  $transport Response transport
66
	 * @param EventsService      $events    Events service
67
	 */
68 286
	public function __construct(Request $request, PluginHooksService $hooks, AjaxService $ajax, ResponseTransport $transport, EventsService $events) {
69 286
		$this->request = $request;
70 286
		$this->hooks = $hooks;
71 286
		$this->ajax = $ajax;
72 286
		$this->transport = $transport;
73 286
		$this->events = $events;
74
		
75 286
		$this->headers = new ResponseHeaderBag();
76 286
	}
77
78
	/**
79
	 * Sets headers to apply to all responses being sent
80
	 *
81
	 * @param string $name    Header name
82
	 * @param string $value   Header value
83
	 * @param bool   $replace Replace existing headers
84
	 * @return void
85
	 */
86 35
	public function setHeader($name, $value, $replace = true) {
87 35
		$this->headers->set($name, $value, $replace);
88 35
	}
89
90
	/**
91
	 * Set a cookie, but allow plugins to customize it first.
92
	 *
93
	 * To customize all cookies, register for the 'init:cookie', 'all' event.
94
	 *
95
	 * @param \ElggCookie $cookie The cookie that is being set
96
	 * @return bool
97
	 */
98 2
	public function setCookie(\ElggCookie $cookie) {
99 2
		if (!$this->events->trigger('init:cookie', $cookie->name, $cookie)) {
100
			return false;
101
		}
102
103 2
		$symfony_cookie = new Cookie(
104 2
			$cookie->name,
105 2
			$cookie->value,
106 2
			$cookie->expire,
107 2
			$cookie->path,
108 2
			$cookie->domain,
109 2
			$cookie->secure,
110 2
			$cookie->httpOnly
111
		);
112
113 2
		$this->headers->setCookie($symfony_cookie);
114 2
		return true;
115
	}
116
117
	/**
118
	 * Get headers set to apply to all responses
119
	 *
120
	 * @param bool $remove_existing Remove existing headers found in headers_list()
121
	 * @return ResponseHeaderBag
122
	 */
123 121
	public function getHeaders($remove_existing = true) {
124
		// Add headers that have already been set by underlying views
125
		// e.g. viewtype page shells set content-type headers
126 121
		$headers_list = headers_list();
127 121
		foreach ($headers_list as $header) {
128
			if (stripos($header, 'HTTP/1.1') !== false) {
129
				continue;
130
			}
131
132
			list($name, $value) = explode(':', $header, 2);
133
			$this->setHeader($name, ltrim($value), false);
134
			if ($remove_existing) {
135
				header_remove($name);
136
			}
137
		}
138
139 121
		return $this->headers;
140
	}
141
142
	/**
143
	 * Creates an HTTP response
144
	 *
145
	 * @param string  $content The response content
146
	 * @param integer $status  The response status code
147
	 * @param array   $headers An array of response headers
148
	 * @return Response
149
	 */
150 102
	public function prepareResponse($content = '', $status = 200, array $headers = []) {
151 102
		$header_bag = $this->getHeaders();
152 102
		$header_bag->add($headers);
153 102
		$response = new Response($content, $status, $header_bag->all());
154 102
		$response->prepare($this->request);
155 102
		return $response;
156
	}
157
158
	/**
159
	 * Creates a redirect response
160
	 *
161
	 * @param string  $url     URL to redirect to
162
	 * @param integer $status  The status code (302 by default)
163
	 * @param array   $headers An array of response headers (Location is always set to the given URL)
164
	 * @return SymfonyRedirectResponse
165
	 * @throws InvalidArgumentException
166
	 */
167 18
	public function prepareRedirectResponse($url, $status = 302, array $headers = []) {
168 18
		$header_bag = $this->getHeaders();
169 18
		$header_bag->add($headers);
170 18
		$response = new SymfonyRedirectResponse($url, $status, $header_bag->all());
171 17
		$response->prepare($this->request);
172 17
		return $response;
173
	}
174
175
	/**
176
	 * Send a response
177
	 *
178
	 * @param Response $response Response object
179
	 * @return Response|false
180
	 */
181 146
	public function send(Response $response) {
182
183 146
		if ($this->response_sent) {
184 35
			if ($this->response_sent !== $response) {
185 4
				_elgg_services()->logger->error('Unable to send the following response: ' . PHP_EOL
186 4
						. (string) $response . PHP_EOL
187 4
						. 'because another response has already been sent: ' . PHP_EOL
188 35
						. (string) $this->response_sent);
189
			}
190
		} else {
191 146
			if (!$this->events->triggerBefore('send', 'http_response', $response)) {
192
				return false;
193
			}
194
195 146
			$request = $this->request;
196 146
			$method = $request->getRealMethod() ? : 'GET';
197 146
			$path = $request->getElggPath();
198
199 146
			_elgg_services()->logger->notice("Responding to {$method} {$path}");
200 146
			if (!$this->transport->send($response)) {
201
				return false;
202
			}
203
204 146
			$this->events->triggerAfter('send', 'http_response', $response);
205 146
			$this->response_sent = $response;
206
			
207 146
			$this->closeSession();
208
		}
209
210 146
		return $this->response_sent;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->response_sent also could return the type true which is incompatible with the documented return type Symfony\Component\HttpFoundation\Response|false.
Loading history...
211
	}
212
213
	/**
214
	 * Returns a response that was sent to the client
215
	 *
216
	 * @return Response|false
217
	 */
218 138
	public function getSentResponse() {
219 138
		return $this->response_sent;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->response_sent also could return the type boolean which is incompatible with the documented return type Symfony\Component\HttpFoundation\Response|false.
Loading history...
220
	}
221
222
	/**
223
	 * Send HTTP response
224
	 *
225
	 * @param ResponseBuilder $response ResponseBuilder instance
226
	 *                                  An instance of an ErrorResponse, OkResponse or RedirectResponse
227
	 * @return Response
228
	 * @throws \InvalidParameterException
229
	 */
230 134
	public function respond(ResponseBuilder $response) {
231
232 134
		$response_type = $this->parseContext();
233 134
		$response = $this->hooks->trigger('response', $response_type, $response, $response);
234 134
		if (!$response instanceof ResponseBuilder) {
235
			throw new InvalidParameterException("Handlers for 'response','$response_type' plugin hook must "
236
			. "return an instanceof " . ResponseBuilder::class);
237
		}
238
239 134
		if ($response->isNotModified()) {
240
			return $this->send($this->prepareResponse('', ELGG_HTTP_NOT_MODIFIED));
241
		}
242
243 134
		$is_xhr = $this->request->isXmlHttpRequest();
244
245 134
		$is_action = false;
246 134
		if (0 === strpos($response_type, 'action:')) {
247 23
			$is_action = true;
248
		}
249
250 134
		if ($is_action && $response->getForwardURL() === null) {
0 ignored issues
show
introduced by
The condition $response->getForwardURL() === null is always false.
Loading history...
251
			// actions must always set a redirect url
252
			$response->setForwardURL(REFERRER);
253
		}
254
255 134
		if ($response->getForwardURL() === REFERRER) {
0 ignored issues
show
introduced by
The condition $response->getForwardURL() === Elgg\Http\REFERRER is always false.
Loading history...
256 19
			$response->setForwardURL($this->request->headers->get('Referer'));
257
		}
258
259 134
		if ($response->getForwardURL() !== null && !$is_xhr) {
260
			// non-xhr requests should issue a forward if redirect url is set
261
			// unless it's an error, in which case we serve an error page
262 18
			if ($this->isAction() || (!$response->isClientError() && !$response->isServerError())) {
263 13
				$response->setStatusCode(ELGG_HTTP_FOUND);
264
			}
265
		}
266
267 134
		if ($is_xhr && ($is_action || $this->ajax->isAjax2Request())) {
268 33
			if (!$this->ajax->isAjax2Request()) {
269
				// xhr actions using legacy ajax API should return 200 with wrapped data
270 6
				$response->setStatusCode(ELGG_HTTP_OK);
271
			}
272
273
			// Actions always respond with JSON on xhr calls
274 33
			$headers = $response->getHeaders();
275 33
			$headers['Content-Type'] = 'application/json; charset=UTF-8';
276 33
			$response->setHeaders($headers);
277
278 33
			if ($response->isOk()) {
279 26
				$response->setContent($this->wrapAjaxResponse($response->getContent(), $response->getForwardURL()));
280
			}
281
		}
282
283 134
		$content = $this->stringify($response->getContent());
284 134
		$status_code = $response->getStatusCode();
285 134
		$headers = $response->getHeaders();
286
287 134
		if ($response->isRedirection()) {
288 16
			$redirect_url = $response->getForwardURL();
289 16
			return $this->redirect($redirect_url, $status_code);
290
		}
291
292 121
		if ($this->ajax->isReady() && $response->isSuccessful()) {
293 20
			return $this->respondFromContent($content, $status_code, $headers);
294
		}
295
296 101
		if ($response->isClientError() || $response->isServerError() || $response instanceof ErrorResponse) {
297 20
			return $this->respondWithError($content, $status_code, $headers);
298
		}
299
300 81
		return $this->respondFromContent($content, $status_code, $headers);
301
	}
302
303
	/**
304
	 * Send error HTTP response
305
	 *
306
	 * @param string $error       Error message
307
	 * @param int    $status_code HTTP status code
308
	 * @param array  $headers     HTTP headers (will be discarded on AJAX requests)
309
	 * @return Response
310
	 * @throws \InvalidParameterException
311
	 */
312 20
	public function respondWithError($error, $status_code = ELGG_HTTP_BAD_REQUEST, array $headers = []) {
313 20
		if ($this->ajax->isReady()) {
314 7
			return $this->send($this->ajax->respondWithError($error, $status_code));
315
		}
316
317 13
		if ($this->isXhr()) {
318
			// xhr calls to non-actions (e.g. ajax/view or ajax/form) need to receive proper HTTP status code
319 5
			return $this->send($this->prepareResponse($error, $status_code, $headers));
320
		}
321
322 8
		$forward_url = $this->getSiteRefererUrl();
323
324 8
		if (!$this->isAction()) {
325
			$params = [
326 8
				'current_url' => current_page_url(),
327 8
				'forward_url' => $forward_url,
328
			];
329
			// For BC, let plugins serve their own error page
330
			// @see elgg_error_page_handler
331 8
			$forward_reason = (string) $status_code;
332
333 8
			$this->hooks->trigger('forward', $forward_reason, $params, $forward_url);
334
335 8
			if ($this->response_sent) {
336
				// Response was sent from a forward hook
337
				return $this->response_sent;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->response_sent also could return the type true which is incompatible with the documented return type Symfony\Component\HttpFoundation\Response.
Loading history...
338
			}
339
340 8
			if (elgg_view_exists('resources/error')) {
341 7
				$params['type'] = $forward_reason;
342 7
				$params['params']['error'] = $error;
343 7
				$error_page = elgg_view_resource('error', $params);
344
			} else {
345 1
				$error_page = $error;
346
			}
347
348 8
			return $this->send($this->prepareResponse($error_page, $status_code));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->send($this...or_page, $status_code)) could also return false which is incompatible with the documented return type Symfony\Component\HttpFoundation\Response. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
349
		}
350
351
		$forward_url = $this->makeSecureForwardUrl($forward_url);
352
		return $this->send($this->prepareRedirectResponse($forward_url));
353
	}
354
355
	/**
356
	 * Send OK response
357
	 *
358
	 * @param string $content     Response body
359
	 * @param int    $status_code HTTP status code
360
	 * @param array  $headers     HTTP headers (will be discarded for AJAX requests)
361
	 *
362
	 * @return Response|false
363
	 */
364 101
	public function respondFromContent($content = '', $status_code = ELGG_HTTP_OK, array $headers = []) {
365
366 101
		if ($this->ajax->isReady()) {
367 20
			$hook_type = $this->parseContext();
368
			// $this->ajax->setStatusCode($status_code);
369 20
			return $this->send($this->ajax->respondFromOutput($content, $hook_type));
370
		}
371
372 81
		return $this->send($this->prepareResponse($content, $status_code, $headers));
373
	}
374
375
	/**
376
	 * Wraps response content in an Ajax2 compatible format
377
	 *
378
	 * @param string $content     Response content
379
	 * @param string $forward_url Forward URL
380
	 * @return string
381
	 */
382 26
	public function wrapAjaxResponse($content = '', $forward_url = null) {
383
384 26
		if (!$this->ajax->isAjax2Request()) {
385 6
			return $this->wrapLegacyAjaxResponse($content, $forward_url);
386
		}
387
388 20
		$content = $this->stringify($content);
389
390 20
		if ($forward_url === REFERRER) {
1 ignored issue
show
introduced by
The condition $forward_url === Elgg\Http\REFERRER is always false.
Loading history...
391
			$forward_url = $this->getSiteRefererUrl();
392
		}
393
394
		$params = [
395 20
			'value' => '',
396 20
			'current_url' => current_page_url(),
397 20
			'forward_url' => elgg_normalize_url($forward_url),
398
		];
399
400 20
		$params['value'] = $this->ajax->decodeJson($content);
401
402 20
		return $this->stringify($params);
403
	}
404
405
	/**
406
	 * Wraps content for compability with legacy Elgg ajax calls
407
	 *
408
	 * @param string $content     Response content
409
	 * @param string $forward_url Forward URL
410
	 * @return string
411
	 */
412 11
	public function wrapLegacyAjaxResponse($content = '', $forward_url = REFERRER) {
413
414 11
		$content = $this->stringify($content);
415
416 11
		if ($forward_url === REFERRER) {
417
			$forward_url = $this->getSiteRefererUrl();
418
		}
419
420
		// always pass the full structure to avoid boilerplate JS code.
421
		$params = [
422 11
			'output' => '',
423 11
			'status' => 0,
424
			'system_messages' => [
425
				'error' => [],
426
				'success' => []
427
			],
428 11
			'current_url' => current_page_url(),
429 11
			'forward_url' => elgg_normalize_url($forward_url),
430
		];
431
432 11
		$params['output'] = $this->ajax->decodeJson($content);
433
434
		// Grab any system messages so we can inject them via ajax too
435 11
		$system_messages = _elgg_services()->systemMessages->dumpRegister();
436
437 11
		if (isset($system_messages['success'])) {
438 2
			$params['system_messages']['success'] = $system_messages['success'];
439
		}
440
441 11
		if (isset($system_messages['error'])) {
442 3
			$params['system_messages']['error'] = $system_messages['error'];
443 3
			$params['status'] = -1;
444
		}
445
446 11
		$response_type = $this->parseContext();
447 11
		list($service, $name) = explode(':', $response_type);
448
		$context = [
449 11
			$service => $name,
450
		];
451 11
		$params = $this->hooks->trigger('output', 'ajax', $context, $params);
452
453 11
		return $this->stringify($params);
454
	}
455
456
	/**
457
	 * Prepares a redirect response
458
	 *
459
	 * @param string $forward_url Redirection URL
460
	 * @param mixed  $status_code HTTP status code or forward reason
461
	 * @return SymfonyRedirectResponse
462
	 * @throws InvalidParameterException
463
	 */
464 30
	public function redirect($forward_url = REFERRER, $status_code = ELGG_HTTP_FOUND) {
465 30
		$location = $forward_url;
466
		
467 30
		if ($forward_url === REFERRER) {
468 3
			$forward_url = $this->getSiteRefererUrl();
469
		}
470
471 30
		$forward_url = $this->makeSecureForwardUrl($forward_url);
472
473
		// allow plugins to rewrite redirection URL
474
		$params = [
475 30
			'current_url' => current_page_url(),
476 30
			'forward_url' => $forward_url,
477 30
			'location' => $location,
478
		];
479
480 30
		$forward_reason = (string) $status_code;
481
482 30
		$forward_url = $this->hooks->trigger('forward', $forward_reason, $params, $forward_url);
483
		
484 30
		if ($this->response_sent) {
485
			// Response was sent from a forward hook
486
			// Clearing handlers to void infinite loops
487 1
			return $this->response_sent;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->response_sent returns the type true which is incompatible with the documented return type Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
488
		}
489
490 30
		if ($forward_url === REFERRER) {
491
			$forward_url = $this->getSiteRefererUrl();
492
		}
493
494 30
		if (!is_string($forward_url)) {
495
			throw new InvalidParameterException("'forward', '$forward_reason' hook must return a valid redirection URL");
496
		}
497
498 30
		$forward_url = $this->makeSecureForwardUrl($forward_url);
499
500 30
		switch ($status_code) {
501 30
			case 'system':
502 30
			case 'csrf':
503
				$status_code = ELGG_HTTP_OK;
504
				break;
505 30
			case 'admin':
506 30
			case 'login':
507 30
			case 'member':
508 30
			case 'walled_garden':
509
			default :
510 30
				$status_code = (int) $status_code;
511 30
				if (!$status_code || $status_code < 100 || $status_code > 599) {
512
					$status_code = ELGG_HTTP_SEE_OTHER;
513
				}
514 30
				break;
515
		}
516
517 30
		if ($this->isXhr()) {
518 12
			if ($status_code < 100 || ($status_code >= 300 && $status_code <= 399) || $status_code > 599) {
519
				// We only want to preserve OK and error codes
520
				// Redirect responses should be converted to OK responses as this is an XHR request
521 9
				$status_code = ELGG_HTTP_OK;
522
			}
523 12
			$output = ob_get_clean();
524 12
			if (!$this->isAction() && !$this->ajax->isAjax2Request()) {
525
				// legacy ajax calls are always OK
526
				// actions are wrapped by ResponseFactory::respond()
527 5
				$status_code = ELGG_HTTP_OK;
528 5
				$output = $this->wrapLegacyAjaxResponse($output, $forward_url);
529
			}
530
531 12
			$response = new OkResponse($output, $status_code, $forward_url);
532 12
			$headers = $response->getHeaders();
533 12
			$headers['Content-Type'] = 'application/json; charset=UTF-8';
534 12
			$response->setHeaders($headers);
535 12
			return $this->respond($response);
536
		}
537
538 18
		if ($this->isAction()) {
539
			// actions should always redirect on non xhr-calls
540 7
			if (!is_int($status_code) || $status_code < 300 || $status_code > 399) {
1 ignored issue
show
introduced by
The condition is_int($status_code) is always true.
Loading history...
541
				$status_code = ELGG_HTTP_SEE_OTHER;
542
			}
543
		}
544
545 18
		$response = new OkResponse('', $status_code, $forward_url);
546 18
		if ($response->isRedirection()) {
547 16
			return $this->send($this->prepareRedirectResponse($forward_url, $status_code));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->send($this...ard_url, $status_code)) returns the type false which is incompatible with the documented return type Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
548
		}
549 2
		return $this->respond($response);
550
	}
551
552
	/**
553
	 * Parses response type to be used as plugin hook type
554
	 * @return string
555
	 */
556 146
	public function parseContext() {
557
558 146
		$segments = $this->request->getUrlSegments();
559
560 146
		$identifier = array_shift($segments);
561 146
		switch ($identifier) {
562 146
			case 'ajax' :
563 21
				$page = array_shift($segments);
564 21
				if ($page === 'view') {
565 14
					$view = implode('/', $segments);
566 14
					return "view:$view";
567 7
				} else if ($page === 'form') {
568 5
					$form = implode('/', $segments);
569 5
					return "form:$form";
570
				}
571 2
				array_unshift($segments, $page);
572 2
				break;
573
574 125
			case 'action' :
575 27
				$action = implode('/', $segments);
576 27
				return "action:$action";
577
		}
578
579 101
		array_unshift($segments, $identifier);
580 101
		$path = implode('/', $segments);
581 101
		return "path:$path";
582
	}
583
584
	/**
585
	 * Check if the request is an XmlHttpRequest
586
	 * @return bool
587
	 */
588 43
	public function isXhr() {
589 43
		return $this->request->isXmlHttpRequest();
590
	}
591
592
	/**
593
	 * Check if the requested path is an action
594
	 * @return bool
595
	 */
596 37
	public function isAction() {
597 37
		if (0 === strpos($this->parseContext(), 'action:')) {
598 11
			return true;
599
		}
600 27
		return false;
601
	}
602
603
	/**
604
	 * Normalizes content into serializable data by walking through arrays
605
	 * and objectifying Elgg entities
606
	 *
607
	 * @param mixed $content Data to normalize
608
	 * @return mixed
609
	 */
610 134
	public function normalize($content = '') {
611 134
		if ($content instanceof ElggEntity) {
612
			$content = (array) $content->toObject();
613
		}
614 134
		if (is_array($content)) {
615 34
			foreach ($content as $key => $value) {
616 34
				$content[$key] = $this->normalize($value);
617
			}
618
		}
619 134
		return $content;
620
	}
621
622
	/**
623
	 * Stringify/serialize response data
624
	 *
625
	 * Casts objects implementing __toString method to strings
626
	 * Serializes non-scalar values to JSON
627
	 *
628
	 * @param mixed $content Content to serialize
629
	 * @return string
630
	 */
631 134
	public function stringify($content = '') {
632 134
		$content = $this->normalize($content);
633 134
		if (empty($content) || (is_object($content) && is_callable($content, '__toString'))) {
0 ignored issues
show
Bug introduced by
'__toString' of type string is incompatible with the type boolean expected by parameter $syntax_only of is_callable(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

633
		if (empty($content) || (is_object($content) && is_callable($content, /** @scrutinizer ignore-type */ '__toString'))) {
Loading history...
634 73
			return (string) $content;
635
		}
636 71
		if (is_scalar($content)) {
637 68
			return $content;
638
		}
639 34
		return json_encode($content, ELGG_JSON_ENCODING);
640
	}
641
642
	/**
643
	 * Replaces response transport
644
	 *
645
	 * @param ResponseTransport $transport Transport interface
646
	 * @return void
647
	 */
648 24
	public function setTransport(ResponseTransport $transport) {
649 24
		$this->transport = $transport;
650 24
	}
651
	
652
	/**
653
	 * Ensures the referer header is a site url
654
	 *
655
	 * @return string
656
	 */
657 11
	protected function getSiteRefererUrl() {
658 11
		$unsafe_url = $this->request->headers->get('Referer');
659 11
		$safe_url = elgg_normalize_site_url($unsafe_url);
660 11
		if ($safe_url !== false) {
661 10
			return $safe_url;
662
		}
663
		
664 1
		return '';
665
	}
666
	
667
	/**
668
	 * Ensure the url has a valid protocol for browser use
669
	 *
670
	 * @param string $url url the secure
671
	 *
672
	 * @return string
673
	 */
674 30
	protected function makeSecureForwardUrl($url) {
675 30
		$url = elgg_normalize_url($url);
676 30
		if (!preg_match('/^(http|https|ftp|sftp|ftps):\/\//', $url)) {
677
			return elgg_get_site_url();
678
		}
679
		
680 30
		return $url;
681
	}
682
	
683
	/**
684
	 * Closes the session
685
	 *
686
	 * Force closing the session so session is saved to the database before headers are sent
687
	 * preventing race conditions with session data
688
	 *
689
	 * @see https://github.com/Elgg/Elgg/issues/12348
690
	 *
691
	 * @return void
692
	 */
693 146
	protected function closeSession() {
694 146
		$session = elgg_get_session();
695 146
		if ($session->isStarted()) {
696 120
			$session->save();
697
		}
698 146
	}
699
}
700