Completed
Push — master ( 69cdb4...c2609a )
by Nazar
03:54
created

Response   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 14
Bugs 3 Features 4
Metric Value
wmc 24
c 14
b 3
f 4
lcom 1
cbo 6
dl 0
loc 185
rs 10

6 Methods

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