Passed
Pull Request — developer (#17138)
by
unknown
18:27
created

Vtiger_Save_Action::preSaveValidation()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 20
ccs 0
cts 0
cp 0
rs 9.2222
cc 6
nc 4
nop 1
crap 42
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
				var_dump(
0 ignored issues
show
Security Debugging Code introduced by
var_dump($skipHandlers, $response) looks like debug code. Are you sure you do not want to remove it?
Loading history...
105
					$skipHandlers,
106
					$response
107
				);
108
				exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
109
				throw new \App\Exceptions\NoPermittedToRecord('ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
0 ignored issues
show
Unused Code introduced by
ThrowNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
110
			}
111
		}
112
		if (!$request->isEmpty('fromView') && 'MassQuickCreate' === $request->getByType('fromView')) {
113
			$this->multiSave($request);
114
		} else {
115
			$this->record->save();
116
			if ($request->has('recordConverter')) {
117
				$converter = \App\RecordConverter::getInstanceById($request->getInteger('recordConverter'))->set('sourceRecord', $request->getInteger('sourceRecord'));
118
				$eventHandler->setParams(['converter' => $converter]);
119
				$eventHandler->trigger(\App\EventHandler::RECORD_CONVERTER_AFTER_SAVE);
120
			}
121
		}
122
		if ($request->getBoolean('relationOperation')) {
123
			$relationId = $request->isEmpty('relationId') ? false : $request->getInteger('relationId');
124
			if ($relationModel = Vtiger_Relation_Model::getInstance(Vtiger_Module_Model::getInstance($request->getByType('sourceModule', 2)), $this->record->getModule(), $relationId)) {
125
				$relationModel->addRelation($request->getInteger('sourceRecord'), $this->record->getId());
126
			}
127
		}
128
	}
129
130
	/**
131
	 * Function to get the record model based on the request parameters.
132
	 *
133
	 * @param \App\Request $request
134
	 *
135
	 * @return Vtiger_Record_Model or Module specific Record Model instance
136
	 */
137
	protected function getRecordModelFromRequest(App\Request $request)
138
	{
139
		if (empty($this->record)) {
140
			$this->record = $request->isEmpty('record', true) ? Vtiger_Record_Model::getCleanInstance($request->getModule()) : Vtiger_Record_Model::getInstanceById($request->getInteger('record'), $request->getModule());
141
		}
142
		$fieldModelList = $this->record->getModule()->getFields();
143
		foreach ($fieldModelList as $fieldName => $fieldModel) {
144
			if (!$fieldModel->isWritable()) {
145
				continue;
146
			}
147
			if ($request->has($fieldName)) {
148
				$fieldModel->getUITypeModel()->setValueFromRequest($request, $this->record);
149
			}
150
		}
151
		if ($request->has('inventory') && $this->record->getModule()->isInventory()) {
152
			$this->record->initInventoryDataFromRequest($request);
153
		}
154
		$fromView = $request->has('fromView') ? $request->getByType('fromView') : ($request->isEmpty('record', true) ? 'Create' : 'Edit');
155
		$fieldsDependency = \App\FieldsDependency::getByRecordModel($fromView, $this->record);
156
		if ($fields = array_merge($fieldsDependency['hide']['frontend'], $fieldsDependency['hide']['backend'])) {
157
			foreach ($fields as $fieldName) {
158
				$this->record->revertPreviousValue($fieldName);
159
			}
160
		}
161
		return $this->record;
162
	}
163
164
	/**
165
	 * Validation before saving.
166
	 *
167
	 * @param App\Request $request
168
	 */
169
	public function preSaveValidation(App\Request $request)
170
	{
171
		$this->getRecordModelFromRequest($request);
172
		$eventHandler = $this->record->getEventHandler();
173
		$result = [];
174
		$skipHandlers = $request->getArray('skipHandlers', \App\Purifier::ALNUM, [], \App\Purifier::INTEGER);
175
		foreach ($eventHandler->getHandlers(\App\EventHandler::EDIT_VIEW_PRE_SAVE) as $handler) {
176
			$handlerId = $handler['eventhandler_id'];
177
			$handlerResponse = $eventHandler->triggerHandler($handler);
178
			if (!($handlerResponse['result'] ?? null) && (!isset($handlerResponse['hash'], $skipHandlers[$handlerId]) || $skipHandlers[$handlerId] !== $handlerResponse['hash'])) {
179
				$result[$handlerId] = $handlerResponse;
180
				if ('confirm' === ($handlerResponse['type'] ?? '')) {
181
					break;
182
				}
183
			}
184
		}
185
		$response = new Vtiger_Response();
186
		$response->setEmitType(Vtiger_Response::$EMIT_JSON);
187
		$response->setResult($result);
188
		$response->emit();
189
	}
190
191
	/**
192
	 * Quick change of record value.
193
	 *
194
	 * @param App\Request $request
195
	 */
196
	public function recordChanger(App\Request $request)
197
	{
198
		$this->getRecordModelFromRequest($request);
199
		$id = $request->getInteger('id');
200
		$field = App\Field::getQuickChangerFields($this->record->getModule()->getId())[$id] ?? false;
201
		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...
202
			throw new \App\Exceptions\NoPermittedToRecord('ERR_NO_PERMISSIONS_FOR_THE_RECORD', 406);
203
		}
204
		$fields = $this->record->getModule()->getFields();
205
		foreach ($field['values'] as $fieldName => $value) {
206
			if (isset($fields[$fieldName]) && $fields[$fieldName]->isEditable()) {
207
				$this->record->set($fieldName, $value);
208
			}
209
		}
210
		$this->record->save();
211
		$response = new Vtiger_Response();
212
		$response->setEmitType(Vtiger_Response::$EMIT_JSON);
213
		$response->setResult(true);
214
		$response->emit();
215
	}
216
217
	/**
218
	 * Multiple record save mode.
219
	 *
220
	 * @param App\Request $request
221
	 *
222
	 * @return void
223
	 */
224
	protected function multiSave(App\Request $request): void
225
	{
226
		$moduleName = $request->getByType('module', 'Alnum');
227
		$multiSaveField = $request->getByType('multiSaveField', 'Alnum');
228
		$sourceModule = $request->getByType('sourceModule', 'Alnum');
229
		$sourceView = $request->getByType('sourceView');
230
		if ('ListView' === $sourceView) {
231
			$request->set('module', $sourceModule);
232
			$ids = Vtiger_Mass_Action::getRecordsListFromRequest($request);
233
			$request->set('module', $moduleName);
234
		} elseif ('RelatedListView' === $sourceView) {
235
			$request->set('module', $request->getByType('relatedModule', 'Alnum'));
236
			$request->set('relatedModule', $request->getByType('sourceModule', 'Alnum'));
237
			$request->set('record', $request->getByType('relatedRecord', 'Alnum'));
238
			$ids = Vtiger_RelationAjax_Action::getRecordsListFromRequest($request);
239
			$request->set('module', $moduleName);
240
		}
241
		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...
242
			$recordModel = \Vtiger_Record_Model::getCleanInstance($this->record->getModuleName());
243
			$recordModel->setData($this->record->getData());
244
			$recordModel->ext = $this->record->ext;
245
			$recordModel->set($multiSaveField, $id);
246
			$recordModel->save();
247
		}
248
	}
249
}
250