Passed
Push — developer ( 4e3135...f5c82a )
by Radosław
30:25 queued 12:59
created

Account::getSource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * Mail account file.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 5.0 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author    Radosław Skrzypczak <[email protected]>
10
 */
11
12
namespace App\Mail;
13
14
/**
15
 * Mail account class.
16
 */
17
class Account extends \App\Base
18
{
19
	/** @var string Mailbox Status: Active */
20
	public const STATUS_ACTIVE = 'PLL_ACTIVE';
21
	/** @var string Mailbox Status: Inactive */
22
	public const STATUS_INACTIVE = 'PL_INACTIVE';
23
	/** @var string Mailbox Status: Locked */
24
	public const STATUS_LOCKED = 'PLL_LOCKED';
25
26
	/** @var string Base module name */
27
	public const MODULE_NAME = 'MailAccount';
28
	/** @var \App\Mail\Server */
29
	private $server;
30
	/** @var \Vtiger_Record_Model */
31
	private $source;
32
	/** @var \App\Integrations\OAuth\AbstractProvider OAuth2 provider */
33
	private $provider;
34
	/** @var string */
35
	private $password;
36
	/** @var string */
37
	private $userName;
38
	/** @var string */
39
	private $refreshToken;
40
	/** @var string Date time */
41
	private $expireTime;
42
	/** @var string */
43
	private $redirectUri;
44
	/**
45
	 * List of scopes that will be used for authentication.
46
	 *
47
	 * @var array
48
	 */
49
	protected $scopes;
50
51
	/** @var int */
52
	private $attempt = 0;
53
54
	/**
55
	 * Get instance by ID.
56
	 *
57
	 * @param int $id
58
	 */
59
	public static function getInstanceById(int $id): ?self
60
	{
61
		$instance = null;
62
		if (\App\Record::isExists($id, self::MODULE_NAME, \App\Record::STATE_ACTIVE)) {
63
			$instance = new static();
64
			$instance->source = \Vtiger_Record_Model::getInstanceById($id, self::MODULE_NAME);
65
			$instance->userName = $instance->source->get('login');
66
			$instance->password = \App\Encryption::getInstance(\App\Module::getModuleId(self::MODULE_NAME))->decrypt($instance->source->get('password'));
67
			$instance->refreshToken = \App\Encryption::getInstance(\App\Module::getModuleId(self::MODULE_NAME))->decrypt($instance->source->get('refresh_token'));
68
			$instance->server = \App\Mail\Server::getInstanceById($instance->source->get('mail_server_id'));
69
			$instance->redirectUri = $instance->server->getRedirectUri();
0 ignored issues
show
Bug introduced by
The method getRedirectUri() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

69
			/** @scrutinizer ignore-call */ 
70
   $instance->redirectUri = $instance->server->getRedirectUri();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
70
		}
71
72
		return $instance;
73
	}
74
75
	public function getSource()
76
	{
77
		return $this->source;
78
	}
79
80
	public function getPassword()
81
	{
82
		return $this->password;
83
	}
84
85
	public function getLogin()
86
	{
87
		return $this->userName;
88
	}
89
90
	/**
91
	 * Mail server instance.
92
	 *
93
	 * @return \App\Mail\Server
94
	 */
95
	public function getServer(): Server
96
	{
97
		return $this->server;
98
	}
99
100
	public function getRefreshToken()
101
	{
102
		return $this->refreshToken;
103
	}
104
105
	public function update(array $fields = [])
106
	{
107
		if (!$fields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields of type array 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...
108
			$fields = ['password', 'refresh_token', 'expire_time'];
109
		}
110
		foreach ($fields as $fieldName) {
111
			$fieldModel = $this->source->getField($fieldName);
112
			$value = null;
113
			switch ($fieldName) {
114
				case 'password':
115
					$value = $fieldModel->getDBValue($this->password);
0 ignored issues
show
Bug introduced by
$this->password of type string is incompatible with the type type expected by parameter $value of Vtiger_Field_Model::getDBValue(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

115
					$value = $fieldModel->getDBValue(/** @scrutinizer ignore-type */ $this->password);
Loading history...
116
					break;
117
				case 'refresh_token':
118
					if (!$this->refreshToken) {
119
						break;
120
					}
121
					$value = $fieldModel->getDBValue($this->refreshToken);
122
					break;
123
				case 'expire_time':
124
					if (!$this->expireTime) {
125
						break;
126
					}
127
					$value = $this->expireTime;
128
					break;
129
				case 'last_login':
130
					$value = date('Y-m-d H:i:s');
131
					break;
132
				default:
133
					break;
134
			}
135
			if (null !== $value) {
136
				$this->source->set($fieldModel->getName(), $value)->setDataForSave([$fieldModel->getTableName() => [$fieldModel->getColumnName() => $value]]);
137
			}
138
		}
139
		if ($this->source->getPreviousValue()) {
140
			$this->source->save();
141
		}
142
	}
143
144
	/**
145
	 * Requests an access token using a specified option set.
146
	 *
147
	 * @param array $options
148
	 *
149
	 * @return string
150
	 */
151
	public function getAccessToken(array $options = [])
152
	{
153
		$provider = $this->getOAuthProvider();
154
		if (isset($options['code'])) {
155
			$provider->getAccessToken('authorization_code', $options);
156
		} elseif ($this->refreshToken) {
157
			$provider->setData(['refreshToken' => $this->refreshToken]);
158
			$provider->refreshToken();
159
		}
160
161
		if ($provider->getToken()) {
162
			$this->password = $provider->getToken();
163
			if ($provider->getRefreshToken()) {
164
				$this->refreshToken = $provider->getRefreshToken();
165
			}
166
			$this->expireTime = date('Y-m-d H:i:s', $provider->getExpires());
167
		}
168
169
		return $provider->getToken();
170
	}
171
172
	/**
173
	 * Get OAuth provider.
174
	 *
175
	 * @return \App\Integrations\OAuth\AbstractProvider
176
	 */
177
	public function getOAuthProvider(): \App\Integrations\OAuth\AbstractProvider
178
	{
179
		if (!$this->provider) {
180
			$this->provider = \App\Integrations\OAuth::getProviderByName($this->getServer()->get('oauth_provider'));
181
			$this->provider->setData([
182
				'clientId' => $this->getServer()->get('client_id'),
183
				'clientSecret' => $this->getServer()->getClientSecret(),
184
				'redirectUri' => $this->redirectUri,
185
				'scopes' => $this->scopes ?: $this->provider->getScopesByAction(self::MODULE_NAME)
0 ignored issues
show
Bug introduced by
The method getScopesByAction() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

185
				'scopes' => $this->scopes ?: $this->provider->/** @scrutinizer ignore-call */ getScopesByAction(self::MODULE_NAME)

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
186
			]);
187
		}
188
189
		return $this->provider;
190
	}
191
192
	/**
193
	 * Check if mail account is active.
194
	 *
195
	 * @return bool
196
	 */
197
	public function isActive(): bool
198
	{
199
		return 'PLL_ACTIVE' === $this->getSource()->get('mailaccount_status');
200
	}
201
202
	/**
203
	 * Open imap connection.
204
	 *
205
	 * @return Connections\Imap
206
	 */
207
	public function openImap(): Connections\Imap
208
	{
209
		$imap = new Connections\Imap([
210
			'host' => $this->getServer()->get('imap_host'),
211
			'port' => $this->getServer()->get('imap_port'),
212
			'encryption' => $this->getServer()->get('imap_encrypt'), //'ssl',
213
			'validate_cert' => (bool) $this->getServer()->get('validate_cert'),
214
			'authentication' => 'oauth2' === $this->getServer()->get('auth_method') ? 'oauth' : null,
215
			'username' => $this->userName,
216
			'password' => $this->password
217
		]);
218
		try {
219
			++$this->attempt;
220
			$imap->connect();
221
			$this->update(['last_login']);
222
		} catch (\Throwable $th) {
223
			// try only once if token has expired
224
			if (1 === $this->attempt && 'oauth2' === $this->server->get('auth_method')) {
225
				$this->getAccessToken();
226
				$this->update();
227
				return $this->openImap();
228
			}
229
			\App\Log::error("IMAP connect - Account: {$this->getSource()->getId()}, message: " . $th->getMessage());
230
			throw $th;
231
		}
232
233
		return $imap;
234
	}
235
236
	/**
237
	 * Lock mail account.
238
	 *
239
	 * @param string $messages
240
	 *
241
	 * @return $this
242
	 */
243
	public function lock(string $messages)
244
	{
245
		$fieldModel = $this->source->getField('logs');
246
247
		$this->source->set('mailaccount_status', self::STATUS_LOCKED);
248
		$messages = \App\Purifier::decodeHtml(\App\Purifier::encodeHtml($messages));
249
		$this->source->set('logs', \App\TextUtils::textTruncate($messages, $fieldModel->getMaxValue(), true, true))->save();
250
251
		return $this;
252
	}
253
254
	/**
255
	 * Unlock mail account.
256
	 *
257
	 * @return $this
258
	 */
259
	public function unlock()
260
	{
261
		$this->source->set('mailaccount_status', self::STATUS_ACTIVE);
262
		$this->source->set('logs', '')->save();
263
264
		return $this;
265
	}
266
267
	/**
268
	 * Deactiveate mail account.
269
	 *
270
	 * @param string|null $messages
271
	 *
272
	 * @return $this
273
	 */
274
	public function deactivate(?string $messages = null)
275
	{
276
		if (null !== $messages) {
277
			$fieldModel = $this->source->getField('logs');
278
			$messages = \App\Purifier::decodeHtml(\App\Purifier::encodeHtml($messages));
279
			$this->source->set('logs', \App\TextUtils::textTruncate($messages, $fieldModel->getMaxValue(), true, true));
280
		}
281
		$this->source->set('mailaccount_status', self::STATUS_INACTIVE)->save();
282
283
		return $this;
284
	}
285
286
	/**
287
	 * Get last UID.
288
	 *
289
	 * @param string $folderName
290
	 *
291
	 * @return int|null
292
	 */
293
	public function getLastUid(string $folderName)
294
	{
295
		return (new \App\Db\Query())->select(['uid'])->from(Scanner::FOLDER_TABLE)
0 ignored issues
show
Bug Best Practice introduced by
The expression return new App\Db\Query(...$folderName))->scalar() also could return the type false|string which is incompatible with the documented return type integer|null.
Loading history...
296
			->where(['user_id' => $this->source->getId(), 'name' => $folderName])->scalar();
297
	}
298
299
	/**
300
	 * Set UID to folder.
301
	 *
302
	 * @param int    $uid
303
	 * @param string $folderName
304
	 *
305
	 * @return bool
306
	 */
307
	public function setLastUid(int $uid, string $folderName): bool
308
	{
309
		return (bool) \App\Db::getInstance()->createCommand()
310
			->update(Scanner::FOLDER_TABLE, ['uid' => $uid], ['user_id' => $this->source->getId(), 'name' => $folderName])->execute();
311
	}
312
313
	/**
314
	 * Get actions.
315
	 *
316
	 * @return array
317
	 */
318
	public function getActions(): array
319
	{
320
		$actions = $this->getSource()->get('scanner_actions');
321
		return $actions ? explode(',', $actions) : [];
322
	}
323
324
	/**
325
	 * Get folders.
326
	 *
327
	 * @return array
328
	 */
329
	public function getFolders(): array
330
	{
331
		$folders = $this->getSource()->get('folders');
332
		return \App\Json::isEmpty($folders) ? [] : \App\Json::decode($folders);
0 ignored issues
show
Bug Best Practice introduced by
The expression return App\Json::isEmpty...\Json::decode($folders) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
333
	}
334
}
335