3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2020 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2009 and the CiviCRM Licensing Exception. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2020
35 * This class delegates to the chosen DataSource to grab the data to be imported.
37 class CRM_Contact_Import_Form_DataSource
extends CRM_Core_Form
{
41 private $_dataSourceIsValid = FALSE;
43 private $_dataSourceClassFile;
45 private $_dataSourceClass;
48 * Set variables up before form is built.
50 * @throws \CRM_Core_Exception
52 public function preProcess() {
54 //Test database user privilege to create table(Temporary) CRM-4725
55 $errorScope = CRM_Core_TemporaryErrorScope
::ignoreException();
56 $daoTestPrivilege = new CRM_Core_DAO();
57 $daoTestPrivilege->query("CREATE TEMPORARY TABLE import_job_permission_one(test int) ENGINE=InnoDB");
58 $daoTestPrivilege->query("CREATE TEMPORARY TABLE import_job_permission_two(test int) ENGINE=InnoDB");
59 $daoTestPrivilege->query("DROP TEMPORARY TABLE IF EXISTS import_job_permission_one, import_job_permission_two");
62 if ($daoTestPrivilege->_lastError
) {
63 $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.'));
67 $config = CRM_Core_Config
::singleton();
68 $handler = opendir($config->uploadDir
);
69 $errorFiles = ['sqlImport.errors', 'sqlImport.conflicts', 'sqlImport.duplicates', 'sqlImport.mismatch'];
71 // check for post max size avoid when called twice
72 $snippet = CRM_Utils_Array
::value('snippet', $_GET, 0);
73 if (empty($snippet)) {
74 CRM_Utils_Number
::formatUnitSize(ini_get('post_max_size'), TRUE);
77 while ($file = readdir($handler)) {
78 if ($file != '.' && $file != '..' &&
79 in_array($file, $errorFiles) && !is_writable($config->uploadDir
. $file)
85 if (!empty($results)) {
86 $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.', [
87 1 => implode(', ', $results),
88 2 => $config->uploadDir
,
92 $this->_dataSourceIsValid
= FALSE;
93 $this->_dataSource
= CRM_Utils_Request
::retrieveValue(
101 $this->_params
= $this->controller
->exportValues($this->_name
);
102 if (!$this->_dataSource
) {
103 //considering dataSource as base criteria instead of hidden_dataSource.
104 $this->_dataSource
= CRM_Utils_Array
::value('dataSource',
106 CRM_Utils_Array
::value('dataSource',
110 $this->assign('showOnlyDataSourceFormPane', FALSE);
113 $this->assign('showOnlyDataSourceFormPane', TRUE);
116 $dataSources = $this->_getDataSources();
117 if ($this->_dataSource
&& isset($dataSources[$this->_dataSource
])) {
118 $this->_dataSourceIsValid
= TRUE;
119 $this->assign('showDataSourceFormPane', TRUE);
120 $dataSourcePath = explode('_', $this->_dataSource
);
121 $templateFile = "CRM/Contact/Import/Form/" . $dataSourcePath[3] . ".tpl";
122 $this->assign('dataSourceFormTemplateFile', $templateFile);
124 elseif ($this->_dataSource
) {
125 $this->invalidConfig('Invalid data source');
130 * Build the form object.
132 public function buildQuickForm() {
134 // If there's a dataSource in the query string, we need to load
135 // the form from the chosen DataSource class
136 if ($this->_dataSourceIsValid
) {
137 $this->_dataSourceClassFile
= str_replace('_', '/', $this->_dataSource
) . ".php";
138 require_once $this->_dataSourceClassFile
;
139 $this->_dataSourceClass
= new $this->_dataSource();
140 $this->_dataSourceClass
->buildQuickForm($this);
143 // Get list of data sources and display them as options
144 $dataSources = $this->_getDataSources();
146 $this->assign('urlPath', "civicrm/import");
147 $this->assign('urlPathVar', 'snippet=4');
149 $this->add('select', 'dataSource', ts('Data Source'), $dataSources, TRUE,
150 ['onchange' => 'buildDataSourceFormBlock(this.value);']
153 // duplicate handling options
154 $duplicateOptions = [];
155 $duplicateOptions[] = $this->createElement('radio',
156 NULL, NULL, ts('Skip'), CRM_Import_Parser
::DUPLICATE_SKIP
158 $duplicateOptions[] = $this->createElement('radio',
159 NULL, NULL, ts('Update'), CRM_Import_Parser
::DUPLICATE_UPDATE
161 $duplicateOptions[] = $this->createElement('radio',
162 NULL, NULL, ts('Fill'), CRM_Import_Parser
::DUPLICATE_FILL
164 $duplicateOptions[] = $this->createElement('radio',
165 NULL, NULL, ts('No Duplicate Checking'), CRM_Import_Parser
::DUPLICATE_NOCHECK
168 $this->addGroup($duplicateOptions, 'onDuplicate',
169 ts('For Duplicate Contacts')
172 $mappingArray = CRM_Core_BAO_Mapping
::getMappings('Import Contact');
174 $this->assign('savedMapping', $mappingArray);
175 $this->addElement('select', 'savedMapping', ts('Mapping Option'), ['' => ts('- select -')] +
$mappingArray);
177 $js = ['onClick' => "buildSubTypes();buildDedupeRules();"];
178 // contact types option
179 $contactOptions = [];
180 if (CRM_Contact_BAO_ContactType
::isActive('Individual')) {
181 $contactOptions[] = $this->createElement('radio',
182 NULL, NULL, ts('Individual'), CRM_Import_Parser
::CONTACT_INDIVIDUAL
, $js
185 if (CRM_Contact_BAO_ContactType
::isActive('Household')) {
186 $contactOptions[] = $this->createElement('radio',
187 NULL, NULL, ts('Household'), CRM_Import_Parser
::CONTACT_HOUSEHOLD
, $js
190 if (CRM_Contact_BAO_ContactType
::isActive('Organization')) {
191 $contactOptions[] = $this->createElement('radio',
192 NULL, NULL, ts('Organization'), CRM_Import_Parser
::CONTACT_ORGANIZATION
, $js
196 $this->addGroup($contactOptions, 'contactType',
200 $this->addElement('select', 'subType', ts('Subtype'));
201 $this->addElement('select', 'dedupe', ts('Dedupe Rule'));
203 CRM_Core_Form_Date
::buildAllowedDateFormats($this);
205 $config = CRM_Core_Config
::singleton();
207 if (CRM_Utils_GeocodeProvider
::getUsableClassName()) {
209 $this->addElement('checkbox', 'doGeocodeAddress', ts('Geocode addresses during import?'));
211 $this->assign('geoCode', $geoCode);
213 $this->addElement('text', 'fieldSeparator', ts('Import Field Separator'), ['size' => 2]);
215 if (Civi
::settings()->get('address_standardization_provider') == 'USPS') {
216 $this->addElement('checkbox', 'disableUSPS', ts('Disable USPS address validation during import?'));
222 'name' => ts('Continue'),
223 'spacing' => ' ',
228 'name' => ts('Cancel'),
234 * Set the default values of various form elements.
239 * reference to the array of default values
241 public function setDefaultValues() {
242 $config = CRM_Core_Config
::singleton();
244 'dataSource' => 'CRM_Import_DataSource_CSV',
245 'onDuplicate' => CRM_Import_Parser
::DUPLICATE_SKIP
,
246 'contactType' => CRM_Import_Parser
::CONTACT_INDIVIDUAL
,
247 'fieldSeparator' => $config->fieldSeparator
,
250 if ($loadeMapping = $this->get('loadedMapping')) {
251 $this->assign('loadedMapping', $loadeMapping);
252 $defaults['savedMapping'] = $loadeMapping;
262 private function _getDataSources() {
263 // Hmm... file-system scanners don't really belong in forms...
264 if (isset(Civi
::$statics[__CLASS__
]['datasources'])) {
265 return Civi
::$statics[__CLASS__
]['datasources'];
268 // Open the data source dir and scan it for class files
269 global $civicrm_root;
270 $dataSourceDir = $civicrm_root . DIRECTORY_SEPARATOR
. 'CRM' . DIRECTORY_SEPARATOR
. 'Import' . DIRECTORY_SEPARATOR
. 'DataSource' . DIRECTORY_SEPARATOR
;
272 if (!is_dir($dataSourceDir)) {
273 $this->invalidConfig("Import DataSource directory $dataSourceDir does not exist");
275 if (!$dataSourceHandle = opendir($dataSourceDir)) {
276 $this->invalidConfig("Unable to access DataSource directory $dataSourceDir");
279 while (($dataSourceFile = readdir($dataSourceHandle)) !== FALSE) {
280 $fileType = filetype($dataSourceDir . $dataSourceFile);
282 if (($fileType == 'file' ||
$fileType == 'link') &&
283 preg_match('/^(.+)\.php$/', $dataSourceFile, $matches)
285 $dataSourceClass = "CRM_Import_DataSource_" . $matches[1];
286 require_once $dataSourceDir . DIRECTORY_SEPARATOR
. $dataSourceFile;
287 $object = new $dataSourceClass();
288 $info = $object->getInfo();
289 if ($object->checkPermission()) {
290 $dataSources[$dataSourceClass] = $info['title'];
294 closedir($dataSourceHandle);
296 Civi
::$statics[__CLASS__
]['datasources'] = $dataSources;
301 * Call the DataSource's postProcess method.
303 public function postProcess() {
304 $this->controller
->resetPage('MapField');
306 if ($this->_dataSourceIsValid
) {
307 // Setup the params array
308 $this->_params
= $this->controller
->exportValues($this->_name
);
311 'onDuplicate' => $this->exportValue('onDuplicate'),
312 'dedupe' => $this->exportValue('dedupe'),
313 'contactType' => $this->exportValue('contactType'),
314 'contactSubType' => $this->exportValue('subType'),
315 'dateFormats' => $this->exportValue('dateFormats'),
316 'savedMapping' => $this->exportValue('savedMapping'),
319 foreach ($storeParams as $storeName => $value) {
320 $this->set($storeName, $value);
322 $this->set('disableUSPS', !empty($this->_params
['disableUSPS']));
324 $this->set('dataSource', $this->_params
['dataSource']);
325 $this->set('skipColumnHeader', CRM_Utils_Array
::value('skipColumnHeader', $this->_params
));
327 $session = CRM_Core_Session
::singleton();
328 $session->set('dateTypes', $storeParams['dateFormats']);
330 // Get the PEAR::DB object
331 $dao = new CRM_Core_DAO();
332 $db = $dao->getDatabaseConnection();
334 //hack to prevent multiple tables.
335 $this->_params
['import_table_name'] = $this->get('importTableName');
336 if (!$this->_params
['import_table_name']) {
337 $this->_params
['import_table_name'] = 'civicrm_import_job_' . md5(uniqid(rand(), TRUE));
340 $this->_dataSourceClass
->postProcess($this->_params
, $db, $this);
342 // We should have the data in the DB now, parse it
343 $importTableName = $this->get('importTableName');
344 $fieldNames = $this->_prepareImportTable($db, $importTableName);
347 $parser = new CRM_Contact_Import_Parser_Contact($mapper);
348 $parser->setMaxLinesToProcess(100);
349 $parser->run($importTableName,
351 CRM_Import_Parser
::MODE_MAPFIELD
,
352 $storeParams['contactType'],
354 $fieldNames['status'],
355 CRM_Import_Parser
::DUPLICATE_SKIP
,
357 CRM_Contact_Import_Parser
::DEFAULT_TIMEOUT
,
358 $storeParams['contactSubType'],
359 $storeParams['dedupe']
362 // add all the necessary variables to the form
366 $this->invalidConfig("Invalid DataSource on form post. This shouldn't happen!");
371 * Add a PK and status column to the import table so we can track our progress.
372 * Returns the name of the primary key and status columns
375 * @param string $importTableName
379 private function _prepareImportTable($db, $importTableName) {
380 /* TODO: Add a check for an existing _status field;
381 * if it exists, create __status instead and return that
384 $statusFieldName = '_status';
385 $primaryKeyName = '_id';
387 $this->set('primaryKeyName', $primaryKeyName);
388 $this->set('statusFieldName', $statusFieldName);
390 /* Make sure the PK is always last! We rely on this later.
391 * Should probably stop doing that at some point, but it
392 * would require moving to associative arrays rather than
393 * relying on numerical order of the fields. This could in
394 * turn complicate matters for some DataSources, which
395 * would also not be good. Decisions, decisions...
398 $alterQuery = "ALTER TABLE $importTableName
399 ADD COLUMN $statusFieldName VARCHAR(32)
400 DEFAULT 'NEW' NOT NULL,
401 ADD COLUMN ${statusFieldName}Msg TEXT,
402 ADD COLUMN $primaryKeyName INT PRIMARY KEY NOT NULL
404 $db->query($alterQuery);
406 return ['status' => $statusFieldName, 'pk' => $primaryKeyName];
410 * General function for handling invalid configuration.
412 * I was going to statusBounce them all but when I tested I was 'bouncing' to weird places
413 * whereas throwing an exception gave no behaviour change. So, I decided to centralise
414 * and we can 'flip the switch' later.
418 * @throws \CRM_Core_Exception
420 protected function invalidConfig($message) {
421 throw new CRM_Core_Exception($message);
425 * Return a descriptive name for the page, used in wizard header
430 public function getTitle() {
431 return ts('Choose Data Source');