CustomView_Record_Model::getDenyUrl()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
/* +***********************************************************************************
3
 * The contents of this file are subject to the vtiger CRM Public License Version 1.0
4
 * ("License"); You may not use this file except in compliance with the License
5
 * The Original Code is:  vtiger CRM Open Source
6
 * The Initial Developer of the Original Code is vtiger.
7
 * Portions created by vtiger are Copyright (C) vtiger.
8
 * All Rights Reserved.
9
 * Contributor(s): YetiForce S.A.
10
 * *********************************************************************************** */
11
12
/**
13
 * CustomView Record Model Class.
14
 */
15
class CustomView_Record_Model extends \App\Base
16
{
17
	/** @var bool Is featured */
18
	protected $isFeatured;
19
	/** @var bool Is default */
20
	protected $isDefault;
21
22
	/** @var array Record changes */
23
	protected $changes = [];
24
25
	/**
26 1
	 * Function to get all the accessible Custom Views, for a given module if specified.
27
	 *
28 1
	 * @param string $moduleName
29
	 * @param bool   $fromFile
30
	 *
31
	 * @return array Array of Vtiger_CustomView_Record models
32
	 */
33
	public static function getAll($moduleName = '', bool $fromFile = true)
34
	{
35
		$currentUser = \App\User::getCurrentUserModel();
36
		$cacheName = "{$moduleName}_{$currentUser->getId()}_{$fromFile}";
37
		if (App\Cache::has('getAllFilters', $cacheName)) {
38
			return App\Cache::get('getAllFilters', $cacheName);
39
		}
40
		$query = (new App\Db\Query())->from('vtiger_customview');
41
		if (!empty($moduleName)) {
42
			$query->where(['entitytype' => $moduleName]);
43
		}
44
		if (!$currentUser->isAdmin()) {
45
			$query->andWhere([
46
				'or',
47
				['userid' => $currentUser->getId()],
48
				['presence' => 0],
49
				['status' => [\App\CustomView::CV_STATUS_DEFAULT, \App\CustomView::CV_STATUS_PUBLIC]],
50
				['and', ['status' => \App\CustomView::CV_STATUS_PRIVATE], ['cvid' => (new \App\Db\Query())->select(['cvid'])->from('u_#__cv_privileges')->where(['member' => $currentUser->getMemberStructure()])]],
51
			]);
52
		}
53
		$dataReader = $query->orderBy(['sequence' => SORT_ASC])->createCommand()->query();
54
		$customViews = [];
55
		while ($row = $dataReader->read()) {
56 1
			$customView = new self();
57
			if (\strlen(App\Purifier::decodeHtml($row['viewname'])) > 40) {
58 1
				$row['viewname'] = substr(App\Purifier::decodeHtml($row['viewname']), 0, 36) . '...';
59
			}
60
			$customViews[$row['cvid']] = $customView->setData($row)->setModule($row['entitytype']);
61
		}
62
		$dataReader->close();
63
64
		$filterDir = 'modules' . DIRECTORY_SEPARATOR . $moduleName . DIRECTORY_SEPARATOR . 'filters';
65
		if ($fromFile && $moduleName && file_exists($filterDir)) {
66
			$view = ['setdefault' => 0, 'setmetrics' => 0, 'status' => 0, 'privileges' => 0];
67
			$filters = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($filterDir, FilesystemIterator::SKIP_DOTS));
68 1
			foreach ($filters as $filter) {
69
				$name = str_replace('.php', '', $filter->getFilename());
70 1
				$handlerClass = Vtiger_Loader::getComponentClassName('Filter', $name, $moduleName);
71
				if (class_exists($handlerClass)) {
72 1
					$handler = new $handlerClass();
73
					$view['viewname'] = $handler->getViewName();
74
					$view['cvid'] = $name;
75
					$view['status'] = App\CustomView::CV_STATUS_SYSTEM;
76
					$customView = new self();
77
					$customViews[$name] = $customView->setData($view)->setModule($moduleName);
78
				}
79
			}
80
		}
81
		\App\Cache::save('getAllFilters', $cacheName, $customViews, \App\Cache::LONG);
82
		return $customViews;
83
	}
84
85
	/**
86
	 * Function to get the instance of Custom View module, given custom view id.
87
	 *
88
	 * @param int $cvId
89
	 *
90
	 * @return CustomView_Record_Model instance, if exists. Null otherwise
91
	 */
92
	public static function getInstanceById($cvId)
