fix CRM-12027
[civicrm-core.git] / CRM / Import / Form / DataSource.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
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 |
12 | Version 3, 19 November 2009. |
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 +--------------------------------------------------------------------+
26*/
27
28/**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2013
32 * $Id$
33 *
34 */
35
36/**
37 * This class delegates to the chosen DataSource to grab the data to be
38 * imported.
39 */
40class CRM_Import_Form_DataSource extends CRM_Core_Form {
41
42 private $_dataSource;
43
44 private $_dataSourceIsValid = FALSE;
45
46 private $_dataSourceClassFile;
47
b0b2638a
DL
48 private $_dataSourceClass;
49
6a488035
TO
50 /**
51 * Function to set variables up before form is built
52 *
53 * @return void
54 * @access public
55 */
56 public function preProcess() {
57
58 //Test database user privilege to create table(Temporary) CRM-4725
59 CRM_Core_Error::ignoreException();
60 $daoTestPrivilege = new CRM_Core_DAO;
61 $daoTestPrivilege->query("CREATE TEMPORARY TABLE import_job_permission_one(test int) ENGINE=InnoDB");
62 $daoTestPrivilege->query("CREATE TEMPORARY TABLE import_job_permission_two(test int) ENGINE=InnoDB");
63 $daoTestPrivilege->query("DROP TABLE IF EXISTS import_job_permission_one, import_job_permission_two");
64 CRM_Core_Error::setCallback();
65
66 if ($daoTestPrivilege->_lastError) {
67 CRM_Core_Error::fatal(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.'));
68 }
69
70 $results = array();
71 $config = CRM_Core_Config::singleton();
72 $handler = opendir($config->uploadDir);
73 $errorFiles = array('sqlImport.errors', 'sqlImport.conflicts', 'sqlImport.duplicates', 'sqlImport.mismatch');
74
75 while ($file = readdir($handler)) {
76 if ($file != '.' && $file != '..' &&
77 in_array($file, $errorFiles) && !is_writable($config->uploadDir . $file)
78 ) {
79 $results[] = $file;
80 }
81 }
82 closedir($handler);
83 if (!empty($results)) {
84 CRM_Core_Error::fatal(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.', array(1 => implode(', ', $results), 2 => $config->uploadDir)));
85 }
86
87 $this->_dataSourceIsValid = FALSE;
88 $this->_dataSource = CRM_Utils_Request::retrieve('dataSource', 'String',
89 CRM_Core_DAO::$_nullObject
90 );
91
92 $this->_params = $this->controller->exportValues($this->_name);
93 if (!$this->_dataSource) {
94 //considering dataSource as base criteria instead of hidden_dataSource.
95 $this->_dataSource = CRM_Utils_Array::value('dataSource',
96 $_POST,
97 CRM_Utils_Array::value('dataSource',
98 $this->_params
99 )
100 );
101 $this->assign('showOnlyDataSourceFormPane', FALSE);
102 }
103 else {
104 $this->assign('showOnlyDataSourceFormPane', TRUE);
105 }
106
107 if (strpos($this->_dataSource, 'CRM_Import_DataSource_') === 0) {
108 $this->_dataSourceIsValid = TRUE;
109 $this->assign('showDataSourceFormPane', TRUE);
110 $dataSourcePath = explode('_', $this->_dataSource);
111 $templateFile = "CRM/Import/Form/" . $dataSourcePath[3] . ".tpl";
112 $this->assign('dataSourceFormTemplateFile', $templateFile);
113 }
114 }
115
116 /**
117 * Function to actually build the form
118 *
119 * @return None
120 * @access public
121 */
122
123 public function buildQuickForm() {
124
125 // If there's a dataSource in the query string, we need to load
126 // the form from the chosen DataSource class
127 if ($this->_dataSourceIsValid) {
128 $this->_dataSourceClassFile = str_replace('_', '/', $this->_dataSource) . ".php";
129 require_once $this->_dataSourceClassFile;
b0b2638a
DL
130 $this->_dataSourceClass = new $this->_dataSource;
131 $this->_dataSourceClass->buildQuickForm( $this );
6a488035
TO
132 }
133
134 // Get list of data sources and display them as options
135 $dataSources = $this->_getDataSources();
136
137 $this->assign('urlPath', "civicrm/import");
138 $this->assign('urlPathVar', 'snippet=4');
139
140 $this->add('select', 'dataSource', ts('Data Source'), $dataSources, TRUE,
141 array('onchange' => 'buildDataSourceFormBlock(this.value);')
142 );
143
144 // duplicate handling options
145 $duplicateOptions = array();
146 $duplicateOptions[] = $this->createElement('radio',
147 NULL, NULL, ts('Skip'), CRM_Import_Parser::DUPLICATE_SKIP
148 );
149 $duplicateOptions[] = $this->createElement('radio',
150 NULL, NULL, ts('Update'), CRM_Import_Parser::DUPLICATE_UPDATE
151 );
152 $duplicateOptions[] = $this->createElement('radio',
153 NULL, NULL, ts('Fill'), CRM_Import_Parser::DUPLICATE_FILL
154 );
155 $duplicateOptions[] = $this->createElement('radio',
156 NULL, NULL, ts('No Duplicate Checking'), CRM_Import_Parser::DUPLICATE_NOCHECK
157 );
158
159 $this->addGroup($duplicateOptions, 'onDuplicate',
160 ts('For Duplicate Contacts')
161 );
162
163 $mappingArray = CRM_Core_BAO_Mapping::getMappings(CRM_Core_OptionGroup::getValue('mapping_type',
164 'Import Contact',
165 'name'
166 ));
167
168 $this->assign('savedMapping', $mappingArray);
169 $this->addElement('select', 'savedMapping', ts('Mapping Option'), array('' => ts('- select -')) + $mappingArray);
170
171
172 $js = array('onClick' => "buildSubTypes();buildDedupeRules();");
173 // contact types option
174 $contactOptions = array();
175 if (CRM_Contact_BAO_ContactType::isActive('Individual')) {
176 $contactOptions[] = $this->createElement('radio',
177 NULL, NULL, ts('Individual'), CRM_Import_Parser::CONTACT_INDIVIDUAL, $js
178 );
179 }
180 if (CRM_Contact_BAO_ContactType::isActive('Household')) {
181 $contactOptions[] = $this->createElement('radio',
182 NULL, NULL, ts('Household'), CRM_Import_Parser::CONTACT_HOUSEHOLD, $js
183 );
184 }
185 if (CRM_Contact_BAO_ContactType::isActive('Organization')) {
186 $contactOptions[] = $this->createElement('radio',
187 NULL, NULL, ts('Organization'), CRM_Import_Parser::CONTACT_ORGANIZATION, $js
188 );
189 }
190
191 $this->addGroup($contactOptions, 'contactType',
192 ts('Contact Type')
193 );
194
195 $this->addElement('select', 'subType', ts('Subtype'));
196 $this->addElement('select', 'dedupe', ts('Dedupe Rule'));
197
198 CRM_Core_Form_Date::buildAllowedDateFormats($this);
199
200 $config = CRM_Core_Config::singleton();
201 $geoCode = FALSE;
202 if (!empty($config->geocodeMethod)) {
203 $geoCode = TRUE;
204 $this->addElement('checkbox', 'doGeocodeAddress', ts('Lookup mapping info during import?'));
205 }
206 $this->assign('geoCode', $geoCode);
207
208 $this->addElement('text', 'fieldSeparator', ts('Import Field Separator'), array('size' => 2));
209
210 $this->addButtons(array(
211 array(
212 'type' => 'upload',
213 'name' => ts('Continue >>'),
214 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
215 'isDefault' => TRUE,
216 ),
217 array(
218 'type' => 'cancel',
219 'name' => ts('Cancel'),
220 ),
221 )
222 );
223 }
224
225 function setDefaultValues() {
226 $config = CRM_Core_Config::singleton();
227 $defaults = array(
228 'dataSource' => 'CRM_Import_DataSource_CSV',
229 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP,
230 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL,
231 'fieldSeparator' => $config->fieldSeparator,
232 );
233
234 if ($loadeMapping = $this->get('loadedMapping')) {
235 $this->assign('loadedMapping', $loadeMapping);
236 $defaults['savedMapping'] = $loadeMapping;
237 }
238
239 return $defaults;
240 }
241
242 private function _getDataSources() {
243 // Open the data source dir and scan it for class files
244 $config = CRM_Core_Config::singleton();
245 $dataSourceDir = $config->importDataSourceDir;
246 $dataSources = array();
247 if (!is_dir($dataSourceDir)) {
248 CRM_Core_Error::fatal("Import DataSource directory $dataSourceDir does not exist");
249 }
250 if (!$dataSourceHandle = opendir($dataSourceDir)) {
251 CRM_Core_Error::fatal("Unable to access DataSource directory $dataSourceDir");
252 }
253
254 while (($dataSourceFile = readdir($dataSourceHandle)) !== FALSE) {
255 $fileType = filetype($dataSourceDir . $dataSourceFile);
256 $matches = array();
257 if (($fileType == 'file' || $fileType == 'link') &&
258 preg_match('/^(.+)\.php$/', $dataSourceFile, $matches)
259 ) {
260 $dataSourceClass = "CRM_Import_DataSource_" . $matches[1];
261 require_once $dataSourceDir . DIRECTORY_SEPARATOR . $dataSourceFile;
b0b2638a
DL
262 $object = new $dataSourceClass;
263 $info = $object->getInfo();
6a488035
TO
264 $dataSources[$dataSourceClass] = $info['title'];
265 }
266 }
267 closedir($dataSourceHandle);
268 return $dataSources;
269 }
270
271 /**
272 * Call the DataSource's postProcess method to take over
273 * and then setup some common data structures for the next step
274 *
275 * @return void
276 * @access public
277 */
278 public function postProcess() {
279 $this->controller->resetPage('MapField');
280
281 if ($this->_dataSourceIsValid) {
282 // Setup the params array
283 $this->_params = $this->controller->exportValues($this->_name);
284
285 $storeParams = array(
286 'onDuplicate' => 'onDuplicate',
287 'dedupe' => 'dedupe',
288 'contactType' => 'contactType',
289 'contactSubType' => 'subType',
290 'dateFormats' => 'dateFormats',
291 'savedMapping' => 'savedMapping',
292 );
293
294 foreach ($storeParams as $storeName => $storeValueName) {
295 $$storeName = $this->exportValue($storeValueName);
296 $this->set($storeName, $$storeName);
297 }
298
299 $this->set('dataSource', $this->_params['dataSource']);
300 $this->set('skipColumnHeader', CRM_Utils_Array::value('skipColumnHeader', $this->_params));
301
302 $session = CRM_Core_Session::singleton();
303 $session->set('dateTypes', $dateFormats);
304
305 // Get the PEAR::DB object
306 $dao = new CRM_Core_DAO();
307 $db = $dao->getDatabaseConnection();
308
309 //hack to prevent multiple tables.
310 $this->_params['import_table_name'] = $this->get('importTableName');
311 if (!$this->_params['import_table_name']) {
312 $this->_params['import_table_name'] = 'civicrm_import_job_' . md5(uniqid(rand(), TRUE));
313 }
314
b0b2638a 315 $this->_dataSourceClass->postProcess( $this->_params, $db, $this );
6a488035
TO
316
317 // We should have the data in the DB now, parse it
318 $importTableName = $this->get('importTableName');
319 $fieldNames = $this->_prepareImportTable($db, $importTableName);
320 $mapper = array();
321
322 $parser = new CRM_Import_Parser_Contact($mapper);
323 $parser->setMaxLinesToProcess(100);
324 $parser->run($importTableName,
325 $mapper,
326 CRM_Import_Parser::MODE_MAPFIELD,
327 $contactType,
328 $fieldNames['pk'],
329 $fieldNames['status'],
330 CRM_Import_Parser::DUPLICATE_SKIP,
331 NULL, NULL, FALSE,
332 CRM_Import_Parser::DEFAULT_TIMEOUT,
333 $contactSubType,
334 $dedupe
335 );
336
337 // add all the necessary variables to the form
338 $parser->set($this);
339 }
340 else {
341 CRM_Core_Error::fatal("Invalid DataSource on form post. This shouldn't happen!");
342 }
343 }
344
345 /**
346 * Add a PK and status column to the import table so we can track our progress
347 * Returns the name of the primary key and status columns
348 *
349 * @return array
350 * @access private
351 */
352 private function _prepareImportTable($db, $importTableName) {
353 /* TODO: Add a check for an existing _status field;
354 * if it exists, create __status instead and return that
355 */
356
357 $statusFieldName = '_status';
358 $primaryKeyName = '_id';
359
360 $this->set('primaryKeyName', $primaryKeyName);
361 $this->set('statusFieldName', $statusFieldName);
362
363 /* Make sure the PK is always last! We rely on this later.
364 * Should probably stop doing that at some point, but it
365 * would require moving to associative arrays rather than
366 * relying on numerical order of the fields. This could in
367 * turn complicate matters for some DataSources, which
368 * would also not be good. Decisions, decisions...
369 */
370
371 $alterQuery = "ALTER TABLE $importTableName
372 ADD COLUMN $statusFieldName VARCHAR(32)
373 DEFAULT 'NEW' NOT NULL,
374 ADD COLUMN ${statusFieldName}Msg TEXT,
375 ADD COLUMN $primaryKeyName INT PRIMARY KEY NOT NULL
376 AUTO_INCREMENT";
377 $db->query($alterQuery);
378
379 return array('status' => $statusFieldName, 'pk' => $primaryKeyName);
380 }
381
382 /**
383 * Return a descriptive name for the page, used in wizard header
384 *
385 *
386 * @return string
387 * @access public
388 */
389 public function getTitle() {
390 return ts('Choose Data Source');
391 }
392}
393