Passed
Push — developer ( 679d71...5797ca )
by Mariusz
15:07
created

Vtiger_CalendarExtSource_Model   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 426
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 53
eloc 234
dl 0
loc 426
rs 6.96
c 1
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstanceById() 0 15 3
B loadExtraSourcesQueryType() 0 77 6
A save() 0 17 2
B formatDate() 0 56 11
A getModule() 0 6 2
A getById() 0 10 2
A getCleanInstance() 0 6 1
B loadExtraSourcesQueryFilter() 0 19 7
A delete() 0 7 1
A formatRow() 0 17 2
A getExtraSourcesQuery() 0 26 6
A getByModule() 0 13 3
A getExtraSourcesCount() 0 9 3
A getRows() 0 14 4

How to fix   Complexity   

Complex Class

Complex classes like Vtiger_CalendarExtSource_Model 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 Vtiger_CalendarExtSource_Model, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Calendar extra source model file.
5
 *
6
 * @package Model
7
 *
8
 * @copyright YetiForce S.A.
9
 * @license   YetiForce Public License 5.0 (licenses/LicenseEN.txt or yetiforce.com)
10
 * @author    Mariusz Krzaczkowski <[email protected]>
11
 */
12
/**
13
 * Calendar extra source model class.
14
 */
15
class Vtiger_CalendarExtSource_Model extends App\Base
16
{
17
	/** @var string[] Extra source details */
18
	const EXTRA_SOURCE_TYPES = [
19
		1 => 'LBL_SOURCE_TYPE_1',
20
		2 => 'LBL_SOURCE_TYPE_2',
21
		3 => 'LBL_SOURCE_TYPE_3',
22
		4 => 'LBL_SOURCE_TYPE_4',
23
	];
24
	/** @var string Extra source table name */
25
	const EXTRA_SOURCE_TABLE = 's_#__calendar_sources';
26
27
	/** @var string Base module name */
28
	protected $baseModuleName;
29
	/** @var string Target module name */
30
	protected $targetModuleName;
31
	/** @var \Vtiger_Module_Model Target module model */
32
	protected $targetModuleModel;
33
	/** @var \App\QueryGenerator Query generator instance */
34
	protected $queryGenerator;
35
	/** @var string[] Name record fields */
36
	protected $nameFields;
37
38
	/**
39
	 * Get calendar extra sources list.
40
	 *
41
	 * @param int $moduleId
42
	 *
43
	 * @return array
44
	 */
45
	public static function getByModule(int $moduleId): array
46
	{
47
		if (\App\Cache::has('Calendar-GetExtraSourcesList', $moduleId)) {
48
			return \App\Cache::get('Calendar-GetExtraSourcesList', $moduleId);
49
		}
50
		$query = (new \App\Db\Query())->from('s_#__calendar_sources')
51
			->where(['base_module' => $moduleId]);
52
		if (!\App\User::getCurrentUserModel()->isAdmin()) {
53
			$query->andWhere(['public' => 1]);
54
		}
55
		$rows = $query->createCommand(\App\Db::getInstance('admin'))->queryAllByGroup(1);
56
		\App\Cache::save('Calendar-GetExtraSourcesList', $moduleId, $rows, \App\Cache::LONG);
57
		return $rows;
58
	}
59
60
	/**
61
	 * Get calendar extra source instance by id.
62
	 *
63
	 * @param int $id
64
	 *
65
	 * @return $this
66
	 */
67
	public static function getInstanceById(int $id)
68
	{
69
		$source = self::getById($id);
70
		if (!$source) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $source 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...
71
			throw new \App\Exceptions\AppException('No calendar extra source found');
72
		}
73
		$moduleName = \App\Module::getModuleName($source['base_module']);
74
		$className = Vtiger_Loader::getComponentClassName('Model', 'CalendarExtSource', $moduleName);
75
		if ($source['color']) {
76
			$source['textColor'] = \App\Colors::getContrast($source['color']);
77
		}
78
		$instance = new $className($source);
79
		$instance->baseModuleName = $moduleName;
80
		$instance->targetModuleName = \App\Module::getModuleName($source['target_module']);
81
		return $instance;
82
	}