93
	{
94
		if ($row = \App\CustomView::getCVDetails($cvId)) {
95
			$customView = new self();
96
			return $customView->setData($row)->setModule($row['entitytype']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $customView->setD...ule($row['entitytype']) returns the type Vtiger_Record_Model which is incompatible with the documented return type CustomView_Record_Model.
Loading history...
97
		}
98
		return null;
99
	}
100
101
	/**
102
	 * Function to get all the custom views, of a given module if specified, grouped by their status.
103
	 *
104
	 * @param string $moduleName
105
	 * @param mixed  $menuId
106
	 *
107
	 * @return <Array> - Associative array of Status label to an array of Vtiger_CustomView_Record models
0 ignored issues
show
Documentation Bug introduced by
The doc comment <Array> at position 0 could not be parsed: Unknown type name '<' at position 0 in <Array>.
Loading history...
108
	 */
109
	public static function getAllByGroup($moduleName = '', $menuId = false)
110
	{
111
		$customViews = self::getAll($moduleName);
112
		$groupedCustomViews = [];
113
		if (!$menuId || empty($filters = \App\CustomView::getModuleFiltersByMenuId($menuId, $moduleName))) {
114
			$filters = array_keys($customViews);
115
		}
116
		foreach ($filters as $id) {
117
			$customView = $customViews[$id];
118
			if ($customView->isSystem()) {
119
				$groupedCustomViews['System'][] = $customView;
120
			} elseif ($customView->isMine()) {
121
				$groupedCustomViews['Mine'][] = $customView;
122
			} elseif ($customView->isPending()) {
123
				$groupedCustomViews['Pending'][] = $customView;
124
			} else {
125
				$groupedCustomViews['Others'][] = $customView;
126
			}
127
		}
128
		return $groupedCustomViews;
129
	}
130
131
	/**
132
	 * Function gives default custom view for a module.
133
	 *
134
	 * @param string $module
135
	 *
136
	 * @return CustomView_Record_Model
137
	 */
138
	public static function getAllFilterByModule($module)
139
	{
140
		$viewId = (new \App\Db\Query())->select(['cvid'])->from('vtiger_customview')->where(['viewname' => 'All', 'entitytype' => $module])->scalar();
141
		if (!$viewId) {
142
			$viewId = App\CustomView::getInstance($module)->getViewId();
143
		}
144
		return self::getInstanceById($viewId);
145
	}
146
147
	/**
148
	 * Function to get Clean instance of this record.
149
	 *
150
	 * @return self
151
	 */
152
	public static function getCleanInstance()
153
	{
154
		return new self();
155
	}
156
157
	/**
158
	 * Function to get the Id.
159
	 *
160
	 * @return int Custom View Id
161
	 */
162
	public function getId()
163
	{
164
		return $this->get('cvid');
165
	}
166
167
	/**
168
	 * Function to get filter name.
169
	 *
170
	 * @return string
171
	 */
172
	public function getName(): string
173
	{
174
		return $this->get('viewname');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('viewname') could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
175
	}
176
177
	/**
178
	 * Function to get the Owner Id.
179
	 *
180
	 * @return <Number> Id of the User who created the Custom View
0 ignored issues
show
Documentation Bug introduced by
The doc comment <Number> at position 0 could not be parsed: Unknown type name '<' at position 0 in <Number>.
Loading history...
181
	 */
182
	public function getOwnerId()
183
	{
184
		return $this->get('userid');
185
	}
186
187
	/**
188
	 * Function to get the Owner Name.
189
	 *
190
	 * @return string Custom View creator User Name
191
	 */
192
	public function getOwnerName()
193
	{
194
		return \App\Fields\Owner::getUserLabel($this->getOwnerId());
0 ignored issues
show
Bug Best Practice introduced by
The expression return App\Fields\Owner:...el($this->getOwnerId()) also could return the type boolean which is incompatible with the documented return type string.
Loading history...
195
	}
196
197
	/**
198
	 * Function to get the Module to which the record belongs.
199
	 *
200
	 * @return Vtiger_Module_Model
201
	 */
202
	public function getModule()
203
	{
204
		return $this->module;
205
	}
206
207
	/**
208
	 * Function to set the Module to which the record belongs.
209
	 *
210
	 * @param string $moduleName
211
	 *
212
	 * @return Vtiger_Record_Model or Module Specific Record Model instance
213
	 */
214
	public function setModule($moduleName)
215
	{
216
		$this->module = Vtiger_Module_Model::getInstance($moduleName);
0 ignored issues
show
Bug Best Practice introduced by
The property module does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
217
218
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type CustomView_Record_Model which is incompatible with the documented return type Vtiger_Record_Model.
Loading history...
219
	}
220
221
	/**
222
	 * Function to set the Module to which the record belongs from the Module model instance.
223
	 *
224
	 * @param Vtiger_Module_Model $module
225
	 *
226
	 * @return Vtiger_Record_Model or Module Specific Record Model instance
227
	 */
228
	public function setModuleFromInstance($module)
229
	{
230
		$this->module = $module;
0 ignored issues
show
Bug Best Practice introduced by
The property module does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
231
232
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type CustomView_Record_Model which is incompatible with the documented return type Vtiger_Record_Model.
Loading history...
233
	}
234
235
	/** {@inheritdoc} */
236
	public function set($key, $value)
237
	{
238
		if ($this->getId() && !\in_array($key, ['cvid', 'entitytype', 'presence']) && (\array_key_exists($key, $this->value) && $this->value[$key] != $value)) {
239
			$this->changes[$key] = $this->get($key);
240
		}
241
		return parent::set($key, $value);
242
	}
243
244
	/**
245
	 * Function to check if the view is marked as default.
246
	 *
247
	 * @return bool true/false
248
	 */
249
	public function isDefault()
250
	{
251
		\App\Log::trace('Entering ' . __METHOD__ . ' method ...');
252
		if (null === $this->isDefault) {
253
			$currentUser = Users_Record_Model::getCurrentUserModel();
254
			$cvId = $this->getId();
255
			if (!$cvId) {
256
				$this->isDefault = false;
257
258
				return false;
259
			}
260
			$this->isDefault = (new App\Db\Query())->from('vtiger_user_module_preferences')
261
				->where(['userid' => 'Users:' . $currentUser->getId(), 'tabid' => $this->getModule()->getId(), 'default_cvid' => $cvId])
262
				->exists();
263
		}
264 1
		\App\Log::trace('Exiting ' . __METHOD__ . ' method ...');
265
266 1
		return $this->isDefault;
267 1
	}
268 1
269 1
	public function isSystem()
270 1
	{
271 1
		return App\CustomView::CV_STATUS_SYSTEM == $this->get('status');
272 1
	}
273 1
274 1
	/**
275 1
	 * Function to check if the view is created by the current user or is default view.
276
	 *
277 1
	 * @return bool true/false
278
	 */
279
	public function isMine()
280
	{
281
		return App\CustomView::CV_STATUS_DEFAULT == $this->get('status') || $this->get('userid') == \App\User::getCurrentUserId();
282
	}
283
284
	/**
285
	 * Function to check if the view is approved to be Public.
286
	 *
287
	 * @return bool true/false
288
	 */
289
	public function isPublic()
290
	{
291
		return !$this->isMine() && App\CustomView::CV_STATUS_PUBLIC == $this->get('status');
292
	}
293
294
	/**
295
	 * Function to check if the view is marked as Private.
296
	 *
297
	 * @return bool true/false
298
	 */
299
	public function isPrivate()
300
	{
301
		return App\CustomView::CV_STATUS_PRIVATE == $this->get('status');
302
	}
303
304
	/**
305
	 * Function to check if the view is requested to be Public and is awaiting for Approval.
306
	 *
307
	 * @return bool true/false
308
	 */
309
	public function isPending()
310
	{
311
		return !$this->isMine() && App\CustomView::CV_STATUS_PENDING == $this->get('status');
312
	}
313
314
	/**
315
	 * Function to check if the view is created by one of the users, who is below the current user in the role hierarchy.
316
	 *
317
	 * @return bool true/false
318
	 */
319
	public function isOthers()
320
	{
321
		return !$this->isMine() && App\CustomView::CV_STATUS_PUBLIC != $this->get('status');
322
	}
323
324
	/**
325
	 * Function which checks if a view is set to Public by the user which may/may not be approved.
326
	 *
327
	 * @return bool true/false
328
	 */
329
	public function isSetPublic()
330
	{
331
		return App\CustomView::CV_STATUS_PUBLIC == $this->get('status') || App\CustomView::CV_STATUS_PENDING == $this->get('status');
332
	}
333
334
	/**
335
	 * Check if filter is featured.
336
	 *
337
	 * @return bool
338
	 */
339
	public function isFeatured(): bool
340
	{
341
		if (null === $this->isFeatured) {
342
			$this->isFeatured = $this->get('featured')
343
			|| (new \App\Db\Query())->from('u_#__featured_filter')->where(['cvid' => $this->getId(), 'user' => \App\User::getCurrentUserModel()->getMemberStructure()])->exists();
344
		}
345
		return $this->isFeatured;
346
	}
347
348
	/**
349
	 * Check if user can change featured.
350
	 *
351
	 * @return bool
352
	 */
353
	public function isFeaturedEditable(): bool
354
	{
355
		return !$this->isFeatured() || (new App\Db\Query())->from('u_#__featured_filter')
356
			->where(['cvid' => $this->getId(), 'user' => \App\PrivilegeUtil::MEMBER_TYPE_USERS . ':' . \App\User::getCurrentUserId()])
357
			->exists();
358
	}
359
360 1
	/**
361
	 * Function to check permission.
362 1
	 *
363 1
	 * @return bool
364
	 */
365 1
	public function isPermitted(): bool
366 1
	{
367 1
		return \App\CustomView::isPermitted($this->getId(), $this->getModule()->getName());
0 ignored issues
show
Bug introduced by
$this->getModule()->getName() of type boolean is incompatible with the type null|string expected by parameter $moduleName of App\CustomView::isPermitted(). ( Ignorable by Annotation )

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

367
		return \App\CustomView::isPermitted($this->getId(), /** @scrutinizer ignore-type */ $this->getModule()->getName());
Loading history...
368 1
	}
369
370 1
	/**
371
	 * Check permission to edit.
372
	 *
373
	 * @throws \Exception
374 1
	 *
375 1
	 * @return bool
376 1
	 */
377 1
	public function isEditable(): bool
378
	{
379
		$returnVal = false;
380
		$moduleModel = $this->getModule();
381
		$moduleName = $moduleModel->get('name');
382 1
		if (!\App\CustomView::getInstance($this->getModule()->getName())->isPermittedCustomView($this->getId())) {
383 1
			$returnVal = false;
384
		} elseif (2 !== $this->get('presence') && \App\User::getCurrentUserModel()->isAdmin()) {
385
			$returnVal = true;
386
		} elseif (0 === $this->get('privileges') || 2 === $this->get('presence')) {
387
			$returnVal = false;
388
		} elseif (!\App\Privilege::isPermitted($moduleName, 'CreateCustomFilter')) {
389
			$returnVal = false;
390
		} elseif ($this->isMine()) {
391
			$returnVal = true;
392
		}
393 1
		return $returnVal;
394
	}
395
396
	/**
397
	 * Function adds a filter to your favorites.
398
	 *
399
	 * @param int    $cvId
400 1
	 * @param string $user
401 1
	 * @param string $action
402 1
	 *
403
	 * @return bool
404
	 */
405
	public function setFeaturedForMember(string $user): bool
406
	{
407
		$result = true;
408
		if (!(new App\Db\Query())->from('u_#__featured_filter')->where(['cvid' => $this->getId(), 'user' => $user])->exists()) {
409
			$result = (bool) \App\Db::getInstance()->createCommand()->insert('u_#__featured_filter', ['user' => $user, 'cvid' => $this->getId()])->execute();
410
		}
411
		return $result;
412
	}
413
414
	/**
415
	 * Removes the filter from the user favorites filters.
416
	 *
417
	 * @param string $user
418
	 *
419
	 * @return bool
420
	 */
421
	public function removeFeaturedForMember(string $user): bool
422
	{
423
		return (bool) \App\Db::getInstance()->createCommand()->delete('u_#__featured_filter', ['user' => $user, 'cvid' => $this->getId()])->execute();
424
	}
425
426
	/**
427
	 * Sets filter as default for user.
428
	 *
429
	 * @param string $user
430
	 *
431
	 * @return bool
432
	 */
433
	public function setDefaultForMember(string $user): bool
434
	{
435
		$dbCommand = \App\Db::getInstance()->createCommand();
436
		$result = true;
437
		if (!(new App\Db\Query())->from('vtiger_user_module_preferences')->where(['default_cvid' => $this->getId(), 'userid' => $user])->exists()) {
438
			$dbCommand->delete('vtiger_user_module_preferences', ['userid' => $user, 'tabid' => $this->getModule()->getId()])->execute();
439
			$result = (bool) $dbCommand->insert('vtiger_user_module_preferences', [
440
				'userid' => $user,
441
				'tabid' => $this->getModule()->getId(),
442
				'default_cvid' => $this->getId(),
443
			])->execute();
444
		}
445
		return $result;
446
	}
447
448
	/**
449
	 * Removes the filter from the user default filters.
450
	 *
451
	 * @param string $user
452
	 *
453
	 * @return bool
454
	 */
455
	public function removeDefaultForMember(string $user): bool
456
	{
457
		return (bool) \App\Db::getInstance()->createCommand()->delete('vtiger_user_module_preferences', ['userid' => $user, 'default_cvid' => $this->getId()])->execute();
458
	}
459
460
	/**
461
	 * Grant permissions for the member.
462
	 *
463
	 * @param int    $cvId
464
	 * @param string $user
465
	 * @param string $action
466
	 *
467
	 * @return bool
468
	 */
469
	public function setPrivilegesForMember(string $user): bool
470
	{
471
		$result = true;
472
		if (!(new App\Db\Query())->from('u_#__cv_privileges')->where(['cvid' => $this->getId(), 'member' => $user])->exists()) {
473
			$result = (bool) \App\Db::getInstance()->createCommand()->insert('u_#__cv_privileges', ['cvid' => $this->getId(), 'member' => $user])->execute();
474
		}
475
		return $result;
476
	}
477
478
	/**
479
	 * Removes permissions for the member.
480
	 *
481
	 * @param string $user
482
	 *
483
	 * @return bool
484
	 */
485
	public function removePrivilegesForMember(string $user): bool
486
	{
487
		return (bool) \App\Db::getInstance()->createCommand()->delete('u_#__cv_privileges', ['cvid' => $this->getId(), 'member' => $user])->execute();
488
	}
489
490
	/**
491
	 * Check the permission to delete.
492
	 *
493
	 * @return bool
494
	 */
495
	public function privilegeToDelete(): bool
496
	{
497
		return $this->isEditable() && 0 != $this->get('presence');
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $this->get('presence') of type mixed|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
498
	}
499
500
	/**
501
	 * Function which provides the records for the current view.
502
	 *
503
	 * @param bool  $skipRecords - List of the RecordIds to be skipped
504
	 * @param mixed $module
505
	 * @param mixed $lockRecords
506
	 *
507
	 * @return int[] List of RecordsIds
508
	 */
509
	public function getRecordIds($skipRecords = false, $module = false, $lockRecords = false)
510 1
	{
511
		$queryGenerator = $this->getRecordsListQuery($skipRecords, $module, $lockRecords)->setFields(['id']);
0 ignored issues
show
Bug introduced by
$skipRecords of type boolean is incompatible with the type integer[] expected by parameter $skipRecords of CustomView_Record_Model::getRecordsListQuery(). ( Ignorable by Annotation )

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

511
		$queryGenerator = $this->getRecordsListQuery(/** @scrutinizer ignore-type */ $skipRecords, $module, $lockRecords)->setFields(['id']);
Loading history...
512 1
		return $queryGenerator->createQuery()->column();
513 1
	}
514
515
	/**
516
	 * Create query.
517
	 *
518
	 * @param int[]  $skipRecords
519
	 * @param string $module
520
	 * @param bool   $lockRecords
521
	 *
522
	 * @return \App\QueryGenerator
523
	 */
524
	public function getRecordsListQuery($skipRecords = false, $module = false, $lockRecords = false)
0 ignored issues
show
Unused Code introduced by
The parameter $module is not used and could be removed. ( Ignorable by Annotation )

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

524
	public function getRecordsListQuery($skipRecords = false, /** @scrutinizer ignore-unused */ $module = false, $lockRecords = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
525
	{
526
		$cvId = $this->getId();
527
		$moduleModel = $this->getModule();
528
		$moduleName = $moduleModel->get('name');
529
		$baseTableName = $moduleModel->get('basetable');
530
		$baseTableId = $moduleModel->get('basetableid');
531
		$queryGenerator = new App\QueryGenerator($moduleName);
532
		if (!empty($cvId) && 0 != $cvId) {
533
			$queryGenerator->initForCustomViewById($cvId);
534
		} else {
535
			$queryGenerator->initForDefaultCustomView();
536
		}
537
		$searchKey = $this->get('search_key');
538
		$searchValue = $this->get('search_value');
539 1
		if (!empty($searchValue) && ($operator = $this->get('operator'))) {
540
			$queryGenerator->addCondition($searchKey, $searchValue, $operator);
541 1
		}
542 1
		$searchParams = $this->getArray('search_params');
543
		if (empty($searchParams)) {
544 1
			$searchParams = [];
545
		}
546 1
		$transformedSearchParams = $queryGenerator->parseBaseSearchParamsToCondition($searchParams);
547 1
		$queryGenerator->parseAdvFilter($transformedSearchParams);
548 1
		if (\is_array($skipRecords) && \count($skipRecords) > 0) {
549 1
			$queryGenerator->addNativeCondition(['not in', "$baseTableName.$baseTableId", $skipRecords]);
550 1
		}
551 1
		if ($this->has('entityState')) {
552 1
			$queryGenerator->setStateCondition($this->get('entityState'));
553 1
		}
554 1
		if (($orderBy = $this->get('orderby')) && \is_array($orderBy)) {
555 1
			foreach ($orderBy as $fieldName => $sortFlag) {
556 1
				[$fieldName, $moduleName, $sourceFieldName] = array_pad(explode(':', $fieldName), 3, false);
557
				if ($sourceFieldName) {
558 1
					$queryGenerator->setRelatedOrder([
559
						'sourceField' => $sourceFieldName,
560
						'relatedModule' => $moduleName,
561
						'relatedField' => $fieldName,
562
						'relatedSortOrder' => $sortFlag,
563 1
					]);
564
				} else {
565 1
					$queryGenerator->setOrder($fieldName, $sortFlag);
566 1
				}
567 1
			}
568 1
		}
569 1
		if ($lockRecords) {
570 1
			$lockFields = Vtiger_CRMEntity::getInstance($moduleName)->getLockFields();
571 1
			$lockFields = array_replace_recursive($lockFields, \App\RecordStatus::getLockStatus($moduleName));
572 1
			foreach ($lockFields as $fieldName => $fieldValues) {
573 1
				$queryGenerator->addNativeCondition(['not in', "$baseTableName.$fieldName", $fieldValues]);
574 1
			}
575 1
		}
576
		return $queryGenerator;
577 1
	}
578 1
579 1
	/**
580 1
	 * Function to save the custom view record.
581 1
	 */
582 1
	public function save()
583 1
	{
584 1
		$db = \App\Db::getInstance();
585
		$currentUserModel = Users_Record_Model::getCurrentUserModel();
586
587
		$cvIdOrg = $cvId = $this->getId();
588
		$setDefault = (int) ($this->get('setdefault'));
589
		$status = $this->get('status');
590
		$featured = $this->get('featured');
591
592
		if (App\CustomView::CV_STATUS_PENDING == $status && $currentUserModel->isAdminUser()) {
593 1
			$status = App\CustomView::CV_STATUS_PUBLIC;
594
			$this->set('status', $status);
595 1
		}
596
		$transaction = $db->beginTransaction();
597 1
		try {
598
			if ('edit' === $this->get('mode')) {
599
				$this->saveToDb();
600
			} else {
601
				if (!$cvId) {
602
					$this->addCustomView();
603
					$cvId = $this->getId();
0 ignored issues
show
Unused Code introduced by
The assignment to $cvId is dead and can be removed.
Loading history...
604
				} else {
605
					$this->updateCustomView();
606
				}
607
608
				$userId = 'Users:' . $currentUserModel->getId();
609
				if (empty($featured) && !empty($cvIdOrg)) {
610
					$this->removeFeaturedForMember($userId);
611
				} elseif (!empty($featured)) {
612
					$this->setFeaturedForMember($userId);
613
				}
614
				if (empty($setDefault) && !empty($cvIdOrg)) {
615
					$this->removeDefaultForMember($userId);
616
				} elseif (!empty($setDefault)) {
617
					$this->setDefaultForMember($userId);
618
				}
619
			}
620
			$transaction->commit();
621
		} catch (\Throwable $ex) {
622
			$transaction->rollBack();
623
			\App\Log::error($ex->__toString());
624
		}
625
		\App\Cache::clear();
626
	}
627
628
	/**
629 1
	 * Save data to the database.
630
	 */
631 1
	public function saveToDb()
632 1
	{
633 1
		$dbCommand = \App\Db::getInstance()->createCommand();
634
		$tableData = array_intersect_key($this->getData(), $this->changes);
635
		if ($tableData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tableData 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...
636
			if (1 === ($tableData['setdefault'] ?? null)) {
637
				$dbCommand->update('vtiger_customview', ['setdefault' => 0], ['entitytype' => $this->getModule()->getName()])->execute();
638
			}
639
			$dbCommand->update('vtiger_customview', $tableData, ['cvid' => $this->getId()])->execute();
640
			if (isset($tableData['sort']) && $this->getId() === App\CustomView::getCurrentView($this->getModule()->getName())) {
641
				\App\CustomView::setSortBy($this->getModule()->getName(), $tableData['sort'] ? \App\Json::decode($tableData['sort']) : null);
0 ignored issues
show
Bug introduced by
$this->getModule()->getName() of type boolean is incompatible with the type string expected by parameter $moduleName of App\CustomView::setSortBy(). ( Ignorable by Annotation )

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

641
				\App\CustomView::setSortBy(/** @scrutinizer ignore-type */ $this->getModule()->getName(), $tableData['sort'] ? \App\Json::decode($tableData['sort']) : null);
Loading history...
642
			}
643
		}
644
	}
645
646
	/**
647
	 * Set value from request.
648
	 *
649
	 * @param \App\Request $request
650 1
	 * @param string       $fieldName
651
	 * @param string       $requestFieldValue
652 1
	 */
653 1
	public function setValueFromRequest(App\Request $request, string $fieldName, string $requestFieldValue)
654
	{
655
		switch ($fieldName) {
656 1
			case 'status':
657 1
			case 'setdefault':
658
			case 'privileges':
659
			case 'featured':
660
				$value = $request->getInteger($requestFieldValue);
661
				break;
662 1
			case 'sort':
663 1
				$value = \App\Json::encode($request->getArray($requestFieldValue, \App\Purifier::STANDARD, [], \App\Purifier::SQL));
664 1
				break;
665 1
			default:
666
				$value = null;
667 1
				break;
668 1
		}
669
		if (null === $value) {
670
			throw new \App\Exceptions\IllegalValue('ERR_ILLEGAL_VALUE');
671
		}
672
		$this->set($fieldName, $value);
673
	}
674
675
	/**
676
	 * Function to delete the custom view record.
677
	 */
678
	public function delete()
679
	{
680
		$dbCommand = App\Db::getInstance()->createCommand();
681
		$cvId = $this->getId();
682
		$dbCommand->delete('vtiger_customview', ['cvid' => $cvId])->execute();
683
		$dbCommand->delete('vtiger_user_module_preferences', ['default_cvid' => $cvId])->execute();
684
		$dbCommand->delete('vtiger_module_dashboard', ['filterid' => $cvId])->execute();
685
		$result = $dbCommand->delete('yetiforce_menu', ['dataurl' => $cvId, 'type' => array_search('CustomFilter', \App\Menu::TYPES)])->execute();
686
		if ($result) {
687
			(new \App\BatchMethod(['method' => '\App\Menu::reloadMenu', 'params' => []]))->save();
688
		}
689
		\App\CustomView::clearCacheById($cvId);
690
		App\Cache::clear();
691
	}
692
693
	/**
694
	 * Returns condition.
695
	 * array() [
696
	 * 'condition' => "AND" or "OR"
697
	 * 'rules' => [[
698
	 *        'fieldname' => name of fields
699
	 *        'operator' => operator, for instance: 'e'
700
	 *        'value' => values
701
	 *    ]]
702
	 * ].
703
	 *
704
	 * @return array
705
	 */
706
	public function getConditions(): array
707
	{
708
		return $this->getId() ? \App\CustomView::getConditions($this->getId()) : [];
709
	}
710
711
	/**
712
	 * Return list of field to detect duplicates.
713
	 *
714
	 * @return array
715
	 */
716
	public function getDuplicateFields(): array
717
	{
718
		return (new \App\Db\Query())->select(['fieldid', 'ignore'])->from('u_#__cv_duplicates')->where(['cvid' => $this->getId()])->all();
719
	}
720
721
	/**
722
	 * Get custom view advanced conditions.
723
	 *
724
	 * @return array
725
	 */
726
	public function getAdvancedConditions(): array
727
	{
728
		return $this->isEmpty('advanced_conditions') ? [] : \App\Json::decode($this->get('advanced_conditions'));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->isEmpty('a...'advanced_conditions')) 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...
729
	}
730
731
	/**
732
	 * Add condition to database.
733
	 *
734
	 * @param array $rule
735
	 * @param int   $parentId
736
	 * @param int   $index
737
	 *
738
	 * @throws \App\Exceptions\Security
739
	 * @throws \yii\db\Exception
740
	 *
741
	 * @return void
742
	 */
743
	private function addCondition(array $rule, int $parentId, int $index)
744
	{
745
		[$fieldName, $fieldModuleName, $sourceFieldName] = array_pad(explode(':', $rule['fieldname']), 3, false);
746
		$operator = $rule['operator'];
747
		$value = $rule['value'] ?? '';
748
749
		if (!$this->get('advfilterlistDbFormat') && !\in_array($operator, array_merge(\App\Condition::OPERATORS_WITHOUT_VALUES, \App\Condition::FIELD_COMPARISON_OPERATORS, array_keys(App\Condition::DATE_OPERATORS)))) {
750
			$value = Vtiger_Module_Model::getInstance($fieldModuleName)->getFieldByName($fieldName)
751
				->getUITypeModel()
752
				->getDbConditionBuilderValue($value, $operator);
753
		}
754
		\App\Db::getInstance()->createCommand()->insert('u_#__cv_condition', [
755
			'group_id' => $parentId,
756
			'field_name' => $fieldName,
757
			'module_name' => $fieldModuleName,
758
			'source_field_name' => $sourceFieldName,
759
			'operator' => $operator,
760
			'value' => $value,
761
			'index' => $index,
762
		])->execute();
763
	}
764
765
	/**
766
	 * Add group to database.
767
	 *
768
	 * @param array|null $rule
769
	 * @param int        $parentId
770
	 * @param int        $index
771
	 *
772
	 * @throws \App\Exceptions\Security
773
	 * @throws \yii\db\Exception
774
	 *
775
	 * @return void
776
	 */
777
	private function addGroup(?array $rule, int $parentId, int $index)
778
	{
779
		if (empty($rule) || empty($rule['rules'])) {
780
			return;
781
		}
782
		$db = \App\Db::getInstance();
783
		$db->createCommand()->insert('u_#__cv_condition_group', [
784
			'cvid' => $this->getId(),
785
			'condition' => 'AND' === $rule['condition'] ? 'AND' : 'OR',
786
			'parent_id' => $parentId,
787
			'index' => $index,
788
		])->execute();
789
		$index = 0;
790
		$parentId = $db->getLastInsertID('u_#__cv_condition_group_id_seq');
791
		foreach ($rule['rules'] as $ruleInfo) {
792
			if (isset($ruleInfo['condition'])) {
793
				$this->addGroup($ruleInfo, $parentId, $index);
0 ignored issues
show
Bug introduced by
$parentId of type string is incompatible with the type integer expected by parameter $parentId of CustomView_Record_Model::addGroup(). ( Ignorable by Annotation )

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

793
				$this->addGroup($ruleInfo, /** @scrutinizer ignore-type */ $parentId, $index);
Loading history...
794
			} else {
795
				$this->addCondition($ruleInfo, $parentId, $index);
0 ignored issues
show
Bug introduced by
$parentId of type string is incompatible with the type integer expected by parameter $parentId of CustomView_Record_Model::addCondition(). ( Ignorable by Annotation )

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

795
				$this->addCondition($ruleInfo, /** @scrutinizer ignore-type */ $parentId, $index);
Loading history...
796
			}
797
			++$index;
798
		}
799
	}
800
801
	/**
802
	 * Set conditions for filter.
803
	 *
804
	 * @return void
805
	 */
806
	public function setConditionsForFilter()
807
	{
808
		$this->addGroup($this->get('advfilterlist'), 0, 0);
809
	}
810
811
	public function setColumnlist()
812
	{
813
		$db = App\Db::getInstance();
814
		$cvId = $this->getId();
815
		foreach ($this->get('columnslist') as $index => $columnInfo) {
816
			$columnInfoExploded = explode(':', $columnInfo);
817
			$db->createCommand()->insert('vtiger_cvcolumnlist', [
818
				'cvid' => $cvId,
819
				'columnindex' => $index,
820
				'field_name' => $columnInfoExploded[0],
821
				'module_name' => $columnInfoExploded[1],
822
				'source_field_name' => $columnInfoExploded[2] ?? null,
823
				'label' => $this->get('customFieldNames')[$columnInfo] ?? ''
824
			])->execute();
825
		}
826
	}
827
828
	/**
829
	 * Function to add the custom view record in db.
830
	 */
831
	protected function addCustomView()
832
	{
833
		$currentUser = Users_Record_Model::getCurrentUserModel();
834
		$moduleName = $this->getModule()->get('name');
835
		$seq = $this->getNextSeq($moduleName);
836
		$db = \App\Db::getInstance();
837
		$db->createCommand()->insert('vtiger_customview', [
838
			'viewname' => $this->get('viewname'),
839
			'setmetrics' => $this->get('setmetrics'),
840
			'entitytype' => $moduleName,
841
			'status' => $this->get('status'),
842
			'userid' => $currentUser->getId(),
843
			'sequence' => $seq,
844
			'featured' => null,
845
			'color' => $this->get('color'),
846 1
			'description' => $this->get('description'),
847
			'advanced_conditions' => $this->get('advanced_conditions'),
848 1
		])->execute();
849
		$this->set('cvid', (int) $db->getLastInsertID('vtiger_customview_cvid_seq'));
850
		$this->setColumnlist();
851 1
		$this->setConditionsForFilter();
852 1
		$this->setDuplicateFields();
853
	}
854 1
855 1
	/**
856 1
	 * Get next sequence.
857
	 *
858
	 * @param string $moduleName
859
	 *
860
	 * @return int
861
	 */
862
	public function getNextSeq($moduleName)
863
	{
864
		$maxSequence = (new \App\Db\Query())->from('vtiger_customview')->where(['entitytype' => $moduleName])->max('sequence');
865
866
		return (int) $maxSequence + 1;
867
	}
868
869
	/**
870
	 * Function to update the custom view record in db.
871
	 */
872
	protected function updateCustomView()
873
	{
874
		$db = App\Db::getInstance();
875
		$dbCommand = $db->createCommand();
876
		$cvId = $this->getId();
877
		$dbCommand->update('vtiger_customview', [
878
			'viewname' => $this->get('viewname'),
879
			'setmetrics' => $this->get('setmetrics'),
880
			'status' => $this->get('status'),
881
			'color' => $this->get('color'),
882
			'description' => $this->get('description'),
883
			'advanced_conditions' => $this->get('advanced_conditions'),
884
		], ['cvid' => $cvId]
885
		)->execute();
886
		$dbCommand->delete('vtiger_cvcolumnlist', ['cvid' => $cvId])->execute();
887
		$dbCommand->delete('u_#__cv_condition_group', ['cvid' => $cvId])->execute();
888
		$dbCommand->delete('u_#__cv_duplicates', ['cvid' => $cvId])->execute();
889
		$this->setColumnlist();
890
		$this->setConditionsForFilter();
891
		$this->setDuplicateFields();
892
	}
893
894
	/**
895
	 * Save fields to detect dupllicates.
896
	 *
897
	 * @return void
898
	 */
899
	private function setDuplicateFields()
900
	{
901
		$fields = $this->get('duplicatefields');
902
		if (empty($fields)) {
903
			return;
904
		}
905
		$dbCommand = App\Db::getInstance()->createCommand();
906
		foreach ($fields as $data) {
907
			$dbCommand->insert('u_#__cv_duplicates', [
908
				'cvid' => $this->getId(),
909
				'fieldid' => $data['fieldid'],
910
				'ignore' => $data['ignore'],
911
			])->execute();
912
		}
913 1
	}
914
915 1
	/**
916
	 * Function to get the list of selected fields for the current custom view.
917
	 *
918
	 * @return array List of Field Column Names
919
	 */
920
	public function getSelectedFields()
921
	{
922
		$cvId = $this->getId();
923
		if (!$cvId) {
924
			return [];
925
		}
926
		$selectedFields = (new App\Db\Query())->select([
927
			'vtiger_cvcolumnlist.columnindex',
928
			'vtiger_cvcolumnlist.field_name',
929
			'vtiger_cvcolumnlist.module_name',
930
			'vtiger_cvcolumnlist.source_field_name',
931
			'vtiger_cvcolumnlist.label',
932
		])
933
			->from('vtiger_cvcolumnlist')
934
			->innerJoin('vtiger_customview', 'vtiger_cvcolumnlist.cvid = vtiger_customview.cvid')
935
			->where(['vtiger_customview.cvid' => $cvId])->orderBy('vtiger_cvcolumnlist.columnindex')
936
			->createCommand()->queryAllByGroup(1);
937
		$result = [];
938
		foreach ($selectedFields as $item) {
939
			$key = "{$item['field_name']}:{$item['module_name']}" . ($item['source_field_name'] ? ":{$item['source_field_name']}" : '');
940
			$result[$key] = $item['label'];
941
		}
942
		return $result;
943
	}
944
945
	/**
946
	 * Function returns approve url.
947
	 *
948
	 * @return string - approve url
949
	 */
950
	public function getCreateUrl()
951
	{
952
		return 'index.php?module=CustomView&view=EditAjax&source_module=' . $this->getModule()->get('name');
953
	}
954
955
	/**
956
	 * Function returns approve url.
957
	 *
958
	 * @param int|null $mid
959
	 *
960
	 * @return string - approve url
961
	 */
962
	public function getEditUrl($mid = null)
963
	{
964
		return 'index.php?module=CustomView&view=EditAjax&source_module=' . $this->getModule()->get('name') . '&record=' . $this->getId() . ($mid ? "&mid={$mid}" : '');
965
	}
966
967
	/**
968
	 * Function returns approve url.
969
	 *
970
	 * @return string - approve url
971
	 */
972
	public function getApproveUrl()
973
	{
974
		return 'index.php?module=CustomView&action=Approve&sourceModule=' . $this->getModule()->get('name') . '&record=' . $this->getId();
975
	}
976
977
	/**
978
	 * Function returns deny url.
979
	 *
980
	 * @return string - deny url
981
	 */
982
	public function getDenyUrl()
983
	{
984
		return 'index.php?module=CustomView&action=Deny&sourceModule=' . $this->getModule()->get('name') . '&record=' . $this->getId();
985
	}
986
987
	/**
988
	 * Function returns duplicate url.
989
	 *
990
	 * @return string - duplicate url
991
	 */
992
	public function getDuplicateUrl()
993
	{
994
		return 'index.php?module=CustomView&view=EditAjax&source_module=' . $this->getModule()->get('name') . '&record=' . $this->getId() . '&duplicate=1';
995
	}
996
997
	/**
998
	 *  Functions returns delete url.
999
	 *
1000
	 * @param int|null $mid
1001
	 *
1002
	 * @return string - delete url
1003
	 */
1004
	public function getDeleteUrl($mid = null)
1005
	{
1006
		return 'index.php?module=CustomView&action=Delete&sourceModule=' . $this->getModule()->get('name') . '&record=' . $this->getId() . ($mid ? "&mid={$mid}" : '');
1007
	}
1008
1009
	/**
1010
	 * Function to approve filter.
1011
	 */
1012
	public function approve()
1013
	{
1014
		App\Db::getInstance()->createCommand()
1015
			->update('vtiger_customview', ['status' => App\CustomView::CV_STATUS_PUBLIC], ['cvid' => $this->getId()])
1016
			->execute();
1017
		\App\CustomView::clearCacheById($this->getId(), $this->getModule()->getName());
0 ignored issues
show
Bug introduced by
$this->getModule()->getName() of type boolean is incompatible with the type null|string expected by parameter $moduleName of App\CustomView::clearCacheById(). ( Ignorable by Annotation )

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

1017
		\App\CustomView::clearCacheById($this->getId(), /** @scrutinizer ignore-type */ $this->getModule()->getName());
Loading history...
1018
	}
1019
1020
	/**
1021
	 * Function deny.
1022
	 */
1023
	public function deny()
1024
	{
1025
		App\Db::getInstance()->createCommand()
1026
			->update('vtiger_customview', ['status' => App\CustomView::CV_STATUS_PRIVATE], ['cvid' => $this->getId()])
1027
			->execute();
1028
		\App\CustomView::clearCacheById($this->getId(), $this->getModule()->getName());
0 ignored issues
show
Bug introduced by
$this->getModule()->getName() of type boolean is incompatible with the type null|string expected by parameter $moduleName of App\CustomView::clearCacheById(). ( Ignorable by Annotation )

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

1028
		\App\CustomView::clearCacheById($this->getId(), /** @scrutinizer ignore-type */ $this->getModule()->getName());
Loading history...
1029
	}
1030
1031
	/**
1032
	 * Function to check duplicates from database.
1033
	 *
1034
	 * @return bool
1035
	 */
1036
	public function checkDuplicate()
1037
	{
1038
		$query = (new App\Db\Query())->from('vtiger_customview')
1039
			->where(['viewname' => $this->get('viewname'), 'entitytype' => $this->getModule()->getName()]);
1040
		$cvid = $this->getId();
1041
		if ($cvid) {
1042
			$query->andWhere(['<>', 'cvid', $cvid]);
1043
		}
1044
		return $query->exists();
1045
	}
1046
1047
	/**
1048
	 * Get sort data.
1049
	 *
1050
	 * @return array
1051
	 */
1052
	public function getSortOrderBy()
1053
	{
1054
		return empty($this->get('sort')) ? [] : \App\Json::decode($this->get('sort'));
0 ignored issues
show
Bug Best Practice introduced by
The expression return empty($this->get(...ode($this->get('sort')) also could return the type string which is incompatible with the documented return type array.
Loading history...
1055
	}
1056
}
1057