Passed
Push — developer ( e06f3b...351755 )
by Mariusz
21:13
created

TextParser::tokenLink()   B

Complexity

Conditions 7
Paths 32

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 18
c 1
b 0
f 0
dl 0
loc 25
ccs 0
cts 0
cp 0
rs 8.8333
cc 7
nc 32
nop 1
crap 56
1
<?php
2
/**
3
 * Text parser file.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 5.0 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author    Mariusz Krzaczkowski <[email protected]>
10
 * @author    Radosław Skrzypczak <[email protected]>
11
 */
12
13
namespace App;
14
15
/**
16
 * Text parser class.
17
 */
18
class TextParser
19
{
20
	/**
21
	 * Examples of supported variables.
22
	 *
23
	 * @var array
24
	 */
25
	public const VARIABLE_EXAMPLES = [
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
		'LBL_TOKEN_LINK' => '$(tokenLink : 85|oneTime=1|buttonName=Test|expirationDate=+5 days|messages=Thank you for reporting)$',
47
	];
48
	/**
49
	 * Default date list.
50
	 *
51
	 * @var string[]
52
	 */
53
	public const VARIABLE_DATES = [
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
		'LBL_DATE_NEXT_WORKING_DAY' => '$(date : nextworkingday)$',
63
	];
64
	/**
65
	 * Variables for entity modules.
66
	 *
67
	 * @var array
68
	 */
69
	protected const VARIABLE_GENERAL = [
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
	 * Variables for entity modules.
80
	 *
81
	 * @var array
82
	 */
83
	protected const VARIABLE_ENTITY = [
84
		'CrmDetailViewURL' => 'LBL_CRM_DETAIL_VIEW_URL',
85
		'PortalDetailViewURL' => 'LBL_PORTAL_DETAIL_VIEW_URL',
86
		'RecordId' => 'LBL_RECORD_ID',
87
		'RecordLabel' => 'LBL_RECORD_LABEL',
88
		'ChangesListChanges' => 'LBL_LIST_OF_CHANGES_IN_RECORD',
89
		'ChangesListValues' => 'LBL_LIST_OF_NEW_VALUES_IN_RECORD',
90
		'Comments' => 'LBL_RECORD_COMMENT',
91
		'SummaryFields' => 'LBL_SUMMARY_FIELDS',
92
	];
93
	/**
94
	 * List of available functions.
95
	 *
96
	 * @var string[]
97
	 */
98
	protected const BASE_FUNCTIONS = [
99
		'general', 'translate', 'record', 'relatedRecord', 'relatedRecordLevel',
100
		'sourceRecord', 'organization', 'employee', 'params', 'custom', 'relatedRecordsList',
101
		'recordsList', 'date', 'inventory', 'userVariable', 'barcode', 'tokenLink'
102
	];
103
	/**
104
	 * List of source modules.
105
	 *
106
	 * @var array
107
	 */
108
	public const SOURCE_MODULES = [
109
		'Campaigns' => ['Leads', 'Accounts', 'Contacts', 'Vendors', 'Partners', 'Competition'],
110
	];
111
	/** @var string[] Uitypes with large data */
112
	protected const LARGE_DATA_UITYPES = ['multiImage', 'image'];
113
	/**
114
	 * Record variables.
115
	 *
116
	 * @var array
117
	 */
118
	protected static $recordVariable = [];
119
	/**
120
	 * Related variables.
121
	 *
122
	 * @var array
123
	 */
124
	protected static $relatedVariable = [];
125
	/**
126
	 * Next level related variables.
127
	 *
128
	 * @var array
129
	 */
130
	protected static $relatedVariableLevel = [];
131
132
	/**
133
	 * Record id.
134
	 *
135
	 * @var int
136
	 */
137
	public $record;
138
139
	/**
140
	 * Module name.
141
	 *
142
	 * @var string
143
	 */
144
	public $moduleName;
145
146
	/**
147
	 * Record model.
148
	 *
149
	 * @var \Vtiger_Record_Model
150
	 */
151
	public $recordModel;
152
153
	/**
154
	 * Parser type.
155
	 *
156
	 * @var string|null
157
	 */
158
	public $type;
159
160
	/**
161
	 * Source record model.
162
	 *
163
	 * @var \Vtiger_Record_Model
164
	 */
165
	protected $sourceRecordModel;
166
167
	/**
168
	 * Content.
169
	 *
170
	 * @var string
171
	 */
172
	protected $content = '';
173
174
	/**
175
	 * Rwa content.
176
	 *
177
	 * @var string
178
	 */
179
	protected $rawContent;
180
181
	/**
182
	 * without translations.
183
	 *
184
	 * @var bool
185
	 */
186
	protected $withoutTranslations = false;
187
188
	/**
189
	 * Language content.
190
	 *
191
	 * @var string
192
	 */
193
	protected $language;
194
195
	/**
196
	 * Additional params.
197
	 *
198
	 * @var array
199
	 */
200
	protected $params;
201
202
	/**
203
	 * Separator to display data when there are several values.
204 4
	 *
205
	 * @var string
206 4
	 */
207 4
	public $relatedRecordSeparator = ',';
208 4
209 4
	/**
210 4
	 * Is the parsing text content html?
211 4
	 *
212
	 * @var bool
213
	 */
214
	public $isHtml = true;
215
216
	/**
217
	 * Use extended parsing.
218
	 *
219
	 * @var bool
220
	 */
221 7
	public $useExtension = false;
222
223 7
	/**
224 7
	 * Variable parser regex.
225 7
	 *
226 7
	 * @var string
227 7
	 */
228 7
	public const VARIABLE_REGEX = '/\$\((\w+) : ([,"\+\#\%\.\:\;\=\-\[\]\&\w\s\|\)\(\:]+)\)\$/u';
229
230
	/** @var bool Permissions condition */
231
	protected $permissions = true;
232
233
	/**
234
	 * Get instance 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 instance 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 instance.
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
		$pattern = '/\$\(\w+( : |%20%3A%20|%20:%20)+([,"\+\#\%\.\:\;\=\-\[\]\&\w\s\|\)\(\:]+)\)\$/u';
401 2
		$replacedContent = preg_replace_callback($pattern, fn ($matches) => str_replace(['%20%3A%20', '%20:%20', '%7C'], [' : ', ' : ', '|'], $matches[0]), $content);
402
		$this->rawContent = $this->content = $replacedContent;
403
		return $this;
404
	}
405
406
	/**
407
	 * Get content.
408
	 *
409
	 * @param mixed $trim
410
	 */
411 1
	public function getContent($trim = false)
412
	{
413 1
		return $trim ? trim($this->content) : $this->content;
414 1
	}
415
416
	/**
417
	 * Function checks if its TextParser type.
418
	 *
419
	 * @param string $text
420
	 *
421
	 * @return int
422
	 */
423
	public static function isVaribleToParse($text)
424 1
	{
425
		return (int) preg_match(static::VARIABLE_REGEX, $text);
426 1
	}
427 1
428
	/**
429 1
	 * Set permissions condition.
430 1
	 *
431
	 * @param bool $permitted
432 1
	 *
433 1
	 * @return $this
434 1
	 */
435
	public function setGlobalPermissions(bool $permitted)
436
	{
437
		$this->permissions = $permitted;
438
		return $this;
439
	}
440
441
	/**
442
	 * All text parse function.
443
	 *
444
	 * @return $this
445
	 */
446
	public function parse()
447
	{
448
		if (empty($this->content)) {
449
			return $this;
450
		}
451
		if (isset($this->language)) {
452
			Language::setTemporaryLanguage($this->language);
453
		}
454
		$this->content = $this->parseData($this->content);
455
		Language::clearTemporaryLanguage();
456
		return $this;
457
	}
458
459
	/**
460
	 * Text parse function.
461
	 *
462
	 * @param string $content
463
	 *
464
	 * @return string
465
	 */
466
	public function parseData(string $content)
467
	{
468
		if ($this->useExtension) {
469
			$content = preg_replace_callback('/<!--[\s]+({% [\s\S]+? %})[\s]+-->/u', fn ($matches) => $matches[1] ?? '', $content);
470
			$twig = new \Twig\Environment(new \Twig\Loader\ArrayLoader(['index' => $content]));
471
			$sandbox = new \Twig\Extension\SandboxExtension(\App\Extension\Twig\SecurityPolicy::getPolicy(), true);
472
			$twig->addExtension($sandbox);
473
			$twig->addFunction(new \Twig\TwigFunction('YFParser', function ($text) {
474 2
				$value = '';
475
				preg_match(static::VARIABLE_REGEX, $text, $matches);
476 2
				if ($matches) {
477 2
					[, $function, $params] = array_pad($matches, 3, '');
478 2
					$value = \in_array($function, static::BASE_FUNCTIONS) ? $this->{$function}($params) : '';
479
				}
480 2
				return $value;
481
			}));
482
			$content = $twig->render('index');
483 2
		}
484 2
		return preg_replace_callback(static::VARIABLE_REGEX, function ($matches) {
485 2
			[, $function, $params] = array_pad($matches, 3, '');
486
			return \in_array($function, static::BASE_FUNCTIONS) ? $this->{$function}($params) : '';
487 2
		}, $content);
488 2
	}
489 1
490 1
	/**
491 1
	 * Text parse function.
492 1
	 *
493 1
	 * @return $this
494
	 */
495
	public function parseTranslations()
496 1
	{
497
		if (isset($this->language)) {
498 2
			Language::setTemporaryLanguage($this->language);
499 2
		}
500
		$this->content = preg_replace_callback('/\$\(translate : ([,"\+\%\.\=\-\[\]\&\w\s\|]+)\)\$/u', function ($matches) {
501
			[, $params] = array_pad($matches, 2, '');
502
			return $this->translate($params);
503
		}, $this->content);
504
		Language::clearTemporaryLanguage();
505
		return $this;
506
	}
507
508
	/**
509 1
	 * Function parse date.
510
	 *
511 1
	 * @param string $param
512 1
	 *
513 1
	 * @return string
514 1
	 */
515 1
	public function date($param)
516 1
	{
517
		if (isset(\App\Condition::DATE_OPERATORS[$param])) {
518 1
			$date = implode(' - ', array_unique(\DateTimeRange::getDateRangeByType($param)));
519 1
		} else {
520 1
			$date = date('Y-m-d', strtotime($param));
521 1
		}
522 1
		return $date;
523 1
	}
524 1
525 1
	/**
526 1
	 * Parsing translations.
527
	 *
528 1
	 * @param string $params
529
	 *
530
	 * @return string
531
	 */
532
	protected function translate($params)
533
	{
534
		if ($this->withoutTranslations) {
535
			return "$(translate : $params)$";
536
		}
537
		if (false === strpos($params, '|')) {
538
			return Language::translate($params);
539
		}
540 5
		$splitParams = explode('|', $params);
541
		$module = array_shift($splitParams);
542 5
		$key = array_shift($splitParams);
543 1
		return Language::translate($key, $module, $splitParams[0] ?? $this->language);
544
	}
545 5
546 5
	/**
547 5
	 * Parsing organization detail.
548 5
	 *
549 1
	 * @param string $params
550
	 *
551 5
	 * @return string
552
	 */
553 1
	protected function organization(string $params): string
554 1
	{
555 1
		if (!$params) {
556 1
			return '';
557 1
		}
558 1
		$returnVal = '';
559
		if (false === strpos($params, '|')) {
560 1
			$id = User::getCurrentUserModel()->get('multiCompanyId');
561
			$fieldName = $params;
562 1
			$params = false;
563
		} else {
564
			[$id, $fieldName, $params] = array_pad(explode('|', $params, 3), 3, false);
565 1
		}
566 1
		if (Record::isExists($id, 'MultiCompany')) {
567 1
			$companyRecordModel = \Vtiger_Record_Model::getInstanceById($id, 'MultiCompany');
568 1
			if ($companyRecordModel->has($fieldName)) {
569 1
				$value = $companyRecordModel->get($fieldName);
570 1
				$fieldModel = $companyRecordModel->getModule()->getFieldByName($fieldName);
571 1
				if ('' === $value || !$fieldModel || !$this->useValue($fieldModel, 'MultiCompany')) {
572 1
					return '';
573 1
				}
574 1
				if ($this->withoutTranslations) {
575 1
					$returnVal = $this->getDisplayValueByType($value, $companyRecordModel, $fieldModel, $params);
576 1
				} else {
577
					$returnVal = $fieldModel->getUITypeModel()->getTextParserDisplayValue($value, $companyRecordModel, $params);
578
				}
579 1
			}
580 1
		}
581 1
		return $returnVal;
582 1
	}
583
584 1
	/**
585 1
	 * Parsing employee detail.
586
	 *
587
	 * @param string $fieldName
588 1
	 *
589 1
	 * @return mixed
590 1
	 */
591 1
	protected function employee($fieldName)
592 1
	{
593 1
		$userId = User::getCurrentUserId();
594 1
		if (Cache::has('TextParserEmployeeDetail', $userId . $fieldName)) {
595
			return Cache::get('TextParserEmployeeDetail', $userId . $fieldName);
596 1
		}
597 1
		if (Cache::has('TextParserEmployeeDetailRows', $userId)) {
598 1
			$employee = Cache::get('TextParserEmployeeDetailRows', $userId);
599
		} else {
600
			$employee = (new Db\Query())->select(['crmid'])->from('vtiger_crmentity')->where(['deleted' => 0, 'setype' => 'OSSEmployees', 'smownerid' => $userId])
601 1
				->scalar();
602 1
			Cache::save('TextParserEmployeeDetailRows', $userId, $employee, Cache::LONG);
603 1
		}
604
		$value = '';
605 1
		if ($employee && Record::isExists($employee, 'OSSEmployees')) {
606
			$relatedRecordModel = \Vtiger_Record_Model::getInstanceById($employee, 'OSSEmployees');
607
			$instance = static::getInstanceByModel($relatedRecordModel);
608 1
			foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
609
				if (isset($this->{$key})) {
610 1
					$instance->{$key} = $this->{$key};
611 1
				}
612
			}
613 1
			$value = $instance->record($fieldName);
614 1
		}
615
		Cache::save('TextParserEmployeeDetail', $userId . $fieldName, $value, Cache::LONG);
616 1
		return $value;
617
	}
618 1
619
	/**
620
	 * Parsing general data.
621
	 *
622
	 * @param string $key
623
	 *
624
	 * @return mixed
625
	 */
626
	protected function general($key)
627
	{
628 1
		switch ($key) {
629
			case 'CurrentDate':
630 1
				return (new \DateTimeField(null))->getDisplayDate();
631
			case 'CurrentTime':
632 1
				return \Vtiger_Util_Helper::convertTimeIntoUsersDisplayFormat(date('H:i:s'));
633 1
			case 'CurrentDateTime':
634 1
				return Fields\DateTime::formatToDisplay('now');
635
			case 'SiteUrl':
636
				return Config::main('site_URL');
637
			case 'PortalUrl':
638 1
				return Config::main('PORTAL_URL');
639 1
			case 'BaseTimeZone':
640
				return Fields\DateTime::getTimeZone();
641
			case 'UserTimeZone':
642 1
				$userModel = User::getCurrentUserModel();
643
				return ($userModel && $userModel->getDetail('time_zone')) ? $userModel->getDetail('time_zone') : Config::main('default_timezone');
644
			default:
645 1
				return $key;
646 1
		}
647 1
	}
648 1
649 1
	/**
650 1
	 * Parsing record data.
651 1
	 *
652 1
	 * @param string $params
653 1
	 * @param mixed  $isPermitted
654
	 *
655
	 * @return string
656
	 */
657 1
	protected function record($params, $isPermitted = true)
658
	{
659
		if (!isset($this->recordModel) || ($isPermitted && !Privilege::isPermitted($this->moduleName, 'DetailView', $this->record))) {
660
			return '';
661
		}
662
		[$key, $params] = array_pad(explode('|', $params, 2), 2, false);
663
		if ($this->recordModel->has($key)) {
664
			$fieldModel = $this->recordModel->getModule()->getFieldByName($key);
665
			if (!$fieldModel || !$this->useValue($fieldModel, $this->moduleName)) {
666
				return '';
667
			}
668
			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...
669
		}
670
		switch ($key) {
671
			case 'CrmDetailViewURL':
672
				return Config::main('site_URL') . 'index.php?module=' . $this->moduleName . '&view=Detail&record=' . $this->record;
673
			case 'PortalDetailViewURL':
674
				$recorIdName = 'id';
675
				if ('HelpDesk' === $this->moduleName) {
676
					$recorIdName = 'ticketid';
677 1
				} elseif ('Faq' === $this->moduleName) {
678 1
					$recorIdName = 'faqid';
679
				} elseif ('Products' === $this->moduleName) {
680
					$recorIdName = 'productid';
681 1
				}
682 1
				return Config::main('PORTAL_URL') . '/index.php?module=' . $this->moduleName . '&action=index&' . $recorIdName . '=' . $this->record;
683 1
			case 'ModuleName':
684 1
				return $this->moduleName;
685 1
			case 'RecordId':
686
				return $this->record;
687
			case 'RecordLabel':
688 1
				return $this->recordModel->getName();
689
			case 'ChangesListChanges':
690
				$value = '';
691
				foreach ($this->recordModel->getPreviousValue() as $fieldName => $oldValue) {
692
					$fieldModel = $this->recordModel->getModule()->getFieldByName($fieldName);
693
					if (!$fieldModel) {
694
						continue;
695
					}
696
					$oldValue = $this->getDisplayValueByField($fieldModel, $oldValue);
697
					$currentValue = $this->getDisplayValueByField($fieldModel);
698 1
					if ($this->withoutTranslations) {
699
						$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

699
						$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...
700 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

700
						$value .= "\$(translate : {$this->moduleName}|{$label})\$ \$(translate : LBL_FROM)\$ $oldValue \$(translate : LBL_TO)\$ " . /** @scrutinizer ignore-type */ $currentValue . ($this->isHtml ? '<br>' : PHP_EOL);
Loading history...
701 1
					} else {
702
						$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

702
						$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...
703 1
						$value .= Language::translate('LBL_FROM') . " $oldValue " . Language::translate('LBL_TO') . " $currentValue" . ($this->isHtml ? '<br>' : PHP_EOL);
704 1
					}
705 1
				}
706 1
				return $value;
707
			case 'ChangesListValues':
708
				$value = '';
709 1
				$changes = $this->recordModel->getPreviousValue();
710
				if (empty($changes)) {
711
					$changes = array_filter($this->recordModel->getData());
712
					unset($changes['createdtime'], $changes['modifiedtime'], $changes['id'], $changes['newRecord'], $changes['modifiedby']);
713
				}
714
				foreach ($changes as $fieldName => $oldValue) {
715
					$fieldModel = $this->recordModel->getModule()->getFieldByName($fieldName);
716
					if (!$fieldModel) {
717
						continue;
718
					}
719 1
					$currentValue = \in_array($fieldModel->getFieldDataType(), self::LARGE_DATA_UITYPES) ? '' : $this->getDisplayValueByField($fieldModel);
720
					if ($this->withoutTranslations) {
721 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

721
						$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...
722 1
						$value .= "\$(translate : {$this->moduleName}|{$label})\$: $currentValue" . ($this->isHtml ? '<br>' : PHP_EOL);
723 1
					} else {
724 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

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

785
				if ('Users' === Fields\Owner::getType(/** @scrutinizer ignore-type */ $relatedValueId)) {
Loading history...
786
					$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

786
					$userRecordModel = \Vtiger_Record_Model::getInstanceById(/** @scrutinizer ignore-type */ $relatedValueId, $relatedModule);
Loading history...
787 1
					if ('Active' === $userRecordModel->get('status')) {
788
						$instance = static::getInstanceByModel($userRecordModel);
789 1
						foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
790 1
							if (isset($this->{$key})) {
791 1
								$instance->{$key} = $this->{$key};
792 1
							}
793 1
						}
794 1
						$return[] = $instance->record($value, false);
795 1
					}
796
					continue;
797 1
				}
798 1
				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

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

965
					$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...
966
					$headers .= "<th class=\"col-type-{$fieldModel->getFieldType()}\">$(translate : {$label}|$relatedModuleName)$</th>";
967
				} else {
968
					$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

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

1051
				$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...
1052 1
				$headers .= "<th class=\"col-type-{$fieldModel->getFieldType()}\" style=\"{$headerStyle}\">$(translate : {$label}|$moduleName)$</th>";
1053 1
			} else {
1054 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

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

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

1309
					$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...
1310 1
					$variables[Language::translate($blockModel->get('label'), $this->moduleName)][] = [
1311
						'var_value' => "$(record : {$fieldModel->getName()})$",
1312 2
						'var_label' => "$(translate : {$this->moduleName}|{$label})$",
1313 2
						'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

1313
						'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...
1314 2
					];
1315 2
				}
1316 2
			}
1317 2
		}
1318 2
		static::$recordVariable[$cacheKey] = $variables;
1319 2
		return $variables;
1320 2
	}
