TextParser::parseTranslations()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 11
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * Text parser file.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 6.5 (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
/**
16
 * Text parser class.
17
 */
18
class TextParser
19
{
20
	/**
21
	 * Examples of supported variables.
22
	 *
23
	 * @var array
24
	 */
25
	public static $variableExamples = [
26
		'LBL_ORGANIZATION_NAME' => '$(organization : company_name)$',
27
		'LBL_ORGANIZATION_LOGO' => '$(organization : logo)$',
28
		'LBL_EMPLOYEE_NAME' => '$(employee : last_name)$',
29
		'LBL_CRM_DETAIL_VIEW_URL' => '$(record : CrmDetailViewURL)$',
30
		'LBL_PORTAL_DETAIL_VIEW_URL' => '$(record : PortalDetailViewURL)$',
31
		'LBL_RECORD_ID' => '$(record : RecordId)$',
32
		'LBL_RECORD_LABEL' => '$(record : RecordLabel)$',
33
		'LBL_LIST_OF_CHANGES_IN_RECORD' => '$(record : ChangesListChanges)$',
34
		'LBL_LIST_OF_NEW_VALUES_IN_RECORD' => '$(record : ChangesListValues)$',
35
		'LBL_RECORD_COMMENT' => '$(record : Comments 5)$, $(record : Comments)$',
36
		'LBL_RELATED_RECORD_LABEL' => '$(relatedRecord : parent_id|email1|Accounts)$, $(relatedRecord : parent_id|email1)$',
37
		'LBL_RELATED_NEXT_LEVEL_RECORD_LABEL' => '$(relatedRecordLevel : projectid|Project|linktoaccountscontacts|email1|Accounts)$',
38
		'LBL_OWNER_EMAIL' => '$(relatedRecord : assigned_user_id|email1|Users)$',
39
		'LBL_SOURCE_RECORD_LABEL' => '$(sourceRecord : RecordLabel)$',
40
		'LBL_CUSTOM_FUNCTION' => '$(custom : ContactsPortalPass)$',
41
		'LBL_RELATED_RECORDS_LIST' => '$(relatedRecordsList : Contacts|firstname,lastname,email|[[["firstname","a","Tom"]]]||5)$',
42
		'LBL_RECORDS_LIST' => '$(recordsList : Contacts|firstname,lastname,email|[[["firstname","a","Tom"]]]||5)$',
43
		'LBL_INVENTORY_TABLE' => '$(inventory : type=table columns=seq,name,qty,unit,price,total,net href=no)$',
44
		'LBL_DYNAMIC_INVENTORY_TABLE' => '$(custom : dynamicInventoryColumnsTable)$',
45
		'LBL_BARCODE' => '$(barcode : type=EAN13 class=DNS1D , value=12345678)$',
46
	];
47
48
	/**
49
	 * Default date list.
50
	 *
51
	 * @var string[]
52
	 */
53
	public static $variableDates = [
54
		'LBL_DATE_TODAY' => '$(date : now)$',
55
		'LBL_DATE_TOMORROW' => '$(date : tomorrow)$',
56
		'LBL_DATE_YESTERDAY' => '$(date : yesterday)$',
57
		'LBL_DATE_FIRST_DAY_OF_THIS_WEEK' => '$(date : monday this week)$',
58
		'LBL_DATE_MONDAY_NEXT_WEEK' => '$(date : monday next week)$',
59
		'LBL_DATE_FIRST_DAY_OF_THIS_MONTH' => '$(date : first day of this month)$',
60
		'LBL_DATE_LAST_DAY_OF_THIS_MONTH' => '$(date : last day of this month)$',
61
		'LBL_DATE_FIRST_DAY_OF_NEXT_MONTH' => '$(date : first day of next month)$',
62
	];
63
64
	/**
65
	 * Variables for entity modules.
66
	 *
67
	 * @var array
68
	 */
69
	public static $variableGeneral = [
70
		'LBL_CURRENT_DATE' => '$(general : CurrentDate)$',
71
		'LBL_CURRENT_TIME' => '$(general : CurrentTime)$',
72
		'LBL_BASE_TIMEZONE' => '$(general : BaseTimeZone)$',
73
		'LBL_USER_TIMEZONE' => '$(general : UserTimeZone)$',
74
		'LBL_SITE_URL' => '$(general : SiteUrl)$',
75
		'LBL_PORTAL_URL' => '$(general : PortalUrl)$',
76
		'LBL_TRANSLATE' => '$(translate : Accounts|LBL_COPY_BILLING_ADDRESS)$, $(translate : LBL_SECONDS)$',
77
	];
78
79
	/**
80
	 * Variables for entity modules.
81
	 *
82
	 * @var array
83
	 */
84
	public static $variableEntity = [
85
		'CrmDetailViewURL' => 'LBL_CRM_DETAIL_VIEW_URL',
86
		'PortalDetailViewURL' => 'LBL_PORTAL_DETAIL_VIEW_URL',
87
		'RecordId' => 'LBL_RECORD_ID',
88
		'RecordLabel' => 'LBL_RECORD_LABEL',
89
		'ChangesListChanges' => 'LBL_LIST_OF_CHANGES_IN_RECORD',
90
		'ChangesListValues' => 'LBL_LIST_OF_NEW_VALUES_IN_RECORD',
91
		'Comments' => 'LBL_RECORD_COMMENT',
92
		'SummaryFields' => 'LBL_SUMMARY_FIELDS',
93
	];
94
95
	/**
96
	 * List of available functions.
97
	 *
98
	 * @var string[]
99
	 */
100
	protected static $baseFunctions = ['general', 'translate', 'record', 'relatedRecord', 'relatedRecordLevel', 'sourceRecord', 'organization', 'employee', 'params', 'custom', 'relatedRecordsList', 'recordsList', 'date', 'inventory', 'userVariable', 'barcode'];
101
102
	/**
103
	 * List of source modules.
104
	 *
105
	 * @var array
106
	 */
107
	public static $sourceModules = [
108
		'Campaigns' => ['Leads', 'Accounts', 'Contacts', 'Vendors', 'Partners', 'Competition'],
109
	];
110
	/**
111
	 * Record variables.
112
	 *
113
	 * @var array
114
	 */
115
	protected static $recordVariable = [];
116
	/**
117
	 * Related variables.
118
	 *
119
	 * @var array
120
	 */
121
	protected static $relatedVariable = [];
122
	/**
123
	 * Next level related variables.
124
	 *
125
	 * @var array
126
	 */
127
	protected static $relatedVariableLevel = [];
128
129
	/**
130
	 * Record id.
131
	 *
132
	 * @var int
133
	 */
134
	public $record;
135
136
	/**
137
	 * Module name.
138
	 *
139
	 * @var string
140
	 */
141
	public $moduleName;
142
143
	/**
144
	 * Record model.
145
	 *
146
	 * @var \Vtiger_Record_Model
147
	 */
148
	public $recordModel;
149
150
	/**
151
	 * Parser type.
152
	 *
153
	 * @var string|null
154
	 */
155
	public $type;
156
157
	/**
158
	 * Source record model.
159
	 *
160
	 * @var \Vtiger_Record_Model
161
	 */
162
	protected $sourceRecordModel;
163
164
	/**
165
	 * Content.
166
	 *
167
	 * @var string
168
	 */
169
	protected $content = '';
170
171
	/**
172
	 * Rwa content.
173
	 *
174
	 * @var string
175
	 */
176
	protected $rawContent;
177
178
	/**
179
	 * without translations.
180
	 *
181
	 * @var bool
182
	 */
183
	protected $withoutTranslations = false;
184
185
	/**
186
	 * Language content.
187
	 *
188
	 * @var string
189
	 */
190
	protected $language;
191
192
	/**
193
	 * Additional params.
194
	 *
195
	 * @var array
196
	 */
197
	protected $params;
198
199
	/**
200
	 * Separator to display data when there are several values.
201
	 *
202
	 * @var string
203
	 */
204 4
	public $relatedRecordSeparator = ',';
205
206 4
	/**
207 4
	 * Is the parsing text content html?
208 4
	 *
209 4
	 * @var bool
210 4
	 */
211 4
	public $isHtml = true;
212
213
	/**
214
	 * Use extended parsing.
215
	 *
216
	 * @var bool
217
	 */
218
	public $useExtension = false;
219
220
	/**
221 7
	 * Variable parser regex.
222
	 *
223 7
	 * @var string
224 7
	 */
225 7
	public const VARIABLE_REGEX = '/\$\((\w+) : ([,"\+\#\%\.\:\;\=\-\[\]\&\w\s\|\)\(\:]+)\)\$/u';
226 7
227 7
	/** @var bool Permissions condition */
228 7
	protected $permissions = true;
229
230
	/** @var string[] Uitypes with large data */
231
	protected $largeDataUiTypes = ['multiImage', 'image'];
232
233
	/**
234
	 * Get instanace by record id.
235
	 *
236
	 * @param int    $record     Record id
237
	 * @param string $moduleName Module name
238 8
	 *
239
	 * @return \self
0 ignored issues
show
Bug introduced by
The type self was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
240 8
	 */
241 8
	public static function getInstanceById(int $record, ?string $moduleName = null)
242 8
	{
243 4
		$class = static::class;
244
		$instance = new $class();
245 8
		$instance->record = $record;
246
		$instance->recordModel = \Vtiger_Record_Model::getInstanceById($record, $moduleName);
247
		$instance->moduleName = $instance->recordModel->getModuleName();
248
		return $instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $instance returns the type App\TextParser which is incompatible with the documented return type self.
Loading history...
249
	}
250
251
	/**
252
	 * Get instanace by record model.
253
	 *
254
	 * @param \Vtiger_Record_Model $recordModel
255 4
	 *
256
	 * @return \self
257 4
	 */
258 4
	public static function getInstanceByModel(\Vtiger_Record_Model $recordModel)
259
	{
260
		$class = static::class;
261
		$instance = new $class();
262
		$instance->record = $recordModel->getId();
263
		$instance->moduleName = $recordModel->getModuleName();
264
		$instance->recordModel = $recordModel;
265
		return $instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $instance returns the type App\TextParser which is incompatible with the documented return type self.
Loading history...
266
	}
267
268 2
	/**
269
	 * Get clean instanace.
270 2
	 *
271 2
	 * @param string $moduleName Module name
272
	 *
273
	 * @return \self
274
	 */
275
	public static function getInstance($moduleName = '')
276
	{
277
		$class = static::class;
278
		$instance = new $class();
279
		if ($moduleName) {
280
			$instance->moduleName = $moduleName;
281 1
		}
282
		return $instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $instance returns the type App\TextParser which is incompatible with the documented return type self.
Loading history...
283 1
	}
284 1
285
	/**
286
	 * Set the active state of extended parsing functionality.
287
	 *
288
	 * @param bool $state
289
	 *
290
	 * @return $this
291
	 */
292
	public function setExtensionState(bool $state)
293
	{
294 3
		$this->useExtension = $state;
295
		return $this;
296 3
	}
297 3
298
	/**
299
	 * Set without translations.
300
	 *
301
	 * @param string $type
302
	 *
303
	 * @return $this
304
	 */
305
	public function withoutTranslations($type = true)
306
	{
307 1
		$this->withoutTranslations = $type;
0 ignored issues
show
Documentation Bug introduced by
It seems like $type can also be of type string. However, the property $withoutTranslations is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
308
		return $this;
309 1
	}
310
311
	/**
312
	 * Set language.
313
	 *
314
	 * @param string $name
315
	 *
316
	 * @return $this
317
	 */
318
	public function setLanguage($name = true)
319
	{
320
		$this->language = $name;
0 ignored issues
show
Documentation Bug introduced by
It seems like $name can also be of type true. However, the property $language is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
321 2
		return $this;
322
	}
323 2
324 2
	/**
325
	 * Set parser type.
326
	 *
327
	 * @param string $type
328
	 *
329
	 * @return $this
330
	 */
331
	public function setType($type)
332
	{
333
		$this->type = $type;
334 19
		return $this;
335
	}
336 19
337 19
	/**
338
	 * Set additional params.
339
	 *
340
	 * @param array $params
341
	 *
342
	 * @return $this
343
	 */
344
	public function setParams($params)
345 16
	{
346
		$this->params = $params;
347 16
		return $this;
348
	}
349
350
	/**
351
	 * Set param value.
352
	 *
353
	 * @param string $key
354
	 * @param mixed  $value
355
	 *
356
	 * @return $this
357 22
	 */
358
	public function setParam(string $key, $value)
359 22
	{
360
		$this->params[$key] = $value;
361
		return $this;
362
	}
363
364
	/**
365
	 * Get additional params.
366
	 *
367 19
	 * @param string $key
368
	 *
369 19
	 * @return mixed
370 2
	 */
371
	public function getParam(string $key)
372 17
	{
373 3
		return $this->params[$key] ?? null;
374
	}
375
376 15
	/**
377 15
	 * Set source record.
378 14
	 *
379
	 * @param int         $record
380 1
	 * @param bool|string $moduleName
381 17
	 * @param mixed       $recordModel
382 17
	 *
383 17
	 * @return $this
384
	 */
385
	public function setSourceRecord($record, $moduleName = false, $recordModel = false)
386
	{
387
		$this->sourceRecordModel = $recordModel ?: \Vtiger_Record_Model::getInstanceById($record, $moduleName ?: Record::getType($record));
388
		return $this;
389
	}
390
391 2
	/**
392
	 * Set content.
393 2
	 *
394 1
	 * @param string $content
395
	 *
396
	 * @return $this
397 1
	 */
398 1
	public function setContent($content)
399 2
	{
400 2
		$this->rawContent = $this->content = str_replace(['%20%3A%20', '%20:%20'], ' : ', $content);
401 2
		return $this;
402
	}
403
404
	/**
405
	 * Get content.
406
	 *
407
	 * @param mixed $trim
408
	 */
409
	public function getContent($trim = false)
410
	{
411 1
		return $trim ? trim($this->content) : $this->content;
412
	}
413 1
414 1
	/**
415
	 * Function checks if its TextParser type.
416
	 *
417
	 * @param string $text
418
	 *
419
	 * @return int
420
	 */
421
	public static function isVaribleToParse($text)
422
	{
423
		return (int) preg_match(static::VARIABLE_REGEX, $text);
424 1
	}
425
426 1
	/**
427 1
	 * Set permissions condition.
428
	 *
429 1
	 * @param bool $permitted
430 1
	 *
431
	 * @return $this
432 1
	 */
433 1
	public function setGlobalPermissions(bool $permitted)
434 1
	{
435
		$this->permissions = $permitted;
436
		return $this;
437
	}
438
439
	/**
440
	 * All text parse function.
441
	 *
442
	 * @return $this
443
	 */
444
	public function parse()
445
	{
446
		if (empty($this->content)) {
447
			return $this;
448
		}
449
		if (isset($this->language)) {
450
			Language::setTemporaryLanguage($this->language);
451
		}
452
		$this->content = $this->parseData($this->content);
453
		Language::clearTemporaryLanguage();
454
		return $this;
455
	}
456
457
	/**
458
	 * Text parse function.
459
	 *
460
	 * @param string $content
461
	 *
462
	 * @return string
463
	 */
464
	public function parseData(string $content)
465
	{
466
		if ($this->useExtension) {
467
			$content = preg_replace_callback('/<!--[\s]+({% [\s\S]+? %})[\s]+-->/u', fn ($matches) => $matches[1] ?? '', $content);
468
			$twig = new \Twig\Environment(new \Twig\Loader\ArrayLoader(['index' => $content]));
469
			$sandbox = new \Twig\Extension\SandboxExtension(\App\Extension\Twig\SecurityPolicy::getPolicy(), true);
470
			$twig->addExtension($sandbox);
471
			$twig->addFunction(new \Twig\TwigFunction('YFParser', function ($text) {
472
				$value = '';
473
				preg_match(static::VARIABLE_REGEX, $text, $matches);
474 2
				if ($matches) {
475
					[, $function, $params] = array_pad($matches, 3, '');
476 2
					$value = \in_array($function, static::$baseFunctions) ? $this->{$function}($params) : '';
477 2
				}
478 2
				return $value;
479
			}));
480 2
			$content = $twig->render('index');
481
		}
482
		return preg_replace_callback(static::VARIABLE_REGEX, function ($matches) {
483 2
			[, $function, $params] = array_pad($matches, 3, '');
484 2
			return \in_array($function, static::$baseFunctions) ? $this->{$function}($params) : '';
485 2
		}, $content);
486
	}
487 2
488 2
	/**
489 1
	 * Text parse function.
490 1
	 *
491 1
	 * @return $this
492 1
	 */
493 1
	public function parseTranslations()
494
	{
495
		if (isset($this->language)) {
496 1
			Language::setTemporaryLanguage($this->language);
497
		}
498 2
		$this->content = preg_replace_callback('/\$\(translate : ([,"\+\%\.\=\-\[\]\&\w\s\|]+)\)\$/u', function ($matches) {
499 2
			[, $params] = array_pad($matches, 2, '');
500
			return $this->translate($params);
501
		}, $this->content);
502
		Language::clearTemporaryLanguage();
503
		return $this;
504
	}
505
506
	/**
507
	 * Function parse date.
508
	 *
509 1
	 * @param string $param
510
	 *
511 1
	 * @return string
512 1
	 */
513 1
	public function date($param)
514 1
	{
515 1
		if (isset(\App\Condition::DATE_OPERATORS[$param])) {
516 1
			$date = implode(' - ', array_unique(\DateTimeRange::getDateRangeByType($param)));
517
		} else {
518 1
			$date = date('Y-m-d', strtotime($param));
519 1
		}
520 1
		return $date;
521 1
	}
522 1
523 1
	/**
524 1
	 * Parsing translations.
525 1
	 *
526 1
	 * @param string $params
527
	 *
528 1
	 * @return string
529
	 */
530
	protected function translate($params)
531
	{
532
		if ($this->withoutTranslations) {
533
			return "$(translate : $params)$";
534
		}
535
		if (false === strpos($params, '|')) {
536
			return Language::translate($params);
537
		}
538
		$splitParams = explode('|', $params);
539
		$module = array_shift($splitParams);
540 5
		$key = array_shift($splitParams);
541
		return Language::translate($key, $module, $splitParams[0] ?? $this->language);
542 5
	}
543 1
544
	/**
545 5
	 * Parsing organization detail.
546 5
	 *
547 5
	 * @param string $params
548 5
	 *
549 1
	 * @return string
550
	 */
551 5
	protected function organization(string $params): string
552
	{
553 1
		if (!$params) {
554 1
			return '';
555 1
		}
556 1
		$returnVal = '';
557 1
		if (false === strpos($params, '|')) {
558 1
			$id = User::getCurrentUserModel()->get('multiCompanyId');
559
			$fieldName = $params;
560 1
			$params = false;
561
		} else {
562 1
			[$id, $fieldName, $params] = array_pad(explode('|', $params, 3), 3, false);
563
		}
564
		if (Record::isExists($id, 'MultiCompany')) {
565 1
			$companyRecordModel = \Vtiger_Record_Model::getInstanceById($id, 'MultiCompany');
566 1
			if ($companyRecordModel->has($fieldName)) {
567 1
				$value = $companyRecordModel->get($fieldName);
568 1
				$fieldModel = $companyRecordModel->getModule()->getFieldByName($fieldName);
569 1
				if ('' === $value || !$fieldModel || !$this->useValue($fieldModel, 'MultiCompany')) {
570 1
					return '';
571 1
				}
572 1
				if ($this->withoutTranslations) {
573 1
					$returnVal = $this->getDisplayValueByType($value, $companyRecordModel, $fieldModel, $params);
574 1
				} else {
575 1
					$returnVal = $fieldModel->getUITypeModel()->getTextParserDisplayValue($value, $companyRecordModel, $params);
576 1
				}
577
			}
578
		}
579 1
		return $returnVal;
580 1
	}
581 1
582 1
	/**
583
	 * Parsing employee detail.
584 1
	 *
585 1
	 * @param string $fieldName
586
	 *
587
	 * @return mixed
588 1
	 */
589 1
	protected function employee($fieldName)
590 1
	{
591 1
		$userId = User::getCurrentUserId();
592 1
		if (Cache::has('TextParserEmployeeDetail', $userId . $fieldName)) {
593 1
			return Cache::get('TextParserEmployeeDetail', $userId . $fieldName);
594 1
		}
595
		if (Cache::has('TextParserEmployeeDetailRows', $userId)) {
596 1
			$employee = Cache::get('TextParserEmployeeDetailRows', $userId);
597 1
		} else {
598 1
			$employee = (new Db\Query())->select(['crmid'])->from('vtiger_crmentity')->where(['deleted' => 0, 'setype' => 'OSSEmployees', 'smownerid' => $userId])
599
				->scalar();
600
			Cache::save('TextParserEmployeeDetailRows', $userId, $employee, Cache::LONG);
601 1
		}
602 1
		$value = '';
603 1
		if ($employee && Record::isExists($employee, 'OSSEmployees')) {
604
			$relatedRecordModel = \Vtiger_Record_Model::getInstanceById($employee, 'OSSEmployees');
605 1
			$instance = static::getInstanceByModel($relatedRecordModel);
606
			foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
607
				if (isset($this->{$key})) {
608 1
					$instance->{$key} = $this->{$key};
609
				}
610 1
			}
611 1
			$value = $instance->record($fieldName);
612
		}
613 1
		Cache::save('TextParserEmployeeDetail', $userId . $fieldName, $value, Cache::LONG);
614 1
		return $value;
615
	}
616 1
617
	/**
618 1
	 * Parsing general data.
619
	 *
620
	 * @param string $key
621
	 *
622
	 * @return mixed
623
	 */
624
	protected function general($key)
625
	{
626
		switch ($key) {
627
			case 'CurrentDate':
628 1
				return (new \DateTimeField(null))->getDisplayDate();
629
			case 'CurrentTime':
630 1
				return \Vtiger_Util_Helper::convertTimeIntoUsersDisplayFormat(date('H:i:s'));
631
			case 'CurrentDateTime':
632 1
				return Fields\DateTime::formatToDisplay('now');
633 1
			case 'SiteUrl':
634 1
				return Config::main('site_URL');
635
			case 'PortalUrl':
636
				return Config::main('PORTAL_URL');
637
			case 'BaseTimeZone':
638 1
				return Fields\DateTime::getTimeZone();
639 1
			case 'UserTimeZone':
640
				$userModel = User::getCurrentUserModel();
641
				return ($userModel && $userModel->getDetail('time_zone')) ? $userModel->getDetail('time_zone') : Config::main('default_timezone');
642 1
			default:
643
				return $key;
644
		}
645 1
	}
646 1
647 1
	/**
648 1
	 * Parsing record data.
649 1
	 *
650 1
	 * @param string $params
651 1
	 * @param mixed  $isPermitted
652 1
	 *
653 1
	 * @return string
654
	 */
655
	protected function record($params, $isPermitted = true)
656
	{
657 1
		if (!isset($this->recordModel) || ($isPermitted && !Privilege::isPermitted($this->moduleName, 'DetailView', $this->record))) {
658
			return '';
659
		}
660
		[$key, $params] = array_pad(explode('|', $params, 2), 2, false);
661
		if ($this->recordModel->has($key)) {
662
			$fieldModel = $this->recordModel->getModule()->getFieldByName($key);
663
			if (!$fieldModel || !$this->useValue($fieldModel, $this->moduleName)) {
664
				return '';
665
			}
666
			return $this->getDisplayValueByField($fieldModel, false, $params);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getDisplay...dModel, false, $params) also could return the type array which is incompatible with the documented return type string.
Loading history...
667
		}
668
		switch ($key) {
669
			case 'CrmDetailViewURL':
670
				return Config::main('site_URL') . 'index.php?module=' . $this->moduleName . '&view=Detail&record=' . $this->record;
671
			case 'PortalDetailViewURL':
672
				$recorIdName = 'id';
673
				if ('HelpDesk' === $this->moduleName) {
674
					$recorIdName = 'ticketid';
675
				} elseif ('Faq' === $this->moduleName) {
676
					$recorIdName = 'faqid';
677 1
				} elseif ('Products' === $this->moduleName) {
678 1
					$recorIdName = 'productid';
679
				}
680
				return Config::main('PORTAL_URL') . '/index.php?module=' . $this->moduleName . '&action=index&' . $recorIdName . '=' . $this->record;
681 1
			case 'ModuleName':
682 1
				return $this->moduleName;
683 1
			case 'RecordId':
684 1
				return $this->record;
685 1
			case 'RecordLabel':
686
				return $this->recordModel->getName();
687
			case 'ChangesListChanges':
688 1
				$value = '';
689
				foreach ($this->recordModel->getPreviousValue() as $fieldName => $oldValue) {
690
					$fieldModel = $this->recordModel->getModule()->getFieldByName($fieldName);
691
					if (!$fieldModel) {
692
						continue;
693
					}
694
					$oldValue = $this->getDisplayValueByField($fieldModel, $oldValue);
695
					$currentValue = $this->getDisplayValueByField($fieldModel);
696
					if ($this->withoutTranslations) {
697
						$label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

697
						$label = \App\Purifier::encodeHtml(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
698 1
						$value .= "\$(translate : {$this->moduleName}|{$label})\$ \$(translate : LBL_FROM)\$ $oldValue \$(translate : LBL_TO)\$ " . $currentValue . ($this->isHtml ? '<br>' : PHP_EOL);
0 ignored issues
show
Bug introduced by
Are you sure $currentValue of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

698
						$value .= "\$(translate : {$this->moduleName}|{$label})\$ \$(translate : LBL_FROM)\$ $oldValue \$(translate : LBL_TO)\$ " . /** @scrutinizer ignore-type */ $currentValue . ($this->isHtml ? '<br>' : PHP_EOL);
Loading history...
699
					} else {
700 1
						$value .= Language::translate($fieldModel->getFieldLabel(), $this->moduleName, $this->language) . ' ';
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

700
						$value .= Language::translate(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel(), $this->moduleName, $this->language) . ' ';

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
701 1
						$value .= Language::translate('LBL_FROM') . " $oldValue " . Language::translate('LBL_TO') . " $currentValue" . ($this->isHtml ? '<br>' : PHP_EOL);
702
					}
703 1
				}
704 1
				return $value;
705 1
			case 'ChangesListValues':
706 1
				$value = '';
707
				$changes = $this->recordModel->getPreviousValue();
708
				if (empty($changes)) {
709 1
					$changes = array_filter($this->recordModel->getData());
710
					unset($changes['createdtime'], $changes['modifiedtime'], $changes['id'], $changes['newRecord'], $changes['modifiedby']);
711
				}
712
				foreach ($changes as $fieldName => $oldValue) {
713
					$fieldModel = $this->recordModel->getModule()->getFieldByName($fieldName);
714
					if (!$fieldModel) {
715
						continue;
716
					}
717
					$currentValue = \in_array($fieldModel->getFieldDataType(), $this->largeDataUiTypes) ? '' : $this->getDisplayValueByField($fieldModel);
718
					if ($this->withoutTranslations) {
719 1
						$label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

719
						$label = \App\Purifier::encodeHtml(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
720
						$value .= "\$(translate : {$this->moduleName}|{$label})\$: $currentValue" . ($this->isHtml ? '<br>' : PHP_EOL);
721 1
					} else {
722 1
						$value .= Language::translate($fieldModel->getFieldLabel(), $this->moduleName, $this->language) . ": $currentValue" . ($this->isHtml ? '<br>' : PHP_EOL);
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

722
						$value .= Language::translate(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel(), $this->moduleName, $this->language) . ": $currentValue" . ($this->isHtml ? '<br>' : PHP_EOL);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
723 1
					}
724 1
				}
725
				return $value;
726 1
			case 'SummaryFields':
727 1
					$value = '';
728 1
					$recordStructure = \Vtiger_RecordStructure_Model::getInstanceFromRecordModel($this->recordModel, \Vtiger_RecordStructure_Model::RECORD_STRUCTURE_MODE_SUMMARY);
729
					$fields = $recordStructure->getStructure()['SUMMARY_FIELDS'] ?? [];
730 1
					foreach ($fields as $fieldName => $fieldModel) {
731 1
						$currentValue = $this->getDisplayValueByField($fieldModel);
732 1
						if ($this->withoutTranslations) {
733 1
							$label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
734 1
							$value .= "\$(translate : {$this->moduleName}|{$label})\$: $currentValue" . ($this->isHtml ? '<br>' : PHP_EOL);
735
						} else {
736 1
							$value .= Language::translate($fieldModel->getFieldLabel(), $this->moduleName, $this->language) . ": $currentValue" . ($this->isHtml ? '<br>' : PHP_EOL);
737 1
						}
738
					}
739
					return $value;
740 1
			default:
741 1
				if (false !== strpos($key, ' ')) {
742
					[$key, $params] = explode(' ', $key);
743
				}
744 1
				if ('Comments' === $key) {
745 1
					return $this->getComments($params);
746
				}
747 1
				break;
748 1
		}
749 1
		return '';
750
	}
751 1
752 1
	/**
753 1
	 * Parsing related record data.
754 1
	 *
755 1
	 * @param string $params
756 1
	 *
757
	 * @return mixed
758
	 */
759
	protected function relatedRecord($params)
760
	{
761
		$params = explode('|', $params);
762 1
		$fieldName = array_shift($params);
763 1
		$relatedField = array_shift($params);
764 1
		$relatedModule = array_shift($params);
765 1
		$value = $params ? $relatedField . '|' . implode('|', $params) : $relatedField;
766 1
		if (
767 1
			!isset($this->recordModel)
768 1
			|| ($this->permissions && !Privilege::isPermitted($this->moduleName, 'DetailView', $this->record))
769 1
			|| $this->recordModel->isEmpty($fieldName)
770
		) {
771
			return '';
772 1
		}
773
		$relatedId = $this->recordModel->get($fieldName);
774
		if (empty($relatedId)) {
775 1
			return '';
776
		}
777 1
		if (empty($relatedModule) && \in_array($this->recordModel->getField($fieldName)->getFieldDataType(), ['owner', 'sharedOwner'])) {
778
			$relatedModule = 'Users';
779
		}
780
		if ('Users' === $relatedModule) {
781
			$return = [];
782
			foreach (explode(',', $relatedId) as $relatedValueId) {
783
				if ('Users' === Fields\Owner::getType($relatedValueId)) {
0 ignored issues
show
Bug introduced by
$relatedValueId of type string is incompatible with the type integer expected by parameter $id of App\Fields\Owner::getType(). ( Ignorable by Annotation )

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

783
				if ('Users' === Fields\Owner::getType(/** @scrutinizer ignore-type */ $relatedValueId)) {
Loading history...
784
					$userRecordModel = \Vtiger_Record_Model::getInstanceById($relatedValueId, $relatedModule);
0 ignored issues
show
Bug introduced by
$relatedValueId of type string is incompatible with the type integer expected by parameter $recordId of Vtiger_Record_Model::getInstanceById(). ( Ignorable by Annotation )

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

784
					$userRecordModel = \Vtiger_Record_Model::getInstanceById(/** @scrutinizer ignore-type */ $relatedValueId, $relatedModule);
Loading history...
785
					if ('Active' === $userRecordModel->get('status')) {
786
						$instance = static::getInstanceByModel($userRecordModel);
787 1
						foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
788
							if (isset($this->{$key})) {
789 1
								$instance->{$key} = $this->{$key};
790 1
							}
791 1
						}
792 1
						$return[] = $instance->record($value, false);
793 1
					}
794 1
					continue;
795 1
				}
796
				foreach (PrivilegeUtil::getUsersByGroup($relatedValueId) as $userId) {
0 ignored issues
show
Bug introduced by
$relatedValueId of type string is incompatible with the type integer expected by parameter $groupId of App\PrivilegeUtil::getUsersByGroup(). ( Ignorable by Annotation )

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

796
				foreach (PrivilegeUtil::getUsersByGroup(/** @scrutinizer ignore-type */ $relatedValueId) as $userId) {
Loading history...
797 1
					$userRecordModel = \Vtiger_Record_Model::getInstanceById($userId, $relatedModule);
798 1
					if ('Active' === $userRecordModel->get('status')) {
799
						$instance = static::getInstanceByModel($userRecordModel);
800
						foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
801 1
							if (isset($this->{$key})) {
802 1
								$instance->{$key} = $this->{$key};
803
							}
804
						}
805 1
						$return[] = $instance->record($value, false);
806 1
					}
807 1
				}
808 1
			}
809
			return implode($this->relatedRecordSeparator, $return);
810 1
		}
811 1
		$module = Record::getType($relatedId);
812 1
		if (!Record::isExists($relatedId) || empty($module) || ($relatedModule && $relatedModule !== $module)) {
813 1
			return '';
814 1
		}
815 1
		$relatedRecordModel = \Vtiger_Record_Model::getInstanceById($relatedId, $module);
816
		if ($this->permissions && !$relatedRecordModel->isViewable()) {
817
			return '';
818 1
		}
819 1
		$instance = static::getInstanceByModel($relatedRecordModel);
820 1
		foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
821
			if (isset($this->{$key})) {
822 1
				$instance->{$key} = $this->{$key};
823 1
			}
824 1
		}
825
		return $instance->record($value);
826 1
	}
827 1
828 1
	/**
829 1
	 * Parsing related record data.
830 1
	 *
831
	 * @param string $params
832 1
	 *
833
	 * @return mixed
834
	 */
835 1
	protected function relatedRecordLevel($params)
836 1
	{
837 1
		[$fieldName, $relatedModule, $relatedRecord] = array_pad(explode('|', $params, 3), 3, '');
838 1
		if (
839 1
			!isset($this->recordModel)
840 1
			|| !Privilege::isPermitted($this->moduleName, 'DetailView', $this->record)
841 1
			|| $this->recordModel->isEmpty($fieldName)
842 1
		) {
843
			return '';
844
		}
845 1
		$relatedId = $this->recordModel->get($fieldName);
846
		if (empty($relatedId)) {
847
			return '';
848 1
		}
849
		$moduleName = Record::getType($relatedId);
850 1
		if (!empty($moduleName) && ($relatedModule && $relatedModule !== $moduleName)) {
851
			return '';
852
		}
853
		if ('Users' === $relatedModule && 'Users' === Fields\Owner::getType($relatedId)) {
0 ignored issues
show
introduced by
The condition 'Users' === App\Fields\Owner::getType($relatedId) is always false.
Loading history...
854
			$relatedRecordModel = \Users_Privileges_Model::getInstanceById($relatedId);
855
			if ('Active' !== $relatedRecordModel->get('status')) {
856
				return '';
857
			}
858
		} else {
859
			$relatedRecordModel = \Vtiger_Record_Model::getInstanceById($relatedId, $moduleName);
860
			if (!$relatedRecordModel->isViewable()) {
861
				return '';
862 7
			}
863
		}
864 7
		$instance = static::getInstanceByModel($relatedRecordModel);
865 7
		foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
866 5
			if (isset($this->{$key})) {
867 5
				$instance->{$key} = $this->{$key};
868
			}
869
		}
870 3
		return $instance->relatedRecord($relatedRecord);
871 2
	}
