Passed
Pull Request — master (#1005)
by Fabio
17:26 queued 11:05
created

THttpResponse::reload()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
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\TConfigurationException;
15
use Prado\Exceptions\TInvalidDataValueException;
16
use Prado\Exceptions\TInvalidOperationException;
17
use Prado\Prado;
18
use Prado\TPropertyValue;
19
20
/**
21
 * THttpResponse class
22
 *
23
 * THttpResponse implements the mechanism for sending output to client users.
24
 *
25
 * To output a string to client, use {@see write()}. By default, the output is
26
 * buffered until {@see flush()} is called or the application ends. The output in
27
 * the buffer can also be cleaned by {@see clear()}. To disable output buffering,
28
 * set BufferOutput property to false.
29
 *
30
 * To send cookies to client, use {@see getCookies()}.
31
 * To redirect client browser to a new URL, use {@see redirect()}.
32
 * To send a file to client, use {@see writeFile()}.
33
 *
34
 * By default, THttpResponse is registered with {@see \Prado\TApplication} as the
35
 * response module. It can be accessed via {@see \Prado\TApplication::getResponse()}.
36
 *
37
 * THttpResponse may be configured in application configuration file as follows
38
 *
39
 * <module id="response" class="Prado\Web\THttpResponse" CacheExpire="20" CacheControl="nocache" BufferOutput="true" />
40
 *
41
 * where {@see getCacheExpire CacheExpire}, {@see getCacheControl CacheControl}
42
 * and {@see getBufferOutput BufferOutput} are optional properties of THttpResponse.
43
 *
44
 * THttpResponse sends charset header if either {@see setCharset() Charset}
45
 * or {@see \Prado\I18N\TGlobalization::setCharset() TGlobalization.Charset} is set.
46
 *
47
 * Since 3.1.2, HTTP status code can be set with the {@see setStatusCode StatusCode} property.
48
 *
49
 * Note: Some HTTP Status codes can require additional header or body information. So, if you use {@see setStatusCode StatusCode}
50
 * in your application, be sure to add theses informations.
51
 * E.g : to make an http authentication :
52
 * ```php
53
 *  public function clickAuth ($sender, $param)
54
 *  {
55
 *     $response=$this->getResponse();
56
 *     $response->setStatusCode(401);
57
 *     $response->appendHeader('WWW-Authenticate: Basic realm="Test"');
58
 *  }
59
 * ```
60
 *
61
 * This event handler will sent the 401 status code (Unauthorized) to the browser, with the WWW-Authenticate header field. This
62
 * will force the browser to ask for a username and a password.
63
 *
64
 * @author Qiang Xue <[email protected]>
65
 * @since 3.0
66
 */
67
class THttpResponse extends \Prado\TModule implements \Prado\IO\ITextWriter
68
{
69
	public const DEFAULT_CONTENTTYPE = 'text/html';
70
	public const DEFAULT_CHARSET = 'UTF-8';
71
72
	/**
73
	 * @var array<int, string> The differents defined status code by RFC 2616 {@see http://www.faqs.org/rfcs/rfc2616}
74
	 */
75
	private static $HTTP_STATUS_CODES = [
76
		100 => 'Continue', 101 => 'Switching Protocols',
77
		200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
78
		300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
79
		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',
80
		500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported',
81
	];
82
83
	/**
84
	 * @var bool whether to buffer output
85
	 */
86
	private $_bufferOutput = true;
87
	/**
88
	 * @var bool if the application is initialized
89
	 */
90
	private $_initialized = false;
91
	/**
92
	 * @var THttpCookieCollection list of cookies to return
93
	 */
94
	private $_cookies;
95
	/**
96
	 * @var int response status code
97
	 */
98
	private $_status = 200;
99
	/**
100
	 * @var string reason correspond to status code
101
	 */
102
	private $_reason = 'OK';
103
	/**
104
	 * @var string HTML writer type
105
	 */
106
	private $_htmlWriterType = '\Prado\Web\UI\THtmlWriter';
107
	/**
108
	 * @var string content type
109
	 */
110
	private $_contentType;
111
	/**
112
	 * @var bool|string character set, e.g. UTF-8 or false if no character set should be send to client
113
	 */
114
	private $_charset = '';
115
	/**
116
	 * @var THttpResponseAdapter adapter.
117
	 */
118
	private $_adapter;
119
	/**
120
	 * @var bool whether http response header has been sent
121
	 */
122
	private $_httpHeaderSent;
123
	/**
124
	 * @var bool whether content-type header has been sent
125
	 */
126
	private $_contentTypeHeaderSent;
127
	/**
128
	 * @var THttpHeadersManager the headers manager module
129
	 */
130
	private $_headersManager;
131 5
	/**
132
	 * @var string the ID of the headers manager module
133
	 */
134
	private $_headersManagerID = '';
135 5
136
	/**
137
	 * Destructor.
138
	 * Flushes any existing content in buffer.
139
	 */
140
	public function __destruct()
141
	{
142
		//if($this->_bufferOutput)
143
		//	@ob_end_flush();
144
		parent::__destruct();
145
	}
146
147
	/**
148
	 * @param THttpResponseAdapter $adapter response adapter
149
	 */
150
	public function setAdapter(THttpResponseAdapter $adapter)
151
	{
152
		$this->_adapter = $adapter;
153
	}
154
155
	/**
156
	 * @return THttpResponseAdapter response adapter, null if not exist.
157
	 */
158
	public function getAdapter()
159
	{
160
		return $this->_adapter;
161
	}
162
163
	/**
164
	 * @return bool true if adapter exists, false otherwise.
165
	 */
166
	public function getHasAdapter()
167 8
	{
168
		return $this->_adapter !== null;
169 8
	}
170 8
171
	/**
172 8
	 * Initializes the module.
173 8
	 * This method is required by IModule and is invoked by application.
174 8
	 * It starts output buffer if it is enabled.
175
	 * @param \Prado\Xml\TXmlElement $config module configuration
176
	 */
177
	public function init($config)
178
	{
179 1
		if ($this->_bufferOutput) {
180
			ob_start();
181 1
		}
182
		$this->_initialized = true;
183
		$this->getApplication()->setResponse($this);
184
		parent::init($config);
185
	}
186
187 1
	/**
188
	 * @return int time-to-live for cached session pages in minutes, this has no effect for nocache limiter. Defaults to 180.
189 1
	 */
190 1
	public function getCacheExpire()
191
	{
192
		return session_cache_expire();
193
	}
194
195 1
	/**
196
	 * @param int $value time-to-live for cached session pages in minutes, this has no effect for nocache limiter.
197 1
	 */
198
	public function setCacheExpire($value)
199
	{
200
		session_cache_expire(TPropertyValue::ensureInteger($value));
201
	}
202
203
	/**
204 1
	 * @return string cache control method to use for session pages
205
	 */
206 1
	public function getCacheControl()
207 1
	{
208
		return session_cache_limiter();
209
	}
210
211
	/**
212
	 * @param string $value cache control method to use for session pages. Valid values
213 1
	 *               include none/nocache/private/private_no_expire/public
214
	 */
215 1
	public function setCacheControl($value)
216
	{
217
		session_cache_limiter(TPropertyValue::ensureEnum($value, ['none', 'nocache', 'private', 'private_no_expire', 'public']));
218 1
	}
219 1
220
	/**
221
	 * @param string $type content type, default is text/html
222
	 */
223
	public function setContentType($type)
224 1
	{
225
		if ($this->_contentTypeHeaderSent) {
226 1
			throw new \Exception('Unable to alter content-type as it has been already sent');
227
		}
228
		$this->_contentType = $type;
229
	}
230
231
	/**
232 1
	 * @return string current content type
233
	 */
234 1
	public function getContentType()
235
	{
236
		return $this->_contentType;
237
	}
238
239
	/**
240 1
	 * @return bool|string output charset.
241
	 */
242 1
	public function getCharset()
243 1
	{
244
		return $this->_charset;
245
	}
246
247
	/**
248 1
	 * @param bool|string $charset output charset.
249
	 */
250 1
	public function setCharset($charset)
251
	{
252
		$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

252
		$this->_charset = (strToLower(/** @scrutinizer ignore-type */ $charset) === 'false') ? false : (string) $charset;
Loading history...
253
	}
254
255
	/**
256
	 * @return bool whether to enable output buffer
257 1
	 */
258
	public function getBufferOutput()
259 1
	{
260 1
		return $this->_bufferOutput;
261
	}
262 1
263
	/**
264 1
	 * @param bool $value whether to enable output buffer
265
	 * @throws TInvalidOperationException if session is started already
266
	 */
267
	public function setBufferOutput($value)
268
	{
269 1
		if ($this->_initialized) {
270
			throw new TInvalidOperationException('httpresponse_bufferoutput_unchangeable');
271 1
		} else {
272
			$this->_bufferOutput = TPropertyValue::ensureBoolean($value);
273
		}
274
	}
275
276
	/**
277
	 * @return int HTTP status code, defaults to 200
278
	 */
279
	public function getStatusCode()
280
	{
281
		return $this->_status;
282 1
	}
283
284 1
	/**
285
	 * Set the HTTP status code for the response.
286
	 * The code and its reason will be sent to client using the currently requested http protocol version (see {@see \Prado\Web\THttpRequest::getHttpProtocolVersion})
287 1
	 * Keep in mind that HTTP/1.0 clients might not understand all status codes from HTTP/1.1
288 1
	 *
289 1
	 * @param int $status HTTP status code
290
	 * @param null|string $reason HTTP status reason, defaults to standard HTTP reasons
291
	 */
292
	public function setStatusCode($status, $reason = null)
293
	{
294
		if ($this->_httpHeaderSent) {
295
			throw new \Exception('Unable to alter response as HTTP header already sent');
296
		}
297
		$status = TPropertyValue::ensureInteger($status);
298
		if (isset(self::$HTTP_STATUS_CODES[$status])) {
299
			$this->_reason = self::$HTTP_STATUS_CODES[$status];
300 1
		} else {
301 1
			if ($reason === null || $reason === '') {
302
				throw new TInvalidDataValueException("response_status_reason_missing");
303
			}
304
			$reason = TPropertyValue::ensureString($reason);
305
			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...
306
				throw new TInvalidDataValueException("response_status_reason_barchars");
307
			}
308
			$this->_reason = $reason;
309
		}
310
		$this->_status = $status;
311
	}
312
313
	/**
314 1
	 * @return string HTTP status reason
315
	 */
316 1
	public function getStatusReason()
317 1
	{
318
		return $this->_reason;
319 1
	}
320
321
	/**
322
	 * @return THttpCookieCollection list of output cookies
323
	 */
324
	public function getCookies()
325
	{
326
		if ($this->_cookies === null) {
327
			$this->_cookies = new THttpCookieCollection($this);
328
		}
329
		return $this->_cookies;
330
	}
331
332
	/**
333
	 * Outputs a string.
334
	 * It may not be sent back to user immediately if output buffer is enabled.
335
	 * @param string $str string to be output
336
	 */
337
	public function write($str)
338
	{
339
		// when starting output make sure we send the headers first
340
		if (!$this->_bufferOutput && !$this->_httpHeaderSent) {
341
			$this->ensureHeadersSent();
342
		}
343
		echo $str;
344
	}
345
346
	/**
347
	 * Sends a file back to user.
348
	 * Make sure not to output anything else after calling this method.
349
	 * @param string $fileName file name
350
	 * @param null|string $content content to be set. If null, the content will be read from the server file pointed to by $fileName.
351
	 * @param null|string $mimeType mime type of the content.
352
	 * @param null|array $headers list of headers to be sent. Each array element represents a header string (e.g. 'Content-Type: text/plain').
353
	 * @param null|bool $forceDownload force download of file, even if browser able to display inline. Defaults to 'true'.
354
	 * @param null|string $clientFileName force a specific file name on client side. Defaults to 'null' means auto-detect.
355
	 * @param null|int $fileSize size of file or content in bytes if already known. Defaults to 'null' means auto-detect.
356
	 * @throws TInvalidDataValueException if the file cannot be found
357
	 */
358
	public function writeFile($fileName, $content = null, $mimeType = null, $headers = null, $forceDownload = true, $clientFileName = null, $fileSize = null)
359
	{
360
		static $defaultMimeTypes = [
361
			'css' => 'text/css',
362
			'gif' => 'image/gif',
363
			'png' => 'image/png',
364
			'jpg' => 'image/jpeg',
365
			'jpeg' => 'image/jpeg',
366
			'htm' => 'text/html',
367
			'html' => 'text/html',
368
			'js' => 'javascript/js',
369
			'pdf' => 'application/pdf',
370
			'xls' => 'application/vnd.ms-excel',
371
		];
372
373
		if ($mimeType === null) {
374
			$mimeType = 'text/plain';
375
			if (function_exists('mime_content_type')) {
376
				$mimeType = mime_content_type($fileName);
377
			} elseif (($ext = strrchr($fileName, '.')) !== false) {
378
				$ext = substr($ext, 1);
379
				if (isset($defaultMimeTypes[$ext])) {
380
					$mimeType = $defaultMimeTypes[$ext];
381
				}
382
			}
383
		}
384
385
		if ($clientFileName === null) {
386
			$clientFileName = basename($fileName);
387
		} else {
388
			$clientFileName = basename($clientFileName);
389
		}
390
391
		if ($fileSize === null || $fileSize < 0) {
392
			$fileSize = ($content === null ? filesize($fileName) : strlen($content));
393
		}
394
395
		$this->sendHttpHeader();
396
		if (is_array($headers)) {
397
			foreach ($headers as $h) {
398
				header($h);
399
			}
400
		} else {
401
			header('Pragma: public');
402
			header('Expires: 0');
403
			header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
404
			header("Content-Type: $mimeType");
405
			$this->_contentTypeHeaderSent = true;
406
		}
407
408
		header('Content-Length: ' . $fileSize);
409
		header("Content-Disposition: " . ($forceDownload ? 'attachment' : 'inline') . "; filename=\"$clientFileName\"");
410
		header('Content-Transfer-Encoding: binary');
411
		if ($content === null) {
412
			readfile($fileName);
413
		} else {
414
			echo $content;
415
		}
416
	}
417
418
	/**
419
	 * Redirects the browser to the specified URL.
420
	 * The current application will be terminated after this method is invoked.
421
	 * @param string $url URL to be redirected to. If the URL is a relative one, the base URL of
422
	 * the current request will be inserted at the beginning.
423
	 */
424
	public function redirect($url)
425
	{
426
		if ($this->getHasAdapter()) {
427
			$this->_adapter->httpRedirect($url);
428
		} else {
429
			$this->httpRedirect($url);
430
		}
431
	}
432
433
	/**
434
	 * Redirect the browser to another URL and exists the current application.
435
	 * This method is used internally. Please use {@see redirect} instead.
436
	 *
437
	 * @since 3.1.5
438
	 * You can set the set {@see setStatusCode StatusCode} to a value between 300 and 399 before
439
	 * calling this function to change the type of redirection.
440
	 * If not specified, StatusCode will be 302 (Found) by default
441
	 *
442
	 * @param string $url URL to be redirected to. If the URL is a relative one, the base URL of
443
	 * the current request will be inserted at the beginning.
444
	 */
445
	public function httpRedirect($url)
446
	{
447
		$this->ensureHeadersSent();
448
449
		// Under IIS, explicitly send an HTTP response including the status code
450
		// this is handled automatically by PHP on Apache and others
451
		$isIIS = (stripos($this->getRequest()->getServerSoftware(), "microsoft-iis") !== false);
452
		if ($url[0] === '/') {
453
			$url = $this->getRequest()->getBaseUrl() . $url;
454
		}
455
		if ($this->_status >= 300 && $this->_status < 400) {
456
			// The status code has been modified to a valid redirection status, send it
457
			if ($isIIS) {
458
				header('HTTP/1.1 ' . $this->_status . ' ' . self::$HTTP_STATUS_CODES[
459
					array_key_exists($this->_status, self::$HTTP_STATUS_CODES)
460
						? $this->_status
461
						: 302
462
					]);
463
			}
464
			header('Location: ' . str_replace('&amp;', '&', $url), true, $this->_status);
465
		} else {
466
			if ($isIIS) {
467
				header('HTTP/1.1 302 ' . self::$HTTP_STATUS_CODES[302]);
468
			}
469
			header('Location: ' . str_replace('&amp;', '&', $url));
470
		}
471
472
		if (!$this->getApplication()->getRequestCompleted()) {
473
			throw new TExitException();
474
		}
475
476
		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...
477
	}
478
479
	/**
480
	 * Reloads the current page.
481
	 * The effect of this method call is the same as user pressing the
482
	 * refresh button on his browser (without post data).
483
	 **/
484
	public function reload()
485
	{
486
		$this->redirect($this->getRequest()->getRequestUri());
487
	}
488
489
	/**
490
	 * Flush the response contents and headers.
491
	 * @param bool $continueBuffering
492
	 */
493
	public function flush($continueBuffering = true)
494
	{
495
		if ($this->getHasAdapter()) {
496
			$this->_adapter->flushContent($continueBuffering);
497
		} else {
498
			$this->flushContent($continueBuffering);
499
		}
500
	}
501
502
	/**
503
	 * Ensures that HTTP response and content-type headers are sent
504
	 */
505
	public function ensureHeadersSent()
506
	{
507
		$this->ensureHttpHeaderSent();
508
		$this->ensureContentTypeHeaderSent();
509
		$headersManager = $this->getHeadersManagerModule();
510
		if ($headersManager !== null) {
511
			$headersManager->ensureHeadersSent();
512
		}
513
	}
514
515
	/**
516
	 * Outputs the buffered content, sends content-type and charset header.
517
	 * This method is used internally. Please use {@see flush} instead.
518
	 * @param bool $continueBuffering whether to continue buffering after flush if buffering was active
519
	 */
520
	public function flushContent($continueBuffering = true)
521
	{
522
		Prado::trace("Flushing output", THttpResponse::class);
523
		$this->ensureHeadersSent();
524
		if ($this->_bufferOutput) {
525
			// avoid forced send of http headers (ob_flush() does that) if there's no output yet
526
			if (ob_get_length() > 0) {
527
				if (!$continueBuffering) {
528
					$this->_bufferOutput = false;
529
					ob_end_flush();
530
				} else {
531
					ob_flush();
532
				}
533
				flush();
534
			}
535
		} else {
536
			flush();
537
		}
538
	}
539
540
	/**
541
	 * Ensures that the HTTP header with the status code and status reason are sent
542
	 */
543
	protected function ensureHttpHeaderSent()
544
	{
545
		if (!$this->_httpHeaderSent) {
546
			$this->sendHttpHeader();
547
		}
548
	}
549
550
	/**
551
	 * Send the HTTP header with the status code (defaults to 200) and status reason (defaults to OK)
552
	 */
553
	protected function sendHttpHeader()
554
	{
555
		$protocol = $this->getRequest()->getHttpProtocolVersion();
556
		if ($this->getRequest()->getHttpProtocolVersion() === null) {
0 ignored issues
show
introduced by
The condition $this->getRequest()->get...tocolVersion() === null is always false.
Loading history...
557
			$protocol = 'HTTP/1.1';
558
		}
559
560
		header($protocol . ' ' . $this->_status . ' ' . $this->_reason, true, TPropertyValue::ensureInteger($this->_status));
561
562
		$this->_httpHeaderSent = true;
563
	}
564
565
	/**
566
	 * Ensures that the HTTP header with the status code and status reason are sent
567
	 */
568
	protected function ensureContentTypeHeaderSent()
569
	{
570
		if (!$this->_contentTypeHeaderSent) {
571
			$this->sendContentTypeHeader();
572
		}
573
	}
574
575
	/**
576
	 * Sends content type header with optional charset.
577
	 */
578
	protected function sendContentTypeHeader()
579
	{
580
		$contentType = $this->_contentType === null ? self::DEFAULT_CONTENTTYPE : $this->_contentType;
581
		$charset = $this->getCharset();
582
		if ($charset === false) {
583
			$this->appendHeader('Content-Type: ' . $contentType);
584
			return;
585
		}
586
587
		if ($charset === '' && ($globalization = $this->getApplication()->getGlobalization(false)) !== null) {
588
			$charset = $globalization->getCharset();
589
		}
590
591
		if ($charset === '') {
592
			$charset = self::DEFAULT_CHARSET;
593
		}
594
		$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

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