THttpResponse::setCacheControl()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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
	/**
128
	 * Destructor.
129
	 * Flushes any existing content in buffer.
130
	 */
131 5
	public function __destruct()
132
	{
133
		//if($this->_bufferOutput)
134
		//	@ob_end_flush();
135 5
		parent::__destruct();
136
	}
137
138
	/**
139
	 * @param THttpResponseAdapter $adapter response adapter
140
	 */
141
	public function setAdapter(THttpResponseAdapter $adapter)
142
	{
143
		$this->_adapter = $adapter;
144
	}
145
146
	/**
147
	 * @return THttpResponseAdapter response adapter, null if not exist.
148
	 */
149
	public function getAdapter()
150
	{
151
		return $this->_adapter;
152
	}
153
154
	/**
155
	 * @return bool true if adapter exists, false otherwise.
156
	 */
157
	public function getHasAdapter()
158
	{
159
		return $this->_adapter !== null;
160
	}
161
162
	/**
163
	 * Initializes the module.
164
	 * This method is required by IModule and is invoked by application.
165
	 * It starts output buffer if it is enabled.
166
	 * @param \Prado\Xml\TXmlElement $config module configuration
167 8
	 */
168
	public function init($config)
169 8
	{
170 8
		if ($this->_bufferOutput) {
171
			ob_start();
172 8
		}
173 8
		$this->_initialized = true;
174 8
		$this->getApplication()->setResponse($this);
175
		parent::init($config);
176
	}
177
178
	/**
179 1
	 * @return int time-to-live for cached session pages in minutes, this has no effect for nocache limiter. Defaults to 180.
180
	 */
181 1
	public function getCacheExpire()
182
	{
183
		return session_cache_expire();
184
	}
185
186
	/**
187 1
	 * @param int $value time-to-live for cached session pages in minutes, this has no effect for nocache limiter.
188
	 */
189 1
	public function setCacheExpire($value)
190 1
	{
191
		session_cache_expire(TPropertyValue::ensureInteger($value));
192
	}
193
194
	/**
195 1
	 * @return string cache control method to use for session pages
196
	 */
197 1
	public function getCacheControl()
198
	{
199
		return session_cache_limiter();
200
	}
201
202
	/**
203
	 * @param string $value cache control method to use for session pages. Valid values
204 1
	 *               include none/nocache/private/private_no_expire/public
205
	 */
206 1
	public function setCacheControl($value)
207 1
	{
208
		session_cache_limiter(TPropertyValue::ensureEnum($value, ['none', 'nocache', 'private', 'private_no_expire', 'public']));
209
	}
210
211
	/**
212
	 * @param string $type content type, default is text/html
213 1
	 */
214
	public function setContentType($type)
215 1
	{
216
		if ($this->_contentTypeHeaderSent) {
217
			throw new \Exception('Unable to alter content-type as it has been already sent');
218 1
		}
219 1
		$this->_contentType = $type;
220
	}
221
222
	/**
223
	 * @return string current content type
224 1
	 */
225
	public function getContentType()
226 1
	{
227
		return $this->_contentType;
228
	}
229
230
	/**
231
	 * @return bool|string output charset.
232 1
	 */
233
	public function getCharset()
234 1
	{
235
		return $this->_charset;
236
	}
237
238
	/**
239
	 * @param bool|string $charset output charset.
240 1
	 */
241
	public function setCharset($charset)
242 1
	{
243 1
		$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

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

581
		$this->appendHeader('Content-Type: ' . $contentType . ';charset=' . /** @scrutinizer ignore-type */ $charset);
Loading history...
582
583
		$this->_contentTypeHeaderSent = true;
584
	}
585
586
	/**
587
	 * Returns the content in the output buffer.
588
	 * The buffer will NOT be cleared after calling this method.
589
	 * Use {@see clear()} is you want to clear the buffer.
590
	 * @return string output that is in the buffer.
591
	 */
592
	public function getContents()
593
	{
594
		Prado::trace("Retrieving output", THttpResponse::class);
595
		return $this->_bufferOutput ? ob_get_contents() : '';
596
	}
597
598
	/**
599
	 * Clears any existing buffered content.
600
	 */
601
	public function clear()
602
	{
603
		if ($this->_bufferOutput && ob_get_length() > 0) {
604
			ob_clean();
605
		}
606
		Prado::trace("Clearing output", THttpResponse::class);
607
	}