83
84
	/**
85
	 * Get calendar extra source clean instance by module name.
86
	 *
87
	 * @param string $moduleName
88
	 *
89
	 * @return $this
90
	 */
91
	public static function getCleanInstance(string $moduleName)
92
	{
93
		$className = Vtiger_Loader::getComponentClassName('Model', 'CalendarExtSource', $moduleName);
94
		$handler = new $className();
95
		$handler->baseModuleName = $moduleName;
96
		return $handler;
97
	}
98
99
	/**
100
	 * Get calendar extra sources data by id.
101
	 *
102
	 * @param int $id
103
	 *
104
	 * @return string[]
105
	 */
106
	public static function getById(int $id): array
107
	{
108
		if (\App\Cache::has('Calendar-GetExtraSourceById', $id)) {
109
			return \App\Cache::get('Calendar-GetExtraSourceById', $id);
110
		}
111
		$row = (new \App\Db\Query())->from(self::EXTRA_SOURCE_TABLE)
112
			->where(['id' => $id])
113
			->one(\App\Db::getInstance('admin'));
114
		\App\Cache::save('Calendar-GetExtraSourceById', $id, $row, \App\Cache::LONG);
115
		return $row;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $row could return the type false which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
116
	}
117
118
	/**
119
	 * Save calendar extra sources.
120
	 *
121
	 * @return int
122
	 */
123
	public function save(): int
124
	{
125
		$dbCommand = \App\Db::getInstance('admin')->createCommand();
126
		if ($id = $this->get('id')) {
127
			$dbCommand->update(self::EXTRA_SOURCE_TABLE, $this->getData(), [
128
				'id' => $id
129
			])->execute();
130
			\App\Cache::save('Calendar-GetExtraSourceById', $id, $this->getData(), \App\Cache::LONG);
131
		} else {
132
			$params = $this->getData();
133
			$params['user_id'] = \App\User::getCurrentUserId();
134
			$dbCommand->insert(self::EXTRA_SOURCE_TABLE, $params)
135
				->execute();
136
			$id = \App\Db::getInstance('admin')->getLastInsertID(self::EXTRA_SOURCE_TABLE . '_id_seq');
137
		}
138
		\App\Cache::delete('Calendar-GetExtraSourcesList', $this->get('base_module'));
139
		return $id;
140
	}
141
142
	/**
143
	 * Delete calendar extra sources.
144
	 *
145
	 * @return bool
146
	 */
147
	public function delete(): bool
148
	{
149
		$dbCommand = \App\Db::getInstance('admin')->createCommand();
150
		$status = $dbCommand->delete(self::EXTRA_SOURCE_TABLE, ['id' => $this->get('id')])->execute();
151
		\App\Cache::delete('Calendar-GetExtraSourceById', $this->get('id'));
152
		\App\Cache::delete('Calendar-GetExtraSourcesList', $this->get('base_module'));
153
		return (bool) $status;
154
	}
155
156
	/**
157
	 * Get module model.
158
	 *
159
	 * @return Vtiger_Module_Model
160
	 */
161
	public function getModule(): Vtiger_Module_Model
162
	{
163
		if (!$this->targetModuleModel) {
164
			$this->targetModuleModel = \Vtiger_Module_Model::getInstance($this->targetModuleName);
165
		}
166
		return $this->targetModuleModel;
167
	}
168
169
	/**
170
	 * Get extra sources query.
171
	 *
172
	 * @return \App\Db\Query
173
	 */
174
	protected function getExtraSourcesQuery(): ?App\Db\Query
