Completed
Push — master ( ec4104...208b1e )
by Nazar
05:01
created

Response::cookie()   C

Complexity

Conditions 8
Paths 64

Size

Total Lines 31
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 8

Importance

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