Passed
Push — developer ( d5cf61...0a1e92 )
by Radosław
18:21
created

Vtiger_Save_Action   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 230
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 57
eloc 124
c 1
b 0
f 0
dl 0
loc 230
ccs 0
cts 53
cp 0
rs 5.04

8 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 19 5
C checkPermission() 0 26 12
A __construct() 0 5 1
A recordChanger() 0 19 6
A multiSave() 0 23 4
B saveRecord() 0 27 11
C getRecordModelFromRequest() 0 25 12
A preSaveValidation() 0 20 6

How to fix   Complexity   

Complex Class

Complex classes like Vtiger_Save_Action often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Vtiger_Save_Action, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* +***********************************************************************************
3
 * The contents of this file are subject to the vtiger CRM Public License Version 1.0
4
 * ("License"); You may not use this file except in compliance with the License
5
 * The Original Code is:  vtiger CRM Open Source
6
 * The Initial Developer of the Original Code is vtiger.
7
 * Portions created by vtiger are Copyright (C) vtiger.
8
 * All Rights Reserved.
9
 * Contributor(s): YetiForce S.A.
10
 * *********************************************************************************** */
11
12
class Vtiger_Save_Action extends \App\Controller\Action
13
{
14
	use \App\Controller\ExposeMethod;
15
	/**
16
	 * Record model instance.
17
	 *
18
	 * @var Vtiger_Record_Model
19
	 */
20
	protected $record;
21
22
	/** {@inheritdoc} */
23
	public function __construct()
24
	{
25
		parent::__construct();
26
		$this->exposeMethod('preSaveValidation');
27
		$this->exposeMethod('recordChanger');
28
	}
29
30
	/**
31
	 * Function to check permission.
32
	 *
33
	 * @param \App\Request $request
34
	 *
35
	 * @throws \App\Exceptions\NoPermittedToRecord
36
	 */
37
	public function checkPermission(App\Request $request)
38
	{
39
		$moduleName = $request->getModule();
40
		if ($request->isEmpty('record', true)) {
41
			$this->record = Vtiger_Record_Model::getCleanInstance($moduleName);
42
			if (!$this->record->isCreatable()) {
43
				throw new \App\Exceptions\NoPermittedToRecord('ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
44
			}
45
		} else {
46
			$recordId = $request->getInteger('record');
47
			if (!\App\Privilege::isPermitted($moduleName, 'DetailView', $recordId)) {
48
				throw new \App\Exceptions\NoPermittedToRecord('ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
49
			}
50
			$this->record = Vtiger_Record_Model::getInstanceById($recordId, $moduleName);
51
			if ('recordChanger' !== $request->getMode() && !$this->record->isEditable()) {
52
				throw new \App\Exceptions\NoPermittedToRecord('ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
53
			}
54
		}
55
		if ($request->getBoolean('_isDuplicateRecord') && !\App\Privilege::isPermitted($moduleName, 'DetailView', $request->getInteger('_duplicateRecord'))) {
56
			throw new \App\Exceptions\NoPermittedToRecord('ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
57
		}
58
		if ($request->has('recordConverter') && !\App\RecordConverter::getInstanceById($request->getInteger('recordConverter'))->isPermitted($request->getInteger('sourceRecord'))) {
59
			throw new \App\Exceptions\NoPermittedToRecord('ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
60
		}
61
		if ($request->getBoolean('relationOperation') && !\App\Privilege::isPermitted($request->getByType('sourceModule', 2), 'DetailView', $request->getInteger('sourceRecord'))) {
62
			throw new \App\Exceptions\NoPermittedToRecord('ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
63
		}
64
	}
65
66
	/**
67
	 * Process.
68
	 *
69
	 * @param \App\Request $request
70
	 */
71
	public function process(App\Request $request)
72
	{
73
		if ($mode = $request->getMode()) {
74
			$this->invokeExposedMethod($mode, $request);
75
		} else {
76
			$this->saveRecord($request);
77
			if ($request->getBoolean('relationOperation')) {
78
				$loadUrl = Vtiger_Record_Model::getInstanceById($request->getInteger('sourceRecord'), $request->getByType('sourceModule', 2))->getDetailViewUrl();
79
			} elseif ($request->getBoolean('returnToList')) {
80
				$loadUrl = $this->record->getModule()->getListViewUrl();
81
			} else {
82
				$this->record->clearPrivilegesCache();
83
				if ($this->record->isViewable()) {
84
					$loadUrl = $this->record->getDetailViewUrl();
85
				} else {
86
					$loadUrl = $this->record->getModule()->getDefaultUrl();
87
				}
88
			}
89
			header("location: $loadUrl");
90
		}
91
	}
92
93
	/** {@inheritdoc} */
94
	public function saveRecord(App\Request $request)
95
	{
96
		$this->getRecordModelFromRequest($request);
97
		$eventHandler = $this->record->getEventHandler();
98
		$skipHandlers = $request->getArray('skipHandlers', \App\Purifier::ALNUM, [], \App\Purifier::INTEGER);
99
		foreach ($eventHandler->getHandlers(\App\EventHandler::EDIT_VIEW_PRE_SAVE) as $handler) {
100
			$handlerId = $handler['eventhandler_id'];
101
			$response = $eventHandler->triggerHandler($handler);
102
103
			if (!($response['result'] ?? null) && (!isset($response['hash'], $skipHandlers[$handlerId]) || $skipHandlers[$handlerId] !== $response['hash'])) {
104
				throw new \App\Exceptions\NoPermittedToRecord($response['message'] ?? 'ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
105
			}
106
		}
107
		if (!$request->isEmpty('fromView') && 'MassQuickCreate' === $request->getByType('fromView')) {
108
			$this->multiSave($request);
109
		} else {
110
			$this->record->save();
111
			if ($request->has('recordConverter')) {
112
				$converter = \App\RecordConverter::getInstanceById($request->getInteger('recordConverter'))->set('sourceRecord', $request->getInteger('sourceRecord'));
113
				$eventHandler->setParams(['converter' => $converter]);
114
				$eventHandler->trigger(\App\EventHandler::RECORD_CONVERTER_AFTER_SAVE);
115
			}
116
		}
117
		if ($request->getBoolean('relationOperation')) {
118
			$relationId = $request->isEmpty('relationId') ? false : $request->getInteger('relationId');
119
			if ($relationModel = Vtiger_Relation_Model::getInstance(Vtiger_Module_Model::getInstance($request->getByType('sourceModule', 2)), $this->record->getModule(), $relationId)) {
120
				$relationModel->addRelation($request->getInteger('sourceRecord'), $this->record->getId());
121
			}
122
		}
123
	}
124
125
	/**
126
	 * Function to get the record model based on the request parameters.
127
	 *
128
	 * @param \App\Request $request
129
	 *
130
	 * @return Vtiger_Record_Model or Module specific Record Model instance
131
	 */
132
	protected function getRecordModelFromRequest(App\Request $request)
133
	{
134
		if (empty($this->record)) {
135
			$this->record = $request->isEmpty('record', true) ? Vtiger_Record_Model::getCleanInstance($request->getModule()) : Vtiger_Record_Model::getInstanceById($request->getInteger('record'), $request->getModule());
136
		}
137
		$fieldModelList = $this->record->getModule()->getFields();
138
		foreach ($fieldModelList as $fieldName => $fieldModel) {
139
			if (!$fieldModel->isWritable()) {
140
				continue;
141
			}
142
			if ($request->has($fieldName)) {
143
				$fieldModel->getUITypeModel()->setValueFromRequest($request, $this->record);
144
			}
145
		}
146
		if ($request->has('inventory') && $this->record->getModule()->isInventory()) {
147
			$this->record->initInventoryDataFromRequest($request);
148
		}
149
		$fromView = $request->has('fromView') ? $request->getByType('fromView') : ($request->isEmpty('record', true) ? 'Create' : 'Edit');
150
		$fieldsDependency = \App\FieldsDependency::getByRecordModel($fromView, $this->record);
151
		if ($fields = array_merge($fieldsDependency['hide']['frontend'], $fieldsDependency['hide']['backend'])) {
152
			foreach ($fields as $fieldName) {
153
				$this->record->revertPreviousValue($fieldName);
154
			}
155
		}
156
		return $this->record;
157
	}
158
159
	/**
160
	 * Validation before saving.
161
	 *
162
	 * @param App\Request $request
163
	 */
164
	public function preSaveValidation(App\Request $request)
165
	{
166
		$this->getRecordModelFromRequest($request);
167
		$eventHandler = $this->record->getEventHandler();
168
		$result = [];
169
		$skipHandlers = $request->getArray('skipHandlers', \App\Purifier::ALNUM, [], \App\Purifier::INTEGER);
170
		foreach ($eventHandler->getHandlers(\App\EventHandler::EDIT_VIEW_PRE_SAVE) as $handler) {
171
			$handlerId = $handler['eventhandler_id'];
172
			$handlerResponse = $eventHandler->triggerHandler($handler);
173
			if (!($handlerResponse['result'] ?? null) && (!isset($handlerResponse['hash'], $skipHandlers[$handlerId]) || $skipHandlers[$handlerId] !== $handlerResponse['hash'])) {
174
				$result[$handlerId] = $handlerResponse;
175
				if ('confirm' === ($handlerResponse['type'] ?? '')) {
176
					break;
177
				}
178
			}
179
		}
180
		$response = new Vtiger_Response();
181
		$response->setEmitType(Vtiger_Response::$EMIT_JSON);
182
		$response->setResult($result);
183
		$response->emit();
184
	}
185
186
	/**
187
	 * Quick change of record value.
188
	 *
189
	 * @param App\Request $request
190
	 */
191
	public function recordChanger(App\Request $request)
192
	{
193
		$this->getRecordModelFromRequest($request);
194
		$id = $request->getInteger('id');
195
		$field = App\Field::getQuickChangerFields($this->record->getModule()->getId())[$id] ?? false;
196
		if (!$field || !App\Field::checkQuickChangerConditions($field, $this->record)) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of App\Field::checkQuickCha...($field, $this->record) targeting App\Field::checkQuickChangerConditions() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
197
			throw new \App\Exceptions\NoPermittedToRecord('ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
198
		}
199
		$fields = $this->record->getModule()->getFields();
200
		foreach ($field['values'] as $fieldName => $value) {
201
			if (isset($fields[$fieldName]) && $fields[$fieldName]->isEditable()) {
202
				$this->record->set($fieldName, $value);
203
			}
204
		}
205
		$this->record->save();
206
		$response = new Vtiger_Response();
207
		$response->setEmitType(Vtiger_Response::$EMIT_JSON);
208
		$response->setResult(true);
209
		$response->emit();
210
	}
211
212
	/**
213
	 * Multiple record save mode.
214
	 *
215
	 * @param App\Request $request
216
	 *
217
	 * @return void
218
	 */
219
	protected function multiSave(App\Request $request): void
220
	{
221
		$moduleName = $request->getByType('module', 'Alnum');
222
		$multiSaveField = $request->getByType('multiSaveField', 'Alnum');
223
		$sourceModule = $request->getByType('sourceModule', 'Alnum');
224
		$sourceView = $request->getByType('sourceView');
225
		if ('ListView' === $sourceView) {
226
			$request->set('module', $sourceModule);
227
			$ids = Vtiger_Mass_Action::getRecordsListFromRequest($request);
228
			$request->set('module', $moduleName);
229
		} elseif ('RelatedListView' === $sourceView) {
230
			$request->set('module', $request->getByType('relatedModule', 'Alnum'));
231
			$request->set('relatedModule', $request->getByType('sourceModule', 'Alnum'));
232
			$request->set('record', $request->getByType('relatedRecord', 'Alnum'));
233
			$ids = Vtiger_RelationAjax_Action::getRecordsListFromRequest($request);
234
			$request->set('module', $moduleName);
235
		}
236
		foreach ($ids as $id) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ids does not seem to be defined for all execution paths leading up to this point.
Loading history...
237
			$recordModel = \Vtiger_Record_Model::getCleanInstance($this->record->getModuleName());
238
			$recordModel->setData($this->record->getData());
239
			$recordModel->ext = $this->record->ext;
240
			$recordModel->set($multiSaveField, $id);
241
			$recordModel->save();
242
		}
243
	}
244
}
245