Completed
Push — master ( 17bd20...4edca1 )
by Nazar
04:17
created

Response::output_default_cli()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 13
rs 9.2
cc 4
eloc 10
nc 3
nop 0
1
<?php
2
/**
3
 * @package   CleverStyle CMS
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs;
9
use
10
	cs\Response\Psr7;
11
12
class Response {
13
	use
14
		Singleton,
15
		Psr7;
16
	/**
17
	 * Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
18
	 *
19
	 * @var string
20
	 */
21
	public $protocol;
22
	/**
23
	 * HTTP status code
24
	 *
25
	 * @var int
26
	 */
27
	public $code;
28
	/**
29
	 * Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`, `content-type`, `accept-language`
30
	 *
31
	 * Values might be strings in case of single value or array of strings in case of multiple values with the same field name
32
	 *
33
	 * @var string[][]
34
	 */
35
	public $headers;
36
	/**
37
	 * String body (is used instead of `$this->body_stream` in most cases, ignored if `$this->body_stream` is present)
38
	 *
39
	 * @var string
40
	 */
41
	public $body;
42
	/**
43
	 * Body in form of stream (might be used instead of `$this->body` in some cases, if present, `$this->body` is ignored)
44
	 *
45
	 * Stream is read/write
46
	 *
47
	 * @var resource
48
	 */
49
	public $body_stream;
50
	/**
51
	 * Initialize response object with specified data
52
	 *
53
	 * @param string               $body
54
	 * @param null|resource|string $body_stream String, like `php://temp` or resource, like `fopen('php://temp', 'a+b')`, if present, `$body` is ignored
55
	 * @param string[]|string[][]  $headers     Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`,
56
	 *                                          `content-type`, `accept-language`; Values might be strings in case of single value or array of strings in case
57
	 *                                          of multiple values with the same field name
58
	 * @param int                  $code        HTTP status code
59
	 * @param string               $protocol    Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
60
	 *
61
	 * @return Response
62
	 */
63
	function init ($body = '', $body_stream = null, $headers = [], $code = 200, $protocol = 'HTTP/1.1') {
64
		$this->protocol = $protocol;
65
		$this->code     = $code;
66
		$this->headers  = _array($headers);
67
		$this->body     = $body;
68
		if ($this->body_stream) {
69
			fclose($this->body_stream);
70
		}
71
		$this->data_stream = is_string($body_stream) ? fopen($body_stream, 'a+b') : $body_stream;
72
		return $this;
73
	}
74
	/**
75
	 * Initialize with typical default settings (headers `Content-Type` and `Vary`, protocol taken from `cs\Request::$protocol`)
76
	 *
77
	 * @return Response
78
	 */
79
	function init_with_typical_default_settings () {
80
		return $this->init(
81
			'',
82
			null,
83
			[
84
				'Content-Type' => 'text/html; charset=utf-8',
85
				'Vary'         => 'Accept-Language,User-Agent,Cookie'
86
			],
87
			200,
88
			Request::instance()->protocol
89
		);
90
	}
91
	/**
92
	 * Set raw HTTP header
93
	 *
94
	 * @param string $field        Field
95
	 * @param string $value        Value, empty string will cause header removal
96
	 * @param bool   $replace      The optional replace parameter indicates whether the header should replace a previous similar header, or add a second header
97
	 *                             of the same type. By default it will replace
98
	 *
99
	 * @return Response
100
	 */
101
	function header ($field, $value, $replace = true) {
102
		$field = strtolower($field);
103
		if ($value === '') {
104
			unset($this->headers[$field]);
105
		} elseif ($replace || !isset($this->headers[$field])) {
106
			$this->headers[$field] = [$value];
107
		} else {
108
			$this->headers[$field][] = $value;
109
		}
110
		return $this;
111
	}
112
	/**
113
	 * Make redirect to specified location
114
	 *
115
	 * @param string $location
116
	 * @param int    $code
117
	 *
118
	 * @return Response
119
	 */
120
	function redirect ($location, $code = 302) {
121
		$this->header('location', $location);
122
		$this->code                 = $code;
123
		Page::instance()->interface = false;
124
		return $this;
125
	}
126
	/**
127
	 * Function for setting cookies, taking into account cookies prefix. Parameters like in system `setcookie()` function, but `$path`, `$domain` and `$secure`
128
	 * are skipped, they are detected automatically
129
	 *
130
	 * This function have side effect of setting cookie on `cs\Request` object
131
	 *
132
	 * @param string $name
133
	 * @param string $value
134
	 * @param int    $expire
135
	 * @param bool   $httponly
136
	 *
137
	 * @return Response
138
	 */
139
	function cookie ($name, $value, $expire = 0, $httponly = false) {
140
		$Request = Request::instance();
141
		$Config  = Config::instance();
142
		$prefix  = '';
143
		$domain  = explode(':', $Request->host)[0];
144
		if ($Config) {
145
			$prefix         = $Config->core['cookie_prefix'];
146
			$cookie_domains = $Config->core['cookie_domain'];
147
			$domain         = isset($cookie_domains[$Request->mirror_index]) ? $cookie_domains[$Request->mirror_index] : $cookie_domains[0];
148
		}
149
		if ($value === '') {
150
			unset($Request->cookie[$name], $Request->cookie[$prefix.$name]);
151
		} else {
152
			$Request->cookie[$name]         = $value;
153
			$Request->cookie[$prefix.$name] = $value;
154
		}
155
		$header = [
156
			rawurlencode($prefix.$name).'='.rawurlencode($value),
157
			'path=/'
158
		];
159
		if ($expire || !$value) {
160
			$header[] = 'expires='.gmdate('D, d-M-Y H:i:s', $expire).' GMT';
161
		}
162
		if ($domain) {
163
			$header[] = "domain=$domain";
164
		}
165
		if ($Request->secure) {
166
			$header[] = 'secure';
167
		}
168
		if ($httponly) {
169
			$header[] = 'HttpOnly';
170
		}
171
		$this->header('set-cookie', implode('; ', $header), false);
172
		return $this;
173
	}
174
	/**
175
	 * Provides default output for all the response data using `header()`, `http_response_code()` and `echo` or `php://output`
176
	 */
177
	function output_default () {
178
		if (Request::instance()->cli_path) {
179
			$this->output_default_cli();
180
		} else {
181
			$this->output_default_web();
182
		}
183
	}
184
	protected function output_default_cli () {
185
		if ($this->code >= 400 && $this->code <= 510) {
186
			echo $this->body;
187
			exit($this->code % 256);
188
		}
189
		if (is_resource($this->body_stream)) {
190
			$position = ftell($this->body_stream);
191
			stream_copy_to_stream($this->body_stream, STDIN);
192
			fseek($this->body_stream, $position);
193
		} else {
194
			echo $this->body;
195
		}
196
	}
197
	protected function output_default_web () {
198
		foreach ($this->headers as $header => $value) {
199
			foreach ($value as $v) {
200
				header("$header: $v", false);
201
			}
202
		}
203
		http_response_code($this->code);
204
		if ($this->code >= 300 && $this->code < 400) {
205
			return;
206
		}
207
		if (is_resource($this->body_stream)) {
208
			$position = ftell($this->body_stream);
209
			stream_copy_to_stream($this->body_stream, fopen('php:://output', 'wb'));
210
			fseek($this->body_stream, $position);
211
		} else {
212
			echo $this->body;
213
		}
214
	}
215
}
216