Use hard-coding when variable is unchangeable
[civicrm-core.git] / CRM / Import / Forms.php
CommitLineData
9d7974eb
EM
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12/**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
7b057b66
EM
18use Civi\Api4\UserJob;
19
9d7974eb
EM
20/**
21 * This class helps the forms within the import flow access submitted & parsed values.
22 */
23class CRM_Import_Forms extends CRM_Core_Form {
24
25 /**
7b057b66
EM
26 * User job id.
27 *
28 * This is the primary key of the civicrm_user_job table which is used to
29 * track the import.
30 *
31 * @var int
32 */
33 protected $userJobID;
34
35 /**
36 * @return int|null
37 */
38 public function getUserJobID(): ?int {
39 if (!$this->userJobID && $this->get('user_job_id')) {
40 $this->userJobID = $this->get('user_job_id');
41 }
42 return $this->userJobID;
43 }
44
45 /**
46 * Set user job ID.
47 *
48 * @param int $userJobID
49 */
50 public function setUserJobID(int $userJobID): void {
51 $this->userJobID = $userJobID;
52 // This set allows other forms in the flow ot use $this->get('user_job_id').
53 $this->set('user_job_id', $userJobID);
54 }
55
56 /**
57 * User job details.
58 *
59 * This is the relevant row from civicrm_user_job.
60 *
61 * @var array
62 */
63 protected $userJob;
64
65 /**
66 * Get User Job.
67 *
68 * API call to retrieve the userJob row.
69 *
70 * @return array
71 *
72 * @throws \API_Exception
73 */
74 protected function getUserJob(): array {
75 if (!$this->userJob) {
76 $this->userJob = UserJob::get()
77 ->addWhere('id', '=', $this->getUserJobID())
78 ->execute()
79 ->first();
80 }
81 return $this->userJob;
82 }
83
84 /**
85 * Get submitted values stored in the user job.
86 *
87 * @return array
88 * @throws \API_Exception
89 */
90 protected function getUserJobSubmittedValues(): array {
91 return $this->getUserJob()['metadata']['submitted_values'];
92 }
93
94 /**
95 * Fields that may be submitted on any form in the flow.
96 *
97 * @var string[]
98 */
99 protected $submittableFields = [
100 // Skip column header is actually a field that would be added from the
101 // datasource - but currently only in contact, it is always there for
102 // other imports, ditto uploadFile.
103 'skipColumnHeader' => 'DataSource',
104 'fieldSeparator' => 'DataSource',
105 'uploadFile' => 'DataSource',
106 'contactType' => 'DataSource',
107 'dateFormats' => 'DataSource',
108 'savedMapping' => 'DataSource',
109 'dataSource' => 'DataSource',
110 ];
111
112 /**
113 * Get the submitted value, accessing it from whatever form in the flow it is
114 * submitted on.
115 *
9d7974eb
EM
116 * @param string $fieldName
117 *
118 * @return mixed|null
7b057b66 119 * @throws \CRM_Core_Exception
9d7974eb
EM
120 */
121 public function getSubmittedValue(string $fieldName) {
7b057b66
EM
122 if ($fieldName === 'dataSource') {
123 // Hard-coded handling for DataSource as it affects the contents of
124 // getSubmittableFields and can cause a loop.
125 return $this->controller->exportValue('DataSource', 'dataSource');
126 }
127 $mappedValues = $this->getSubmittableFields();
9d7974eb
EM
128 if (array_key_exists($fieldName, $mappedValues)) {
129 return $this->controller->exportValue($mappedValues[$fieldName], $fieldName);
130 }
131 return parent::getSubmittedValue($fieldName);
132
133 }
134
7b057b66
EM
135 /**
136 * Get values submitted on any form in the multi-page import flow.
137 *
138 * @return array
139 */
140 public function getSubmittedValues(): array {
141 $values = [];
142 foreach (array_keys($this->getSubmittableFields()) as $key) {
143 $values[$key] = $this->getSubmittedValue($key);
144 }
145 return $values;
146 }
147
39dc35d4
EM
148 /**
149 * Get the available datasource.
150 *
151 * Permission dependent, this will look like
152 * [
153 * 'CRM_Import_DataSource_CSV' => 'Comma-Separated Values (CSV)',
154 * 'CRM_Import_DataSource_SQL' => 'SQL Query',
155 * ]
156 *
157 * The label is translated.
158 *
159 * @return array
160 */
161 protected function getDataSources(): array {
162 $dataSources = [];
163 foreach (['CRM_Import_DataSource_SQL', 'CRM_Import_DataSource_CSV'] as $dataSourceClass) {
164 $object = new $dataSourceClass();
165 if ($object->checkPermission()) {
166 $dataSources[$dataSourceClass] = $object->getInfo()['title'];
167 }
168 }
169 return $dataSources;
170 }
171
d452dfe6
EM
172 /**
173 * Get the name of the datasource class.
174 *
175 * This function prioritises retrieving from GET and POST over 'submitted'.
176 * The reason for this is the submitted array will hold the previous submissions
177 * data until after buildForm is called.
178 *
179 * This is problematic in the forward->back flow & option changing flow. As in....
180 *
181 * 1) Load DataSource form - initial default datasource is set to CSV and the
182 * form is via ajax (this calls DataSourceConfig to get the data).
183 * 2) User changes the source to SQL - the ajax updates the html but the
184 * form was built with the expectation that the csv-specific fields would be
185 * required.
186 * 3) When the user submits Quickform calls preProcess and buildForm and THEN
187 * retrieves the submitted values based on what has been added in buildForm.
188 * Only the submitted values for fields added in buildForm are available - but
189 * these have to be added BEFORE the submitted values are determined. Hence
190 * we look in the POST or GET to get the updated value.
191 *
192 * Note that an imminent refactor will involve storing the values in the
193 * civicrm_user_job table - this will hopefully help with a known (not new)
194 * issue whereby the previously submitted values (eg. skipColumnHeader has
195 * been checked or sql has been filled in) are not loaded via the ajax request.
196 *
197 * @return string|null
198 *
199 * @throws \CRM_Core_Exception
200 */
c2562331 201 protected function getDataSourceClassName(): string {
d452dfe6
EM
202 $className = CRM_Utils_Request::retrieveValue(
203 'dataSource',
204 'String'
205 );
206 if (!$className) {
207 $className = $this->getSubmittedValue('dataSource');
208 }
209 if (!$className) {
210 $className = $this->getDefaultDataSource();
211 }
212 if ($this->getDataSources()[$className]) {
213 return $className;
214 }
215 throw new CRM_Core_Exception('Invalid data source');
216 }
217
218 /**
219 * Allow the datasource class to add fields.
220 *
221 * This is called as a snippet in DataSourceConfig and
222 * also from DataSource::buildForm to add the fields such
223 * that quick form picks them up.
224 *
225 * @throws \CRM_Core_Exception
226 */
227 protected function buildDataSourceFields(): void {
7b057b66
EM
228 $dataSourceClass = $this->getDataSourceObject();
229 if ($dataSourceClass) {
230 $dataSourceClass->buildQuickForm($this);
231 }
232 }
233
234 /**
235 * Get the relevant datasource object.
236 *
237 * @return \CRM_Import_DataSource|null
238 *
239 * @throws \CRM_Core_Exception
240 */
241 protected function getDataSourceObject(): ?CRM_Import_DataSource {
242 $className = $this->getDataSourceClassName();
243 if ($className) {
244 /* @var CRM_Import_DataSource $dataSource */
245 return new $className($this->getUserJobID());
246 }
247 return NULL;
248 }
249
250 /**
251 * Allow the datasource class to add fields.
252 *
253 * This is called as a snippet in DataSourceConfig and
254 * also from DataSource::buildForm to add the fields such
255 * that quick form picks them up.
256 *
257 * @throws \CRM_Core_Exception
258 */
259 protected function getDataSourceFields(): array {
d452dfe6
EM
260 $className = $this->getDataSourceClassName();
261 if ($className) {
7b057b66 262 /* @var CRM_Import_DataSource $dataSourceClass */
d452dfe6 263 $dataSourceClass = new $className();
7b057b66 264 return $dataSourceClass->getSubmittableFields();
d452dfe6 265 }
7b057b66 266 return [];
d452dfe6
EM
267 }
268
269 /**
270 * Get the default datasource.
271 *
272 * @return string
273 */
274 protected function getDefaultDataSource(): string {
275 return 'CRM_Import_DataSource_CSV';
276 }
277
7b057b66
EM
278 /**
279 * Get the fields that can be submitted in the Import form flow.
280 *
281 * These could be on any form in the flow & are accessed the same way from
282 * all forms.
283 *
284 * @return string[]
285 * @throws \CRM_Core_Exception
286 */
287 protected function getSubmittableFields(): array {
288 $dataSourceFields = array_fill_keys($this->getDataSourceFields(), 'DataSource');
289 return array_merge($this->submittableFields, $dataSourceFields);
290 }
291
292 /**
293 * Create a user job to track the import.
294 *
295 * @return int
296 *
297 * @throws \API_Exception
298 */
299 protected function createUserJob(): int {
300 $id = UserJob::create(FALSE)
301 ->setValues([
302 'created_id' => CRM_Core_Session::getLoggedInContactID(),
303 'type_id:name' => 'contact_import',
304 'status_id:name' => 'draft',
305 // This suggests the data could be cleaned up after this.
306 'expires_date' => '+ 1 week',
307 'metadata' => [
308 'submitted_values' => $this->getSubmittedValues(),
309 ],
310 ])
311 ->execute()
312 ->first()['id'];
313 $this->setUserJobID($id);
314 return $id;
315 }
316
317 /**
318 * @param string $key
319 * @param array $data
320 *
321 * @throws \API_Exception
322 * @throws \Civi\API\Exception\UnauthorizedException
323 */
324 protected function updateUserJobMetadata(string $key, array $data): void {
325 $metaData = array_merge(
326 $this->getUserJob()['metadata'],
327 [$key => $data]
328 );
329 UserJob::update(FALSE)
330 ->addWhere('id', '=', $this->getUserJobID())
331 ->setValues(['metadata' => $metaData])
332 ->execute();
333 $this->userJob['metadata'] = $metaData;
334 }
335
9d7974eb 336}