175
	{
176
		if (
177
			!\App\Privilege::isPermitted($this->targetModuleName)
178
			|| !\App\CustomView::getCustomViewById($this->get('custom_view'))
179
		) {
180
			return null;
181
		}
182
		$this->queryGenerator = new App\QueryGenerator($this->targetModuleName);
183
		$this->queryGenerator->initForCustomViewById($this->get('custom_view'));
184
		$this->targetModuleModel = $this->queryGenerator->getModuleModel();
185
		if ($this->get('include_filters')) {
186
			$this->loadExtraSourcesQueryFilter();
187
		}
188
		$this->queryGenerator->clearFields();
189
		$this->queryGenerator->setField('assigned_user_id');
190
		if ($this->get('field_label')) {
191
			$this->nameFields = [$this->getModule()->getField($this->get('field_label'))->getName()];
192
		} else {
193
			$this->nameFields = $this->getModule()->getNameFields();
194
		}
195
		foreach ($this->nameFields as $field) {
196
			$this->queryGenerator->setField($field);
197
		}
198
		$this->loadExtraSourcesQueryType();
199
		return $this->queryGenerator->createQuery();
200
	}
201
202
	/**
203
	 * Load extra sources query condition by type.
204
	 *
205
	 * @return void
206
	 */
207
	protected function loadExtraSourcesQueryType(): void
208
	{
209
		$fieldA = $this->getModule()->getField($this->get('fieldid_a_date'));
210
		$startDateInstance = DateTimeField::convertToDBTimeZone($this->get('start'));
211
		$startDateTime = $startDateInstance->format('Y-m-d H:i:s');
212
		$startDate = $startDateInstance->format('Y-m-d');
213
		$endDateInstance = DateTimeField::convertToDBTimeZone($this->get('end'));
214
		$endDateTime = $endDateInstance->format('Y-m-d H:i:s');
215
		$endDate = $endDateInstance->format('Y-m-d');
216
		if ('datetime' === $fieldA->getFieldDataType()) {
217
			$startFormatted = $startDateTime;
218
			$endFormatted = $endDateTime;
219
		} else {
220
			$startFormatted = $startDate;
221
			$endFormatted = $endDate;
222
		}
223
		$columnA = $fieldA->getTableName() . '.' . $fieldA->getColumnName();
224
		$this->queryGenerator->setField($fieldA->getName());
225
		switch ($this->get('type')) {
226
			case 1:
227
				$this->queryGenerator->addNativeCondition([
228
					'and', ['>=', $columnA, $startFormatted], ['<=', $columnA,  $endFormatted]
229
				]);
230
				break;
231
			case 3:
232
				$fieldB = $this->getModule()->getField($this->get('fieldid_b_date'));
233
				$columnB = $fieldB->getTableName() . '.' . $fieldB->getColumnName();
234
				$this->queryGenerator->setField($fieldB->getName());
235
				$this->queryGenerator->addNativeCondition([
236
					'or',
237
					['and', ['>=', $columnA, $startFormatted], ['<=', $columnA,  $endFormatted]],
238
					['and', ['>=', $columnB, $startFormatted], ['<=', $columnB,  $endFormatted]],
239
					['and', ['<', $columnA, $startFormatted], ['>', $columnB,  $endFormatted]],
240
				]);
241
				break;
242
			case 2:
243
				$fieldTimeA = $this->getModule()->getField($this->get('fieldid_a_time'));
244
				$this->queryGenerator->setField($fieldTimeA->getName());
245
				$columnATime = $fieldTimeA->getTableName() . '.' . $fieldTimeA->getColumnName();
246
				$this->queryGenerator->addNativeCondition([
247
					'or',
248
					[
249
						'and',
250
						['>=', new \yii\db\Expression("CONCAT($columnA, ' ', $columnATime)"),  $startDateTime],
251
						['<=', new \yii\db\Expression("CONCAT($columnA, ' ', $columnATime)"),  $endDateTime],
252
					],
253
				]);
254
				break;
255
			case 4:
256
				$fieldTimeA = $this->getModule()->getField($this->get('fieldid_a_time'));
257
				$fieldB = $this->getModule()->getField($this->get('fieldid_b_date'));
258
				$fieldTimeB = $this->getModule()->getField($this->get('fieldid_b_time'));
259
				$columnATime = $fieldTimeA->getTableName() . '.' . $fieldTimeA->getColumnName();
260
				$columnB = $fieldB->getTableName() . '.' . $fieldB->getColumnName();
261
				$columnBTime = $fieldTimeB->getTableName() . '.' . $fieldTimeB->getColumnName();
262
				$this->queryGenerator->setField($fieldTimeA->getName());
263
				$this->queryGenerator->setField($fieldB->getName());
264
				$this->queryGenerator->setField($fieldTimeB->getName());
265
				$this->queryGenerator->addNativeCondition([
266
					'or',
267
					[
268
						'and',
269
						['>=', new \yii\db\Expression("CONCAT($columnA, ' ', $columnATime)"),  $startDateTime],
270
						['<=', new \yii\db\Expression("CONCAT($columnA, ' ', $columnATime)"),  $endDateTime],
271
					],
272
					[
273
						'and',
274
						['>=', new \yii\db\Expression("CONCAT($columnB, ' ', $columnBTime)"),  $startDateTime],
275
						['<=', new \yii\db\Expression("CONCAT($columnB, ' ', $columnBTime)"),  $endDateTime],
276
					],
277
					[
278
						'and', ['<', $columnA, $startDate], ['>', $columnB,  $endDate],
279
					],
280
				]);
281
				break;
282
			default:
283
				break;
284
		}
285
	}