872 2
873 2
	/**
874
	 * Parsing source record data.
875
	 *
876
	 * @param string $fieldName
877 7
	 *
878 1
	 * @return mixed
879
	 */
880 7
	protected function sourceRecord($fieldName)
881 3
	{
882
		if (empty($this->sourceRecordModel) || !Privilege::isPermitted($this->sourceRecordModel->getModuleName(), 'DetailView', $this->sourceRecordModel->getId())) {
883 6
			return '';
884
		}
885
		$instance = static::getInstanceByModel($this->sourceRecordModel);
886
		foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
887
			if (isset($this->{$key})) {
888
				$instance->{$key} = $this->{$key};
889
			}
890
		}
891
		return $instance->record($fieldName);
892
	}
893
894
	/**
895
	 * Parsing related records list.
896 3
	 *
897
	 * @param string $params Parameter construction: RelatedModuleNameOrRelationId|Columns|Conditions|CustomViewIdOrName|Limit, Example: Contacts|firstname,lastname,modifiedtime|[[["firstname","a","Tom"]]]||2
898 3
	 *
899 3
	 * @return string
900 1
	 */
901 1
	protected function relatedRecordsList($params)
902 1
	{
903 3
		[$relatedModuleName, $columns, $conditions, $viewIdOrName, $limit, $maxLength] = array_pad(explode('|', $params), 6, '');
904 1
		if (is_numeric($relatedModuleName)) {
905 1
			if ($relationListView = \Vtiger_RelationListView_Model::getInstance($this->recordModel, '', $relatedModuleName)) {
906 1
				$relatedModuleName = $relationListView->getRelatedModuleModel()->getName();
907 1
			}
908 1
		} else {
909
			$relationListView = \Vtiger_RelationListView_Model::getInstance($this->recordModel, $relatedModuleName);
910 1
		}
911 1
		if (!$relationListView || !Privilege::isPermitted($relatedModuleName)) {
0 ignored issues
show
introduced by
$relationListView is of type Vtiger_RelationListView_Model, thus it always evaluated to true.
Loading history...
912
			return '';
913 1
		}
914 1
		$pagingModel = new \Vtiger_Paging_Model();
915 3
		$pagingModel->set('limit', (int) $limit);
916 1
		if ($viewIdOrName) {
917 1
			if (!is_numeric($viewIdOrName)) {
918 3
				$customView = CustomView::getInstance($relatedModuleName);
919
				if ($cvId = $customView->getViewIdByName($viewIdOrName)) {
920
					$viewIdOrName = $cvId;
921
				} else {
922
					$viewIdOrName = false;
923
					Log::warning("No view found. Module: $relatedModuleName, view name: $viewIdOrName", 'TextParser');
924
				}
925
			}
926
			if ($viewIdOrName) {
927
				$relationListView->getQueryGenerator()->initForCustomViewById($viewIdOrName);
928
			}
929
			if ($cvId && ($customViewModel = \CustomView_Record_Model::getInstanceById($cvId)) && ($orderBy = $customViewModel->getSortOrderBy())) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cvId does not seem to be defined for all execution paths leading up to this point.
Loading history...
930
				$relationListView->set('orderby', $orderBy);
931
			}
932
		}
