Passed
Push — development ( b93807...dda237 )
by Emanuele
01:10 queued 23s
created

Headers::removeHeader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 9
nc 2
nop 1
dl 0
loc 17
rs 9.9666
c 1
b 0
f 0
1
<?php
2
3
/**
4
 *
5
 * @package   ElkArte Forum
6
 * @copyright ElkArte Forum contributors
7
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
8
 *
9
 * @version 2.0 dev
10
 *
11
 */
12
13
namespace ElkArte\Http;
14
15
use ElkArte\HttpReq;
16
17
class Headers
18
{
19
	/** @var string Default content type */
20
	protected $contentType = 'text/html';
21
22
	/** @var string Default character set */
23
	protected $charset = 'UTF-8';
24
25
	/** @var int Default HTTP return code */
26
	protected $httpCode = 200;
27
28
	/** @var array Holds any normal headers collected */
29
	protected $headers = [];
30
31
	/** @var array Holds any special (raw) headers collected */
32
	protected $specialHeaders = [];
33
34
	/** @var \ElkArte\HttpReq|null */
35
	protected $req;
36
37
	/** @var \ElkArte\Http\Headers Sole private \ElkArte\HttpReq instance */
38
	private static $header = null;
39
40
	/**
41
	 * Headers constructor.
42
	 */
43
	public function __construct()
44
	{
45
		$this->req = HttpReq::instance();
46
	}
47
48
	/**
49
	 * Sets a redirect location header
50
	 *
51
	 * What it does:
52
	 *
53
	 * - Adds in scripturl if needed
54
	 * - Calls call_integration_hook integrate_redirect before headers are sent
55
	 *
56
	 * @event integrate_redirect called before headers are sent
57
	 * @param string $setLocation = '' The URL to redirect to
58
	 * @param int $httpCode defaults to 200
59
	 */
60
	public function redirect($setLocation = '', $httpCode = null)
61
	{
62
		global $scripturl;
63
64
		if ($setLocation === '')
65
		{
66
			return $this->header('Location');
67
		}
68
69
		// Convert relative URL to site url
70
		if (preg_match('~^(ftp|http)[s]?://~', $setLocation) === 0)
71
		{
72
			$setLocation = $scripturl . ($setLocation !== '' ? '?' . $setLocation : '');
73
		}
74
75
		// Put the session ID in.
76
		if (empty($_COOKIE) && defined('SID') && !empty(SID))
77
		{
78
			$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
79
		}
80
		// Keep that debug in their for template debugging!
81
		elseif (isset($this->req->debug))
82
		{
83
			$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
84
		}
85
86
		// Maybe integrations want to change where we are heading?
87
		call_integration_hook('integrate_redirect', array(&$setLocation));
88
89
		// Set the location header and code
90
		$this
91
			->header('Location', $setLocation)
92
			->httpCode = $httpCode ?? 302;
93
94
		return $this;
95
	}
96
97
	/**
98
	 * Run maintance function and then send the all collected headers
99
	 */
100
	public function send()
101
	{
102
		handleMaintance();
103
		$this->sendHeaders();
104
	}
105
106
	/**
107
	 * Normally used for a header that starts with the string "HTTP/" (case is not significant),
108
	 * which will be used to figure out the HTTP status code to send.  You could stuff in any
109
	 * complete header you wanted as the value is sent via header($value)
110
	 *
111
	 * @param $value
112
	 * @return $this
113
	 */
114
	public function headerSpecial($value)
115
	{
116
		$this->specialHeaders[] = $value;
117
118
		return $this;
119
	}
120
121
	/**
122
	 * Adds headers to the header array for eventual output to browser
123
	 *
124
	 * @param string $name Name of the header
125
	 * @param string|null $value Value for the header
126
	 *
127
	 * @return $this
128
	 */
129
	public function header($name, $value = null)
130
	{
131
		$name = $this->standardizeHeaderName($name);
132
133
		// Add new or overwrite
134
		$this->headers[$name] = $value;
135
136
		return $this;
137
	}
138
139
	/**
140
	 * Converts / Fixes header names in to a standard format to enable consistent
141
	 * search replace is etc.
142
	 *
143
	 * @param string $name
144
	 * @return string
145
	 */
146
	protected function standardizeHeaderName($name)
147
	{
148
		// Combine spaces and Convert dashes "clear    Site-Data" => "clear Site Data"
149
		$name = preg_replace('~\s+~', ' ', str_replace('-', ' ', trim($name)));
150
151
		// Now ucword the header and add back the dash => Clear-Site-Data
152
		$name = str_replace(' ', '-', ucwords($name));
153
154
		return $name;
155
	}
156
157
	/**
158
	 * Set the http header code, like 404, 200, 301, etc  Only output if
159
	 * content type is not empty
160
	 *
161
	 * @param int $httpCode
162
	 * @return $this
163
	 */
164
	public function httpCode($httpCode)
165
	{
166
		$this->httpCode = intval($httpCode);
167
168
		return $this;
169
	}
170
171
	/**
172
	 * Sets the context type based on if this is an image or not.  Calls
173
	 * setDownloadFileNameHeader to set the proper content disposition.
174
	 *
175
	 * @param string $mime_type
176
	 * @param string $fileName
177
	 * @param string $disposition 'attachment' or 'inline';
178
	 * @return $this
179
	 */
180
	public function setAttachmentFileParams($mime_type, $fileName, $disposition = 'attachment')
181
	{
182
		// If its an image set the content type to the image/type defined in the mime_type
183
		if (!empty($mime_type) && strpos($mime_type, 'image/') === 0)
184
		{
185
			$this->contentType($mime_type, '');
186
		}
187
		// Otherwise arbitrary binary data
188
		else
189
		{
190
			$this->contentType('application/octet-stream', '');
191
		}
192
193
		// Set the content disposition and name
194
		$this->setDownloadFileNameHeader($fileName, $disposition);
195
196
		return $this;
197
	}
198
199
	/**
200
	 * Set the proper filename header accounting for UTF-8 characters in the name
201
	 *
202
	 * @param string $fileName That would be the name
203
	 * @param string $disposition 'inline' or 'attachment'
204
	 */
205
	private function setDownloadFileNameHeader($fileName, $disposition = false)
206
	{
207
		$type = ($disposition ? 'inline' : 'attachment');
208
209
		$fileName = str_replace('"', '', $fileName);
210
211
		// Send as UTF-8 if the name requires that
212
		$altName = '';
213
		if (preg_match('~[\x80-\xFF]~', $fileName))
214
		{
215
			$altName = "; filename*=UTF-8''" . rawurlencode($fileName);
216
		}
217
218
		$this->header('Content-Disposition',$type . '; filename="' . $fileName . '"' . $altName);
219
220
		return $this;
221
	}
222
223
	/**
224
	 * Sets the content type and character set.  Replaces an existing one if called multiple times
225
	 * so the last call to this method will be what is output.
226
	 *
227
	 * @param string|null $contentType
228
	 * @param string|null $charset
229
	 * @return $this
230
	 */
231
	public function contentType($contentType, $charset = null)
232
	{
233
		$this->contentType = $contentType;
234
235
		if ($charset !== null)
236
		{
237
			$this->charset($charset);
238
		}
239
240
		return $this;
241
	}
242
243
	/**
244
	 * Sets the character set in use, defaults to utf-8
245
	 *
246
	 * @param string $charset
247
	 * @return $this
248
	 */
249
	public function charset($charset)
250
	{
251
		$this->charset = $charset;
252
253
		return $this;
254
	}
255
256
	/**
257
	 * Removes a single header if set or all headers if we need to restart
258
	 * the process, such as during an error or other.
259
	 *
260
	 * @param string $name
261
	 * @return $this
262
	 */
263
	public function removeHeader($name)
264
	{
265
		// Full reset like nothing had been sent
266
		if ($name === 'all')
267
		{
268
			$this->headers = [];
269
			$this->specialHeaders = [];
270
			$this->contentType = '';
271
			$this->charset = 'UTF-8';
272
			$this->httpCode = 200;
273
		}
274
275
		// Or remove a specific header
276
		$name = $this->standardizeHeaderName($name);
277
		unset($this->headers[$name]);
278
279
		return $this;
280
	}
281
282
	/**
283
	 * Send the collection of headers using standard php header() function
284
	 */
285
	public function sendHeaders()
286
	{
287
		foreach ($this->headers as $header => $value)
288
		{
289
			header("$header: $value", true);
290
		}
291
292
		foreach ($this->specialHeaders as $header)
293
		{
294
			header($header, true);
295
		}
296
297
		if ($this->contentType)
298
		{
299
			header('Content-Type: ' . $this->contentType
300
				. ($this->charset ? '; charset=' . $this->charset : ''), true, $this->httpCode);
301
		}
302
	}
303
304
	/**
305
	 * Retrieve the sole instance of this class.
306
	 *
307
	 * @return \ElkArte\Http\Headers
308
	 */
309
	public static function instance()
310
	{
311
		if (self::$header === null)
312
		{
313
			self::$header = new Headers();
314
		}
315
316
		return self::$header;
317
	}
318
}
319