608
609
	/**
610
	 * @param null|int $case Either {@see CASE_UPPER} or {@see CASE_LOWER} or as is null (default)
611
	 * @return array
612
	 */
613
	public function getHeaders($case = null)
614
	{
615
		$result = [];
616
		$headers = headers_list();
617
		foreach ($headers as $header) {
618
			$tmp = explode(':', $header);
619
			$key = trim(array_shift($tmp));
620
			$value = trim(implode(':', $tmp));
621
			if (isset($result[$key])) {
622
				$result[$key] .= ', ' . $value;
623
			} else {
624
				$result[$key] = $value;
625
			}
626
		}
627
628
		if ($case !== null) {
629
			return array_change_key_case($result, $case);
630
		}
631
632
		return $result;
633
	}
634
635
	/**
636
	 * Sends a header.
637
	 * @param string $value header
638
	 * @param bool $replace whether the header should replace a previous similar header, or add a second header of the same type
639
	 */
640
	public function appendHeader($value, $replace = true)
641
	{
642
		Prado::trace("Sending header '$value'", THttpResponse::class);
643
		header($value, $replace);
644
	}
645
646
	/**
647
	 * Writes a log message into error log.
648
	 * This method is simple wrapper of PHP function error_log.
649
	 * @param string $message The error message that should be logged
650
	 * @param int $messageType where the error should go
651
	 * @param string $destination The destination. Its meaning depends on the message parameter as described above
652
	 * @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.
653
	 * @see http://us2.php.net/manual/en/function.error-log.php
654
	 */
655
	public function appendLog($message, $messageType = 0, $destination = '', $extraHeaders = '')
656
	{
657
		error_log($message, $messageType, $destination, $extraHeaders);
658
	}
659
660
	/**
661
	 * Sends a cookie.
662
	 * Do not call this method directly. Operate with the result of {@see getCookies} instead.
663
	 * @param THttpCookie $cookie cook to be sent
664
	 */
665
	public function addCookie($cookie)
666
	{
667
		$request = $this->getRequest();
668
		if ($request->getEnableCookieValidation()) {
669
			$value = $this->getApplication()->getSecurityManager()->hashData($cookie->getValue());
670
		} else {
671
			$value = $cookie->getValue();
672
		}
673
674
		setcookie(
675
			$cookie->getName(),
676
			$value,
677
			$cookie->getPhpOptions()
678
		);
679
	}
680
681
	/**
682
	 * Deletes a cookie.
683
	 * Do not call this method directly. Operate with the result of {@see getCookies} instead.
684
	 * @param THttpCookie $cookie cook to be deleted
685
	 */
686
	public function removeCookie($cookie)
687
	{
688
		$options = $cookie->getPhpOptions();
689
		$options['expires'] = 0;
690
		setcookie(
691
			$cookie->getName(),
692
			null,
693
			$options
694
		);
695
	}
696
697
	/**
698
	 * @return string the type of HTML writer to be used, defaults to THtmlWriter
699
	 */
700
	public function getHtmlWriterType()
701
	{
702
		return $this->_htmlWriterType;
703
	}
704
705
	/**
706
	 * @param string $value the type of HTML writer to be used, may be the class name or the namespace
707
	 */
708
	public function setHtmlWriterType($value)
709
	{
710
		$this->_htmlWriterType = $value;
711
	}
712
713
	/**
714
	 * Creates a new instance of HTML writer.
715
	 * If the type of the HTML writer is not supplied, {@see getHtmlWriterType HtmlWriterType} will be assumed.
716
	 * @param string $type type of the HTML writer to be created. If null, {@see getHtmlWriterType HtmlWriterType} will be assumed.
717
	 */
718
	public function createHtmlWriter($type = null)
719
	{
720
		if ($type === null) {
721
			$type = $this->getHtmlWriterType();
722
		}
723
		if ($this->getHasAdapter()) {
724
			return $this->_adapter->createNewHtmlWriter($type, $this);
725
		} else {
726
			return $this->createNewHtmlWriter($type, $this);
727
		}
728
	}
729
730
	/**
731
	 * Create a new html writer instance.
732
	 * This method is used internally. Please use {@see createHtmlWriter} instead.
733
	 * @param string $type type of HTML writer to be created.
734
	 * @param \Prado\IO\ITextWriter $writer text writer holding the contents.
735
	 */
736
	public function createNewHtmlWriter($type, $writer)
737
	{
738
		return Prado::createComponent($type, $writer);
739
	}
740
}
741