286
287
	/**
288
	 * Load extra sources query condition by type.
289
	 *
290
	 * @return void
291
	 */
292
	protected function loadExtraSourcesQueryFilter(): void
293
	{
294
		$conditions = [];
295
		if (!empty($this->get('user')) && isset($this->get('user')['selectedIds'][0])) {
296
			$selectedUsers = $this->get('user');
297
			$selectedIds = $selectedUsers['selectedIds'];
298
			if ('all' !== $selectedIds[0]) {
299
				$conditions[] = ['vtiger_crmentity.smownerid' => $selectedIds];
300
				$subQuery = (new \App\Db\Query())->select(['crmid'])
301
					->from('u_#__crmentity_showners')
302
					->where(['userid' => $selectedIds]);
303
				$conditions[] = ['vtiger_crmentity.crmid' => $subQuery];
304
			}
305
			if (isset($selectedUsers['excludedIds']) && 'all' === $selectedIds[0]) {
306
				$conditions[] = ['not in', 'vtiger_crmentity.smownerid', $selectedUsers['excludedIds']];
307
			}
308
		}
309
		if ($conditions) {
310
			$this->queryGenerator->addNativeCondition(array_merge(['or'], $conditions));
311
		}
312
	}
313
314
	/**
315
	 * Get calendar extra source counter.
316
	 *
317
	 * @return int
318
	 */
319
	public function getExtraSourcesCount(): int
320
	{
321
		$privileges = Users_Privileges_Model::getCurrentUserPrivilegesModel();
322
		if ($privileges->hasModuleActionPermission($this->baseModuleName, 'CalendarExtraSources')) {
323
			if ($query = $this->getExtraSourcesQuery()) {
324
				return $query->count();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->count() could return the type string which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
325
			}
326
		}
327
		return 0;
328
	}
329
330
	/**
331
	 * Get calendar extra source rows.
332
	 *
333
	 * @return array
334
	 */
335
	public function getRows(): array
336
	{
337
		$query = $this->getExtraSourcesQuery();
338
		$privileges = Users_Privileges_Model::getCurrentUserPrivilegesModel();
339
		if (!$query || !$privileges->hasModuleActionPermission($this->baseModuleName, 'CalendarExtraSources')) {
340
			return [];
341
		}
342
		$dataReader = $query->createCommand()->query();
343
		$result = [];
344
		while ($row = $dataReader->read()) {
345
			$result[] = $this->formatRow($row);
346
		}
347
		$dataReader->close();
348
		return $result;
349
	}
350
351
	/**
352
	 * Format record data.
353
	 *
354
	 * @param array $row
355
	 *
356
	 * @return array
357
	 */
