Merge pull request #3585 from atif-shaikh/CRM-14199
[civicrm-core.git] / CRM / Contact / Import / Form / DataSource.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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-2014
32 * $Id$
33 *
34 */
35
36 /**
37 * This class delegates to the chosen DataSource to grab the data to be
38 * imported.
39 */
40 class CRM_Contact_Import_Form_DataSource extends CRM_Core_Form {
41
42 private $_dataSource;
43
44 private $_dataSourceIsValid = FALSE;
45
46 private $_dataSourceClassFile;
47
48 private $_dataSourceClass;
49
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 $errorScope = CRM_Core_TemporaryErrorScope::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 unset($errorScope);
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 // check for post max size
76 CRM_Core_Config_Defaults::formatUnitSize(ini_get('post_max_size'), TRUE);
77
78 while ($file = readdir($handler)) {
79 if ($file != '.' && $file != '..' &&
80 in_array($file, $errorFiles) && !is_writable($config->uploadDir . $file)
81 ) {
82 $results[] = $file;
83 }
84 }
85 closedir($handler);
86 if (!empty($results)) {
87 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)));
88 }
89
90 $this->_dataSourceIsValid = FALSE;
91 $this->_dataSource = CRM_Utils_Request::retrieve(
92 'dataSource',
93 'String',
94 CRM_Core_DAO::$_nullObject,
95 FALSE,
96 NULL,
97 'GET'
98 );
99
100 $this->_params = $this->controller->exportValues($this->_name);
101 if (!$this->_dataSource) {
102 //considering dataSource as base criteria instead of hidden_dataSource.
103 $this->_dataSource = CRM_Utils_Array::value('dataSource',
104 $_POST,
105 CRM_Utils_Array::value('dataSource',
106 $this->_params
107 )
108 );
109 $this->assign('showOnlyDataSourceFormPane', FALSE);
110 }
111 else {
112 $this->assign('showOnlyDataSourceFormPane', TRUE);
113 }
114
115 if (strpos($this->_dataSource, 'CRM_Import_DataSource_') === 0) {
116 $this->_dataSourceIsValid = TRUE;
117 $this->assign('showDataSourceFormPane', TRUE);
118 $dataSourcePath = explode('_', $this->_dataSource);
119 $templateFile = "CRM/Contact/Import/Form/" . $dataSourcePath[3] . ".tpl";
120 $this->assign('dataSourceFormTemplateFile', $templateFile);
121 }
122 }
123
124 /**
125 * Function to actually build the form
126 *
127 * @return void
128 * @access public
129 */
130
131 public function buildQuickForm() {
132
133 // If there's a dataSource in the query string, we need to load
134 // the form from the chosen DataSource class
135 if ($this->_dataSourceIsValid) {
136 $this->_dataSourceClassFile = str_replace('_', '/', $this->_dataSource) . ".php";
137 require_once $this->_dataSourceClassFile;
138 $this->_dataSourceClass = new $this->_dataSource;
139 $this->_dataSourceClass->buildQuickForm( $this );
140 }
141
142 // Get list of data sources and display them as options
143 $dataSources = $this->_getDataSources();
144
145 $this->assign('urlPath', "civicrm/import");
146 $this->assign('urlPathVar', 'snippet=4');
147
148 $this->add('select', 'dataSource', ts('Data Source'), $dataSources, TRUE,
149 array('onchange' => 'buildDataSourceFormBlock(this.value);')
150 );
151
152 // duplicate handling options
153 $duplicateOptions = array();
154 $duplicateOptions[] = $this->createElement('radio',
155 NULL, NULL, ts('Skip'), CRM_Import_Parser::DUPLICATE_SKIP
156 );
157 $duplicateOptions[] = $this->createElement('radio',
158 NULL, NULL, ts('Update'), CRM_Import_Parser::DUPLICATE_UPDATE
159 );
160 $duplicateOptions[] = $this->createElement('radio',
161 NULL, NULL, ts('Fill'), CRM_Import_Parser::DUPLICATE_FILL
162 );
163 $duplicateOptions[] = $this->createElement('radio',
164 NULL, NULL, ts('No Duplicate Checking'), CRM_Import_Parser::DUPLICATE_NOCHECK
165 );
166
167 $this->addGroup($duplicateOptions, 'onDuplicate',
168 ts('For Duplicate Contacts')
169 );
170
171 $mappingArray = CRM_Core_BAO_Mapping::getMappings(CRM_Core_OptionGroup::getValue('mapping_type',
172 'Import Contact',
173 'name'
174 ));
175
176 $this->assign('savedMapping', $mappingArray);
177 $this->addElement('select', 'savedMapping', ts('Mapping Option'), array('' => ts('- select -')) + $mappingArray);
178
179
180 $js = array('onClick' => "buildSubTypes();buildDedupeRules();");
181 // contact types option
182 $contactOptions = array();
183 if (CRM_Contact_BAO_ContactType::isActive('Individual')) {
184 $contactOptions[] = $this->createElement('radio',
185 NULL, NULL, ts('Individual'), CRM_Import_Parser::CONTACT_INDIVIDUAL, $js
186 );
187 }
188 if (CRM_Contact_BAO_ContactType::isActive('Household')) {
189 $contactOptions[] = $this->createElement('radio',
190 NULL, NULL, ts('Household'), CRM_Import_Parser::CONTACT_HOUSEHOLD, $js
191 );
192 }
193 if (CRM_Contact_BAO_ContactType::isActive('Organization')) {
194 $contactOptions[] = $this->createElement('radio',
195 NULL, NULL, ts('Organization'), CRM_Import_Parser::CONTACT_ORGANIZATION, $js
196 );
197 }
198
199 $this->addGroup($contactOptions, 'contactType',
200 ts('Contact Type')
201 );
202
203 $this->addElement('select', 'subType', ts('Subtype'));
204 $this->addElement('select', 'dedupe', ts('Dedupe Rule'));
205
206 CRM_Core_Form_Date::buildAllowedDateFormats($this);
207
208 $config = CRM_Core_Config::singleton();
209 $geoCode = FALSE;
210 if (!empty($config->geocodeMethod)) {
211 $geoCode = TRUE;
212 $this->addElement('checkbox', 'doGeocodeAddress', ts('Lookup mapping info during import?'));
213 }
214 $this->assign('geoCode', $geoCode);
215
216 $this->addElement('text', 'fieldSeparator', ts('Import Field Separator'), array('size' => 2));
217
218 $this->addButtons(array(
219 array(
220 'type' => 'upload',
221 'name' => ts('Continue >>'),
222 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
223 'isDefault' => TRUE,
224 ),
225 array(
226 'type' => 'cancel',
227 'name' => ts('Cancel'),
228 ),
229 )
230 );
231 }
232
233 /**
234 * This virtual function is used to set the default values of
235 * various form elements
236 *
237 * access public
238 *
239 * @return array reference to the array of default values
240 *
241 */
242 /**
243 * @return array
244 */
245 function setDefaultValues() {
246 $config = CRM_Core_Config::singleton();
247 $defaults = array(
248 'dataSource' => 'CRM_Import_DataSource_CSV',
249 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP,
250 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL,
251 'fieldSeparator' => $config->fieldSeparator,
252 );
253
254 if ($loadeMapping = $this->get('loadedMapping')) {
255 $this->assign('loadedMapping', $loadeMapping);
256 $defaults['savedMapping'] = $loadeMapping;
257 }
258
259 return $defaults;
260 }
261
262 /**
263 * @return array
264 * @throws Exception
265 */
266 private function _getDataSources() {
267 // Open the data source dir and scan it for class files
268 $config = CRM_Core_Config::singleton();
269 $dataSourceDir = $config->importDataSourceDir;
270 $dataSources = array();
271 if (!is_dir($dataSourceDir)) {
272 CRM_Core_Error::fatal("Import DataSource directory $dataSourceDir does not exist");
273 }
274 if (!$dataSourceHandle = opendir($dataSourceDir)) {
275 CRM_Core_Error::fatal("Unable to access DataSource directory $dataSourceDir");
276 }
277
278 while (($dataSourceFile = readdir($dataSourceHandle)) !== FALSE) {
279 $fileType = filetype($dataSourceDir . $dataSourceFile);
280 $matches = array();
281 if (($fileType == 'file' || $fileType == 'link') &&
282 preg_match('/^(.+)\.php$/', $dataSourceFile, $matches)
283 ) {
284 $dataSourceClass = "CRM_Import_DataSource_" . $matches[1];
285 require_once $dataSourceDir . DIRECTORY_SEPARATOR . $dataSourceFile;
286 $object = new $dataSourceClass;
287 $info = $object->getInfo();
288 $dataSources[$dataSourceClass] = $info['title'];
289 }
290 }
291 closedir($dataSourceHandle);
292 return $dataSources;
293 }
294
295 /**
296 * Call the DataSource's postProcess method to take over
297 * and then setup some common data structures for the next step
298 *
299 * @return void
300 * @access public
301 */
302 public function postProcess() {
303 $this->controller->resetPage('MapField');
304
305 if ($this->_dataSourceIsValid) {
306 // Setup the params array
307 $this->_params = $this->controller->exportValues($this->_name);
308
309 $storeParams = array(
310 'onDuplicate' => 'onDuplicate',
311 'dedupe' => 'dedupe',
312 'contactType' => 'contactType',
313 'contactSubType' => 'subType',
314 'dateFormats' => 'dateFormats',
315 'savedMapping' => 'savedMapping',
316 );
317
318 foreach ($storeParams as $storeName => $storeValueName) {
319 $$storeName = $this->exportValue($storeValueName);
320 $this->set($storeName, $$storeName);
321 }
322
323 $this->set('dataSource', $this->_params['dataSource']);
324 $this->set('skipColumnHeader', CRM_Utils_Array::value('skipColumnHeader', $this->_params));
325
326 $session = CRM_Core_Session::singleton();
327 $session->set('dateTypes', $dateFormats);
328
329 // Get the PEAR::DB object
330 $dao = new CRM_Core_DAO();
331 $db = $dao->getDatabaseConnection();
332
333 //hack to prevent multiple tables.
334 $this->_params['import_table_name'] = $this->get('importTableName');
335 if (!$this->_params['import_table_name']) {
336 $this->_params['import_table_name'] = 'civicrm_import_job_' . md5(uniqid(rand(), TRUE));
337 }
338
339 $this->_dataSourceClass->postProcess( $this->_params, $db, $this );
340
341 // We should have the data in the DB now, parse it
342 $importTableName = $this->get('importTableName');
343 $fieldNames = $this->_prepareImportTable($db, $importTableName);
344 $mapper = array();
345
346 $parser = new CRM_Contact_Import_Parser_Contact($mapper);
347 $parser->setMaxLinesToProcess(100);
348 $parser->run($importTableName,
349 $mapper,
350 CRM_Import_Parser::MODE_MAPFIELD,
351 $contactType,
352 $fieldNames['pk'],
353 $fieldNames['status'],
354 CRM_Import_Parser::DUPLICATE_SKIP,
355 NULL, NULL, FALSE,
356 CRM_Contact_Import_Parser::DEFAULT_TIMEOUT,
357 $contactSubType,
358 $dedupe
359 );
360
361 // add all the necessary variables to the form
362 $parser->set($this);
363 }
364 else {
365 CRM_Core_Error::fatal("Invalid DataSource on form post. This shouldn't happen!");
366 }
367 }
368
369 /**
370 * Add a PK and status column to the import table so we can track our progress
371 * Returns the name of the primary key and status columns
372 *
373 * @param $db
374 * @param $importTableName
375 *
376 * @return array
377 * @access private
378 */
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
382 */
383
384 $statusFieldName = '_status';
385 $primaryKeyName = '_id';
386
387 $this->set('primaryKeyName', $primaryKeyName);
388 $this->set('statusFieldName', $statusFieldName);
389
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...
396 */
397
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
403 AUTO_INCREMENT";
404 $db->query($alterQuery);
405
406 return array('status' => $statusFieldName, 'pk' => $primaryKeyName);
407 }
408
409 /**
410 * Return a descriptive name for the page, used in wizard header
411 *
412 *
413 * @return string
414 * @access public
415 */
416 public function getTitle() {
417 return ts('Choose Data Source');
418 }
419 }
420