933
		if ($columns) {
934
			$relationListView->setFields($columns);
935
		} else {
936
			$fields = array_filter($relationListView->getHeaders(), fn ($fieldModel) => !$fieldModel->get('fromOutsideList'));
937
			$relationListView->setFields(array_keys($fields));
938
		}
939
		if ($conditions) {
940
			$transformedSearchParams = $relationListView->getQueryGenerator()->parseBaseSearchParamsToCondition(Json::decode($conditions));
941 3
			$relationListView->set('search_params', $transformedSearchParams);
942
		}
943
		return $this->relatedRecordsListPrinter($relationListView, $pagingModel, (int) $maxLength);
944
	}
945
946
	/**
947
	 * Printer related records list.
948
	 *
949
	 * @param \Vtiger_RelationListView_Model $relationListView
950
	 * @param \Vtiger_Paging_Model           $pagingModel
951
	 * @param int                            $maxLength
952
	 *
953
	 * @return string
954
	 */
955
	protected function relatedRecordsListPrinter(\Vtiger_RelationListView_Model $relationListView, \Vtiger_Paging_Model $pagingModel, int $maxLength): string
956
	{
957
		$relatedModuleName = $relationListView->getRelationModel()->getRelationModuleName();
958 3
		$rows = $headers = '';
959
		$fields = $relationListView->getRelationModel()->getQueryFields();
960 1
		foreach ($fields as $fieldModel) {
961
			if ($fieldModel->isViewable() || $fieldModel->get('fromOutsideList')) {
962
				if ($this->withoutTranslations) {
963
					$label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

963
					$label = \App\Purifier::encodeHtml(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
964
					$headers .= "<th class=\"col-type-{$fieldModel->getFieldType()}\">$(translate : {$label}|$relatedModuleName)$</th>";
965
				} else {
966
					$headers .= "<th class=\"col-type-{$fieldModel->getFieldType()}\">" . Language::translate($fieldModel->getFieldLabel(), $relatedModuleName) . '</th>';
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

966
					$headers .= "<th class=\"col-type-{$fieldModel->getFieldType()}\">" . Language::translate(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel(), $relatedModuleName) . '</th>';

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
967
				}
968
			}
969
		}
970 1
		$counter = 0;
971
		foreach ($relationListView->getEntries($pagingModel) as $relatedRecordModel) {
972 1
			++$counter;
973 1
			$rows .= '<tr class="row-' . $counter . '">';
974 1
			foreach ($fields as $fieldModel) {
975 1
				$value = $this->getDisplayValueByField($fieldModel, $relatedRecordModel);
976
				if (false !== $value) {
977 1
					if ($maxLength) {
978 1
						$value = TextUtils::textTruncate($value, $maxLength);
979 1
					}
980 1
					$rows .= "<td class=\"col-type-{$fieldModel->getFieldType()}\">{$value}</td>";
981 1
				}
982
			}
983
			$rows .= '</tr>';
984 1
		}
985
		return empty($rows) ? '' : "<table style=\"border-collapse:collapse;width:100%\" class=\"related-records-list\"><thead><tr>{$headers}</tr></thead><tbody>{$rows}</tbody></table>";
986
	}
987 1
988
	/**
989
	 * Parsing records list.
990
	 *
991
	 * @param string $params Parameter construction: ModuleName|Columns|Conditions|CustomViewIdOrName|Limit, Example: Contacts|firstname,lastname,modifiedtime|[[["firstname","a","Tom"]]]||2
992
	 *
993
	 * @return string
994
	 */
995
	protected function recordsList($params)
996
	{
997
		[$moduleName, $columns, $conditions, $viewIdOrName, $limit, $maxLength, $params] = array_pad(explode('|', $params, 7), 7, '');
998 4
		$paramsArray = $params ? self::parseFieldParam($params) : [];
999
		$cvId = 0;
1000 4
		if ($viewIdOrName) {
1001
			if (!is_numeric($viewIdOrName)) {
1002
				$customView = CustomView::getInstance($moduleName);
1003
				if ($cvIdByName = $customView->getViewIdByName($viewIdOrName)) {
1004
					$viewIdOrName = $cvIdByName;
1005
				} else {
1006
					$viewIdOrName = false;
1007
					Log::warning("No view found. Module: $moduleName, view name: $viewIdOrName", 'TextParser');
1008
				}
1009
			}
1010 2
			if ($viewIdOrName) {
1011
				$cvId = $viewIdOrName;
1012 2
			}
1013 2
		}
1014
		$listView = \Vtiger_ListView_Model::getInstance($moduleName, $cvId);
1015 1
		if ($cvId && ($customViewModel = \CustomView_Record_Model::getInstanceById($cvId)) && ($orderBy = $customViewModel->getSortOrderBy())) {
1016
			$listView->set('orderby', $orderBy);
1017
		}
1018
		$limit = (int) $limit;
1019
		$listView->getQueryGenerator()->setLimit((int) ($limit ?: \App\Config::main('list_max_entries_per_page', 20)));
1020
		if ($columns) {
1021
			$headerFields = [];
1022
			foreach (explode(',', $columns) as $fieldName) {
1023
				$headerFields[] = [
1024
					'field_name' => $fieldName,
1025 1
					'module_name' => $moduleName,
1026
				];
1027 1
			}
1028
			$listView->set('header_fields', $headerFields);
1029
			$listView->getQueryGenerator()->setFields(explode(',', $columns));
1030
			$listView->getQueryGenerator()->setField('id');
1031
		}
1032
		if ($conditions) {
1033 1
			$transformedSearchParams = $listView->getQueryGenerator()->parseBaseSearchParamsToCondition(Json::decode($conditions));
1034 1
			$listView->set('search_params', $transformedSearchParams);
1035 1
		}
1036
		if (($pdf = $this->getParam('pdf')) && $pdf->get('module_name') === $moduleName && ($ids = $pdf->getVariable('recordsId'))) {
1037 1
			$listView->getQueryGenerator()->addCondition('id', $ids, 'e', 1);
1038 1
		}
1039 1
		$rows = $headers = $headerStyle = $borderStyle = '';
1040 1
		$fields = $listView->getListViewHeaders();
1041 1
		if (isset($paramsArray['headerStyle']) && 'background' === $paramsArray['headerStyle']) {
1042
			$headerStyle = 'background-color:#ddd;';
1043
		}
1044 1
		if (isset($paramsArray['table']) && 'border' === $paramsArray['table']) {
1045 1
			$borderStyle = 'border:1px solid  #ddd;';
1046 1
		}
1047 1
		foreach ($fields as $fieldModel) {
1048 1
			if ($this->withoutTranslations) {
1049
				$label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1049
				$label = \App\Purifier::encodeHtml(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1050
				$headers .= "<th class=\"col-type-{$fieldModel->getFieldType()}\" style=\"{$headerStyle}\">$(translate : {$label}|$moduleName)$</th>";
1051
			} else {
1052 1
				$headers .= "<th class=\"col-type-{$fieldModel->getFieldType()}\" style=\"{$headerStyle}\">" . Language::translate($fieldModel->getFieldLabel(), $moduleName) . '</th>';
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1052
				$headers .= "<th class=\"col-type-{$fieldModel->getFieldType()}\" style=\"{$headerStyle}\">" . Language::translate(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel(), $moduleName) . '</th>';

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1053 1
			}
1054 1
		}
1055 1
		$counter = 0;
1056
		foreach ($listView->getAllEntries() as $relatedRecordModel) {
1057 1
			++$counter;
1058
			$rows .= '<tr class="row-' . $counter . '">';
1059 1
			foreach ($fields as $fieldModel) {
1060 1
				$value = $this->getDisplayValueByField($fieldModel, $relatedRecordModel, $params);
1061
				if (false !== $value) {
1062
					if ((int) $maxLength) {
1063
						$value = TextUtils::textTruncate($value, (int) $maxLength);
1064
					}
1065
					$rows .= "<td class=\"col-type-{$fieldModel->getFieldType()}\" style=\"{$borderStyle}\">{$value}</td>";
1066
				}
1067
			}
1068
			$rows .= '</tr>';
1069
		}
1070
		if (empty($rows)) {
1071
			return '';
1072 1
		}
1073
		$headers = "<tr>{$headers}</tr>";
1074 1
		$table = "class=\"records-list\" style=\"border-collapse:collapse;width:100%;{$borderStyle}\"";
1075 1
		if (isset($paramsArray['addCounter']) && '1' === $paramsArray['addCounter']) {
1076 1
			$headers = '<tr><th colspan="' . \count($fields) . '">' . Language::translate('LBL_NUMBER_OF_ALL_ENTRIES') . ": $counter</th></th></tr>$headers";
1077
		}
1078 1
		return "<table {$table}><thead>{$headers}</thead><tbody>{$rows}</tbody></table>";
1079 1
	}
1080 1
1081 1
	/**
1082 1
	 * Get record display value.
1083 1
	 *
1084 1
	 * @param \Vtiger_Field_Model             $fieldModel
1085
	 * @param bool|mixed|\Vtiger_Record_Model $value
1086
	 * @param string                          $params
1087
	 *
1088 1
	 * @return array|bool|mixed|string
1089 1
	 */
1090 1
	protected function getDisplayValueByField(\Vtiger_Field_Model $fieldModel, $value = false, $params = null)
1091 1
	{
1092 1
		$model = $this->recordModel;
1093 1
		if (false === $value) {
1094 1
			$value = \App\Utils\Completions::decode($this->recordModel->get($fieldModel->getName()), \App\Utils\Completions::FORMAT_TEXT);
1095 1
			if (!$fieldModel->isViewEnabled() && !$fieldModel->get('fromOutsideList')) {
1096
				return '';
1097
			}
1098
		} elseif (\is_object($value)) {
1099
			$model = $value;
1100 1
			$value = $value->get($fieldModel->getName());
1101 1
			if (!$fieldModel->isViewEnabled() && !$fieldModel->get('fromOutsideList')) {
1102
				return false;
1103
			}
1104
		}
1105
		if ('' === $value) {
1106
			return '';
1107
		}
1108
		if ($this->withoutTranslations) {
1109 1
			return $this->getDisplayValueByType($value, $model, $fieldModel, $params);
1110
		}
1111 1
		return $fieldModel->getUITypeModel()->getTextParserDisplayValue($value, $model, $params);
1112 1
	}
1113
1114 1
	/**
1115 1
	 * Get record display value by type.
1116 1
	 *
1117 1
	 * @param mixed                $value
1118 1
	 * @param \Vtiger_Record_Model $recordModel
1119 1
	 * @param \Vtiger_Field_Model  $fieldModel
1120
	 * @param string               $params
1121
	 *
1122 1
	 * @return array|mixed|string
1123 1
	 */
1124 1
	protected function getDisplayValueByType($value, \Vtiger_Record_Model $recordModel, \Vtiger_Field_Model $fieldModel, $params)
1125 1
	{
1126 1
		switch ($fieldModel->getFieldDataType()) {
1127 1
			case 'boolean':
1128 1
				$value = (1 === $value) ? 'LBL_YES' : 'LBL_NO';
1129 1
				$value = "$(translate : $value)$";
1130 1
				break;
1131
			case 'multipicklist':
1132
				$value = explode(' |##| ', $value);
1133
				$trValue = [];
1134
				$countValue = \count($value);
1135
				for ($i = 0; $i < $countValue; ++$i) {
1136 1
					$trValue[] = "$(translate : {$recordModel->getModuleName()}|{$value[$i]})$";
1137
				}
1138
				if (\is_array($trValue)) {
0 ignored issues
show
introduced by
The condition is_array($trValue) is always true.
Loading history...
1139
					$trValue = implode(' |##| ', $trValue);
1140
				}
1141
				$value = str_ireplace(' |##| ', ', ', $trValue);
1142
				break;
1143
			case 'picklist':
1144
				$value = "$(translate : {$recordModel->getModuleName()}|$value)$";
1145
				break;
1146 1
			case 'time':
1147
				$userModel = \Users_Privileges_Model::getCurrentUserModel();
1148 1
				$value = \DateTimeField::convertToUserTimeZone(date('Y-m-d') . ' ' . $value)->format('H:i:s');
1149 1
				if (12 === (int) $userModel->get('hour_format')) {
1150 1
					if ($value) {
1151
						[$hours, $minutes] = array_pad(explode(':', $value), 2, '');
1152 1
						$format = '$(translate : PM)$';
1153 1
						if ($hours > 12) {
1154 1
							$hours = (int) $hours - 12;
1155 1
						} elseif ($hours < 12) {
1156 1
							$format = '$(translate : AM)$';
1157 1
						}
1158
						//If hours zero then we need to make it as 12 AM
1159 1
						if ('00' == $hours) {
1160
							$hours = '12';
1161 1
							$format = '$(translate : AM)$';
1162 1
						}
1163 1
						$value = "$hours:$minutes $format";
1164 1
					} else {
1165 1
						$value = '';
1166 1
					}
1167 1
				}
1168
				break;
1169
			case 'tree':
1170
				$template = $fieldModel->getFieldParams();
1171 1
				$value = $parentName = '';
1172 1
				if ($row = Fields\Tree::getValueByTreeId($template, $value)) {
0 ignored issues
show
Bug introduced by
$template of type array is incompatible with the type integer expected by parameter $templateId of App\Fields\Tree::getValueByTreeId(). ( Ignorable by Annotation )

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

1172
				if ($row = Fields\Tree::getValueByTreeId(/** @scrutinizer ignore-type */ $template, $value)) {
Loading history...
1173 1
					if ($row['depth'] > 0) {
1174 1
						$pieces = explode('::', $row['parentTree']);
1175 1
						end($pieces);
1176 1
						$parent = prev($pieces);
1177 1
						$parentRow = Fields\Tree::getValueByTreeId($template, $parent);
1178 1
						$parentName = "($(translate : {$recordModel->getModuleName()}|{$parentRow['name']})$) ";
1179 1
					}
1180 1
					$value = $parentName . "$(translate : {$recordModel->getModuleName()}|{$row['name']})$";
1181 1
				}
1182
				break;
1183
			default:
1184
				return $fieldModel->getUITypeModel()->getTextParserDisplayValue($value, $recordModel, $params);
1185
		}
1186
		return $value;
1187
	}
1188 1
1189
	/**
1190 1
	 * Get last comments.
1191
	 *
1192
	 * @param mixed $params
1193
	 *
1194
	 * @return string
1195
	 */
1196
	protected function getComments($params = false)
1197
	{
1198 1
		[$limit, $showAuthor] = array_pad(explode('|', $params, 2), 2, false);
1199
		$query = (new \App\Db\Query())->select(['commentcontent', 'userid'])->from('vtiger_modcomments')->where(['related_to' => $this->record])->orderBy(['modcommentsid' => SORT_DESC]);
1200
		if ($limit) {
1201
			$query->limit($limit);
1202 1
		}
1203 1
		$commentsList = '';
1204
		foreach ($query->all() as $comment) {
1205 1
			if ('' != $comment['commentcontent']) {
1206 1
				$commentsList .= '<br><br>';
1207
				if ('true' === $showAuthor) {
1208
					$commentsList .= Purifier::encodeHtml(\App\Fields\Owner::getUserLabel($comment['userid'])) . ': ';
1209
				}
1210
				$commentsList .= nl2br($comment['commentcontent']);
1211
			}
1212
		}
1213
		return ltrim($commentsList, '<br><br>');
1214 1
	}
1215
1216 1
	/**
1217 1
	 * Check if this content can be used.
1218 1
	 *
1219 1
	 * @param \Vtiger_Field_Model $fieldModel
1220 1
	 * @param string              $moduleName
1221 1
	 *
1222
	 * @return bool
1223
	 */
1224
	protected function useValue($fieldModel, $moduleName)
1225 1
	{
1226 1
		return true;
1227
	}
1228
1229 1
	/**
1230
	 * Parsing params.
1231
	 *
1232 1
	 * @param string $key
1233
	 *
1234
	 * @return string
1235
	 */
1236
	protected function params(string $key)
1237
	{
1238
		return isset($this->params[$key]) ? \App\Purifier::purifyHtml($this->params[$key]) : '';
1239
	}
1240 1
1241
	/**
1242 1
	 * Parsing custom.
1243 1
	 *
1244 1
	 * @param string $params
1245 1
	 *
1246 1
	 * @return string
1247 1
	 */
1248 1
	protected function custom($params)
1249 1
	{
1250
		$instance = null;
1251
		if (false !== strpos($params, '||')) {
1252 1
			$params = explode('||', $params);
1253
			$parserName = array_shift($params);
1254
			$baseParams = $params;
1255
			$params = [];
1256 1
		} else {
1257
			$params = explode('|', $params);
1258
			$parserName = array_shift($params);
1259
			$baseParams = $params;
1260
		}
1261
		$module = false;
1262
		if (!empty($params)) {
1263
			$module = array_shift($params);
1264 1
			if (!Module::getModuleId($module)) {
1265
				$module = $this->moduleName;
1266 1
			}
1267 1
		}
1268 1
		$className = "\\App\\TextParser\\$parserName";
1269 1
		if ($module && $handlerClass = \Vtiger_Loader::getComponentClassName('TextParser', $parserName, $module, false)) {
1270 1
			$className = $handlerClass;
1271 1
		}
1272 1
		if (!class_exists($className)) {
1273
			Log::error("Not found custom class: $parserName|{$module}");
1274
		} else {
1275 1
			$instance = new $className($this, $baseParams);
1276
		}
1277
		return $instance && $instance->isActive() ? $instance->process() : '';
1278
	}
1279
1280
	/**
1281
	 * Get record variables.
1282
	 *
1283 1
	 * @param bool|string $fieldType
1284
	 *
1285 1
	 * @return array
1286 1
	 */
1287 1
	public function getRecordVariable($fieldType = false)
1288 1
	{
1289 1
		$cacheKey = "{$this->moduleName}|$fieldType";
1290
		if (isset(static::$recordVariable[$cacheKey])) {
1291
			return static::$recordVariable[$cacheKey];
1292 1
		}
1293
		$variables = [];
1294
		if (!$fieldType) {
1295
			foreach (static::$variableEntity as $key => $name) {
1296
				$variables[Language::translate('LBL_ENTITY_VARIABLES', 'Other.TextParser')][] = [
1297
					'var_value' => "$(record : $key)$",
1298
					'var_label' => "$(translate : Other.TextParser|$name)$",
1299
					'label' => Language::translate($name, 'Other.TextParser'),
1300
				];
1301
			}
1302
		}
1303
		$moduleModel = \Vtiger_Module_Model::getInstance($this->moduleName);
1304
		foreach ($moduleModel->getBlocks() as $blockModel) {
1305
			foreach ($blockModel->getFields() as $fieldModel) {
1306
				if ($fieldModel->isViewable() && !($fieldType && $fieldModel->getFieldDataType() !== $fieldType)) {
1307 2
					$label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1307
					$label = \App\Purifier::encodeHtml(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1308
					$variables[Language::translate($blockModel->get('label'), $this->moduleName)][] = [
1309 2
						'var_value' => "$(record : {$fieldModel->getName()})$",
1310 1
						'var_label' => "$(translate : {$this->moduleName}|{$label})$",
1311
						'label' => Language::translate($fieldModel->getFieldLabel(), $this->moduleName),
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1311
						'label' => Language::translate(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel(), $this->moduleName),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1312 2
					];
1313 2
				}
1314 2
			}
1315 2
		}
1316 2
		static::$recordVariable[$cacheKey] = $variables;
1317 2
		return $variables;
1318 2
	}
1319 2
1320 2
	/**
1321 2
	 * Get source variables.
1322 2
	 *
1323 2
	 * @return array
1324 2
	 */
1325 2
	public function getSourceVariable()
1326
	{
1327
		if (empty(self::$sourceModules[$this->moduleName])) {
1328
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
1329
		}
1330 2
		$variables = [];
1331 1
		foreach (static::$variableEntity as $key => $name) {
1332
			$variables['LBL_ENTITY_VARIABLES'][] = [
1333 2
				'var_value' => "$(sourceRecord : $key)$",
1334
				'var_label' => "$(translate : Other.TextParser|$name)$",
1335 2
				'label' => Language::translate($name, 'Other.TextParser'),
1336 2
			];
1337 2
		}
1338 2
		foreach (self::$sourceModules[$this->moduleName] as $moduleName) {
1339 2
			$moduleModel = \Vtiger_Module_Model::getInstance($moduleName);
1340 2
			foreach ($moduleModel->getBlocks() as $blockModel) {
1341 2
				foreach ($blockModel->getFields() as $fieldModel) {
1342
					if ($fieldModel->isViewable()) {
1343 2
						$label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1343
						$label = \App\Purifier::encodeHtml(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1344 2
						$variables[$moduleName][$blockModel->get('label')][] = [
1345
							'var_value' => "$(sourceRecord : {$fieldModel->getName()})$",
1346 2
							'var_label' => "$(translate : $moduleName|{$label})$",
1347 1
							'label' => Language::translate($fieldModel->getFieldLabel(), $moduleName),
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1347
							'label' => Language::translate(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel(), $moduleName),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1348 1
						];
1349 1
					}
1350 1
				}
1351
			}
1352
		}
1353 1
		return $variables;
1354
	}
1355
1356 2
	/**
1357
	 * Get related variables.
1358 2
	 *
1359 1
	 * @param bool|string $fieldType
1360
	 * @param bool        $skipEmpty
1361 2
	 *
1362 2
	 * @return array
1363 2
	 */
1364 2
	public function getRelatedVariable($fieldType = false, $skipEmpty = false)
1365
	{
1366
		$cacheKey = "{$this->moduleName}|$fieldType|{$skipEmpty}";
1367
		if (isset(static::$relatedVariable[$cacheKey])) {
1368
			return static::$relatedVariable[$cacheKey];
1369
		}
1370
		$moduleModel = \Vtiger_Module_Model::getInstance($this->moduleName);
1371
		$variables = [];
1372
		$entityVariables = Language::translate('LBL_ENTITY_VARIABLES', 'Other.TextParser');
1373
		foreach ($moduleModel->getFieldsByType(array_merge(\Vtiger_Field_Model::$referenceTypes, ['userCreator', 'owner', 'sharedOwner'])) as $parentFieldName => $field) {
1374
			if ('owner' === $field->getFieldDataType() || 'sharedOwner' === $field->getFieldDataType()) {
1375
				$relatedModules = ['Users'];
1376 27
			} else {
1377
				$relatedModules = $field->getReferenceList();
1378 27
			}
1379 3
			$parentFieldNameLabel = Language::translate($field->getFieldLabel(), $this->moduleName);
1380
			if (!$fieldType) {
1381 27
				foreach (static::$variableEntity as $key => $name) {
1382 27
					$variables[$parentFieldName]["$parentFieldNameLabel - $entityVariables"][] = [
1383 1
						'var_value' => "$(relatedRecord : $parentFieldName|$key)$",
1384 1
						'var_label' => "$(translate : Other.TextParser|$key)$",
1385 1
						'label' => $parentFieldNameLabel . ': ' . Language::translate($name, 'Other.TextParser'),
1386
					];
1387
				}
1388
			}
1389
			$relRecord = false;
1390
			if ($skipEmpty && $this->recordModel && !(($relId = $this->recordModel->get($field->getName()))
1391
				&& (
1392
					\in_array($field->getFieldDataType(), ['userCreator', 'owner', 'sharedOwner'])
1393
					|| ((Record::isExists($relId)) && ($relRecord = \Vtiger_Record_Model::getInstanceById($relId))->isViewable() && ($relatedModules = [Record::getType($relId)]))
1394 27
				)
1395
			)) {
1396
				continue;
1397
			}
1398
1399
			foreach ($relatedModules as $relatedModule) {
1400
				$relatedModuleLang = Language::translate($relatedModule, $relatedModule);
1401
				foreach (\Vtiger_Module_Model::getInstance($relatedModule)->getBlocks() as $blockModel) {
1402
					foreach ($blockModel->getFields() as $fieldName => $fieldModel) {
1403
						if (
1404 5783
							$fieldModel->isViewable()
1405
							&& !($fieldType && $fieldModel->getFieldDataType() !== $fieldType)
1406 5783
							&& (!$relRecord || ($relRecord && !$relRecord->isEmpty($fieldModel->getName())))
1407 5783
						) {
1408
							$labelGroup = "$parentFieldNameLabel: ($relatedModuleLang) " . Language::translate($blockModel->get('label'), $relatedModule);
1409
							$label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1409
							$label = \App\Purifier::encodeHtml(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1410
							$variables[$parentFieldName][$labelGroup][] = [
1411
								'var_value' => "$(relatedRecord : $parentFieldName|$fieldName|$relatedModule)$",
1412
								'var_label' => "$(translate : $relatedModule|{$label})$",
1413
								'label' => "$parentFieldNameLabel: ($relatedModuleLang) " . Language::translate($fieldModel->getFieldLabel(), $relatedModule),
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1413
								'label' => "$parentFieldNameLabel: ($relatedModuleLang) " . Language::translate(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel(), $relatedModule),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1414
							];
1415
						}
1416
					}
1417
				}
1418
			}
1419
		}
1420
		static::$relatedVariable[$cacheKey] = $variables;
1421
		return $variables;
1422
	}
1423
1424
	/**
1425
	 * Get related variables.
1426
	 *
1427
	 * @param bool|string $fieldType
1428
	 *
1429
	 * @return array
1430
	 */
1431
	public function getRelatedLevelVariable($fieldType = false)
1432
	{
1433
		$cacheKey = "{$this->moduleName}|$fieldType";
1434
		if (isset(static::$relatedVariableLevel[$cacheKey])) {
1435
			return static::$relatedVariableLevel[$cacheKey];
1436
		}
1437
		$moduleModel = \Vtiger_Module_Model::getInstance($this->moduleName);
1438
		$variables = [];
1439
		foreach ($moduleModel->getFieldsByType(array_merge(\Vtiger_Field_Model::$referenceTypes, ['userCreator', 'owner'])) as $parentFieldName => $fieldModel) {
1440
			if ('owner' === $fieldModel->getFieldDataType()) {
1441
				$relatedModules = ['Users'];
1442
			} else {
1443
				$relatedModules = $fieldModel->getReferenceList();
1444
			}
1445
			$parentFieldNameLabel = Language::translate($fieldModel->getFieldLabel(), $this->moduleName);
1446
			foreach ($relatedModules as $relatedModule) {
1447
				$relatedModuleLang = Language::translate($relatedModule, $relatedModule);
1448
				foreach (\Vtiger_Module_Model::getInstance($relatedModule)->getFieldsByType(array_merge(\Vtiger_Field_Model::$referenceTypes, ['userCreator', 'owner', 'sharedOwner'])) as $parentFieldNameNextLevel => $fieldModelNextLevel) {
1449
					if ('owner' === $fieldModelNextLevel->getFieldDataType() || 'sharedOwner' === $fieldModelNextLevel->getFieldDataType()) {
1450
						$relatedModulesNextLevel = ['Users'];
1451
					} else {
1452
						$relatedModulesNextLevel = $fieldModelNextLevel->getReferenceList();
1453
					}
1454
					$parentFieldNameLabelNextLevel = Language::translate($fieldModelNextLevel->getFieldLabel(), $relatedModule);
1455
					foreach ($relatedModulesNextLevel as $relatedModuleNextLevel) {
1456
						$relatedModuleLangNextLevel = Language::translate($relatedModuleNextLevel, $relatedModuleNextLevel);
1457
						foreach (\Vtiger_Module_Model::getInstance($relatedModuleNextLevel)->getBlocks() as $blockModel) {
1458
							foreach ($blockModel->getFields() as $fieldName => $fieldModel) {
0 ignored issues
show
Comprehensibility Bug introduced by
$fieldModel is overwriting a variable from outer foreach loop.
Loading history...
1459
								if ($fieldModel->isViewable() && !($fieldType && $fieldModel->getFieldDataType() !== $fieldType)) {
1460
									$labelGroup = "{$parentFieldNameLabel}($relatedModuleLang) -> {$parentFieldNameLabelNextLevel}($relatedModuleLangNextLevel) " . Language::translate($blockModel->get('label'), $relatedModuleNextLevel);
1461
									$label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1461
									$label = \App\Purifier::encodeHtml(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1462
									$variables[$labelGroup][] = [
1463
										'var_value' => "$(relatedRecordLevel : $parentFieldName|$relatedModule|$parentFieldNameNextLevel|$fieldName|$relatedModuleNextLevel)$",
1464
										'var_label' => "$(translate : $relatedModuleNextLevel|{$label})$",
1465
										'label' => "{$parentFieldNameLabel}($relatedModuleLang) -> {$parentFieldNameLabelNextLevel}($relatedModuleLangNextLevel) " . Language::translate($fieldModel->getFieldLabel(), $relatedModuleNextLevel),
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

1465
										'label' => "{$parentFieldNameLabel}($relatedModuleLang) -> {$parentFieldNameLabelNextLevel}($relatedModuleLangNextLevel) " . Language::translate(/** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel(), $relatedModuleNextLevel),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1466
									];
1467
								}
1468
							}
1469
						}
1470
					}
1471
				}
1472
			}
1473
		}
1474
		static::$relatedVariableLevel[$cacheKey] = $variables;
1475
		return $variables;
1476
	}
1477
1478
	/**
1479
	 * Get general variables.
1480
	 *
1481
	 * @return array
1482
	 */
1483
	public function getGeneralVariable()
1484
	{
1485
		$variables = [
1486
			'LBL_ENTITY_VARIABLES' => array_map(fn ($value) => Language::translate($value, 'Other.TextParser'), array_flip(static::$variableGeneral)),
1487
		];
1488
		$variables['LBL_CUSTOM_VARIABLES'] = array_merge($this->getBaseGeneralVariable(), $this->getModuleGeneralVariable());
1489
		return $variables;
1490
	}
1491
1492
	/**
1493
	 * Get general variables base function.
1494
	 *
1495
	 * @return array
1496
	 */
1497
	protected function getBaseGeneralVariable()
1498
	{
1499
		$variables = [];
1500
		foreach ((new \DirectoryIterator(__DIR__ . \DIRECTORY_SEPARATOR . 'TextParser')) as $fileInfo) {
1501
			$fileName = $fileInfo->getBasename('.php');
1502
			if ('dir' !== $fileInfo->getType() && 'Base' !== $fileName && 'php' === $fileInfo->getExtension()) {
1503
				$className = '\App\TextParser\\' . $fileName;
1504
				if (!class_exists($className)) {
1505
					Log::warning('Not found custom class');
1506
					continue;
1507
				}
1508
				$instance = new $className($this);
1509
				if (isset($this->type) && $this->type !== $instance->type) {
1510
					continue;
1511
				}
1512
				$key = $instance->default ?? "$(custom : $fileName)$";
1513
				$variables[$key] = Language::translate($instance->name, 'Other.TextParser');
1514
			}
1515
		}
1516
		return $variables;
1517
	}
1518
1519
	/**
1520
	 * Get general variables module function.
1521
	 *
1522
	 * @return array
1523
	 */
1524
	protected function getModuleGeneralVariable()
1525
	{
1526
		$variables = [];
1527
		if ($this->moduleName && is_dir(("modules/{$this->moduleName}/textparsers/"))) {
1528
			foreach ((new \DirectoryIterator("modules/{$this->moduleName}/textparsers/")) as $fileInfo) {
1529
				$fileName = $fileInfo->getBasename('.php');
1530
				if ('dir' !== $fileInfo->getType() && 'php' === $fileInfo->getExtension()) {
1531
					$handlerClass = \Vtiger_Loader::getComponentClassName('TextParser', $fileName, $this->moduleName);
1532
					$instanceClass = new $handlerClass($this);
1533
					if (isset($this->type) && $this->type !== $instanceClass->type) {
1534
						continue;
1535
					}
1536
					$variables["$(custom : $fileName|{$this->moduleName})$"] = Language::translate($instanceClass->name, $this->moduleName);
1537
				}
1538
			}
1539
		}
1540
		return $variables;
1541
	}
1542
1543
	/**
1544
	 * Get related modules list.
1545
	 *
1546
	 * @return array
1547
	 */
1548
	public function getRelatedListVariable()
1549
	{
1550
		$moduleModel = \Vtiger_Module_Model::getInstance($this->moduleName);
1551
		$variables = [];
1552
		foreach ($moduleModel->getRelations() as $relation) {
1553
			$var = $relation->get('relatedModuleName');
1554
			if ($relation->get('field_name')) {
1555
				$var = $relation->get('relation_id');
1556
			}
1557
			$variables[] = [
1558
				'key' => "$(relatedRecordsList : $var|__FIELDS_NAME__|__CONDITIONS__|__VIEW_ID_OR_NAME__|__LIMIT__|__MAX_LENGTH__)$",
1559
				'label' => Language::translate($relation->get('label'), $relation->get('relatedModuleName')),
1560
			];
1561
		}
1562
		return $variables;
1563
	}
1564
1565
	/**
1566
	 * Get base modules list.
1567
	 *
1568
	 * @return array
1569
	 */
1570
	public function getBaseListVariable()
1571
	{
1572
		$variables = [];
1573
		foreach (\vtlib\Functions::getAllModules() as $module) {
1574
			$variables[] = [
1575
				'key' => "$(recordsList : {$module['name']})$",
1576
				'label' => Language::translate($module['name'], $module['name']),
1577
			];
1578
		}
1579
		return $variables;
1580
	}
1581
1582
	/**
1583
	 * Gets user variables.
1584
	 *
1585
	 * @param string $text
1586
	 * @param bool   $useRegex
1587
	 *
1588
	 * @return array
1589
	 */
1590
	public function getUserVariables(string $text, bool $useRegex = true)
1591
	{
1592
		$data = [];
1593
		if ($useRegex) {
1594
			preg_match_all('/\$\(userVariable : ([,"\+\%\.\=\-\[\]\&\w\s\|\)\(\:]+)\)\$/u', str_replace(['%20%3A%20', '%20:%20'], ' : ', $text), $matches);
1595
			$matches = $matches[1] ?? [];
1596
		} else {
1597
			$matches = [$text];
1598
		}
1599
		foreach ($matches as $param) {
1600
			$part = self::parseFieldParam($param);
1601
			if (!empty($part['name']) && !(isset($data[$part['name']]))) {
1602
				$data[$part['name']] = $part;
1603
			}
1604
		}
1605
		return $data;
1606
	}
1607
1608
	/**
1609
	 * Parsing user variable.
1610
	 *
1611
	 * @param string $params
1612
	 *
1613
	 * @return string
1614
	 */
1615
	protected function userVariable($params)
1616
	{
1617
		$instance = null;
1618
		$className = '\\App\\TextParser\\' . ucfirst(__FUNCTION__);
1619
		if (!class_exists($className)) {
1620
			Log::error("Not found custom class: $className");
1621
		} else {
1622
			$instance = new $className($this, $params);
1623
		}
1624
		return $instance && $instance->isActive() ? $instance->process() : '';
1625
	}
1626
1627
	/**
1628
	 * Parsing inventory.
1629
	 *
1630
	 * @param string $params
1631
	 *
1632
	 * @return string
1633
	 */
1634
	protected function inventory($params)
1635
	{
1636
		if (!$this->recordModel->getModule()->isInventory()) {
1637
			return '';
1638
		}
1639
		$config = $this->parseParams($params);
1640
		if ('table' === $config['type']) {
1641
			return $this->getInventoryTable($config);
1642
		}
1643
		return '';
1644
	}
1645
1646
	/**
1647
	 * Get an instance of barcode text parser.
1648
	 *
1649
	 * @param string $params
1650
	 *
1651
	 * @return string
1652
	 */
1653
	protected function barcode($params): string
1654
	{
1655
		$params = $this->parseParams($params);
1656
		if (isset($params['value'])) {
1657
			$valueForParse = $params['value'];
1658
		}
1659
		if (isset($params['fieldName'])) {
1660
			$valueForParse = $this->recordModel->get($params['fieldName']);
1661
		}
1662
		if ($valueForParse) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $valueForParse does not seem to be defined for all execution paths leading up to this point.
Loading history...
1663
			$className = '\Milon\Barcode\\' . $params['class'];
1664
			if (!class_exists($className)) {
1665
				throw new \App\Exceptions\AppException('ERR_CLASS_NOT_FOUND||' . $className);
1666
			}
1667
			$qrCodeGenerator = new $className();
1668
			$qrCodeGenerator->setStorPath(__DIR__ . Config::main('tmp_dir'));
1669
			$barcodeHeight = $this->params['height'] ?? 2;
1670
			$barcodeWidth = $this->params['width'] ?? 30;
1671
			$barcodeType = $this->params['type'] ?? 'EAN13';
1672
			$showText = $this->params['showText'] ?? true;
1673
			$png = $qrCodeGenerator->getBarcodePNG($valueForParse, $barcodeType, $barcodeHeight, $barcodeWidth, [0, 0, 0], $showText);
1674
			return '<img src="data:image/png;base64,' . $png . '"/>';
1675
		}
1676
		return '';
1677
	}
1678
1679
	/**
1680
	 * Get inventory param.
1681
	 *
1682
	 * @param string $params
1683
	 *
1684
	 * @return array
1685
	 */
1686
	protected function parseParams(string $params): array
1687
	{
1688
		preg_match('/type=(\w+)/', $params, $matches);
1689
		$config = [
1690
			'type' => ($matches[1] ?? false),
1691
		];
1692
		$params = ltrim($params, $matches[0] . ' ');
1693
		foreach (explode(' , ', $params) as $value) {
1694
			parse_str($value, $row);
1695
			$config += $row;
1696
		}
1697
		if (isset($config['columns'])) {
1698
			$config['columns'] = explode(',', $config['columns']);
1699
		}
1700
		return $config;
1701
	}
1702
1703
	/**
1704
	 * Parsing inventory table.
1705
	 *
1706
	 * @param array $config
1707
	 *
1708
	 * @return string
1709
	 */
1710
	public function getInventoryTable(array $config): string
1711
	{
1712
		$rawText = empty($config['href']) || 'yes' !== $config['href'];
1713
		$inventory = \Vtiger_Inventory_Model::getInstance($this->moduleName);
1714
		$fields = $inventory->getFieldsByBlocks();
1715
		$inventoryRows = $this->recordModel->getInventoryData();
1716
		$baseCurrency = \Vtiger_Util_Helper::getBaseCurrency();
1717
		$firstRow = current($inventoryRows);
1718
		if ($inventory->isField('currency')) {
1719
			if (!empty($firstRow) && null !== $firstRow['currency']) {
1720
				$currency = $firstRow['currency'];
1721
			} else {
1722
				$currency = $baseCurrency['id'];
1723
			}
1724
			$currencyData = \App\Fields\Currency::getById($currency);
1725
			$currencySymbol = $currencyData['currency_symbol'];
1726
		}
1727
		$html = '';
1728
		if (!empty($fields[1])) {
1729
			$fieldsTextAlignRight = ['Unit', 'TotalPrice', 'Tax', 'MarginP', 'Margin', 'Purchase', 'Discount', 'NetPrice', 'GrossPrice', 'UnitPrice', 'Quantity', 'TaxPercent'];
1730
			$fieldsWithCurrency = ['TotalPrice', 'Purchase', 'NetPrice', 'GrossPrice', 'UnitPrice', 'Discount', 'Margin', 'Tax'];
1731
			$html .= '<table class="inventory-table" style="border-collapse:collapse;width:100%"><thead><tr>';
1732
			$columns = [];
1733
			$customFieldClassSeq = 0;
1734
			$labels = isset($config['labels']) ? explode(',', $config['labels']) : [];
1735
			$width = isset($config['width']) ? preg_replace('/[^[:alnum:]]/', '', explode(',', $config['width'])) : [];
1736
			foreach ($config['columns'] as $key => $name) {
1737
				if (false !== strpos($name, '||')) {
1738
					[$title,$value] = explode('||', $name, 2);
1739
					if ('(' === $title[0] && ')' === substr($title, -1)) {
1740
						$title = $this->parseVariable("\${$title}\$");
1741
					}
1742
					++$customFieldClassSeq;
1743
					$html .= '<th class="col-type-customField' . $customFieldClassSeq . '" style="border:1px solid #ddd">' . $title . '</th>';
1744
					$columns[$title] = $value;
1745
					continue;
1746
				}
1747
				if ('seq' === $name) {
1748
					$html .= '<th class="col-type-ItemNumber" style="border:1px solid #ddd">' . Language::translate('LBL_ITEM_NUMBER', $this->moduleName) . '</th>';
1749
					$columns[$name] = false;
1750
					continue;
1751
				}
1752
				if (empty($fields[1][$name]) && empty($fields[2][$name])) {
1753
					continue;
1754
				}
1755
				$field = $fields[1][$name] ?? $fields[2][$name];
1756
				if (!$field->isVisible()) {
1757
					continue;
1758
				}
1759
				$html .= '<th class="col-type-' . $field->getType() . '" style="border:1px solid #ddd ' . (empty($width[$key]) ? '' : ";width: {$width[$key]}") . '">' . (empty($labels[$key]) ? Language::translate($field->get('label'), $this->moduleName) : Purifier::encodeHtml($labels[$key])) . '</th>';
1760
				$columns[$field->getColumnName()] = $field;
1761
			}
1762
			$html .= '</tr></thead><tbody>';
1763
			$counter = 0;
1764
			foreach ($inventoryRows as $inventoryRow) {
1765
				++$counter;
1766
				$html .= '<tr class="row-' . $counter . '">';
1767
				$customFieldClassSeq = 0;
1768
				foreach ($columns as $name => $field) {
1769
					if ('seq' === $name) {
1770
						$html .= '<td class="col-type-ItemNumber" style="border:1px solid #ddd;font-weight:bold;">' . $counter . '</td>';
1771
					} elseif (!\is_object($field)) {
1772
						if ('(' === $field[0] && ')' === substr($field, -1)) {
1773
							$field = $this->parseVariable("\${$field}\$", $inventoryRow['name'] ?? 0);
1774
						}
1775
						++$customFieldClassSeq;
1776
						$html .= '<td class="col-type-customField' . $customFieldClassSeq . '" style="border:1px solid #ddd;font-weight:bold;">' . $field . '</td>';
1777
					} elseif ('ItemNumber' === $field->getType()) {
1778
						$html .= '<td class="col-type-ItemNumber" style="border:1px solid #ddd;font-weight:bold;">' . $counter . '</td>';
1779
					} elseif ('ean' === $name) {
1780
						$itemValue = $inventoryRow[$name];
1781
						$html .= '<td class="col-type-barcode" style="border:1px solid #ddd;padding:0px 4px;' . (\in_array($field->getType(), $fieldsTextAlignRight) ? 'text-align:right;' : '') . '"><div data-barcode="EAN13" data-code="' . $itemValue . '" data-size="1" data-height="16"></div></td>';
1782
					} else {
1783
						$itemValue = $inventoryRow[$name];
1784
						$html .= '<td class="col-type-' . $field->getType() . '" style="border:1px solid #ddd;padding:0px 4px;' . (\in_array($field->getType(), $fieldsTextAlignRight) ? 'text-align:right;' : '') . '">';
1785
						if ('Name' === $field->getType()) {
1786
							$html .= '<strong>' . $field->getDisplayValue($itemValue, $inventoryRow, $rawText) . '</strong>';
1787
							foreach ($inventory->getFieldsByType('Comment') as $commentField) {
1788
								if ($commentField->isVisible() && ($value = $inventoryRow[$commentField->getColumnName()])) {
1789
									$comment = $commentField->getDisplayValue($value, $inventoryRow, $rawText);
1790
									if ($comment) {
1791
										$html .= '<br>' . $comment;
1792
									}
1793
								}
1794
							}
1795
						} elseif (\in_array($field->getType(), $fieldsWithCurrency, true)) {
1796
							$html .= \CurrencyField::appendCurrencySymbol($field->getDisplayValue($itemValue, $inventoryRow, $rawText), $currencySymbol);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $currencySymbol does not seem to be defined for all execution paths leading up to this point.
Loading history...
1797
						} else {
1798
							$html .= $field->getDisplayValue($itemValue, $inventoryRow, $rawText);
1799
						}
1800
						$html .= '</td>';
1801
					}
1802
				}
1803
				$html .= '</tr>';
1804
			}
1805
1806
			$html .= '</tbody><tfoot><tr>';
1807
			foreach ($columns as $name => $field) {
1808
				$tb = $style = '';
1809
				if (\is_object($field) && $field->isSummary()) {
1810
					$style = 'border:1px solid #ddd;';
1811
					$sum = 0;
1812
					foreach ($inventoryRows as $inventoryRow) {
1813
						$sum += $inventoryRow[$name];
1814
					}
1815
					$tb = \CurrencyField::appendCurrencySymbol(\CurrencyField::convertToUserFormat($sum, null, true), $currencySymbol);
0 ignored issues
show
Bug introduced by
CurrencyField::convertTo...ormat($sum, null, true) of type string is incompatible with the type double|integer expected by parameter $currencyValue of CurrencyField::appendCurrencySymbol(). ( Ignorable by Annotation )

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

1815
					$tb = \CurrencyField::appendCurrencySymbol(/** @scrutinizer ignore-type */ \CurrencyField::convertToUserFormat($sum, null, true), $currencySymbol);
Loading history...
Deprecated Code introduced by
The function CurrencyField::convertToUserFormat() has been deprecated: Recommend using function \App\Fields\Currency::formatToDisplay ( Ignorable by Annotation )

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

1815
					$tb = \CurrencyField::appendCurrencySymbol(/** @scrutinizer ignore-deprecated */ \CurrencyField::convertToUserFormat($sum, null, true), $currencySymbol);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1816
				}
1817
				$html .= '<th class="col-type-' . (\is_object($field) ? $field->getType() : $name) . '" style="padding:0px 4px;text-align:right;' . $style . '">' . $tb . '</th>';
1818
			}
1819
			$html .= '</tr></tfoot></table>';
1820
		}
1821
		return $html;
1822
	}
1823
1824
	/**
1825
	 * Parse variable.
1826
	 *
1827
	 * @param string $variable
1828
	 * @param int    $id
1829
	 *
1830
	 * @return string
1831
	 */
1832
	protected function parseVariable(string $variable, int $id = 0): string
1833
	{
1834
		if ($id && Record::isExists($id)) {
1835
			$recordModel = \Vtiger_Record_Model::getInstanceById($id);
1836
			if (!$recordModel->isViewable()) {
1837
				return '';
1838
			}
1839
			$instance = static::getInstanceByModel($recordModel);
1840
		} else {
1841
			$instance = static::getInstance();
1842
		}
1843
		foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
1844
			if (isset($this->{$key})) {
1845
				$instance->{$key} = $this->{$key};
1846
			}
1847
		}
1848
		$instance->setContent($variable)->parse();
1849
		return $instance->getContent();
1850
	}
1851
1852
	/**
1853
	 * Parse custom params.
1854
	 *
1855
	 * @param string $param
1856
	 *
1857
	 * @return array
1858
	 */
1859
	public static function parseFieldParam(string $param): array
1860
	{
1861
		$part = [];
1862
		if ($param) {
1863
			foreach (explode('|', $param) as $type) {
1864
				[$name, $value] = array_pad(explode('=', $type, 2), 2, '');
1865
				$part[$name] = $value;
1866
			}
1867
		}
1868
		return $part;
1869
	}
1870
}
1871