Passed
Push — csp ( 63856a...2ca554 )
by Fabio
06:52 queued 38s
created

THttpResponse::setHeadersManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * THttpResponse class
5
 *
6
 * @author Qiang Xue <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\Web;
12
13
use Prado\Exceptions\TExitException;
14
use Prado\Exceptions\TInvalidDataValueException;
15
use Prado\Exceptions\TInvalidOperationException;
16
use Prado\Prado;
17
use Prado\TPropertyValue;
18
19
/**
20
 * THttpResponse class
21
 *
22
 * THttpResponse implements the mechanism for sending output to client users.
23
 *
24
 * To output a string to client, use {@see write()}. By default, the output is
25
 * buffered until {@see flush()} is called or the application ends. The output in
26
 * the buffer can also be cleaned by {@see clear()}. To disable output buffering,
27
 * set BufferOutput property to false.
28
 *
29
 * To send cookies to client, use {@see getCookies()}.
30
 * To redirect client browser to a new URL, use {@see redirect()}.
31
 * To send a file to client, use {@see writeFile()}.
32
 *
33
 * By default, THttpResponse is registered with {@see \Prado\TApplication} as the
34
 * response module. It can be accessed via {@see \Prado\TApplication::getResponse()}.
35
 *
36
 * THttpResponse may be configured in application configuration file as follows
37
 *
38
 * <module id="response" class="Prado\Web\THttpResponse" CacheExpire="20" CacheControl="nocache" BufferOutput="true" />
39
 *
40
 * where {@see getCacheExpire CacheExpire}, {@see getCacheControl CacheControl}
41
 * and {@see getBufferOutput BufferOutput} are optional properties of THttpResponse.
42
 *
43
 * THttpResponse sends charset header if either {@see setCharset() Charset}
44
 * or {@see \Prado\I18N\TGlobalization::setCharset() TGlobalization.Charset} is set.
45
 *
46
 * Since 3.1.2, HTTP status code can be set with the {@see setStatusCode StatusCode} property.
47
 *
48
 * Note: Some HTTP Status codes can require additional header or body information. So, if you use {@see setStatusCode StatusCode}
49
 * in your application, be sure to add theses informations.
50
 * E.g : to make an http authentication :
51
 * ```php
52
 *  public function clickAuth ($sender, $param)
53
 *  {
54
 *     $response=$this->getResponse();
55
 *     $response->setStatusCode(401);
56
 *     $response->appendHeader('WWW-Authenticate: Basic realm="Test"');
57
 *  }
58
 * ```
59
 *
60
 * This event handler will sent the 401 status code (Unauthorized) to the browser, with the WWW-Authenticate header field. This
61
 * will force the browser to ask for a username and a password.
62
 *
63
 * @author Qiang Xue <[email protected]>
64
 * @since 3.0
65
 */
