Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
fee14197 | 4 | | CiviCRM version 5 | |
6a488035 | 5 | +--------------------------------------------------------------------+ |
f299f7db | 6 | | Copyright CiviCRM LLC (c) 2004-2020 | |
6a488035 TO |
7 | +--------------------------------------------------------------------+ |
8 | | This file is a part of CiviCRM. | | |
9 | | | | |
10 | | CiviCRM is free software; you can copy, modify, and distribute it | | |
11 | | under the terms of the GNU Affero General Public License | | |
c73475ea | 12 | | Version 3, 19 November 2009 and the CiviCRM Licensing Exception. | |
6a488035 TO |
13 | | | |
14 | | CiviCRM is distributed in the hope that it will be useful, but | | |
15 | | WITHOUT ANY WARRANTY; without even the implied warranty of | | |
16 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | | |
17 | | See the GNU Affero General Public License for more details. | | |
18 | | | | |
19 | | You should have received a copy of the GNU Affero General Public | | |
20 | | License and the CiviCRM Licensing Exception along | | |
21 | | with this program; if not, contact CiviCRM LLC | | |
22 | | at info[AT]civicrm[DOT]org. If you have questions about the | | |
23 | | GNU Affero General Public License or the licensing of CiviCRM, | | |
24 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | | |
25 | +--------------------------------------------------------------------+ | |
d25dd0ee | 26 | */ |
6a488035 TO |
27 | |
28 | /** | |
29 | * | |
30 | * @package CRM | |
f299f7db | 31 | * @copyright CiviCRM LLC (c) 2004-2020 |
6a488035 TO |
32 | */ |
33 | ||
34 | /** | |
f12c6f7d | 35 | * This class delegates to the chosen DataSource to grab the data to be imported. |
6a488035 | 36 | */ |
719a6fec | 37 | class CRM_Contact_Import_Form_DataSource extends CRM_Core_Form { |
6a488035 TO |
38 | |
39 | private $_dataSource; | |
40 | ||
41 | private $_dataSourceIsValid = FALSE; | |
42 | ||
43 | private $_dataSourceClassFile; | |
44 | ||
b0b2638a DL |
45 | private $_dataSourceClass; |
46 | ||
6a488035 | 47 | /** |
fe482240 | 48 | * Set variables up before form is built. |
54c32137 | 49 | * |
50 | * @throws \CRM_Core_Exception | |
6a488035 TO |
51 | */ |
52 | public function preProcess() { | |
53 | ||
54 | //Test database user privilege to create table(Temporary) CRM-4725 | |
6a4257d4 | 55 | $errorScope = CRM_Core_TemporaryErrorScope::ignoreException(); |
bed98343 | 56 | $daoTestPrivilege = new CRM_Core_DAO(); |
3aa9d6ce SL |
57 | $tempTable1 = CRM_Utils_SQL_TempTable::build()->getName(); |
58 | $tempTable2 = CRM_Utils_SQL_TempTable::build()->getName(); | |
59 | $daoTestPrivilege->query("CREATE TEMPORARY TABLE {$tempTable1} (test int) ENGINE=InnoDB"); | |
60 | $daoTestPrivilege->query("CREATE TEMPORARY TABLE {$tempTable2} (test int) ENGINE=InnoDB"); | |
61 | $daoTestPrivilege->query("DROP TEMPORARY TABLE IF EXISTS {$tempTable1}, {$tempTable2}"); | |
6a4257d4 | 62 | unset($errorScope); |
6a488035 TO |
63 | |
64 | if ($daoTestPrivilege->_lastError) { | |
15f71590 | 65 | $this->invalidConfig(ts('Database Configuration Error: Insufficient permissions. Import requires that the CiviCRM database user has permission to create temporary tables. Contact your site administrator for assistance.')); |
6a488035 TO |
66 | } |
67 | ||
be2fb01f | 68 | $results = []; |
353ffa53 TO |
69 | $config = CRM_Core_Config::singleton(); |
70 | $handler = opendir($config->uploadDir); | |
be2fb01f | 71 | $errorFiles = ['sqlImport.errors', 'sqlImport.conflicts', 'sqlImport.duplicates', 'sqlImport.mismatch']; |
6a488035 | 72 | |
f4a17080 | 73 | // check for post max size avoid when called twice |
74 | $snippet = CRM_Utils_Array::value('snippet', $_GET, 0); | |
75 | if (empty($snippet)) { | |
2e966dd5 | 76 | CRM_Utils_Number::formatUnitSize(ini_get('post_max_size'), TRUE); |
f4a17080 | 77 | } |
66dc6009 | 78 | |
6a488035 TO |
79 | while ($file = readdir($handler)) { |
80 | if ($file != '.' && $file != '..' && | |
81 | in_array($file, $errorFiles) && !is_writable($config->uploadDir . $file) | |
82 | ) { | |
83 | $results[] = $file; | |
84 | } | |
85 | } | |
86 | closedir($handler); | |
87 | if (!empty($results)) { | |
15f71590 | 88 | $this->invalidConfig(ts('<b>%1</b> file(s) in %2 directory are not writable. Listed file(s) might be used during the import to log the errors occurred during Import process. Contact your site administrator for assistance.', [ |
69078420 SL |
89 | 1 => implode(', ', $results), |
90 | 2 => $config->uploadDir, | |
91 | ])); | |
6a488035 TO |
92 | } |
93 | ||
94 | $this->_dataSourceIsValid = FALSE; | |
54c32137 | 95 | $this->_dataSource = CRM_Utils_Request::retrieveValue( |
0a11d4d8 DL |
96 | 'dataSource', |
97 | 'String', | |
0a11d4d8 | 98 | NULL, |
54c32137 | 99 | FALSE, |
0a11d4d8 | 100 | 'GET' |
6a488035 TO |
101 | ); |
102 | ||
103 | $this->_params = $this->controller->exportValues($this->_name); | |
104 | if (!$this->_dataSource) { | |
105 | //considering dataSource as base criteria instead of hidden_dataSource. | |
106 | $this->_dataSource = CRM_Utils_Array::value('dataSource', | |
107 | $_POST, | |
108 | CRM_Utils_Array::value('dataSource', | |
109 | $this->_params | |
110 | ) | |
111 | ); | |
112 | $this->assign('showOnlyDataSourceFormPane', FALSE); | |
113 | } | |
114 | else { | |
115 | $this->assign('showOnlyDataSourceFormPane', TRUE); | |
116 | } | |
117 | ||
8ccb59ba TO |
118 | $dataSources = $this->_getDataSources(); |
119 | if ($this->_dataSource && isset($dataSources[$this->_dataSource])) { | |
6a488035 TO |
120 | $this->_dataSourceIsValid = TRUE; |
121 | $this->assign('showDataSourceFormPane', TRUE); | |
122 | $dataSourcePath = explode('_', $this->_dataSource); | |
719a6fec | 123 | $templateFile = "CRM/Contact/Import/Form/" . $dataSourcePath[3] . ".tpl"; |
6a488035 TO |
124 | $this->assign('dataSourceFormTemplateFile', $templateFile); |
125 | } | |
8ccb59ba | 126 | elseif ($this->_dataSource) { |
15f71590 | 127 | $this->invalidConfig('Invalid data source'); |
8ccb59ba | 128 | } |
6a488035 TO |
129 | } |
130 | ||
131 | /** | |
fe482240 | 132 | * Build the form object. |
6a488035 | 133 | */ |
6a488035 TO |
134 | public function buildQuickForm() { |
135 | ||
136 | // If there's a dataSource in the query string, we need to load | |
137 | // the form from the chosen DataSource class | |
138 | if ($this->_dataSourceIsValid) { | |
139 | $this->_dataSourceClassFile = str_replace('_', '/', $this->_dataSource) . ".php"; | |
140 | require_once $this->_dataSourceClassFile; | |
bed98343 | 141 | $this->_dataSourceClass = new $this->_dataSource(); |
481a74f4 | 142 | $this->_dataSourceClass->buildQuickForm($this); |
6a488035 TO |
143 | } |
144 | ||
145 | // Get list of data sources and display them as options | |
146 | $dataSources = $this->_getDataSources(); | |
147 | ||
148 | $this->assign('urlPath', "civicrm/import"); | |
149 | $this->assign('urlPathVar', 'snippet=4'); | |
150 | ||
151 | $this->add('select', 'dataSource', ts('Data Source'), $dataSources, TRUE, | |
be2fb01f | 152 | ['onchange' => 'buildDataSourceFormBlock(this.value);'] |
6a488035 TO |
153 | ); |
154 | ||
155 | // duplicate handling options | |
be2fb01f | 156 | $duplicateOptions = []; |
6a488035 | 157 | $duplicateOptions[] = $this->createElement('radio', |
a05662ef | 158 | NULL, NULL, ts('Skip'), CRM_Import_Parser::DUPLICATE_SKIP |
6a488035 TO |
159 | ); |
160 | $duplicateOptions[] = $this->createElement('radio', | |
a05662ef | 161 | NULL, NULL, ts('Update'), CRM_Import_Parser::DUPLICATE_UPDATE |
6a488035 TO |
162 | ); |
163 | $duplicateOptions[] = $this->createElement('radio', | |
a05662ef | 164 | NULL, NULL, ts('Fill'), CRM_Import_Parser::DUPLICATE_FILL |
6a488035 TO |
165 | ); |
166 | $duplicateOptions[] = $this->createElement('radio', | |
a05662ef | 167 | NULL, NULL, ts('No Duplicate Checking'), CRM_Import_Parser::DUPLICATE_NOCHECK |
6a488035 TO |
168 | ); |
169 | ||
170 | $this->addGroup($duplicateOptions, 'onDuplicate', | |
171 | ts('For Duplicate Contacts') | |
172 | ); | |
173 | ||
f8df7165 | 174 | $mappingArray = CRM_Core_BAO_Mapping::getMappings('Import Contact'); |
6a488035 TO |
175 | |
176 | $this->assign('savedMapping', $mappingArray); | |
be2fb01f | 177 | $this->addElement('select', 'savedMapping', ts('Mapping Option'), ['' => ts('- select -')] + $mappingArray); |
6a488035 | 178 | |
be2fb01f | 179 | $js = ['onClick' => "buildSubTypes();buildDedupeRules();"]; |
6a488035 | 180 | // contact types option |
be2fb01f | 181 | $contactOptions = []; |
6a488035 TO |
182 | if (CRM_Contact_BAO_ContactType::isActive('Individual')) { |
183 | $contactOptions[] = $this->createElement('radio', | |
a05662ef | 184 | NULL, NULL, ts('Individual'), CRM_Import_Parser::CONTACT_INDIVIDUAL, $js |
6a488035 TO |
185 | ); |
186 | } | |
187 | if (CRM_Contact_BAO_ContactType::isActive('Household')) { | |
188 | $contactOptions[] = $this->createElement('radio', | |
a05662ef | 189 | NULL, NULL, ts('Household'), CRM_Import_Parser::CONTACT_HOUSEHOLD, $js |
6a488035 TO |
190 | ); |
191 | } | |
192 | if (CRM_Contact_BAO_ContactType::isActive('Organization')) { | |
193 | $contactOptions[] = $this->createElement('radio', | |
a05662ef | 194 | NULL, NULL, ts('Organization'), CRM_Import_Parser::CONTACT_ORGANIZATION, $js |
6a488035 TO |
195 | ); |
196 | } | |
197 | ||
198 | $this->addGroup($contactOptions, 'contactType', | |
199 | ts('Contact Type') | |
200 | ); | |
201 | ||
202 | $this->addElement('select', 'subType', ts('Subtype')); | |
203 | $this->addElement('select', 'dedupe', ts('Dedupe Rule')); | |
204 | ||
205 | CRM_Core_Form_Date::buildAllowedDateFormats($this); | |
206 | ||
207 | $config = CRM_Core_Config::singleton(); | |
208 | $geoCode = FALSE; | |
4882d275 | 209 | if (CRM_Utils_GeocodeProvider::getUsableClassName()) { |
6a488035 | 210 | $geoCode = TRUE; |
3255187a | 211 | $this->addElement('checkbox', 'doGeocodeAddress', ts('Geocode addresses during import?')); |
6a488035 TO |
212 | } |
213 | $this->assign('geoCode', $geoCode); | |
214 | ||
be2fb01f | 215 | $this->addElement('text', 'fieldSeparator', ts('Import Field Separator'), ['size' => 2]); |
6a488035 | 216 | |
fd830836 DRJ |
217 | if (Civi::settings()->get('address_standardization_provider') == 'USPS') { |
218 | $this->addElement('checkbox', 'disableUSPS', ts('Disable USPS address validation during import?')); | |
219 | } | |
220 | ||
be2fb01f | 221 | $this->addButtons([ |
69078420 SL |
222 | [ |
223 | 'type' => 'upload', | |
224 | 'name' => ts('Continue'), | |
225 | 'spacing' => ' ', | |
226 | 'isDefault' => TRUE, | |
227 | ], | |
228 | [ | |
229 | 'type' => 'cancel', | |
230 | 'name' => ts('Cancel'), | |
231 | ], | |
232 | ]); | |
6a488035 TO |
233 | } |
234 | ||
86538308 | 235 | /** |
2b4bc760 | 236 | * Set the default values of various form elements. |
86538308 EM |
237 | * |
238 | * access public | |
239 | * | |
a6c01b45 CW |
240 | * @return array |
241 | * reference to the array of default values | |
86538308 | 242 | */ |
00be9182 | 243 | public function setDefaultValues() { |
6a488035 | 244 | $config = CRM_Core_Config::singleton(); |
be2fb01f | 245 | $defaults = [ |
6a488035 | 246 | 'dataSource' => 'CRM_Import_DataSource_CSV', |
a05662ef CW |
247 | 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP, |
248 | 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL, | |
6a488035 | 249 | 'fieldSeparator' => $config->fieldSeparator, |
be2fb01f | 250 | ]; |
6a488035 TO |
251 | |
252 | if ($loadeMapping = $this->get('loadedMapping')) { | |
253 | $this->assign('loadedMapping', $loadeMapping); | |
254 | $defaults['savedMapping'] = $loadeMapping; | |
255 | } | |
256 | ||
257 | return $defaults; | |
258 | } | |
259 | ||
86538308 EM |
260 | /** |
261 | * @return array | |
262 | * @throws Exception | |
263 | */ | |
6a488035 | 264 | private function _getDataSources() { |
8ccb59ba TO |
265 | // Hmm... file-system scanners don't really belong in forms... |
266 | if (isset(Civi::$statics[__CLASS__]['datasources'])) { | |
267 | return Civi::$statics[__CLASS__]['datasources']; | |
268 | } | |
269 | ||
6a488035 | 270 | // Open the data source dir and scan it for class files |
201d210b TO |
271 | global $civicrm_root; |
272 | $dataSourceDir = $civicrm_root . DIRECTORY_SEPARATOR . 'CRM' . DIRECTORY_SEPARATOR . 'Import' . DIRECTORY_SEPARATOR . 'DataSource' . DIRECTORY_SEPARATOR; | |
be2fb01f | 273 | $dataSources = []; |
6a488035 | 274 | if (!is_dir($dataSourceDir)) { |
15f71590 | 275 | $this->invalidConfig("Import DataSource directory $dataSourceDir does not exist"); |
6a488035 TO |
276 | } |
277 | if (!$dataSourceHandle = opendir($dataSourceDir)) { | |
15f71590 | 278 | $this->invalidConfig("Unable to access DataSource directory $dataSourceDir"); |
6a488035 TO |
279 | } |
280 | ||
281 | while (($dataSourceFile = readdir($dataSourceHandle)) !== FALSE) { | |
282 | $fileType = filetype($dataSourceDir . $dataSourceFile); | |
be2fb01f | 283 | $matches = []; |
6a488035 TO |
284 | if (($fileType == 'file' || $fileType == 'link') && |
285 | preg_match('/^(.+)\.php$/', $dataSourceFile, $matches) | |
286 | ) { | |
287 | $dataSourceClass = "CRM_Import_DataSource_" . $matches[1]; | |
288 | require_once $dataSourceDir . DIRECTORY_SEPARATOR . $dataSourceFile; | |
bed98343 | 289 | $object = new $dataSourceClass(); |
353ffa53 | 290 | $info = $object->getInfo(); |
8ccb59ba TO |
291 | if ($object->checkPermission()) { |
292 | $dataSources[$dataSourceClass] = $info['title']; | |
293 | } | |
6a488035 TO |
294 | } |
295 | } | |
296 | closedir($dataSourceHandle); | |
8ccb59ba TO |
297 | |
298 | Civi::$statics[__CLASS__]['datasources'] = $dataSources; | |
6a488035 TO |
299 | return $dataSources; |
300 | } | |
301 | ||
302 | /** | |
f12c6f7d | 303 | * Call the DataSource's postProcess method. |
6a488035 TO |
304 | */ |
305 | public function postProcess() { | |
306 | $this->controller->resetPage('MapField'); | |
307 | ||
308 | if ($this->_dataSourceIsValid) { | |
309 | // Setup the params array | |
310 | $this->_params = $this->controller->exportValues($this->_name); | |
311 | ||
be2fb01f | 312 | $storeParams = [ |
791465be | 313 | 'onDuplicate' => $this->exportValue('onDuplicate'), |
314 | 'dedupe' => $this->exportValue('dedupe'), | |
315 | 'contactType' => $this->exportValue('contactType'), | |
316 | 'contactSubType' => $this->exportValue('subType'), | |
317 | 'dateFormats' => $this->exportValue('dateFormats'), | |
318 | 'savedMapping' => $this->exportValue('savedMapping'), | |
be2fb01f | 319 | ]; |
6a488035 | 320 | |
791465be | 321 | foreach ($storeParams as $storeName => $value) { |
322 | $this->set($storeName, $value); | |
6a488035 | 323 | } |
fd830836 | 324 | $this->set('disableUSPS', !empty($this->_params['disableUSPS'])); |
6a488035 TO |
325 | |
326 | $this->set('dataSource', $this->_params['dataSource']); | |
327 | $this->set('skipColumnHeader', CRM_Utils_Array::value('skipColumnHeader', $this->_params)); | |
328 | ||
329 | $session = CRM_Core_Session::singleton(); | |
791465be | 330 | $session->set('dateTypes', $storeParams['dateFormats']); |
6a488035 TO |
331 | |
332 | // Get the PEAR::DB object | |
333 | $dao = new CRM_Core_DAO(); | |
334 | $db = $dao->getDatabaseConnection(); | |
335 | ||
336 | //hack to prevent multiple tables. | |
337 | $this->_params['import_table_name'] = $this->get('importTableName'); | |
338 | if (!$this->_params['import_table_name']) { | |
339 | $this->_params['import_table_name'] = 'civicrm_import_job_' . md5(uniqid(rand(), TRUE)); | |
340 | } | |
341 | ||
481a74f4 | 342 | $this->_dataSourceClass->postProcess($this->_params, $db, $this); |
6a488035 TO |
343 | |
344 | // We should have the data in the DB now, parse it | |
345 | $importTableName = $this->get('importTableName'); | |
353ffa53 | 346 | $fieldNames = $this->_prepareImportTable($db, $importTableName); |
be2fb01f | 347 | $mapper = []; |
6a488035 | 348 | |
719a6fec | 349 | $parser = new CRM_Contact_Import_Parser_Contact($mapper); |
6a488035 TO |
350 | $parser->setMaxLinesToProcess(100); |
351 | $parser->run($importTableName, | |
352 | $mapper, | |
a05662ef | 353 | CRM_Import_Parser::MODE_MAPFIELD, |
791465be | 354 | $storeParams['contactType'], |
6a488035 TO |
355 | $fieldNames['pk'], |
356 | $fieldNames['status'], | |
a05662ef | 357 | CRM_Import_Parser::DUPLICATE_SKIP, |
6a488035 | 358 | NULL, NULL, FALSE, |
719a6fec | 359 | CRM_Contact_Import_Parser::DEFAULT_TIMEOUT, |
791465be | 360 | $storeParams['contactSubType'], |
361 | $storeParams['dedupe'] | |
6a488035 TO |
362 | ); |
363 | ||
364 | // add all the necessary variables to the form | |
365 | $parser->set($this); | |
366 | } | |
367 | else { | |
15f71590 | 368 | $this->invalidConfig("Invalid DataSource on form post. This shouldn't happen!"); |
6a488035 TO |
369 | } |
370 | } | |
371 | ||
372 | /** | |
fe482240 | 373 | * Add a PK and status column to the import table so we can track our progress. |
6a488035 TO |
374 | * Returns the name of the primary key and status columns |
375 | * | |
77b97be7 | 376 | * @param $db |
100fef9d | 377 | * @param string $importTableName |
77b97be7 | 378 | * |
6a488035 | 379 | * @return array |
6a488035 TO |
380 | */ |
381 | private function _prepareImportTable($db, $importTableName) { | |
382 | /* TODO: Add a check for an existing _status field; | |
e70a7fc0 TO |
383 | * if it exists, create __status instead and return that |
384 | */ | |
6a488035 TO |
385 | |
386 | $statusFieldName = '_status'; | |
387 | $primaryKeyName = '_id'; | |
388 | ||
389 | $this->set('primaryKeyName', $primaryKeyName); | |
390 | $this->set('statusFieldName', $statusFieldName); | |
391 | ||
392 | /* Make sure the PK is always last! We rely on this later. | |
e70a7fc0 TO |
393 | * Should probably stop doing that at some point, but it |
394 | * would require moving to associative arrays rather than | |
395 | * relying on numerical order of the fields. This could in | |
396 | * turn complicate matters for some DataSources, which | |
397 | * would also not be good. Decisions, decisions... | |
398 | */ | |
6a488035 TO |
399 | |
400 | $alterQuery = "ALTER TABLE $importTableName | |
401 | ADD COLUMN $statusFieldName VARCHAR(32) | |
402 | DEFAULT 'NEW' NOT NULL, | |
403 | ADD COLUMN ${statusFieldName}Msg TEXT, | |
404 | ADD COLUMN $primaryKeyName INT PRIMARY KEY NOT NULL | |
405 | AUTO_INCREMENT"; | |
406 | $db->query($alterQuery); | |
407 | ||
be2fb01f | 408 | return ['status' => $statusFieldName, 'pk' => $primaryKeyName]; |
6a488035 TO |
409 | } |
410 | ||
15f71590 | 411 | /** |
412 | * General function for handling invalid configuration. | |
413 | * | |
414 | * I was going to statusBounce them all but when I tested I was 'bouncing' to weird places | |
415 | * whereas throwing an exception gave no behaviour change. So, I decided to centralise | |
416 | * and we can 'flip the switch' later. | |
417 | * | |
418 | * @param $message | |
419 | * | |
420 | * @throws \CRM_Core_Exception | |
421 | */ | |
422 | protected function invalidConfig($message) { | |
423 | throw new CRM_Core_Exception($message); | |
424 | } | |
425 | ||
6a488035 TO |
426 | /** |
427 | * Return a descriptive name for the page, used in wizard header | |
428 | * | |
429 | * | |
430 | * @return string | |
6a488035 TO |
431 | */ |
432 | public function getTitle() { | |
433 | return ts('Choose Data Source'); | |
434 | } | |
96025800 | 435 | |
6a488035 | 436 | } |