1321 2
1322 2
	/**
1323 2
	 * Get source variables.
1324 2
	 *
1325 2
	 * @return array
1326
	 */
1327
	public function getSourceVariable()
1328
	{
1329
		if (empty(self::SOURCE_MODULES[$this->moduleName])) {
1330 2
			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...
1331 1
		}
1332
		$variables = [];
1333 2
		foreach (static::VARIABLE_ENTITY as $key => $name) {
1334
			$variables['LBL_ENTITY_VARIABLES'][] = [
1335 2
				'var_value' => "$(sourceRecord : $key)$",
1336 2
				'var_label' => "$(translate : Other.TextParser|$name)$",
1337 2
				'label' => Language::translate($name, 'Other.TextParser'),
1338 2
			];
1339 2
		}
1340 2
		foreach (self::SOURCE_MODULES[$this->moduleName] as $moduleName) {
1341 2
			$moduleModel = \Vtiger_Module_Model::getInstance($moduleName);
1342
			foreach ($moduleModel->getBlocks() as $blockModel) {
1343 2
				foreach ($blockModel->getFields() as $fieldModel) {
1344 2
					if ($fieldModel->isViewable()) {
1345
						$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

1345
						$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...
1346 2
						$variables[$moduleName][$blockModel->get('label')][] = [
1347 1
							'var_value' => "$(sourceRecord : {$fieldModel->getName()})$",
1348 1
							'var_label' => "$(translate : $moduleName|{$label})$",
1349 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

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

1411
							$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...
1412
							$variables[$parentFieldName][$labelGroup][] = [
1413
								'var_value' => "$(relatedRecord : $parentFieldName|$fieldName|$relatedModule)$",
1414
								'var_label' => "$(translate : $relatedModule|{$label})$",
1415
								'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

1415
								'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...
1416
							];
1417
						}
1418
					}
1419
				}
1420
			}
1421
		}
1422
		static::$relatedVariable[$cacheKey] = $variables;
1423
		return $variables;
1424
	}
