Passed
Push — developer ( a1cff0...4caea0 )
by Radosław
15:49
created

getFieldInstanceByName()   F

Complexity

Conditions 20
Paths 34

Size

Total Lines 218
Code Lines 193

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 420

Importance

Changes 0
Metric Value
eloc 193
dl 0
loc 218
ccs 0
cts 0
cp 0
rs 3.3333
c 0
b 0
f 0
cc 20
nc 34
nop 1
crap 420

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/* +**********************************************************************************
3
 * The contents of this file are subject to the vtiger CRM Public License Version 1.1
4
 * ("License"); You may not use this file except in compliance with the License
5
 * The Original Code is:  vtiger CRM Open Source
6
 * The Initial Developer of the Original Code is vtiger.
7
 * Portions created by vtiger are Copyright (C) vtiger.
8
 * All Rights Reserved.
9
 * Contributor(s): YetiForce S.A.
10
 * ********************************************************************************** */
11
12
class Settings_LayoutEditor_Module_Model extends Vtiger_Module_Model
13
{
14
	/** {@inheritdoc} */
15
	public $name = 'LayoutEditor';
16
	/** @var string Parent name */
17
	public $parent = 'Settings';
18
	/** {@inheritdoc} */
19
	public $isentitytype = false;
20
	/** @var string[] List of supported modules */
21
	public static $supportedModules = false;
22
23
	/**
24
	 * Related view types.
25
	 *
26
	 * @var string[]
27
	 */
28
	private static $relatedViewType = [
29
		'RelatedTab' => 'LBL_RELATED_TAB_TYPE',
30
		'DetailTop' => 'LBL_DETAIL_TOP_TYPE',
31
		'DetailBottom' => 'LBL_DETAIL_BOTTOM_TYPE',
32
		'SummaryTop' => 'LBL_SUMMARY_TOP_TYPE',
33
		'SummaryBottom' => 'LBL_SUMMARY_BOTTOM_TYPE',
34
	];
35
36
	/**
37
	 * Gets parent name.
38
	 *
39
	 * @return string
40
	 */
41
	public function getParentName()
42
	{
43
		return $this->parent;
44
	}
45
46
	/**
47
	 * Function that returns all the fields for the module.
48
	 *
49
	 * @param mixed $blockInstance
50
	 *
51
	 * @return Vtiger_Field_Model[] - list of field models
52
	 */
53
	public function getFields($blockInstance = false)
54
	{
55
		if (empty($this->fieldsModule)) {
56
			$fieldList = [];
57
			$blocks = $this->getBlocks();
58
			$blockId = [];
59
			foreach ($blocks as $block) {
60
				$blockId[] = $block->get('id');
61
			}
62
			if (\count($blockId) > 0) {
63
				$fieldList = Settings_LayoutEditor_Field_Model::getInstanceFromBlockIdList($blockId);
64
			}
65
			$this->fieldsModule = $fieldList;
0 ignored issues
show
Bug Best Practice introduced by
The property fieldsModule does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
66
		}
67
		return $this->fieldsModule;
68
	}
69
70
	/**
71
	 * Function returns all the blocks for the module.
72
	 *
73
	 * @return <Array of Vtiger_Block_Model> - list of block models
0 ignored issues
show
Documentation Bug introduced by
The doc comment <Array at position 0 could not be parsed: Unknown type name '<' at position 0 in <Array.
Loading history...
74
	 */
75
	public function getBlocks()
76
	{
77
		if (empty($this->blocks)) {
78
			$blocksList = [];
79
			$moduleBlocks = Settings_LayoutEditor_Block_Model::getAllForModule($this);
80
			foreach ($moduleBlocks as $block) {
81
				if (!$block->get('label')) {
82
					continue;
83
				}
84
				if ('HelpDesk' === $this->getName() && 'LBL_COMMENTS' === $block->get('label')) {
85 21
					continue;
86
				}
87
88 21
				if ('LBL_ITEM_DETAILS' != $block->get('label')) {
89
					$blocksList[$block->get('label')] = $block;
90
				}
91
			}
92
			$this->blocks = $blocksList;
93
		}
94
		return $this->blocks;
95
	}
96
97
	/**
98
	 * List of supported field types.
99
	 *
100
	 * @return string[]
101
	 */
102
	public function getAddSupportedFieldTypes()
103
	{
104
		return [
105
			'Text', 'Decimal', 'Integer',  'Currency',  'Percent', 'AdvPercentage', 'Date', 'Time', 'DateTime', 'RangeTime', 'Phone', 'Email', 'MultiEmail', 'MultiDomain', 'Picklist', 'MultiSelectCombo', 'MultipicklistTags', 'Country', 'URL', 'Checkbox', 'TextArea', 'Related1M', 'MultiReference', 'Editor', 'Tree', 'CategoryMultipicklist', 'Image', 'MultiImage',  'MultiAttachment', 'MultiReferenceValue', 'ServerAccess', 'Skype', 'Twitter', 'Token', 'Smtp', 'MapCoordinates', 'Group',
106
		];
107
	}
108
109
	/**
110
	 * Function which will give information about the field types that are supported for add.
111
	 *
112
	 * @return array
113
	 */
114
	public function getAddFieldTypeInfo()
115
	{
116
		$fieldTypesInfo = [];
117
		$addFieldSupportedTypes = $this->getAddSupportedFieldTypes();
118
		$lengthSupportedFieldTypes = ['Text', 'Decimal', 'Integer', 'Currency', 'Editor', 'AdvPercentage'];
119
		foreach ($addFieldSupportedTypes as $fieldType) {
120
			$details = [];
121
			if (\in_array($fieldType, $lengthSupportedFieldTypes)) {
122
				$details['lengthsupported'] = true;
123
			}
124
			if ('Editor' === $fieldType) {
125
				$details['noLimitForLength'] = true;
126
			}
127
			if ('Decimal' === $fieldType || 'Currency' === $fieldType || 'AdvPercentage' === $fieldType) {
128
				$details['decimalSupported'] = true;
129
				$details['maxFloatingDigits'] = 5;
130
				if ('Currency' === $fieldType) {
131
					$details['decimalReadonly'] = true;
132
				}
133
				//including mantisaa and integer part
134
				$details['maxLength'] = 64;
135
			}
136
			if ('Picklist' === $fieldType || 'MultiSelectCombo' === $fieldType || 'MultipicklistTags' === $fieldType) {
137
				$details['preDefinedValueExists'] = true;
138
				//text area value type , can give multiple values
139
				$details['preDefinedValueType'] = 'text';
140
				if ('Picklist' === $fieldType) {
141
					$details['picklistoption'] = true;
142
				}
143
			}
144
			if ('Related1M' === $fieldType) {
145
				$details['ModuleListMultiple'] = true;
146
			}
147
			$fieldTypesInfo[$fieldType] = $details;
148
		}
149 21
		return $fieldTypesInfo;
150
	}
151 21
152 21
	/**
153 21
	 * Verification of data.
154 21
	 *
155 21
	 * @param array $data
156
	 * @param bool  $throw
157
	 */
158 21
	public function validate(array $data, bool $throw = true)
159
	{
160
		$message = null;
161 21
		$code = null;
162
		$result = false;
163
		foreach ($data as $key => $value) {
164 21
			switch ($key) {
165
				case 'fieldLabel':
166
					if ($result = $this->checkFieldLabelExists($value)) {
167 21
						$message = \App\Language::translate('LBL_DUPLICATE_FIELD_EXISTS', 'Settings::LayoutEditor');
168
						$code = 513;
169
					}
170 21
					break;
171 21
				case 'fieldName':
172
					$value = strtolower($value);
173
					if ($result = $this->checkFieldNameCharacters($value)) {
174 21
						$message = \App\Language::translate('LBL_INVALIDCHARACTER', 'Settings::LayoutEditor');
175 3
						$code = 512;
176 3
					} elseif ($result = $this->checkFieldNameExists($value)) {
177
						$message = \App\Language::translate('LBL_DUPLICATE_FIELD_EXISTS', 'Settings::LayoutEditor');
178
						$code = 512;
179 3
					} elseif ($result = $this->checkFieldNameIsAnException($value)) {
180 3
						$message = \App\Language::translate('LBL_FIELD_NAME_IS_RESERVED', 'Settings::LayoutEditor');
181
						$code = 512;
182
					} elseif (\strlen($value) > 30) {
183 3
						$message = \App\Language::translate('LBL_EXCEEDED_MAXIMUM_NUMBER_CHARACTERS_FOR_FIELD_NAME', 'Settings::LayoutEditor');
184
						$code = 512;
185
					} elseif (isset($data['fieldType']) && \in_array($data['fieldType'], ['Picklist', 'MultiSelectCombo']) && ($result = $this->checkIfPicklistFieldNameReserved($value))) {
186
						$message = \App\Language::translate('LBL_FIELD_NAME_IS_RESERVED', 'Settings::LayoutEditor');
187
						$code = 512;
188 21
					}
189 21
					break;
190 21
				case 'fieldType':
191 21
					if ($result = !\in_array($value, $this->getAddSupportedFieldTypes())) {
192
						$message = \App\Language::translate('LBL_WRONG_FIELD_TYPE', 'Settings::LayoutEditor');
193
						$code = 513;
194
					}
195
					break;
196
				case 'pickListValues':
197
					foreach ($value as $val) {
198
						if (($result = preg_match('/[\<\>\"\#\,]/', $val)) || ($result = preg_match('/[\<\>\"\#\,]/', \App\Purifier::decodeHtml($val)))) {
199 21
							$message = \App\Language::translateArgs('ERR_SPECIAL_CHARACTERS_NOT_ALLOWED', 'Other.Exceptions', '<>"#,');
200 2
							$code = 512;
201 19
						} elseif ($result = \strlen($val) > 200) {
202 1
							$message = \App\Language::translate('ERR_EXCEEDED_NUMBER_CHARACTERS', 'Other.Exceptions');
203 1
							$code = 512;
204 1
						}
205 1
					}
206 1
					if (\count($value) !== \count(array_unique(array_map('strtolower', $value)))) {
207 1
						$message = \App\Language::translate('LBL_DUPLICATES_VALUES_FOUND', 'Other.Exceptions');
208 18
						$code = 512;
209
					}
210
					break;
211 21
				default:
212 21
					break;
213 21
			}
214 21
			if ($result) {
215 21
				if ($throw) {
216 21
					throw new \App\Exceptions\AppException($message, $code);
217 21
				}
218 21
				return [$key => $message];
219 21
			}
220 21
		}
221 21
		return $result;
222 21
	}
223 21
224 21
	/**
225 21
	 * Add field.
226 21
	 *
227 1
	 * @param string $fieldType
228
	 * @param int    $blockId
229 21
	 * @param array  $params
230 21
	 *
231
	 * @throws Exception
232 21
	 *
233 21
	 * @return \Settings_LayoutEditor_Field_Model
234 21
	 */
235 3
	public function addField($fieldType, $blockId, $params)
236
	{
237 21
		$label = $params['fieldLabel'];
238 1
		$name = strtolower($params['fieldName']);
239
		$pickListValues = [];
240
		if (\array_key_exists('pickListValues', $params)) {
241 1
			$pickListValues = $params['pickListValues'] = \is_string($params['pickListValues']) ? [$params['pickListValues']] : $params['pickListValues'];
242
		}
243 1
		$fieldParams = '';
244 1
		$this->validate($params);
245 1
		$moduleName = $this->getName();
246 1
		$tableName = $this->getTableName($params['fieldTypeList']);
247
		switch ($fieldType) {
248
			case 'Tree':
249 21
			case 'CategoryMultipicklist':
250 21
				$fieldParams = (int) $params['tree'];
251
				break;
252
			case 'MultiReferenceValue':
253
				$fieldParams = [
254
					'module' => $params['MRVModule'],
255
					'field' => $params['MRVField'],
256
					'filterField' => $params['MRVFilterField'] ?? null,
257
					'filterValue' => $params['MRVFilterValue'] ?? null,
258
				];
259
				\App\Db::getInstance()->createCommand()->insert('s_#__multireference', ['source_module' => $moduleName, 'dest_module' => $params['MRVModule']])->execute();
260
				break;
261 21
			case 'ServerAccess':
262
				$fieldParams = (int) $params['server'];
263 21
				break;
264 21
			case 'Token':
265 21
				(new \App\BatchMethod(['method' => '\App\Fields\Token::setTokens', 'params' => [$name, $moduleName]]))->save();
266 21
				break;
267 1
			case 'MultiReference':
268 1
				$fieldParams = [
269 1
					'module' => $params['referenceModule']
270 1
				];
271 1
				break;
272 20
			case 'MapCoordinates':
273
				$fieldParams = [
274
					'showType' => $params['isCoordinateType'] ?? 0,
275 20
					'type' => $params['type'] ?? null,
276 1
					'showMap' => $params['isCoordinateMap'] ?? 0,
277 1
					'showLocation' => $params['isCoordinateMeLokaction'] ?? 0,
278 1
				];
279 1
				break;
280 1
			case 'Group':
281
				$fieldParams = [
282 1
					'showAllGroups' => $params['showAllGroups'] ?? 0
283 1
				];
284 19
				break;
285 1
			default:
286 1
				break;
287 1
		}
288 1
		$details = $this->getTypeDetailsForAddField($fieldType, $params);
289 18
		$fieldModel = new Settings_LayoutEditor_Field_Model();
290 1
		$fieldModel->set('name', $name)
291 1
			->set('table', $tableName)
292 1
			->set('generatedtype', $params['generatedtype'] ?? 2)
293 1
			->set('helpinfo', $params['helpinfo'] ?? '')
294
			->set('uitype', $details['uitype'])
295
			->set('label', $label)
296 1
			->set('typeofdata', $details['typeofdata'])
297
			->set('quickcreate', $params['quickcreate'] ?? 1)
298 1
			->set('summaryfield', $params['summaryfield'] ?? 0)
299 1
			->set('header_field', $params['header_field'] ?? null)
300 1
			->set('fieldparams', $params['fieldparams'] ?? ($fieldParams ? \App\Json::encode($fieldParams) : ''))
301 1
			->set('columntype', $details['dbType']);
302 17
		if ('Editor' === $fieldType) {
303 1
			$fieldModel->set('maximumlength', $params['fieldLength'] ?? null);
304 1
		}
305 1
		if (isset($details['displayType']) || isset($params['displayType'])) {
306 1
			$fieldModel->set('displaytype', $params['displayType'] ?? $details['displayType']);
307 16
		}
308 1
		$blockModel = Vtiger_Block_Model::getInstance($blockId, $moduleName);
0 ignored issues
show
Bug introduced by
$moduleName of type string is incompatible with the type boolean|vtlib\ModuleBasic expected by parameter $moduleInstance of Vtiger_Block_Model::getInstance(). ( Ignorable by Annotation )

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

308
		$blockModel = Vtiger_Block_Model::getInstance($blockId, /** @scrutinizer ignore-type */ $moduleName);
Loading history...
309 1
		$blockModel->addField($fieldModel);
310 1
		if ('Phone' === $fieldType) {
311 1
			$fieldInstance = new vtlib\Field();
312 15
			$fieldInstance->name = $name . '_extra';
313 1
			$fieldInstance->table = $tableName;
314 1
			$fieldInstance->label = 'FL_PHONE_CUSTOM_INFORMATION';
315 1
			$fieldInstance->column = $name . '_extra';
316 1
			$fieldInstance->uitype = 1;
317 14
			$fieldInstance->displaytype = 3;
318 1
			$fieldInstance->maxlengthtext = 100;
319 1
			$fieldInstance->typeofdata = 'V~O';
320 1
			$fieldInstance->save($blockModel);
321 1
		}
322 13
		if ('Picklist' === $fieldType || 'MultiSelectCombo' === $fieldType || 'MultipicklistTags' === $fieldType) {
323 2
			$fieldModel->setPicklistValues($pickListValues);
324 2
		}
325 1
		if ('Related1M' === $fieldType) {
326
			if (!\is_array($params['referenceModule'])) {
327 2
				$moduleList[] = $params['referenceModule'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$moduleList was never initialized. Although not strictly required by PHP, it is generally a good practice to add $moduleList = array(); before regardless.
Loading history...
328 2
			} else {
329 2
				$moduleList = $params['referenceModule'];
330 11
			}
331 1
			$fieldModel->setRelatedModules($moduleList);
332 1
			foreach ($moduleList as $module) {
333 1
				$targetModule = vtlib\Module::getInstance($module);
334 1
				$targetModule->setRelatedList($this, $moduleName, ['Add'], 'getDependentsList', $name);
335 10
			}
336 1
		}
337 1
		App\Cache::clear();
338 1
		return $fieldModel;
339 1
	}
340 9
341 1
	/**
342 1
	 * Function defines details of the created field.
343 1
	 *
344 1
	 * @param string $fieldType
345 8
	 * @param array  $params
346 1
	 *
347 1
	 * @return (sting|int)[]
348 1
	 */
349 1
	public function getTypeDetailsForAddField($fieldType, $params)
350 7
	{
351 1
		$displayType = 1;
352 1
		$importerType = new \App\Db\Importers\Base();
353 1
		switch ($fieldType) {
354 1
			case 'Text':
355 6
				$fieldLength = $params['fieldLength'];
356 1
				$uichekdata = 'V~O~LE~' . $fieldLength;
357 1
				$uitype = 1;
358 1
				$type = $importerType->stringType($fieldLength)->defaultValue('');
359
				break;
360
			case 'AdvPercentage':
361 1
				$uitype = 365;
362
				// no break
363 1
			case 'Decimal':
364 1
				$fieldLength = $params['fieldLength'];
365 5
				$decimal = $params['decimal'];
366 1
				$uitype = $uitype ?? 7;
367 1
				$dbfldlength = $fieldLength + $decimal + 1;
368 1
				$type = $importerType->decimal($dbfldlength, $decimal);
369 1
				// Fix for http://trac.vtiger.com/cgi-bin/trac.cgi/ticket/6363
370 4
				$uichekdata = 'NN~O';
371 1
				break;
372 1
			case 'Percent':
373 1
				$uitype = 9;
374 1
				$type = $importerType->decimal(5, 2);
375 1
				$uichekdata = 'N~O~2~2';
376 3
				break;
377 1
			case 'Currency':
378 1
				$fieldLength = $params['fieldLength'];
379 1
				$decimal = $params['decimal'];
380 1
				$uitype = 71;
381 2
				if (1 == $fieldLength) {
382 1
					$dbfldlength = $fieldLength + $decimal + 2;
383 1
				} else {
384 1
					$dbfldlength = $fieldLength + $decimal + 1;
385 1
				}
386 1
				$decimal = $decimal + 3;
387 1
				$type = $importerType->decimal($dbfldlength, $decimal);
388
				$uichekdata = 'N~O';
389
				break;
390
			case 'Date':
391
				$uichekdata = 'D~O';
392 1
				$uitype = 5;
393
				$type = $importerType->date();
394
				break;
395
			case 'Email':
396
				$uitype = 13;
397 1
				$type = $importerType->stringType(100)->defaultValue('');
398 1
				$uichekdata = 'E~O';
399 1
				break;
400 1
			case 'Time':
401 1
				$uitype = 14;
402
				$type = $importerType->time();
403
				$uichekdata = 'T~O';
404
				break;
405
			case 'Phone':
406
				$uitype = 11;
407
				$type = $importerType->stringType(30)->defaultValue('');
408
				$uichekdata = 'V~O';
409
				break;
410
			case 'Picklist':
411
				$uitype = 16;
412
				if (!empty($params['isRoleBasedPickList'])) {
413
					$uitype = 15;
414
				}
415
				$type = $importerType->stringType()->defaultValue('');
416
				$uichekdata = 'V~O';
417
				break;
418
			case 'URL':
419
				$uitype = 17;
420
				$type = $importerType->stringType()->defaultValue('');
421
				$uichekdata = 'V~O';
422
				break;
423
			case 'MultipicklistTags':
424
				$uitype = 18;
425
				$type = $importerType->text();
426
				$uichekdata = 'V~O';
427
				break;
428
			case 'Checkbox':
429
				$uitype = 56;
430
				$type = $importerType->boolean()->defaultValue(false);
431
				$uichekdata = 'C~O';
432
				break;
433
			case 'TextArea':
434
				$uitype = 21;
435
				$type = $importerType->text();
436
				$uichekdata = 'V~O';
437
				break;
438
			case 'MultiSelectCombo':
439
				$uitype = 33;
440
				$type = $importerType->text();
441
				$uichekdata = 'V~O';
442
				break;
443
			case 'Skype':
444
				$uitype = 85;
445
				$type = $importerType->stringType()->defaultValue('');
446
				$uichekdata = 'V~O';
447 21
				break;
448 21
			case 'Integer':
449 21
				$fieldLength = $params['fieldLength'];
450 21
				$uitype = 7;
451
				$type = $importerType->integer($fieldLength)->defaultValue(0);
452
				$uichekdata = 'I~O';
453
				break;
454
			case 'Related1M':
455
				$uitype = 10;
456
				$type = $importerType->integer(10)->defaultValue(0)->unsigned();
457
				$uichekdata = 'V~O';
458
				break;
459
			case 'Editor':
460
				$fieldLength = $params['fieldLength'];
461 21
				$uitype = 300;
462
				$type = $importerType->text($fieldLength);
463 21
				$uichekdata = 'V~O';
464
				break;
465
			case 'Tree':
466 21
				$uitype = 302;
467
				$type = $importerType->stringType(30)->defaultValue('');
468
				$uichekdata = 'V~O';
469 21
				break;
470
			case 'MultiReferenceValue':
471
				$uitype = 305;
472
				$type = $importerType->text();
473
				$uichekdata = 'V~O';
474
				$displayType = 5;
475
				break;
476
			case 'MultiImage':
477
				$uitype = 311;
478
				$type = $importerType->text();
479 21
				$uichekdata = 'V~O';
480
				break;
481 21
			case 'Image':
482
				$uitype = 69;
483
				$type = $importerType->text();
484
				$uichekdata = 'V~O';
485
				break;
486
			case 'CategoryMultipicklist':
487
				$uitype = 309;
488
				$type = $importerType->text();
489
				$uichekdata = 'V~O';
490
				break;
491 21
			case 'DateTime':
492
				$uichekdata = 'DT~O';
493 21
				$uitype = 79;
494 21
				$type = $importerType->dateTime();
495
				break;
496
			case 'Country':
497
				$uitype = 35;
498
				$uichekdata = 'V~O';
499
				$type = $importerType->stringType(255);
500
				break;
501
			case 'Twitter':
502
				$fieldLength = Vtiger_Twitter_UIType::MAX_LENGTH;
503
				$uichekdata = 'V~O~LE~' . $fieldLength;
504 21
				$uitype = 313;
505
				$type = $importerType->stringType($fieldLength)->defaultValue('');
506 21
				break;
507
			case 'MultiEmail':
508
				$uitype = 314;
509
				$type = $importerType->text();
510
				$uichekdata = 'V~O';
511
				break;
512
			case 'Smtp':
513
				$uitype = 316;
514
				$uichekdata = 'V~O';
515
				$type = $importerType->integer()->defaultValue(null)->unsigned();
516
				break;
517
			case 'ServerAccess':
518
				$uitype = 318;
519
				$uichekdata = 'C~O';
520
				$type = $importerType->boolean()->defaultValue(false);
521
				break;
522
			case 'MultiDomain':
523
				$uitype = 319;
524
				$uichekdata = 'V~O';
525
				$type = $importerType->text();
526
				break;
527
			case 'RangeTime':
528
				$uitype = 308;
529 22
				$uichekdata = 'I~O';
530
				$type = $importerType->integer()->null();
531 22
				break;
532 22
			case 'Token':
533 22
				$uitype = 324;
534 22
				$uichekdata = 'V~O';
535 22
				$displayType = 3;
536
				$type = $importerType->stringType(Vtiger_Token_UIType::MAX_LENGTH)->defaultValue('');
537 22
				break;
538
			case 'MultiAttachment':
539
				$uitype = 330;
540
				$type = $importerType->text();
541
				$uichekdata = 'V~O';
542
				break;
543
			case 'MultiReference':
544
				$uitype = 321;
545
				$type = $importerType->text();
546
				$uichekdata = 'V~O';
547
				break;
548
			case 'MapCoordinates':
549
				$uitype = 331;
550
				$type = $importerType->stringType(100);
551
				$uichekdata = 'V~O';
552
				break;
553
			case 'Group':
554
				$uitype = 333;
555
				$type = $importerType->integer(10);
556
				$uichekdata = 'I~O';
557
				break;
558
			default:
559
				break;
560
		}
561
		return [
562
			'uitype' => $uitype,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $uitype does not seem to be defined for all execution paths leading up to this point.
Loading history...
563
			'typeofdata' => $uichekdata,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $uichekdata does not seem to be defined for all execution paths leading up to this point.
Loading history...
564
			'dbType' => $type,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.
Loading history...
565
			'displayType' => $displayType,
566
		];
567
	}
568
569
	public function getTableName($type)
570
	{
571
		if (\is_int($type)) {
572
			$focus = CRMEntity::getInstance($this->getName());
573
			if (0 == $type) {
574
				$tableName = $focus->table_name;
575
			} elseif (1 == $type) {
576
				if (isset($focus->customFieldTable)) {
577
					$tableName = $focus->customFieldTable[0];
578
				} else {
579
					$tableName = $focus->table_name . 'cf';
580
				}
581
			}
582
		} else {
583
			$tableName = $type;
584
		}
585
		return $tableName;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tableName does not seem to be defined for all execution paths leading up to this point.
Loading history...
586
	}
587
588
	/**
589
	 * Check field name characters.
590
	 *
591
	 * @param string $name
592
	 *
593
	 * @return bool
594
	 */
595
	public function checkFieldNameCharacters($name): bool
596
	{
597 1
		return preg_match('#[^a-z0-9_]#is', $name) || !preg_match('/[a-z]/i', $name) || false !== strpos($name, ' ');
598
	}
599 1
600 1
	/**
601
	 * Check if label exists.
602
	 *
603 1
	 * @param string $fieldLabel
604
	 *
605
	 * @return bool
606
	 */
607
	public function checkFieldLabelExists(string $fieldLabel): bool
608
	{
609
		return (new \App\Db\Query())->from('vtiger_field')->where(['fieldlabel' => $fieldLabel, 'tabid' => $this->getId()])->exists();
610
	}
611
612
	/**
613
	 * Check if field exists.
614
	 *
615 1
	 * @param string $fieldName
616
	 *
617
	 * @return bool
618
	 */
619
	public function checkFieldNameExists(string $fieldName): bool
620
	{
621
		return (new \App\Db\Query())->from('vtiger_field')->where(['tabid' => $this->getId()])
622
			->andWhere(['or', ['fieldname' => $fieldName], ['columnname' => $fieldName]])->exists();
623
	}
624
625
	/**
626
	 * Check if the field name is reserved.
627
	 *
628
	 * @param string $fieldName
629
	 *
630
	 * @return bool
631
	 */
632
	public function checkFieldNameIsAnException(string $fieldName)
633
	{
634
		return \in_array($fieldName, [
635
			'id', 'seq', 'header_type', 'header_class',
636
			'module', 'parent', 'action', 'mode', 'view', 'selected_ids',
637
			'excluded_ids', 'search_params', 'search_key', 'page', 'operator',
638
			'source_module', 'viewname', 'sortorder', 'orderby', 'inventory', 'private', 'src_record', 'relationid', 'relation_id', 'picklist', 'overwritten_shownerid', 'relationoperation', 'sourcemodule', 'sourcerecord'
639
		]);
640
	}
641
642
	public static function getSupportedModules()
643
	{
644
		if (empty(self::$supportedModules)) {
645
			self::$supportedModules = self::getEntityModulesList();
646
		}
647
		return self::$supportedModules;
648
	}
649
650
	/**
651
	 * Get instance by name.
652
	 *
653
	 * @param string $moduleName
654
	 *
655
	 * @return self
656
	 */
657
	public static function getInstanceByName($moduleName)
658
	{
659
		$moduleInstance = Vtiger_Module_Model::getInstance($moduleName);
660
		$objectProperties = get_object_vars($moduleInstance);
661
		$selfInstance = new self();
662
		foreach ($objectProperties as $properName => $propertyValue) {
663
			$selfInstance->{$properName} = $propertyValue;
664
		}
665
		return $selfInstance;
666
	}
667
668
	/**
669
	 * Function to get Entity module names list.
670
	 *
671
	 * @return string[] List of Entity modules
672
	 */
673
	public static function getEntityModulesList()
674
	{
675
		$restrictedModules = ['Integration', 'Dashboard'];
676
		return (new \App\Db\Query())->select(['name', 'module' => 'name'])->from('vtiger_tab')->where(['presence' => [0, 2], 'isentitytype' => 1])->andWhere(['not in', 'name', $restrictedModules])->createCommand()->queryAllByGroup();
677
	}
678
679
	/**
680
	 * Function to check field is editable or not.
681
	 *
682
	 * @return bool
683
	 */
684
	public function isSortableAllowed()
685
	{
686
		return true;
687
	}
688
689
	/**
690
	 * Function to check blocks are sortable for the module.
691
	 *
692
	 * @return bool
693
	 */
694
	public function isBlockSortableAllowed()
695
	{
696
		if ('ModComments' === $this->getName()) {
697
			return false;
698
		}
699
		return true;
700
	}
701
702
	/**
703
	 * Function to check fields are sortable for the block.
704
	 *
705
	 * @param mixed $blockName
706
	 *
707
	 * @return bool
708
	 */
709
	public function isFieldsSortableAllowed($blockName)
710
	{
711
		$moduleName = $this->getName();
712
		$blocksEliminatedArray = ['HelpDesk' => ['LBL_TICKET_RESOLUTION', 'LBL_COMMENTS'],
713
			'Faq' => ['LBL_COMMENT_INFORMATION'],
714
			'Calendar' => ['LBL_TASK_INFORMATION', 'LBL_DESCRIPTION_INFORMATION', 'LBL_REMINDER_INFORMATION', 'LBL_RECURRENCE_INFORMATION'],
715
		];
716
		if (\in_array($moduleName, ['HelpDesk', 'Faq'])) {
717
			if (!empty($blocksEliminatedArray[$moduleName])) {
718
				if (\in_array($blockName, $blocksEliminatedArray[$moduleName])) {
719
					return false;
720
				}
721
			} else {
722
				return false;
723
			}
724
		}
725
		return true;
726
	}
727
728
	public function getRelations()
729
	{
730
		if (null === $this->relations) {
731
			$this->relations = Vtiger_Relation_Model::getAllRelations($this, false, true, true);
732
		}
733
		return $this->relations;
734
	}
735
736
	/**
737
	 * Function returns available templates for tree type field.
738
	 *
739
	 * @param string $sourceModule
740
	 *
741
	 * @return array
742
	 */
743
	public function getTreeTemplates($sourceModule)
744
	{
745
		$sourceModule = \App\Module::getModuleId($sourceModule);
746
		$query = (new \App\Db\Query())->select(['templateid', 'name'])->from('vtiger_trees_templates')->where(['tabid' => $sourceModule])->orWhere(['like', 'share', ",$sourceModule,"]);
747
		$treeList = [];
748
		$dataReader = $query->createCommand()->query();
749
		while ($row = $dataReader->read()) {
750
			$treeList[$row['templateid']] = $row['name'];
751
		}
752
		$dataReader->close();
753
754
		return $treeList;
755
	}
756
757
	public static function getRelationsTypes(?string $moduleName = null)
758
	{
759
		$types = [
760
			'getRelatedList' => 'PLL_RELATED_LIST',
761
			//'getDependentsList' => 'PLL_DEPENDENTS_LIST',
762
			'getManyToMany' => 'PLL_SPLITED_RELATED_LIST',
763
			'getAttachments' => 'PLL_ATTACHMENTS',
764
			// 'getActivities' => 'PLL_ACTIVITIES',
765
			'getEmails' => 'PLL_EMAILS',
766
		];
767
		if ('OSSMailView' === $moduleName) {
768
			$types['getRecordToMails'] = 'PLL_RECORD_TO_MAILS';
769
		}
770
		return $types;
771
	}
772
773
	public static function getRelationsActions()
774
	{
775
		return [
776
			'ADD' => 'PLL_ADD',
777
			'SELECT' => 'PLL_SELECT',
778
		];
779
	}
780
781
	/**
782
	 * Update related view type.
783
	 *
784
	 * @param int      $relationId
785
	 * @param string[] $type
786
	 */
787
	public static function updateRelatedViewType($relationId, $type)
788
	{
789
		\App\Db::getInstance()->createCommand()->update('vtiger_relatedlists', ['view_type' => implode(',', $type)], ['relation_id' => $relationId])->execute();
790
		\App\Relation::clearCacheById($relationId);
791
	}
792
793
	/**
794
	 * Get related view types.
795
	 *
796
	 * @return string[]
797
	 */
798
	public static function getRelatedViewTypes()
799
	{
800
		return static::$relatedViewType;
0 ignored issues
show
Bug introduced by
Since $relatedViewType is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $relatedViewType to at least protected.
Loading history...
801
	}
802
803
	/**
804
	 * Check if picklist field can have that name.
805
	 *
806
	 * @param string $fieldName
807
	 *
808
	 * @return bool
809
	 */
810
	public function checkIfPicklistFieldNameReserved(string $fieldName): bool
811
	{
812
		return (
813
			\App\Fields\Picklist::isPicklistExist($fieldName)
814
			&& !(new \App\Db\Query())->from('vtiger_field')->where(['or', ['fieldname' => $fieldName], ['columnname' => $fieldName]])->exists()
815
		) || \in_array($fieldName, (new \App\Db\Query())->select(['fieldname'])->from('vtiger_field')->where(['tabid' => \App\Module::getModuleId('Users'), 'uitype' => [16, 15, 33, 115]])->column());
816
	}
817
818
	/**
819
	 * Get missing system fields.
820
	 *
821
	 * @return \Vtiger_Field_Model[]
822
	 */
823
	public function getMissingSystemFields(): array
824
	{
825
		$fields = $this->getFields();
826
		$systemFields = \App\Field::SYSTEM_FIELDS;
827
		$missingFields = [];
828
		foreach (Settings_WebserviceApps_Module_Model::getServers() as $id => $field) {
829
			$name = 'share_externally_' . $id;
830
			$systemFields[$name] = array_merge($systemFields['share_externally'], [
831
				'name' => $name,
832
				'column' => $name,
833
				'label' => $field['name'] . ' (' . \App\Language::translate($field['type'], 'Settings:WebserviceApps') . ')',
834
				'fieldparams' => $id,
835
			]);
836
		}
837
		unset($systemFields['share_externally']);
838
		foreach ($systemFields as $name => $field) {
839
			$validationConditions = $field['validationConditions'];
840
			if ($validationConditions === ['name']) {
841
				$exist = isset($fields[$name]);
842
			} else {
843
				$exist = true;
844
				foreach ($validationConditions as $validationCondition) {
845
					$status = true;
846
					foreach ($fields as $fieldModel) {
847
						if ($fieldModel->get($validationCondition) == $field[$validationCondition]) {
848
							$status = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $status is dead and can be removed.
Loading history...
849
							continue 2;
850
						}
851
					}
852
					$exist = !$status;
0 ignored issues
show
introduced by
The condition $status is always true.
Loading history...
853
				}
854
			}
855
			if (!$exist) {
856
				unset($field['validationConditions']);
857
				$missingFields[$name] = \Vtiger_Field_Model::init($this->name, $field, $field['name']);
858
			}
859
		}
860
		return $missingFields;
861
	}
862
863
	/**
864
	 * Create system field.
865
	 *
866
	 * @param string $sysName
867
	 * @param int    $blockId
868
	 * @param array  $params
869
	 *
870
	 * @return void
871
	 */
872
	public function addSystemField(string $sysName, int $blockId, array $params = []): void
873
	{
874
		$missingSystemFields = $this->getMissingSystemFields();
875
		if (empty($missingSystemFields[$sysName])) {
876
			throw new \App\Exceptions\AppException(\App\Language::translate('LBL_DUPLICATE_FIELD_EXISTS', 'Settings::LayoutEditor'), 512);
877
		}
878
		$fieldModel = $missingSystemFields[$sysName];
879
		if ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
880
			foreach ($params as $key => $value) {
881
				$fieldModel->set($key, $value);
882
			}
883
		}
884
		if ($this->checkFieldLabelExists($fieldModel->get('name'))) {
885
			throw new \App\Exceptions\AppException(\App\Language::translate('LBL_DUPLICATE_FIELD_EXISTS', 'Settings::LayoutEditor'), 513);
886
		}
887
		if ($this->checkFieldNameCharacters($fieldModel->get('name'))) {
888
			throw new \App\Exceptions\AppException(\App\Language::translate('LBL_INVALIDCHARACTER', 'Settings::LayoutEditor'), 512);
889
		}
890
		if ($this->checkFieldNameExists($fieldModel->get('name'))) {
891
			throw new \App\Exceptions\AppException(\App\Language::translate('LBL_DUPLICATE_FIELD_EXISTS', 'Settings::LayoutEditor'), 512);
892
		}
893
		$blockModel = Vtiger_Block_Model::getInstance($blockId, $this->name);
0 ignored issues
show
Bug introduced by
$this->name of type string is incompatible with the type boolean|vtlib\ModuleBasic expected by parameter $moduleInstance of Vtiger_Block_Model::getInstance(). ( Ignorable by Annotation )

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

893
		$blockModel = Vtiger_Block_Model::getInstance($blockId, /** @scrutinizer ignore-type */ $this->name);
Loading history...
894
		$blockModel->addField($fieldModel);
895
	}
896
897
	/**
898
	 * Get fields for webservice apps.
899
	 *
900
	 * @param int $webserviceApp
901
	 *
902
	 * @return array
903
	 */
904
	public function getFieldsForWebserviceApps(int $webserviceApp): array
905
	{
906
		return (new \App\Db\Query())->from('w_#__fields_server')->where(['serverid' => $webserviceApp])->indexBy('fieldid')->all(\App\Db::getInstance('webservice')) ?: [];
907
	}
908
909
	public function getEditFields()
910
	{
911
		$editFields = ['label', 'presence', 'quickcreate', 'summaryfield', 'generatedtype', 'masseditable', 'header_field',
912
			'displaytype', 'maxlengthtext', 'maxwidthcolumn', 'tabindex', 'mandatory', 'icon'];
913
		foreach ($editFields as $fieldName) {
914
			$propertyModel = $this->getFieldInstanceByName($fieldName);
915
			if (null !== $this->get($fieldName)) {
916
				$propertyModel->set('fieldvalue', $this->get($fieldName));
917
			} elseif (($defaultValue = $propertyModel->get('defaultvalue')) !== null) {
918
				$propertyModel->set('fieldvalue', $defaultValue);
919
			}
920
			$fields[$fieldName] = $propertyModel;
921
		}
922
923
		return $fields;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fields seems to be defined by a foreach iteration on line 913. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
924
	}
925
926
	/**
927
	 * Get fields instance by name.
928
	 *
929
	 * @param string $name
930
	 *
931
	 * @return Vtiger_Field_Model
932
	 */
933
	public function getFieldInstanceByName($name)
934
	{
935
		$params = [];
936
		$qualifiedModuleName = 'Settings:LayoutEditor';
937
		// $tableName = $this->getTableName();
938
		switch ($name) {
939
			case 'icon':
940
				$params = [
941
					'name' => $name,
942
					'column' => $name,
943
					'label' => 'LBL_ICON',
944
					'uitype' => 62,
945
					'typeofdata' => 'V~O',
946
					'maximumlength' => '255',
947
					'purifyType' => \App\Purifier::TEXT,
948
					'table' => 'vtiger_field',
949
					'fieldDataType' => 'icon'
950
				];
951
				break;
952
			case 'fieldlabel':
953
				$params = [
954
					'name' => $name,
955
					'column' => $name,
956
					'label' => 'LBL_LABEL',
957
					'uitype' => 1,
958
					'typeofdata' => 'V~M',
959
					'maximumlength' => '50',
960
					'purifyType' => \App\Purifier::TEXT
961
				];
962
				break;
963
			case 'mandatory':
964
				$params = [
965
					'name' => $name,
966
					'column' => $name,
967
					'label' => 'LBL_MANDATORY_FIELD',
968
					'uitype' => 56,
969
					'typeofdata' => 'C~O',
970
					'maximumlength' => '1',
971
					'purifyType' => \App\Purifier::BOOL
972
				];
973
				break;
974
			case 'presence':
975
				$params = [
976
					'name' => $name,
977
					'column' => $name,
978
					'label' => 'LBL_ACTIVE',
979
					'uitype' => 56,
980
					'typeofdata' => 'C~O',
981
					'maximumlength' => '1',
982
					'purifyType' => \App\Purifier::BOOL
983
				];
984
				break;
985
			case 'quickcreate':
986
				$params = [
987
					'name' => $name,
988
					'column' => $name,
989
					'label' => 'LBL_QUICK_CREATE',
990
					'uitype' => 56,
991
					'typeofdata' => 'C~O',
992
					'maximumlength' => '1',
993
					'purifyType' => \App\Purifier::BOOL
994
				];
995
				break;
996
			case 'summaryfield':
997
				$params = [
998
					'name' => $name,
999
					'column' => $name,
1000
					'label' => 'LBL_SUMMARY_FIELD',
1001
					'uitype' => 56,
1002
					'typeofdata' => 'C~O',
1003
					'maximumlength' => '1',
1004
					'purifyType' => \App\Purifier::BOOL
1005
				];
1006
				break;
1007
			case 'header_field':
1008
				$params = [
1009
					'name' => $name,
1010
					'column' => $name,
1011
					'label' => 'LBL_HEADER_FIELD',
1012
					'uitype' => 56,
1013
					'typeofdata' => 'C~O',
1014
					'maximumlength' => '1',
1015
					'purifyType' => \App\Purifier::BOOL
1016
				];
1017
				break;
1018
			case 'masseditable':
1019
				$params = [
1020
					'name' => $name,
1021
					'column' => $name,
1022
					'label' => 'LBL_MASS_EDIT',
1023
					'uitype' => 56,
1024
					'typeofdata' => 'C~O',
1025
					'maximumlength' => '1',
1026
					'purifyType' => \App\Purifier::BOOL
1027
				];
1028
				break;
1029
			case 'generatedtype':
1030
				$params = [
1031
					'name' => $name,
1032
					'column' => $name,
1033
					'label' => 'LBL_GENERATED_TYPE',
1034
					'uitype' => 56,
1035
					'typeofdata' => 'C~O',
1036
					'maximumlength' => '1',
1037
					'purifyType' => \App\Purifier::BOOL,
1038
					'isEditableReadOnly' => !App\Config::developer('CHANGE_GENERATEDTYPE')
1039
				];
1040
				break;
1041
			case 'defaultvalue':
1042
				$params = [
1043
					'name' => $name,
1044
					'column' => $name,
1045
					'label' => 'LBL_DEFAULT_VALUE',
1046
					'uitype' => 56,
1047
					'typeofdata' => 'C~O',
1048
					'maximumlength' => '1',
1049
					'purifyType' => \App\Purifier::BOOL
1050
				];
1051
				break;
1052
			case 'fieldMask':
1053
				$params = [
1054
					'name' => $name,
1055
					'column' => $name,
1056
					'label' => 'LBL_FIELD_MASK',
1057
					'uitype' => 1,
1058
					'typeofdata' => 'V~O',
1059
					'maximumlength' => '25',
1060
					'purifyType' => \App\Purifier::TEXT,
1061
					'tooltip' => 'LBL_FIELD_MASK_INFO'
1062
				];
1063
				break;
1064
			case 'close_state':
1065
				$params = [
1066
					'name' => $name,
1067
					'column' => $name,
1068
					'label' => 'LBL_CLOSES_RECORD',
1069
					'uitype' => 56,
1070
					'typeofdata' => 'C~O',
1071
					'maximumlength' => '5',
1072
					'purifyType' => \App\Purifier::BOOL,
1073
					'tooltip' => 'LBL_BLOCKED_RECORD_INFO',
1074
					'table' => 'u_#__picklist_close_state'
1075
				];
1076
				break;
1077
			case 'icon':
1078
				$params = [
1079
					'name' => $name,
1080
					'column' => $name,
1081
					'label' => 'LBL_ICON',
1082
					'uitype' => 62,
1083
					'typeofdata' => 'V~O',
1084
					'maximumlength' => '255',
1085
					'purifyType' => \App\Purifier::TEXT,
1086
					'table' => $tableName
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tableName seems to be never defined.
Loading history...
1087
				];
1088
				break;
1089
			case 'time_counting':
1090
				$params = [
1091
					'name' => $name,
1092
					'column' => $name,
1093
					'label' => 'LBL_TIME_COUNTING',
1094
					'uitype' => 16,
1095
					'typeofdata' => 'V~M',
1096
					'maximumlength' => '250',
1097
					'purifyType' => \App\Purifier::INTEGER,
1098
					'tooltip' => 'LBL_TIME_COUNTING_INFO',
1099
					'defaultvalue' => 0,
1100
					'picklistValues' => [
1101
						0 => \App\Language::translate('LBL_NONE', '_Base'),
1102
						\App\RecordStatus::TIME_COUNTING_REACTION => \App\Language::translate('LBL_TIME_COUNTING_REACTION', $qualifiedModuleName),
1103
						\App\RecordStatus::TIME_COUNTING_RESOLVE => \App\Language::translate('LBL_TIME_COUNTING_RESOLVE', $qualifiedModuleName),
1104
						\App\RecordStatus::TIME_COUNTING_IDLE => \App\Language::translate('LBL_TIME_COUNTING_IDLE', $qualifiedModuleName)
1105
					],
1106
					'table' => $tableName
1107
				];
1108
				break;
1109
			case 'record_state':
1110
				$params = [
1111
					'name' => $name,
1112
					'column' => $name,
1113
					'label' => 'LBL_RECORD_STATE',
1114
					'uitype' => 16,
1115
					'typeofdata' => 'V~M',
1116
					'maximumlength' => '250',
1117
					'purifyType' => \App\Purifier::INTEGER,
1118
					'tooltip' => 'LBL_RECORD_STATE_INFO',
1119
					'defaultvalue' => \App\RecordStatus::RECORD_STATE_NO_CONCERN,
1120
					'picklistValues' => [],
1121
					'table' => $tableName
1122
				];
1123
				foreach (\App\RecordStatus::getLabels() as $key => $value) {
1124
					$params['picklistValues'][$key] = \App\Language::translate($value, $qualifiedModuleName);
1125
				}
1126
				break;
1127
			case 'roles':
1128
				$params = [
1129
					'name' => $name,
1130
					'column' => $name,
1131
					'label' => 'LBL_ASSIGN_TO_ROLE',
1132
					'uitype' => 33,
1133
					'typeofdata' => 'V~O',
1134
					'maximumlength' => '500',
1135
					'purifyType' => \App\Purifier::TEXT,
1136
					'defaultvalue' => 'all',
1137
					'picklistValues' => [
1138
						'all' => \App\Language::translate('LBL_ALL_ROLES', $qualifiedModuleName)
1139
					],
1140
					'table' => $tableName
1141
				];
1142
				foreach (\Settings_Roles_Record_Model::getAll() as $key => $roleModel) {
1143
					$params['picklistValues'][$key] = \App\Language::translate($roleModel->get('rolename'), 'Settings:Roles');
1144
				}
1145
				break;
1146
			default:
1147
				break;
1148
		}
1149
1150
		return $params ? \Vtiger_Field_Model::init($qualifiedModuleName, $params, $name)->set('sourceFieldModel', $this->fieldModel) : null;
0 ignored issues
show
Bug introduced by
The property fieldModel does not exist on Settings_LayoutEditor_Module_Model. Did you mean fieldsModule?
Loading history...
1151
	}
1152
}
1153