Completed
Push — master ( d4da57...736ee5 )
by Nazar
04:18
created

Response::init()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 11
rs 9.4285
cc 3
eloc 9
nc 4
nop 5
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
10
class Response {
11
	use
12
		Singleton;
13
	/**
14
	 * Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
15
	 *
16
	 * @var string
17
	 */
18
	public $protocol;
19
	/**
20
	 * HTTP status code
21
	 *
22
	 * @var int
23
	 */
24
	public $code;
25
	/**
26
	 * Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`, `content-type`, `accept-language`
27
	 *
28
	 * Values might be strings in case of single value or array of strings in case of multiple values with the same field name
29
	 *
30
	 * @var string[]|string[][]
31
	 */
32
	public $headers;
33
	/**
34
	 * String body (is used instead of `$this->body_stream` in most cases)
35
	 *
36
	 * @var string
37
	 */
38
	public $body;
39
	/**
40
	 * Body in form of stream (might be used instead of `$this->body` in some cases, if present, `$this->body` is ignored)
41
	 *
42
	 * Stream is read/write
43
	 *
44
	 * @var resource
45
	 */
46
	public $body_stream;
47
	/**
48
	 * Initialize response object with specified data
49
	 *
50
	 * @param string               $body
51
	 * @param null|resource|string $body_stream String, like `php://temp` or resource, like `fopen('php://temp', 'ba+')`, if present, `$body` is ignored
52
	 * @param string[]|string[][]  $headers     Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`,
53
	 *                                          `content-type`, `accept-language`; Values might be strings in case of single value or array of strings in case
54
	 *                                          of multiple values with the same field name
55
	 * @param int                  $code        HTTP status code
56
	 * @param string               $protocol    Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
57
	 *
58
	 * @return Response
59
	 */
60
	function init ($body = '', $body_stream = null, $headers = [], $code = 200, $protocol = 'HTTP/1.1') {
61
		$this->protocol = $protocol;
62
		$this->code     = $code;
63
		$this->headers  = $headers;
64
		$this->body     = $body;
65
		if ($this->body_stream) {
66
			fclose($this->body_stream);
67
		}
68
		$this->data_stream = is_string($body_stream) ? fopen($body_stream, 'ba+') : $body_stream;
69
		return $this;
70
	}
71
	/**
72
	 * Set raw HTTP header
73
	 *
74
	 * @param string $field        Field
75
	 * @param string $value        Value, empty string will cause header removal
76
	 * @param bool   $replace      The optional replace parameter indicates whether the header should replace a previous similar header, or add a second header
77
	 *                             of the same type. By default it will replace
78
	 *
79
	 * @return Response
80
	 */
81
	function header ($field, $value, $replace = true) {
82
		$field = strtolower($field);
83
		if ($value === '') {
84
			unset($this->headers[$field]);
85
		} elseif ($replace || !isset($this->headers[$field])) {
86
			$this->headers[$field] = [$value];
87
		} else {
88
			$this->headers[$field][] = $value;
89
		}
90
		return $this;
91
	}
92
	/**
93
	 * Make redirect to specified location
94
	 *
95
	 * @param string   $location
96
	 * @param int|null $code
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $code a bit more specific; maybe use integer.
Loading history...
97
	 *
98
	 * @return Response
99
	 */
100
	function redirect ($location, $code = 302) {
101
		$this->header('location', $location);
102
		$this->code = $code;
103
		return $this;
104
	}
105
	/**
106
	 * Function for setting cookies, taking into account cookies prefix. Parameters like in system `setcookie()` function, but $path, $domain and $secure
107
	 * are skipped, they are detected automatically
108
	 *
109
	 * This function have side effect of setting cookie on `cs\Request` object
110
	 *
111
	 * @param string $name
112
	 * @param string $value
113
	 * @param int    $expire
114
	 * @param bool   $httponly
115
	 *
116
	 * @return Response
117
	 */
118
	function cookie ($name, $value, $expire = 0, $httponly = false) {
119
		$Request = Request::instance();
120
		$Config  = Config::instance();
121
		$prefix  = '';
122
		$secure  = $Request->secure;
123
		$domain  = explode(':', $Request->host)[0];
124
		if ($Config) {
125
			$Route          = Route::instance();
126
			$prefix         = $Config->core['cookie_prefix'];
127
			$cookie_domains = $Config->core['cookie_domain'];
128
			/** @noinspection OffsetOperationsInspection */
129
			$domain = isset($cookie_domains[$Route->mirror_index]) ? $cookie_domains[$Route->mirror_index] : $cookie_domains[0];
130
		}
131
		if ($value === '') {
132
			unset($Request->cookie[$name], $Request->cookie[$prefix.$name]);
133
		} else {
134
			$Request->cookie[$name]         = $value;
135
			$Request->cookie[$prefix.$name] = $value;
136
		}
137
		$header = [
138
			rawurlencode($prefix.$name).'='.rawurlencode($value),
139
			'path=/'
140
		];
141
		if ($expire || !$value) {
142
			$header[] = 'expires='.gmdate('D, d-M-Y H:i:s', $expire).' GMT';
143
		}
144
		if ($domain) {
145
			$header[] = "domain=$domain";
146
		}
147
		if ($secure) {
148
			$header[] = 'secure';
149
		}
150
		if ($httponly) {
151
			$header[] = 'HttpOnly';
152
		}
153
		$this->header('set-cookie', implode('; ', $header), false);
154
		return $this;
155
	}
156
	/**
157
	 * Provides standard output for all the response data
158
	 */
159
	function standard_output () {
160
		foreach ($this->headers as $header => $value) {
161
			foreach ($value as $v) {
0 ignored issues
show
Bug introduced by
The expression $value of type string|array<integer,string> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
162
				header("$header: $v", false);
163
			}
164
		}
165
		http_response_code($this->code);
166
		if (is_resource($this->body_stream)) {
167
			$position = ftell($this->body_stream);
168
			rewind($this->body_stream);
169
			stream_copy_to_stream($this->body_stream, fopen('php:://output', 'w'));
170
			fseek($this->body_stream, $position);
171
		} else {
172
			echo $this->body;
173
		}
174
	}
175
}
176