1425
1426
	/**
1427
	 * Get related variables.
1428
	 *
1429
	 * @param bool|string $fieldType
1430
	 *
1431
	 * @return array
1432
	 */
1433
	public function getRelatedLevelVariable($fieldType = false)
1434
	{
1435
		$cacheKey = "{$this->moduleName}|$fieldType";
1436
		if (isset(static::$relatedVariableLevel[$cacheKey])) {
1437
			return static::$relatedVariableLevel[$cacheKey];
1438
		}
1439
		$moduleModel = \Vtiger_Module_Model::getInstance($this->moduleName);
1440
		$variables = [];
1441
		foreach ($moduleModel->getFieldsByType(array_merge(\Vtiger_Field_Model::$referenceTypes, ['userCreator', 'owner'])) as $parentFieldName => $fieldModel) {
1442
			if ('owner' === $fieldModel->getFieldDataType()) {
1443
				$relatedModules = ['Users'];
1444
			} else {
1445
				$relatedModules = $fieldModel->getReferenceList();
1446
			}
1447
			$parentFieldNameLabel = Language::translate($fieldModel->getFieldLabel(), $this->moduleName);
1448
			foreach ($relatedModules as $relatedModule) {
1449
				$relatedModuleLang = Language::translate($relatedModule, $relatedModule);
1450
				foreach (\Vtiger_Module_Model::getInstance($relatedModule)->getFieldsByType(array_merge(\Vtiger_Field_Model::$referenceTypes, ['userCreator', 'owner', 'sharedOwner'])) as $parentFieldNameNextLevel => $fieldModelNextLevel) {
1451
					if ('owner' === $fieldModelNextLevel->getFieldDataType() || 'sharedOwner' === $fieldModelNextLevel->getFieldDataType()) {
1452
						$relatedModulesNextLevel = ['Users'];
1453
					} else {
1454
						$relatedModulesNextLevel = $fieldModelNextLevel->getReferenceList();
1455
					}
1456
					$parentFieldNameLabelNextLevel = Language::translate($fieldModelNextLevel->getFieldLabel(), $relatedModule);
1457
					foreach ($relatedModulesNextLevel as $relatedModuleNextLevel) {
1458
						$relatedModuleLangNextLevel = Language::translate($relatedModuleNextLevel, $relatedModuleNextLevel);
1459
						foreach (\Vtiger_Module_Model::getInstance($relatedModuleNextLevel)->getBlocks() as $blockModel) {
1460
							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...
1461
								if ($fieldModel->isViewable() && !($fieldType && $fieldModel->getFieldDataType() !== $fieldType)) {
1462
									$labelGroup = "{$parentFieldNameLabel}($relatedModuleLang) -> {$parentFieldNameLabelNextLevel}($relatedModuleLangNextLevel) " . Language::translate($blockModel->get('label'), $relatedModuleNextLevel);
1463
									$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

1463
									$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...
1464
									$variables[$labelGroup][] = [
1465
										'var_value' => "$(relatedRecordLevel : $parentFieldName|$relatedModule|$parentFieldNameNextLevel|$fieldName|$relatedModuleNextLevel)$",
1466
										'var_label' => "$(translate : $relatedModuleNextLevel|{$label})$",
1467
										'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

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

1848
						$tb = \CurrencyField::appendCurrencySymbol(/** @scrutinizer ignore-type */ \App\Fields\Currency::formatToDisplay($sum, null, true), $currencySymbol);
Loading history...
1849
					}
1850
					$html .= '<th class="col-type-' . (\is_object($field) ? $field->getType() : $name) . '" style="padding:0px 4px;text-align:right;' . $style . '">' . $tb . '</th>';
1851
				}
1852
				$html .= '</tr></tfoot>';
1853
			}
1854
			$html .= '</table>';
1855
		}
1856
		return $html;
1857
	}
1858
1859
	/**
1860
	 * Parsing inventory.
1861
	 *
1862
	 * @param string $params
1863
	 *
1864
	 * @return string
1865
	 */
1866
	protected function tokenLink(string $params): string
1867
	{
1868
		[$workflowId, $params] = array_pad(explode('|', $params, 2), 2, '');
1869
		$paramsArray = $params ? self::parseFieldParam($params) : [];
1870
		$oneTime = (isset($paramsArray['oneTime']) && 1 != $paramsArray['oneTime']) ? false : true;
1871
		$expirationDate = '+ 30 days';
1872
		if (!empty($paramsArray['expirationDate'])) {
1873
			$expirationDate = urldecode($paramsArray['expirationDate']);
1874
		}
1875
		$expirationDateTime = date('Y-m-d H:i:s', strtotime($expirationDate));
1876
		\App\Utils\Tokens::generate(
1877
			'\App\Utils\Tokens::runWorkflow',
1878
			[
1879
				'recordId' => $this->recordModel->getId(),
1880
				'workflowId' => $workflowId,
1881
				'messages' => empty($paramsArray['messages']) ? '' : urldecode($paramsArray['messages']),
1882
			],
1883
			$expirationDateTime,
1884
			$oneTime
1885
		);
1886
		$url = \App\Utils\Tokens::generateLink();
1887
		if (!empty($paramsArray['buttonName'])) {
1888
			return '<a href="' . $url . '" target="_blank">' . $paramsArray['buttonName'] . '</a>';
1889
		}
1890
		return $url;
1891
	}
1892
1893
	/**
1894
	 * Parse variable.
1895
	 *
1896
	 * @param string $variable
1897
	 * @param int    $id
1898
	 *
1899
	 * @return string
1900
	 */
1901
	protected function parseVariable(string $variable, int $id = 0): string
1902
	{
1903
		if ($id && Record::isExists($id)) {
1904
			$record = \Vtiger_Record_Model::getInstanceById($id);
1905
			if (!$record->isViewable()) {
1906
				return '';
1907
			}
1908
			$instance = static::getInstanceByModel($record);
1909
		} else {
1910
			$instance = static::getInstance();
1911
		}
1912
		foreach (['withoutTranslations', 'language', 'emailoptout'] as $key) {
1913
			if (isset($this->{$key})) {
1914
				$instance->{$key} = $this->{$key};
1915
			}
1916
		}
1917
		$instance->setContent($variable)->parse();
1918
		return $instance->getContent();
1919
	}
1920
1921
	/**
1922
	 * Parse custom params.
1923
	 *
1924
	 * @param string $param
1925
	 *
1926
	 * @return array
1927
	 */
1928
	public static function parseFieldParam(string $param): array
1929
	{
1930
		$part = [];
1931
		if ($param) {
1932
			foreach (explode('|', $param) as $type) {
1933
				[$name, $value] = array_pad(explode('=', $type, 2), 2, '');
1934
				$part[$name] = $value;
1935
			}
1936
		}
1937
		return $part;
1938
	}
1939
}
1940