Passed
Push — developer ( 11270d...25b277 )
by Radosław
17:22
created

RecordFinder::getQueryForFields()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 22
rs 9.4222
c 0
b 0
f 0
cc 5
nc 5
nop 3
1
<?php
2
/**
3
 * Mail record finder 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    Mariusz Krzaczkowski <[email protected]>
10
 * @author    Radosław Skrzypczak <[email protected]>
11
 */
12
13
namespace App\Mail;
14
15
/**
16
 * Mail record finder class.
17
 */
18
class RecordFinder
19
{
20
	/** @var array Cache. */
21
	private static $cache = [];
22
23
	/** @var array fields by module */
24
	private $fields = [];
25
26
	/**
27
	 * Get instance.
28
	 *
29
	 * @return self
30
	 */
31
	public static function getInstance(): self
32
	{
33
		return new self();
34
	}
35
36
	/**
37
	 * Set fields.
38
	 *
39
	 * @param array $fields fields by module
40
	 *
41
	 * @example ['Contacts' => ['email1','email2']]
42
	 *
43
	 * @return self
44
	 */
45
	public function setFields(array $fields): self
46
	{
47
		$this->fields = $fields;
48
		return $this;
49
	}
50
51
	/**
52
	 * Find record ids by email addresses.
53
	 *
54
	 * @param string|array $emails
55
	 * @param array        $modulesFields
56
	 *
57
	 * @return array
58
	 */
59
	public function findByEmail($emails): array
60
	{
61
		$idByEmail = [];
62
63
		if (!empty($emails)) {
64
			if (!\is_array($emails)) {
65
				$emails = explode(',', $emails);
66
			}
67
			foreach ($this->fields as $module => $fields) {
68
				$idByEmail = array_replace_recursive($idByEmail, $this->findByFields($module, $fields, $emails));
69
			}
70
		}
71
72
		return $idByEmail;
73
	}
74
75
	/**
76
	 * Find record ids.
77
	 *
78
	 * @param string $moduleName
79
	 * @param array  $fields
80
	 * @param array  $searchValue
81
	 * @param bool   $reload
82
	 *
83
	 * @return array
84
	 */
85
	public function findByFields(string $moduleName, array $fields, array $searchValue, bool $reload = false): array
86
	{
87
		$return = [];
88
		$cache = $moduleName . ':' . implode('|', $fields);
89
		if ($reload) {
90
			unset(self::$cache[$cache]);
91
		} else {
92
			foreach ($searchValue as $i => $value) {
93
				if (isset(self::$cache[$cache][$value])) {
94
					$return[$value] = self::$cache[$cache][$value];
95
					$return = array_filter($return);
96
					unset($searchValue[$i]);
97
				} else {
98
					self::$cache[$cache][$value] = [];
99
				}
100
			}
101
		}
102
103
		$moduleModel = \Vtiger_Module_Model::getInstance($moduleName);
104
		if ($searchValue && ($fields = array_filter($fields, fn ($name) => ($fieldsModel = $moduleModel->getFieldByName($name)) && $fieldsModel->isActiveField()))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $searchValue 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...
105
			$dataReader = $this->getQueryForFields($moduleName, $fields, $searchValue)->createQuery()->createCommand()->query();
106
			while ($row = $dataReader->read()) {
107
				foreach ($fields as $fieldName) {
108
					$fieldModel = $moduleModel->getFieldByName($fieldName);
109
					$rowValue = $row[$fieldName];
110
					$recordId = $row['id'];
111
					switch ($fieldModel->getFieldDataType()) {
112
						case 'multiDomain':
113
							$rowDomains = $rowValue ? array_filter(explode(',', $rowValue)) : [];
114
							foreach ($searchValue as $email) {
115
								$domain = mb_strtolower(explode('@', $email)[1]);
116
								if (\in_array($domain, $rowDomains)) {
117
									self::$cache[$cache][$email][$recordId] = $return[$email][$recordId] = $recordId;
118
								}
119
							}
120
							break;
121
						case 'multiEmail':
122
							$rowEmails = $rowValue ? \App\Json::decode($rowValue) : [];
123
							foreach ($rowEmails as $emailData) {
124
								$email = $emailData['e'];
125
								if (\in_array($email, $searchValue)) {
126
									self::$cache[$cache][$email][$recordId] = $return[$email][$recordId] = $recordId;
127
								}
128
							}
129
							break;
130
						case 'recordNumber':
131
						default:
132
							if (\in_array($rowValue, $searchValue)) {
133
								self::$cache[$cache][$rowValue][$recordId] = $return[$rowValue][$recordId] = $recordId;
134
							}
135
							break;
136
					}
137
				}
138
			}
139
		}
140
141
		return $return;
142
	}
143
144
	/**
145
	 * Get query object.
146
	 *
147
	 * @param string $moduleName
148
	 * @param array  $fields
149
	 * @param array  $conditions
150
	 *
151
	 * @return \App\QueryGenerator
152
	 */
153
	public function getQueryForFields(string $moduleName, array $fields, array $conditions): \App\QueryGenerator
154
	{
155
		$queryGenerator = new \App\QueryGenerator($moduleName);
156
		$queryGenerator->setFields(array_merge(['id'], $fields));
157
		$queryGenerator->permissions = false;
158
159
		foreach ($fields as $fieldName) {
160
			$fieldModel = $queryGenerator->getModuleField($fieldName);
161
			switch ($fieldModel->getFieldDataType()) {
162
					case 'multiDomain':
163
						$domains = array_map(fn ($email) => mb_strtolower(explode('@', $email)[1]), $conditions);
164
						$queryGenerator->addCondition($fieldName, $domains, 'e', false);
165
						break;
166
					case 'multiEmail':
167
					case 'recordNumber':
168
					default:
169
						$queryGenerator->addCondition($fieldName, $conditions, 'e', false);
170
						break;
171
				}
172
		}
173
174
		return $queryGenerator;
175
	}
176
177
	/**
178
	 * Find email address.
179
	 *
180
	 * @param string $subject
181
	 * @param array  $modulesFields
182
	 *
183
	 * @return array
184
	 */
185
	public function findBySubject($subject): array
186
	{
187
		$records = [];
188
		foreach ($this->fields as $moduleName => $fields) {
189
			if ($fields && ($numbers = self::getRecordNumberFromString($subject, $moduleName, true))) {
190
				$records = array_replace_recursive($records, $this->findByFields($moduleName, $fields, (array) $numbers));
191
			}
192
		}
193
194
		return $records;
195
	}
196
197
	/**
198
	 * Gets the prefix from text.
199
	 *
200
	 * @param string $value
201
	 * @param string $moduleName
202
	 * @param bool   $multi
203
	 *
204
	 * @return bool|string|array
205
	 */
206
	public static function getRecordNumberFromString(string $value, string $moduleName, bool $multi = false)
207
	{
208
		$moduleData = \App\Fields\RecordNumber::getInstance($moduleName);
209
		$prefix = str_replace(['\{\{YYYY\}\}', '\{\{YY\}\}', '\{\{MM\}\}', '\{\{DD\}\}', '\{\{M\}\}', '\{\{D\}\}'], ['\d{4}', '\d{2}', '\d{2}', '\d{2}', '\d{1,2}', '\d{1,2}'], preg_quote($moduleData->get('prefix'), '/'));
210
		$postfix = str_replace(['\{\{YYYY\}\}', '\{\{YY\}\}', '\{\{MM\}\}', '\{\{DD\}\}', '\{\{M\}\}', '\{\{D\}\}'], ['\d{4}', '\d{2}', '\d{2}', '\d{2}', '\d{1,2}', '\d{1,2}'], preg_quote($moduleData->get('postfix'), '/'));
211
		$redex = preg_replace_callback('/\\\\{\\\\{picklist\\\\:([a-z0-9_]+)\\\\}\\\\}/i', function ($matches) {
212
			$picklistPrefix = array_column(\App\Fields\Picklist::getValues($matches[1]), 'prefix');
213
			if (!$picklistPrefix) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $picklistPrefix 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...
214
				return '';
215
			}
216
			return '((' . implode('|', $picklistPrefix) . ')*)';
217
		}, '/\[' . $prefix . '([0-9]*)' . $postfix . '\]/');
218
		if ($multi) {
219
			preg_match_all($redex, $value, $match);
220
			if (!empty($match[0])) {
221
				$return = [];
222
				foreach ($match[0] as $row) {
223
					$return[] = trim($row, '[,]');
224
				}
225
				return $return;
226
			}
227
		} else {
228
			preg_match($redex, $value, $match);
229
			if (!empty($match)) {
230
				return trim($match[0], '[,]');
231
			}
232
		}
233
		return false;
234
	}
235
236
	/**
237
	 * Find user email.
238
	 *
239
	 * @param array $emails
240
	 *
241
	 * @return string[]
242
	 */
243
	public static function findUserEmail(array $emails): array
244
	{
245
		foreach ($emails as $key => $email) {
246
			if (!\Users_Module_Model::checkMailExist($email)) {
247
				unset($emails[$key]);
248
			}
249
		}
250
		return $emails;
251
	}
252
}
253