358
	protected function formatRow(array $row): array
359
	{
360
		$item = [
361
			'id' => $row['id'],
362
			'editable' => false,
363
		];
364
		$this->formatDate($row, $item);
365
		$title = '';
366
		foreach ($this->nameFields as $field) {
367
			$title .= ' ' . \App\Purifier::encodeHtml($row[$field]);
368
		}
369
		$item['title'] = trim($title);
370
		$item['backgroundColor'] = $this->get('color');
371
		$item['textColor'] = $this->get('textColor');
372
		$item['className'] = 'js-show-modal js-quick-detail-modal js-popover-tooltip--record ownerCBr_' . $row['assigned_user_id'];
373
		$item['url'] = 'index.php?module=' . $this->targetModuleName . '&view=Detail&record=' . $row['id'];
374
		return $item;
375
	}
376
377
	/**
378
	 * Format dates.
379
	 *
380
	 * @param array $row
381
	 * @param array $item
382
	 *
383
	 * @return void
384
	 */
385
	protected function formatDate(array $row, array &$item): void
386
	{
387
		$fieldA = $this->getModule()->getField($this->get('fieldid_a_date'));
388
		$valueA = $row[$fieldA->getColumnName()];
389
		switch ($this->get('type')) {
390
			case 3:
391
				$fieldB = $this->getModule()->getField($this->get('fieldid_b_date'));
392
				$valueB = $row[$fieldB->getColumnName()];
393
				if ($valueB) {
394
					if ('datetime' === $fieldB->getFieldDataType()) {
395
						$date = new DateTimeField($valueB);
396
						$item['end'] = $date->getFullcalenderValue();
397
						$item['end_display'] = $date->getDisplayDateTimeValue();
398
					} else {
399
						$item['end'] = $valueB;
400
						$item['end_display'] = \App\Fields\Date::formatToDisplay($valueB);
401
					}
402
				}
403
				// no break
404
			case 1:
405
				if ($valueA) {
406
					if ('datetime' === $fieldA->getFieldDataType()) {
407
						$date = new DateTimeField($valueA);
408
						$item['start'] = $date->getFullcalenderValue();
409
						$item['start_display'] = $date->getDisplayDateTimeValue();
410
					} else {
411
						$item['start'] = $valueA;
412
						$item['start_display'] = \App\Fields\Date::formatToDisplay($valueA);
413
					}
414
				} else {
415
					$item['start'] = $item['end'];
416
					$item['start_display'] = $item['end_display'];
417
				}
418
				break;
419
			case 4:
420
				$valueB = $row[$this->getModule()->getField($this->get('fieldid_b_date'))->getColumnName()];
421
				$valueTimeB = $row[$this->getModule()->getField($this->get('fieldid_b_time'))->getColumnName()];
422
				if ($valueTimeB) {
423
					$date = new DateTimeField($valueB . ' ' . $valueTimeB);
0 ignored issues
show
Bug introduced by
$valueB . ' ' . $valueTimeB of type string is incompatible with the type type expected by parameter $value of DateTimeField::__construct(). ( Ignorable by Annotation )

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

423
					$date = new DateTimeField(/** @scrutinizer ignore-type */ $valueB . ' ' . $valueTimeB);
Loading history...
424
					$item['end'] = $date->getFullcalenderValue();
425
					$item['end_display'] = $date->getDisplayDateTimeValue();
426
				}
427
				// no break
428
			case 2:
429
				$valueTimeA = $row[$this->getModule()->getField($this->get('fieldid_a_time'))->getColumnName()];
430
				if ($valueA) {
431
					$date = new DateTimeField($valueA . ' ' . $valueTimeA);
432
					$item['start'] = $date->getFullcalenderValue();
433
					$item['start_display'] = $date->getDisplayDateTimeValue();
434
				} else {
435
					$item['start'] = $item['end'];
436
					$item['start_display'] = $item['end_display'];
437
				}
438
				break;
439
			default:
440
				break;
441
		}
442
	}
443
}
444