Passed
Push — developer ( dedd13...2a4a55 )
by Mariusz
14:30
created

Response   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 324
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 45
eloc 111
dl 0
loc 324
ccs 0
cts 78
cp 0
rs 8.8
c 0
b 0
f 0

How to fix   Complexity   

Complex Class

Complex classes like Response often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Response, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Web service response file.
4
 *
5
 * @package API
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 5.0 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author    Mariusz Krzaczkowski <[email protected]>
10
 */
11
12
namespace Api\Core;
13
14
/**
15
 * Web service response class.
16
 */
17
class Response
18
{
19
	/**
20
	 * Access control allow headers.
21
	 *
22
	 * @var string[]
23
	 */
24
	protected $acceptableHeaders = ['x-api-key', 'x-encrypted', 'x-token'];
25
	/**
26
	 * Access control allow methods.
27
	 *
28
	 * @var string[]
29
	 */
30
	protected $acceptableMethods = [];
31
	/**
32
	 * Request instance.
33
	 *
34
	 * @var \Api\Core\Request
35
	 */
36
	protected $request;
37
	protected static $instance = false;
38
	protected $body;
39
	/**
40
	 * File instance.
41
	 *
42
	 * @var \App\Fields\File
43
	 */
44
	protected $file;
45
	/**
46
	 * Headers.
47
	 *
48
	 * @var array
49
	 */
50
	protected $headers = [];
51
	/**
52
	 * @var int Response status code.
53
	 */
54
	protected $status = 200;
55
	/**
56
	 * @var string Response data type.
57
	 */
58
	protected $responseType;
59
	/**
60
	 * @var string Reason phrase.
61
	 */
62
	protected $reasonPhrase;
63
	/**
64
	 * @var string Reason content type
65
	 */
66
	protected $contentType;
67
68
	/**
69
	 * Get instance.
70
	 *
71
	 * @return self
72
	 */
73
	public static function getInstance(): self
74
	{
75
		if (!static::$instance) {
76
			static::$instance = new self();
77
		}
78
		return static::$instance;
79
	}
80
81
	/**
82
	 * Add header.
83
	 *
84
	 * @param string $key
85
	 * @param mixed  $value
86
	 *
87
	 * @return void
88
	 */
89
	public function addHeader(string $key, $value): void
90
	{
91
		$this->headers[$key] = $value;
92
	}
93
94
	/**
95
	 * Set status code.
96
	 *
97
	 * @param int $status
98
	 *
99
	 * @return void
100
	 */
101
	public function setStatus(int $status): void
102
	{
103
		if (is_numeric($status)) {
104
			$this->status = $status;
105
		}
106
	}
107
108
	/**
109
	 * Set reason phrase.
110
	 *
111
	 * @param string $reasonPhrase
112
	 *
113
	 * @return void
114
	 */
115
	public function setReasonPhrase(string $reasonPhrase): void
116
	{
117
		$this->reasonPhrase = $reasonPhrase;
118
	}
119
120
	/**
121
	 * Set body data.
122
	 *
123
	 * @param array $body
124
	 *
125
	 * @return void
126
	 */
127
	public function setBody(array $body): void
128
	{
129
		$this->body = $body;
130
		$this->responseType = 'data';
131
	}
132
133
	/**
134
	 * Set file instance.
135
	 *
136
	 * @param \App\Fields\File $file
137
	 *
138
	 * @return void
139
	 */
140
	public function setFile(\App\Fields\File $file): void
141
	{
142
		$this->file = $file;
143
		$this->responseType = 'file';
144
	}
145
146
	/**
147
	 * Set request.
148
	 *
149
	 * @param \Api\Core\Request $request
150
	 *
151
	 * @return void
152
	 */
153
	public function setRequest(Request $request): void
154
	{
155
		$this->request = $request;
156
	}
157
158
	/**
159
	 * Set acceptable methods.
160
	 *
161
	 * @param string[] $methods
162
	 *
163
	 * @return void
164
	 */
165
	public function setAcceptableMethods(array $methods)
166
	{
167
		$this->acceptableMethods = array_merge($this->acceptableMethods, $methods);
168
	}
169
170
	/**
171
	 * Set acceptable headers.
172
	 *
173
	 * @param string[] $headers
174
	 *
175
	 * @return void
176
	 */
177
	public function setAcceptableHeaders(array $headers)
178
	{
179
		$this->acceptableHeaders = array_merge($this->acceptableHeaders, $headers);
180
	}
181
182
	/**
183
	 * Set acceptable headers.
184
	 *
185
	 * @param string $type
186
	 *
187
	 * @return void
188
	 */
189
	public function setContentType(string $type): void
190
	{
191
		$this->contentType = $type;
192
	}
193
194
	/**
195
	 * Get reason phrase.
196
	 *
197
	 * @return string
198
	 */
199
	private function getReasonPhrase(): string
200
	{
201
		if (isset($this->reasonPhrase)) {
202
			return str_ireplace(["\r\n", "\r", "\n"], ' ', $this->reasonPhrase);
203
		}
204
		$statusCodes = [
205
			200 => 'OK',
206
			401 => 'Unauthorized',
207
			403 => 'Forbidden',
208
			404 => 'Not Found',
209
			405 => 'Method Not Allowed',
210
			500 => 'Internal Server Error',
211
		];
212
		return $statusCodes[$this->status] ?? $statusCodes[500];
213
	}
214
215
	public function send()
216
	{
217
		$encryptDataTransfer = \App\Config::api('ENCRYPT_DATA_TRANSFER') ? 1 : 0;
218
		if (200 !== $this->status || 'data' !== $this->responseType) {
219
			$encryptDataTransfer = 0;
220
		}
221
		if (empty($this->contentType)) {
222
			$requestContentType = strtolower(\App\Request::_getServer('HTTP_ACCEPT'));
223
			if (empty($requestContentType) || '*/*' === $requestContentType) {
224
				$this->contentType = $this->request->contentType;
225
			} else {
226
				$this->contentType = $requestContentType;
227
			}
228
		}
229
		$headersSent = headers_sent();
230
		if (!$headersSent) {
231
			header('Access-Control-Allow-Origin: *');
232
			header('Access-Control-Allow-Methods: ' . implode(', ', $this->acceptableMethods));
233
			header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, ' . implode(', ', $this->acceptableHeaders));
234
			header(\App\Request::_getServer('SERVER_PROTOCOL') . ' ' . $this->status . ' ' . $this->getReasonPhrase());
235
			header('Encrypted: ' . $encryptDataTransfer);
236
			foreach ($this->headers as $key => $header) {
237
				header(\strtolower($key) . ': ' . $header);
238
			}
239
		}
240
		if ($encryptDataTransfer) {
241
			header('Content-disposition: attachment; filename="api.json"');
242
			if (!empty($this->body)) {
243
				echo $this->encryptData($this->body);
244
			}
245
		} else {
246
			switch ($this->responseType) {
247
				case 'data':
248
					if (!empty($this->body)) {
249
						if (!$headersSent) {
250
							header("Content-type: {$this->contentType}");
251
						}
252
						if (false !== strpos($this->contentType, 'application/xml')) {
253
							if (!$headersSent) {
254
								header('Content-disposition: attachment; filename="api.xml"');
255
							}
256
							echo $this->encodeXml($this->body);
257
						} else {
258
							if (!$headersSent) {
259
								header('Content-disposition: attachment; filename="api.json"');
260
							}
261
							echo $this->encodeJson($this->body);
262
						}
263
					}
264
					break;
265
				case 'file':
266
					if (isset($this->file) && file_exists($this->file->getPath())) {
267
						header('Content-type: ' . $this->file->getMimeType());
268
						header('Content-transfer-encoding: binary');
269
						header('Content-length: ' . $this->file->getSize());
270
						header('Content-disposition: attachment; filename="' . $this->file->getName() . '"');
271
						readfile($this->file->getPath());
272
					}
273
					break;
274
			}
275
		}
276
		$this->debugResponse();
277
	}
278
279
	public function encryptData($data)
280
	{
281
		openssl_public_encrypt($data, $encrypted, 'file://' . ROOT_DIRECTORY . \DIRECTORY_SEPARATOR . \App\Config::api('PUBLIC_KEY'), OPENSSL_PKCS1_OAEP_PADDING);
282
		return $encrypted;
283
	}
284
285
	/**
286
	 * Debug response function.
287
	 *
288
	 * @return void
289
	 */
290
	public function debugResponse()
291
	{
292
		if (\App\Config::debug('apiLogAllRequests')) {
293
			$log = '============ Request ' . \App\RequestUtil::requestId() . ' (Response) ======  ' . date('Y-m-d H:i:s') . "  ======\n";
294
			$log .= 'REQUEST_METHOD: ' . \App\Request::getRequestMethod() . PHP_EOL;
295
			$log .= 'REQUEST_URI: ' . $_SERVER['REQUEST_URI'] . PHP_EOL;
296
			$log .= 'QUERY_STRING: ' . $_SERVER['QUERY_STRING'] . PHP_EOL;
297
			$log .= 'PATH_INFO: ' . ($_SERVER['PATH_INFO'] ?? '') . PHP_EOL;
298
			$log .= 'IP: ' . $_SERVER['REMOTE_ADDR'] . PHP_EOL;
299
			if ($this->body) {
300
				$log .= "----------- Response data -----------\n";
301
				$log .= print_r($this->body, true) . PHP_EOL;
302
			}
303
			$path = ROOT_DIRECTORY . '/cache/logs/webserviceDebug.log';
304
			if (isset(\Api\Controller::$container)) {
305
				$path = ROOT_DIRECTORY . '/cache/logs/webservice' . \Api\Controller::$container . 'Debug.log';
306
			}
307
			file_put_contents($path, $log, FILE_APPEND);
308
		}
309
	}
310
311
	/**
312
	 * Encode json data output.
313
	 *
314
	 * @param array $responseData
315
	 *
316
	 * @return string
317
	 */
318
	public function encodeJson($responseData): string
319
	{
320
		return json_encode($responseData, JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE);
321
	}
322
323
	public function encodeXml($responseData)
324
	{
325
		$xml = new \SimpleXMLElement('<?xml version="1.0"?><data></data>');
326
		$this->toXml($responseData, $xml);
327
		return $xml->asXML();
328
	}
329
330
	public function toXml($data, \SimpleXMLElement &$xmlData)
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_VARIABLE, expecting T_STRING or T_NAME_QUALIFIED or T_NAME_FULLY_QUALIFIED or T_NAME_RELATIVE on line 330 at column 49
Loading history...
331
	{
332
		foreach ($data as $key => $value) {
333
			if (is_numeric($key)) {
334
				$key = 'item' . $key;
335
			}
336
			if (\is_array($value)) {
337
				$subnode = $xmlData->addChild($key);
338
				$this->toXml($value, $subnode);
339
			} else {
340
				$xmlData->addChild("$key", htmlspecialchars("$value"));
341
			}
342
		}
343
	}
344
}
345