Passed
Push — developer ( a9af3c...f6f186 )
by Mariusz
35:26
created

Api::getOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * Basic communication file.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce Sp. z o.o.
8
 * @license   YetiForce Public License 3.0 (licenses/LicenseEN.txt or yetiforce.com))
9
 * @author    Mariusz Krzaczkowski <[email protected]>
10
 * @author    Radosław Skrzypczak <[email protected]>
11
 */
12
13
namespace App;
14
15
class Api
16
{
17
	/** @var self API Instance */
18
	protected static $instance;
19
20
	/** @var array Headers. */
21
	protected $header = [];
22
23
	/** @var \GuzzleHttp\Client Guzzle http client */
24
	protected $httpClient;
25
26
	/** @var array The default configuration of GuzzleHttp. */
27
	protected $options = [];
28
29
	/**
30
	 * Api class constructor.
31
	 *
32
	 * @param array $header
33
	 * @param array $options
34
	 */
35
	public function __construct(array $header, array $options)
36
	{
37
		$this->header = $header;
38
		$this->httpClient = new \GuzzleHttp\Client(array_merge($options, Config::$options));
39
	}
40
41
	/**
42
	 * Initiate API instance.
43
	 *
44
	 * @return self instance
0 ignored issues
show
Documentation introduced by
Should the return type not be \self?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
45
	 */
46
	public static function getInstance(): self
47
	{
48
		if (!isset(self::$instance)) {
49
			$userInstance = User::getUser();
50
			$header = [
51
				'User-Agent' => 'YetiForcePortal2',
52
				'X-Encrypted' => Config::$encryptDataTransfer ? 1 : 0,
53
				'X-Api-Key' => Config::$apiKey,
54
				'Content-Type' => 'application/json',
55
			];
56
			if ($userInstance->has('logged')) {
57
				$header['X-Token'] = $userInstance->get('token');
58
			}
59
			if ($userInstance->has('companyId')) {
60
				$header['X-Parent-Id'] = $userInstance->get('companyId');
61
			}
62
			self::$instance = new self($header, [
63
				'http_errors' => false,
64
				'base_uri' => Config::$apiUrl,
65
				'auth' => [Config::$serverName, Config::$serverPass]
66
			]);
67
		}
68
		return self::$instance;
69
	}
70
71
	/**
72
	 * Call API method.
73
	 *
74
	 * @param string $method
75
	 * @param array  $data
76
	 * @param string $requestType Default get
77
	 *
78
	 * @return array|false
79
	 */
80
	public function call(string $method, array $data = [], string $requestType = 'get')
81
	{
82
		$rawRequest = $data;
83
		$startTime = microtime(true);
84
		$headers = $this->getHeaders();
85
		try {
86
			if (\in_array($requestType, ['get', 'delete'])) {
87
				$response = $this->httpClient->request($requestType, $method, ['headers' => $headers]);
88
			} else {
89
				$data = Json::encode($data);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $data. This often makes code more readable.
Loading history...
90
				if (Config::$encryptDataTransfer) {
91
					$data = $this->encryptData($data);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $data. This often makes code more readable.
Loading history...
Documentation introduced by
$data is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
92
				}
93
				$response = $this->httpClient->request($requestType, $method, ['headers' => $headers, 'body' => $data]);
94
			}
95
			$rawResponse = (string) $response->getBody();
96
			$encryptedHeader = $response->getHeader('encrypted');
97
			if ($encryptedHeader && 1 == $response->getHeader('encrypted')[0]) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $encryptedHeader of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
98
				$rawResponse = $this->decryptData($rawResponse);
0 ignored issues
show
Documentation introduced by
$rawResponse is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
99
			}
100
			$responseBody = Json::decode($rawResponse);
101
		} catch (\Throwable $e) {
102
			if (Config::$logs) {
103
				$this->addLogs($method, $data, '', $e->__toString());
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $data can also be of type string; however, App\Api::addLogs() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
104
			}
105
			throw new Exceptions\AppException('An error occurred while communicating with the CRM', 500, $e);
106
		}
107
		if (\App\Config::$debugApi) {
108
			$_SESSION['debugApi'][] = [
109
				'date' => date('Y-m-d H:i:s', $startTime),
110
				'time' => round(microtime(true) - $startTime, 2),
111
				'method' => $method,
112
				'requestType' => strtoupper($requestType),
113
				'rawRequest' => [$headers, $rawRequest],
114
				'rawResponse' => $rawResponse,
115
				'response' => $responseBody,
116
				'trace' => Debug::getBacktrace()
117
			];
118
		}
119
		if (Config::$logs) {
120
			$this->addLogs($method, $data, $response, $rawResponse);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type string; however, App\Api::addLogs() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Documentation introduced by
$response is of type object<Psr\Http\Message\ResponseInterface>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
121
		}
122
		if (empty($responseBody) || 200 !== $response->getStatusCode()) {
123
			throw new Exceptions\AppException('API returned an error: ' . $response->getReasonPhrase(), $response->getStatusCode());
124
		}
125
		if (isset($responseBody['error'])) {
126
			$_SESSION['systemError'][] = $responseBody['error'];
127
			throw new Exceptions\AppException($responseBody['error']['message'], $responseBody['error']['code'] ?? 500);
128
		}
129
		if (isset($responseBody['result'])) {
130
			return $responseBody['result'];
131
		}
132
	}
133
134
	/**
135
	 * Headers.
136
	 *
137
	 * @return array
138
	 */
139
	public function getHeaders(): array
140
	{
141
		return $this->header;
142
	}
143
144
	/**
145
	 * @param array $data
146
	 *
147
	 * @return string Encrypted string
148
	 */
149
	public function encryptData($data): string
150
	{
151
		$publicKey = 'file://' . ROOT_DIRECTORY . \DIRECTORY_SEPARATOR . Config::$publicKey;
152
		openssl_public_encrypt(Json::encode($data), $encrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
153
		return $encrypted;
154
	}
155
156
	/**
157
	 * @param array $data
158
	 *
159
	 * @throws \App\Exceptions\AppException
160
	 *
161
	 * @return array Decrypted string
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
162
	 */
163
	public function decryptData($data)
164
	{
165
		$privateKey = 'file://' . ROOT_DIRECTORY . \DIRECTORY_SEPARATOR . Config::$privateKey;
166
		if (!$privateKey = openssl_pkey_get_private($privateKey)) {
167
			throw new Exceptions\AppException('Private Key failed');
168
		}
169
		$privateKey = openssl_pkey_get_private($privateKey);
170
		openssl_private_decrypt($data, $decrypted, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
171
		return $decrypted;
172
	}
173
174
	/**
175
	 * @param string $method
176
	 * @param array  $data
177
	 * @param array  $response
178
	 * @param mixed  $rawResponse
179
	 */
180
	public function addLogs($method, $data, $response, $rawResponse = false)
181
	{
182
		$value['method'] = $method;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$value was never initialized. Although not strictly required by PHP, it is generally a good practice to add $value = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
183
		$value['data'] = $data;
184
		if ($rawResponse) {
185
			$value['rawResponse'] = $rawResponse;
186
		}
187
		$value['response'] = $response;
188
		\App\Log::info($value, 'Api');
189
	}
190
191
	/**
192
	 * Set custom headers.
193
	 *
194
	 * @param array $headers
195
	 *
196
	 * @return self
197
	 */
198
	public function setCustomHeaders(array $headers): self
199
	{
200
		$this->header = $this->getHeaders();
201
		foreach ($headers as $key => $value) {
202
			$this->header[strtolower($key)] = $value;
203
		}
204
		return $this;
205
	}
206
}
207