66
class THttpResponse extends \Prado\TModule implements \Prado\IO\ITextWriter
67
{
68
	public const DEFAULT_CONTENTTYPE = 'text/html';
69
	public const DEFAULT_CHARSET = 'UTF-8';
70
71
	/**
72
	 * @var array<int, string> The differents defined status code by RFC 2616 {@see http://www.faqs.org/rfcs/rfc2616}
73
	 */
74
	private static $HTTP_STATUS_CODES = [
75
		100 => 'Continue', 101 => 'Switching Protocols',
76
		200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
77
		300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
78
		400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
79
		500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported',
80
	];
81
82
	/**
83
	 * @var bool whether to buffer output
84
	 */
85
	private $_bufferOutput = true;
86
	/**
87
	 * @var bool if the application is initialized
88
	 */
89
	private $_initialized = false;
90
	/**
91
	 * @var THttpCookieCollection list of cookies to return
92
	 */
93
	private $_cookies;
94
	/**
95
	 * @var int response status code
96
	 */
97
	private $_status = 200;
98
	/**
99
	 * @var string reason correspond to status code
100
	 */
101
	private $_reason = 'OK';
102
	/**
103
	 * @var string HTML writer type
104
	 */
105
	private $_htmlWriterType = '\Prado\Web\UI\THtmlWriter';
106
	/**
107
	 * @var string content type
108
	 */
109
	private $_contentType;
110
	/**
111
	 * @var bool|string character set, e.g. UTF-8 or false if no character set should be send to client
112
	 */
113
	private $_charset = '';
114
	/**
115
	 * @var THttpResponseAdapter adapter.
116
	 */
117
	private $_adapter;
118
	/**
119
	 * @var bool whether http response header has been sent
120
	 */
121
	private $_httpHeaderSent;
122
	/**
123
	 * @var bool whether content-type header has been sent
124
	 */
125
	private $_contentTypeHeaderSent;
126
	/**
127
	 * @var THeaderManager the headers manager module
0 ignored issues
show
Bug introduced by
The type Prado\Web\THeaderManager was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
128
	 */
129
	private $_headersManager;
130
	/**
131
	 * @var string the ID of the headers manager module
132
	 */
133
	private $_headersManagerID = '';
134
135
	/**
136
	 * Destructor.
137
	 * Flushes any existing content in buffer.
138
	 */
139
	public function __destruct()
140
	{
141
		//if($this->_bufferOutput)
142
		//	@ob_end_flush();
143
		parent::__destruct();
144
	}
145
146
	/**
147
	 * @param THttpResponseAdapter $adapter response adapter
148
	 */
149
	public function setAdapter(THttpResponseAdapter $adapter)
150
	{
151
		$this->_adapter = $adapter;
152
	}
153
154
	/**
155
	 * @return THttpResponseAdapter response adapter, null if not exist.
156
	 */
157
	public function getAdapter()
158
	{
159
		return $this->_adapter;
160
	}
161
162
	/**
163
	 * @return bool true if adapter exists, false otherwise.
164
	 */
165
	public function getHasAdapter()
166
	{
167
		return $this->_adapter !== null;
168
	}
169
170
	/**
171
	 * Initializes the module.
172
	 * This method is required by IModule and is invoked by application.
173
	 * It starts output buffer if it is enabled.
174
	 * @param \Prado\Xml\TXmlElement $config module configuration
175
	 */
176
	public function init($config)
177
	{
178
		if ($this->_bufferOutput) {
179
			ob_start();
180
		}
181
		$this->_initialized = true;
182
		$this->getApplication()->setResponse($this);
183
		parent::init($config);
184
	}
185
186
	/**
187
	 * @return int time-to-live for cached session pages in minutes, this has no effect for nocache limiter. Defaults to 180.
188
	 */
189
	public function getCacheExpire()
190
	{
191
		return session_cache_expire();
192
	}
193
194
	/**
195
	 * @param int $value time-to-live for cached session pages in minutes, this has no effect for nocache limiter.
196
	 */
197
	public function setCacheExpire($value)
198
	{
199
		session_cache_expire(TPropertyValue::ensureInteger($value));
200
	}
201
202
	/**
203
	 * @return string cache control method to use for session pages
204
	 */
205
	public function getCacheControl()
206
	{
207
		return session_cache_limiter();
208
	}
209
210
	/**
211
	 * @param string $value cache control method to use for session pages. Valid values
212
	 *               include none/nocache/private/private_no_expire/public
213
	 */
214
	public function setCacheControl($value)
215
	{
216
		session_cache_limiter(TPropertyValue::ensureEnum($value, ['none', 'nocache', 'private', 'private_no_expire', 'public']));
217
	}
218
219
	/**
220
	 * @param string $type content type, default is text/html
221
	 */
222
	public function setContentType($type)
223
	{
224
		if ($this->_contentTypeHeaderSent) {
225
			throw new \Exception('Unable to alter content-type as it has been already sent');
226
		}
227
		$this->_contentType = $type;
228
	}
229
230
	/**
231
	 * @return string current content type
232
	 */
233
	public function getContentType()
234
	{
235
		return $this->_contentType;
236
	}
237
238
	/**
239
	 * @return bool|string output charset.
240
	 */
241
	public function getCharset()
242
	{
243
		return $this->_charset;
244
	}
245
246
	/**
247
	 * @param bool|string $charset output charset.
248
	 */
249
	public function setCharset($charset)
250
	{
251
		$this->_charset = (strToLower($charset) === 'false') ? false : (string) $charset;
0 ignored issues
show
Bug introduced by
It seems like $charset can also be of type boolean; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

251
		$this->_charset = (strToLower(/** @scrutinizer ignore-type */ $charset) === 'false') ? false : (string) $charset;
Loading history...
252
	}
253
254
	/**
255
	 * @return bool whether to enable output buffer
256
	 */
257
	public function getBufferOutput()
258
	{
259
		return $this->_bufferOutput;
260
	}
261
262
	/**
263
	 * @param bool $value whether to enable output buffer
264
	 * @throws TInvalidOperationException if session is started already
265
	 */
266
	public function setBufferOutput($value)
267
	{
268
		if ($this->_initialized) {
269
			throw new TInvalidOperationException('httpresponse_bufferoutput_unchangeable');
270
		} else {
271
			$this->_bufferOutput = TPropertyValue::ensureBoolean($value);
272
		}
273
	}
274
275
	/**
276
	 * @return int HTTP status code, defaults to 200
277
	 */
278
	public function getStatusCode()
279
	{
280
		return $this->_status;
281
	}
282
283
	/**
284
	 * Set the HTTP status code for the response.
285
	 * The code and its reason will be sent to client using the currently requested http protocol version (see {@see \Prado\Web\THttpRequest::getHttpProtocolVersion})
286
	 * Keep in mind that HTTP/1.0 clients might not understand all status codes from HTTP/1.1
287
	 *
288
	 * @param int $status HTTP status code
289
	 * @param null|string $reason HTTP status reason, defaults to standard HTTP reasons
290
	 */
291
	public function setStatusCode($status, $reason = null)
292
	{
293
		if ($this->_httpHeaderSent) {
294
			throw new \Exception('Unable to alter response as HTTP header already sent');
295
		}
296
		$status = TPropertyValue::ensureInteger($status);
297
		if (isset(self::$HTTP_STATUS_CODES[$status])) {
298
			$this->_reason = self::$HTTP_STATUS_CODES[$status];
299
		} else {
300
			if ($reason === null || $reason === '') {
301
				throw new TInvalidDataValueException("response_status_reason_missing");
302
			}
303
			$reason = TPropertyValue::ensureString($reason);
304
			if (strpos($reason, "\r") != false || strpos($reason, "\n") != false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($reason, ' ') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($reason, ' ') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
305
				throw new TInvalidDataValueException("response_status_reason_barchars");
306
			}
307
			$this->_reason = $reason;
308
		}
309
		$this->_status = $status;
310
	}
311
312
	/**
313
	 * @return string HTTP status reason
314
	 */
315
	public function getStatusReason()
316
	{
317
		return $this->_reason;
318
	}
319
320
	/**
321
	 * @return THttpCookieCollection list of output cookies
322
	 */
323
	public function getCookies()
324
	{
325
		if ($this->_cookies === null) {
326
			$this->_cookies = new THttpCookieCollection($this);
327
		}
328
		return $this->_cookies;
329
	}
330
331
	/**
332
	 * Outputs a string.
333
	 * It may not be sent back to user immediately if output buffer is enabled.
334
	 * @param string $str string to be output
335
	 */
336
	public function write($str)
337
	{
338
		// when starting output make sure we send the headers first
339
		if (!$this->_bufferOutput && !$this->_httpHeaderSent) {
340
			$this->ensureHeadersSent();
341
		}
342
		echo $str;
343
	}
344
345
	/**
346
	 * Sends a file back to user.
347
	 * Make sure not to output anything else after calling this method.
348
	 * @param string $fileName file name
349
	 * @param null|string $content content to be set. If null, the content will be read from the server file pointed to by $fileName.
350
	 * @param null|string $mimeType mime type of the content.
351
	 * @param null|array $headers list of headers to be sent. Each array element represents a header string (e.g. 'Content-Type: text/plain').
352
	 * @param null|bool $forceDownload force download of file, even if browser able to display inline. Defaults to 'true'.
353
	 * @param null|string $clientFileName force a specific file name on client side. Defaults to 'null' means auto-detect.
354
	 * @param null|int $fileSize size of file or content in bytes if already known. Defaults to 'null' means auto-detect.
355
	 * @throws TInvalidDataValueException if the file cannot be found
356
	 */
357
	public function writeFile($fileName, $content = null, $mimeType = null, $headers = null, $forceDownload = true, $clientFileName = null, $fileSize = null)
358
	{
359
		static $defaultMimeTypes = [
360
			'css' => 'text/css',
361
			'gif' => 'image/gif',
362
			'png' => 'image/png',
363
			'jpg' => 'image/jpeg',
364
			'jpeg' => 'image/jpeg',
365
			'htm' => 'text/html',
366
			'html' => 'text/html',
367
			'js' => 'javascript/js',
368
			'pdf' => 'application/pdf',
369
			'xls' => 'application/vnd.ms-excel',
370
		];
371
372
		if ($mimeType === null) {
373
			$mimeType = 'text/plain';
374
			if (function_exists('mime_content_type')) {
375
				$mimeType = mime_content_type($fileName);
376
			} elseif (($ext = strrchr($fileName, '.')) !== false) {
377
				$ext = substr($ext, 1);
378
				if (isset($defaultMimeTypes[$ext])) {
379
					$mimeType = $defaultMimeTypes[$ext];
380
				}
381
			}
382
		}
383
384
		if ($clientFileName === null) {
385
			$clientFileName = basename($fileName);
386
		} else {
387
			$clientFileName = basename($clientFileName);
388
		}
389
390
		if ($fileSize === null || $fileSize < 0) {
391
			$fileSize = ($content === null ? filesize($fileName) : strlen($content));
392
		}
393
394
		$this->sendHttpHeader();
395
		if (is_array($headers)) {
396
			foreach ($headers as $h) {
397
				header($h);
398
			}
399
		} else {
400
			header('Pragma: public');
401
			header('Expires: 0');
402
			header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
403
			header("Content-Type: $mimeType");
404
			$this->_contentTypeHeaderSent = true;
405
		}
406
407
		header('Content-Length: ' . $fileSize);
408
		header("Content-Disposition: " . ($forceDownload ? 'attachment' : 'inline') . "; filename=\"$clientFileName\"");
409
		header('Content-Transfer-Encoding: binary');
410
		if ($content === null) {
411
			readfile($fileName);
412
		} else {
413
			echo $content;
414
		}
415
	}
416
417
	/**
418
	 * Redirects the browser to the specified URL.
419
	 * The current application will be terminated after this method is invoked.
420
	 * @param string $url URL to be redirected to. If the URL is a relative one, the base URL of
421
	 * the current request will be inserted at the beginning.
422
	 */
423
	public function redirect($url)
424
	{
425
		if ($this->getHasAdapter()) {
426
			$this->_adapter->httpRedirect($url);
427
		} else {
428
			$this->httpRedirect($url);
429
		}
430
	}
431
432
	/**
433
	 * Redirect the browser to another URL and exists the current application.
434
	 * This method is used internally. Please use {@see redirect} instead.
435
	 *
436
	 * @since 3.1.5
437
	 * You can set the set {@see setStatusCode StatusCode} to a value between 300 and 399 before
438
	 * calling this function to change the type of redirection.
439
	 * If not specified, StatusCode will be 302 (Found) by default
440
	 *
441
	 * @param string $url URL to be redirected to. If the URL is a relative one, the base URL of
442
	 * the current request will be inserted at the beginning.
443
	 */
444
	public function httpRedirect($url)
445
	{
446
		$this->ensureHeadersSent();
447
448
		// Under IIS, explicitly send an HTTP response including the status code
449
		// this is handled automatically by PHP on Apache and others
450
		$isIIS = (stripos($this->getRequest()->getServerSoftware(), "microsoft-iis") !== false);
451
		if ($url[0] === '/') {
452
			$url = $this->getRequest()->getBaseUrl() . $url;
453
		}
454
		if ($this->_status >= 300 && $this->_status < 400) {
455
			// The status code has been modified to a valid redirection status, send it
456
			if ($isIIS) {
457
				header('HTTP/1.1 ' . $this->_status . ' ' . self::$HTTP_STATUS_CODES[
458
					array_key_exists($this->_status, self::$HTTP_STATUS_CODES)
459
						? $this->_status
460
						: 302
461
					]);
462
			}
463
			header('Location: ' . str_replace('&amp;', '&', $url), true, $this->_status);
464
		} else {
465
			if ($isIIS) {
466
				header('HTTP/1.1 302 ' . self::$HTTP_STATUS_CODES[302]);
467
			}
468
			header('Location: ' . str_replace('&amp;', '&', $url));
469
		}
470
471
		if (!$this->getApplication()->getRequestCompleted()) {
472
			throw new TExitException();
473
		}
474
475
		exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
476
	}
477
478
	/**
479
	 * Reloads the current page.
480
	 * The effect of this method call is the same as user pressing the
481
	 * refresh button on his browser (without post data).
482
	 **/
483
	public function reload()
484
	{
485
		$this->redirect($this->getRequest()->getRequestUri());
486
	}
487
488
	/**
489
	 * Flush the response contents and headers.
490
	 * @param bool $continueBuffering
491
	 */
492
	public function flush($continueBuffering = true)
493
	{
494
		if ($this->getHasAdapter()) {
495
			$this->_adapter->flushContent($continueBuffering);
496
		} else {
497
			$this->flushContent($continueBuffering);
498
		}
499
	}
500
501
	/**
502
	 * Ensures that HTTP response and content-type headers are sent
503
	 */
504
	public function ensureHeadersSent()
505
	{
506
		$this->ensureHttpHeaderSent();
507
		$this->ensureContentTypeHeaderSent();
508
		$headersManager = $this->getHeadersManagerModule();
509
		if($headersManager !== null) {
510
			$headersManager->ensureHeadersSent();
511
		}
512
	}
513
514
	/**
515
	 * Outputs the buffered content, sends content-type and charset header.
516
	 * This method is used internally. Please use {@see flush} instead.
517
	 * @param bool $continueBuffering whether to continue buffering after flush if buffering was active
518
	 */
519
	public function flushContent($continueBuffering = true)
520
	{
521
		Prado::trace("Flushing output", THttpResponse::class);
522
		$this->ensureHeadersSent();
523
		if ($this->_bufferOutput) {
524
			// avoid forced send of http headers (ob_flush() does that) if there's no output yet
525
			if (ob_get_length() > 0) {
526
				if (!$continueBuffering) {
527
					$this->_bufferOutput = false;
528
					ob_end_flush();
529
				} else {
530
					ob_flush();
531
				}
532
				flush();
533
			}
534
		} else {
535
			flush();
536
		}
537
	}
538
539
	/**
540
	 * Ensures that the HTTP header with the status code and status reason are sent
541
	 */
542
	protected function ensureHttpHeaderSent()
543
	{
544
		if (!$this->_httpHeaderSent) {
545
			$this->sendHttpHeader();
546
		}
547
	}
548
549
	/**
550
	 * Send the HTTP header with the status code (defaults to 200) and status reason (defaults to OK)
551
	 */
552
	protected function sendHttpHeader()
553
	{
554
		$protocol = $this->getRequest()->getHttpProtocolVersion();
555
		if ($this->getRequest()->getHttpProtocolVersion() === null) {
0 ignored issues
show
introduced by
The condition $this->getRequest()->get...tocolVersion() === null is always false.
Loading history...
556
			$protocol = 'HTTP/1.1';
557
		}
558
559
		header($protocol . ' ' . $this->_status . ' ' . $this->_reason, true, TPropertyValue::ensureInteger($this->_status));
560
561
		$this->_httpHeaderSent = true;
562
	}
563
564
	/**
565
	 * Ensures that the HTTP header with the status code and status reason are sent
566
	 */
567
	protected function ensureContentTypeHeaderSent()
568
	{
569
		if (!$this->_contentTypeHeaderSent) {
570
			$this->sendContentTypeHeader();
571
		}
572
	}
573
574
	/**
575
	 * Sends content type header with optional charset.
576
	 */
577
	protected function sendContentTypeHeader()
578
	{
579
		$contentType = $this->_contentType === null ? self::DEFAULT_CONTENTTYPE : $this->_contentType;
580
		$charset = $this->getCharset();
581
		if ($charset === false) {
582
			$this->appendHeader('Content-Type: ' . $contentType);
583
			return;
584
		}
585
586
		if ($charset === '' && ($globalization = $this->getApplication()->getGlobalization(false)) !== null) {
587
			$charset = $globalization->getCharset();
588
		}
589
590
		if ($charset === '') {
591
			$charset = self::DEFAULT_CHARSET;
592
		}
593
		$this->appendHeader('Content-Type: ' . $contentType . ';charset=' . $charset);
0 ignored issues
show
Bug introduced by
Are you sure $charset of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

593
		$this->appendHeader('Content-Type: ' . $contentType . ';charset=' . /** @scrutinizer ignore-type */ $charset);
Loading history...
594
595
		$this->_contentTypeHeaderSent = true;
596
	}
597
598
	/**
599
	 * Returns the content in the output buffer.
600
	 * The buffer will NOT be cleared after calling this method.
601
	 * Use {@see clear()} is you want to clear the buffer.
602
	 * @return string output that is in the buffer.
603
	 */
604
	public function getContents()
605
	{
606
		Prado::trace("Retrieving output", THttpResponse::class);
607
		return $this->_bufferOutput ? ob_get_contents() : '';
608
	}
609
610
	/**
611
	 * Clears any existing buffered content.
612
	 */
613
	public function clear()
614
	{
615
		if ($this->_bufferOutput && ob_get_length() > 0) {
616
			ob_clean();
617
		}
618
		Prado::trace("Clearing output", THttpResponse::class);
619
	}
620
621
	/**
622
	 * @param null|int $case Either {@see CASE_UPPER} or {@see CASE_LOWER} or as is null (default)
623
	 * @return array
624
	 */
625
	public function getHeaders($case = null)
626
	{
627
		$result = [];
628
		$headers = headers_list();
629
		foreach ($headers as $header) {
630
			$tmp = explode(':', $header);
631
			$key = trim(array_shift($tmp));
632
			$value = trim(implode(':', $tmp));
633
			if (isset($result[$key])) {
634
				$result[$key] .= ', ' . $value;
635
			} else {
636
				$result[$key] = $value;
637
			}
638
		}
639
640
		if ($case !== null) {
641
			return array_change_key_case($result, $case);
642
		}
643
644
		return $result;
645
	}
646
647
	/**
648
	 * Sends a header.
649
	 * @param string $value header
650
	 * @param bool $replace whether the header should replace a previous similar header, or add a second header of the same type
651
	 */
652
	public function appendHeader($value, $replace = true)
653
	{
654
		Prado::trace("Sending header '$value'", THttpResponse::class);
655
		header($value, $replace);
656
	}
657
658
	/**
659
	 * Writes a log message into error log.
660
	 * This method is simple wrapper of PHP function error_log.
661
	 * @param string $message The error message that should be logged
662
	 * @param int $messageType where the error should go
663
	 * @param string $destination The destination. Its meaning depends on the message parameter as described above
664
	 * @param string $extraHeaders The extra headers. It's used when the message parameter is set to 1. This message type uses the same internal function as mail() does.
665
	 * @see http://us2.php.net/manual/en/function.error-log.php
666
	 */
667
	public function appendLog($message, $messageType = 0, $destination = '', $extraHeaders = '')
668
	{
669
		error_log($message, $messageType, $destination, $extraHeaders);
670
	}
671
672
	/**
673
	 * Sends a cookie.
674
	 * Do not call this method directly. Operate with the result of {@see getCookies} instead.
675
	 * @param THttpCookie $cookie cook to be sent
676
	 */
677
	public function addCookie($cookie)
678
	{
679
		$request = $this->getRequest();
680
		if ($request->getEnableCookieValidation()) {
681
			$value = $this->getApplication()->getSecurityManager()->hashData($cookie->getValue());
682
		} else {
683
			$value = $cookie->getValue();
684
		}
685
686
		setcookie(
687
			$cookie->getName(),
688
			$value,
689
			$cookie->getPhpOptions()
690
		);
691
	}
692
693
	/**
694
	 * Deletes a cookie.
695
	 * Do not call this method directly. Operate with the result of {@see getCookies} instead.
696
	 * @param THttpCookie $cookie cook to be deleted
697
	 */
698
	public function removeCookie($cookie)
699
	{
700
		$options = $cookie->getPhpOptions();
701
		$options['expires'] = 0;
702
		setcookie(
703
			$cookie->getName(),
704
			null,
705
			$options
706
		);
707
	}
708
709
	/**
710
	 * @return string the type of HTML writer to be used, defaults to THtmlWriter
711
	 */
712
	public function getHtmlWriterType()
713
	{
714
		return $this->_htmlWriterType;
715
	}
716
717
	/**
718
	 * @param string $value the type of HTML writer to be used, may be the class name or the namespace
719
	 */
720
	public function setHtmlWriterType($value)
721
	{
722
		$this->_htmlWriterType = $value;
723
	}
724
725
	/**
726
	 * Creates a new instance of HTML writer.
727
	 * If the type of the HTML writer is not supplied, {@see getHtmlWriterType HtmlWriterType} will be assumed.
728
	 * @param string $type type of the HTML writer to be created. If null, {@see getHtmlWriterType HtmlWriterType} will be assumed.
729
	 */
730
	public function createHtmlWriter($type = null)
731
	{
732
		if ($type === null) {
733
			$type = $this->getHtmlWriterType();
734
		}
735
		if ($this->getHasAdapter()) {
736
			return $this->_adapter->createNewHtmlWriter($type, $this);
737
		} else {
738
			return $this->createNewHtmlWriter($type, $this);
739
		}
740
	}
741
742
	/**
743
	 * Create a new html writer instance.
744
	 * This method is used internally. Please use {@see createHtmlWriter} instead.
745
	 * @param string $type type of HTML writer to be created.
746
	 * @param \Prado\IO\ITextWriter $writer text writer holding the contents.
747
	 */
748
	public function createNewHtmlWriter($type, $writer)
749
	{
750
		return Prado::createComponent($type, $writer);
751
	}
752
753
	/**
754
	 * @return string the ID of the URL manager module
755
	 */
756
	public function getHeadersManager()
757
	{
758
		return $this->_headersManagerID;
759
	}
760
761
	/**
762
	 * Sets the headers manager module.
763
	 * By default no header manager module is used
764
	 * You may specify a different module for headers managing tasks
765
	 * by loading it as an application module and setting this property
766
	 * with the module ID.
767
	 * @param string $value the ID of the URL manager module
768
	 */
769
	public function setHeadersManager($value)
770
	{
771
		$this->_headersManagerID = $value;
772
	}
773
774
	/**
775
	 * @return null|THttpHeadersManager the URL manager module
776
	 */
777
	public function getHeadersManagerModule()
778
	{
779
		if (empty($this->_headersManagerID)) {
780
			return null;
781
		}
782
783
		$this->_headersManager = $this->getApplication()->getModule($this->_headersManagerID);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getApplication()-...his->_headersManagerID) can also be of type Prado\TModule. However, the property $_headersManager is declared as type Prado\Web\THeaderManager. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
784
		if ($this->_headersManager === null) {
785
			throw new TConfigurationException('httpresponse_headersmanager_inexist', $this->_headersManagerID);
0 ignored issues
show
Bug introduced by
The type Prado\Web\TConfigurationException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
786
		}
787
		if (!($this->_headersManager instanceof THttpHeadersManager)) {
788
			throw new TConfigurationException('httpresponse_headersmanager_invalid', $this->_headersManagerID);
789
		}
790
791
		return $this->_headersManager;
792
	}
793
}
794