INFRA-132 - phpcbf Drupal.WhiteSpace.ScopeIndent.IncorrectExact
[civicrm-core.git] / CRM / Contact / Import / Form / DataSource.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
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 |
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
32 * $Id$
33 *
34 */
35
36/**
37 * This class delegates to the chosen DataSource to grab the data to be
38 * imported.
39 */
719a6fec 40class CRM_Contact_Import_Form_DataSource extends CRM_Core_Form {
6a488035
TO
41
42 private $_dataSource;
43
44 private $_dataSourceIsValid = FALSE;
45
46 private $_dataSourceClassFile;
47
b0b2638a
DL
48 private $_dataSourceClass;
49
6a488035 50 /**
100fef9d 51 * Set variables up before form is built
6a488035
TO
52 *
53 * @return void
6a488035
TO
54 */
55 public function preProcess() {
56
57 //Test database user privilege to create table(Temporary) CRM-4725
6a4257d4 58 $errorScope = CRM_Core_TemporaryErrorScope::ignoreException();
bed98343 59 $daoTestPrivilege = new CRM_Core_DAO();
6a488035
TO
60 $daoTestPrivilege->query("CREATE TEMPORARY TABLE import_job_permission_one(test int) ENGINE=InnoDB");
61 $daoTestPrivilege->query("CREATE TEMPORARY TABLE import_job_permission_two(test int) ENGINE=InnoDB");
62 $daoTestPrivilege->query("DROP TABLE IF EXISTS import_job_permission_one, import_job_permission_two");
6a4257d4 63 unset($errorScope);
6a488035
TO
64
65 if ($daoTestPrivilege->_lastError) {
66 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.'));
67 }
68
353ffa53
TO
69 $results = array();
70 $config = CRM_Core_Config::singleton();
71 $handler = opendir($config->uploadDir);
6a488035
TO
72 $errorFiles = array('sqlImport.errors', 'sqlImport.conflicts', 'sqlImport.duplicates', 'sqlImport.mismatch');
73
f4a17080 74 // check for post max size avoid when called twice
75 $snippet = CRM_Utils_Array::value('snippet', $_GET, 0);
76 if (empty($snippet)) {
77 CRM_Core_Config_Defaults::formatUnitSize(ini_get('post_max_size'), TRUE);
78 }
66dc6009 79
6a488035
TO
80 while ($file = readdir($handler)) {
81 if ($file != '.' && $file != '..' &&
82 in_array($file, $errorFiles) && !is_writable($config->uploadDir . $file)
83 ) {
84 $results[] = $file;
85 }
86 }
87 closedir($handler);
88 if (!empty($results)) {
353ffa53
TO
89 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(
90 1 => implode(', ', $results),
bed98343 91 2 => $config->uploadDir,
353ffa53 92 )));
6a488035
TO
93 }
94
95 $this->_dataSourceIsValid = FALSE;
0a11d4d8
DL
96 $this->_dataSource = CRM_Utils_Request::retrieve(
97 'dataSource',
98 'String',
99 CRM_Core_DAO::$_nullObject,
100 FALSE,
101 NULL,
102 'GET'
6a488035
TO
103 );
104
105 $this->_params = $this->controller->exportValues($this->_name);
106 if (!$this->_dataSource) {
107 //considering dataSource as base criteria instead of hidden_dataSource.
108 $this->_dataSource = CRM_Utils_Array::value('dataSource',
109 $_POST,
110 CRM_Utils_Array::value('dataSource',
111 $this->_params
112 )
113 );
114 $this->assign('showOnlyDataSourceFormPane', FALSE);
115 }
116 else {
117 $this->assign('showOnlyDataSourceFormPane', TRUE);
118 }
119
120 if (strpos($this->_dataSource, 'CRM_Import_DataSource_') === 0) {
121 $this->_dataSourceIsValid = TRUE;
122 $this->assign('showDataSourceFormPane', TRUE);
123 $dataSourcePath = explode('_', $this->_dataSource);
719a6fec 124 $templateFile = "CRM/Contact/Import/Form/" . $dataSourcePath[3] . ".tpl";
6a488035
TO
125 $this->assign('dataSourceFormTemplateFile', $templateFile);
126 }
127 }
128
129 /**
c490a46a 130 * Build the form object
6a488035 131 *
355ba699 132 * @return void
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,
152 array('onchange' => 'buildDataSourceFormBlock(this.value);')
153 );
154
155 // duplicate handling options
156 $duplicateOptions = array();
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
174 $mappingArray = CRM_Core_BAO_Mapping::getMappings(CRM_Core_OptionGroup::getValue('mapping_type',
353ffa53
TO
175 'Import Contact',
176 'name'
177 ));
6a488035
TO
178
179 $this->assign('savedMapping', $mappingArray);
180 $this->addElement('select', 'savedMapping', ts('Mapping Option'), array('' => ts('- select -')) + $mappingArray);
181
6a488035
TO
182 $js = array('onClick' => "buildSubTypes();buildDedupeRules();");
183 // contact types option
184 $contactOptions = array();
185 if (CRM_Contact_BAO_ContactType::isActive('Individual')) {
186 $contactOptions[] = $this->createElement('radio',
a05662ef 187 NULL, NULL, ts('Individual'), CRM_Import_Parser::CONTACT_INDIVIDUAL, $js
6a488035
TO
188 );
189 }
190 if (CRM_Contact_BAO_ContactType::isActive('Household')) {
191 $contactOptions[] = $this->createElement('radio',
a05662ef 192 NULL, NULL, ts('Household'), CRM_Import_Parser::CONTACT_HOUSEHOLD, $js
6a488035
TO
193 );
194 }
195 if (CRM_Contact_BAO_ContactType::isActive('Organization')) {
196 $contactOptions[] = $this->createElement('radio',
a05662ef 197 NULL, NULL, ts('Organization'), CRM_Import_Parser::CONTACT_ORGANIZATION, $js
6a488035
TO
198 );
199 }
200
201 $this->addGroup($contactOptions, 'contactType',
202 ts('Contact Type')
203 );
204
205 $this->addElement('select', 'subType', ts('Subtype'));
206 $this->addElement('select', 'dedupe', ts('Dedupe Rule'));
207
208 CRM_Core_Form_Date::buildAllowedDateFormats($this);
209
210 $config = CRM_Core_Config::singleton();
211 $geoCode = FALSE;
212 if (!empty($config->geocodeMethod)) {
213 $geoCode = TRUE;
214 $this->addElement('checkbox', 'doGeocodeAddress', ts('Lookup mapping info during import?'));
215 }
216 $this->assign('geoCode', $geoCode);
217
218 $this->addElement('text', 'fieldSeparator', ts('Import Field Separator'), array('size' => 2));
219
220 $this->addButtons(array(
221 array(
222 'type' => 'upload',
f212d37d 223 'name' => ts('Continue'),
6a488035
TO
224 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
225 'isDefault' => TRUE,
226 ),
227 array(
228 'type' => 'cancel',
229 'name' => ts('Cancel'),
230 ),
231 )
232 );
233 }
234
86538308
EM
235 /**
236 * This virtual function is used to set the default values of
237 * various form elements
238 *
239 * access public
240 *
a6c01b45
CW
241 * @return array
242 * reference to the array of default values
86538308
EM
243 */
244 /**
245 * @return array
246 */
00be9182 247 public function setDefaultValues() {
6a488035
TO
248 $config = CRM_Core_Config::singleton();
249 $defaults = array(
250 'dataSource' => 'CRM_Import_DataSource_CSV',
a05662ef
CW
251 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP,
252 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL,
6a488035
TO
253 'fieldSeparator' => $config->fieldSeparator,
254 );
255
256 if ($loadeMapping = $this->get('loadedMapping')) {
257 $this->assign('loadedMapping', $loadeMapping);
258 $defaults['savedMapping'] = $loadeMapping;
259 }
260
261 return $defaults;
262 }
263
86538308
EM
264 /**
265 * @return array
266 * @throws Exception
267 */
6a488035
TO
268 private function _getDataSources() {
269 // Open the data source dir and scan it for class files
353ffa53 270 $config = CRM_Core_Config::singleton();
6a488035 271 $dataSourceDir = $config->importDataSourceDir;
353ffa53 272 $dataSources = array();
6a488035
TO
273 if (!is_dir($dataSourceDir)) {
274 CRM_Core_Error::fatal("Import DataSource directory $dataSourceDir does not exist");
275 }
276 if (!$dataSourceHandle = opendir($dataSourceDir)) {
277 CRM_Core_Error::fatal("Unable to access DataSource directory $dataSourceDir");
278 }
279
280 while (($dataSourceFile = readdir($dataSourceHandle)) !== FALSE) {
281 $fileType = filetype($dataSourceDir . $dataSourceFile);
282 $matches = array();
283 if (($fileType == 'file' || $fileType == 'link') &&
284 preg_match('/^(.+)\.php$/', $dataSourceFile, $matches)
285 ) {
286 $dataSourceClass = "CRM_Import_DataSource_" . $matches[1];
287 require_once $dataSourceDir . DIRECTORY_SEPARATOR . $dataSourceFile;
bed98343 288 $object = new $dataSourceClass();
353ffa53 289 $info = $object->getInfo();
6a488035
TO
290 $dataSources[$dataSourceClass] = $info['title'];
291 }
292 }
293 closedir($dataSourceHandle);
294 return $dataSources;
295 }
296
297 /**
298 * Call the DataSource's postProcess method to take over
299 * and then setup some common data structures for the next step
300 *
301 * @return void
6a488035
TO
302 */
303 public function postProcess() {
304 $this->controller->resetPage('MapField');
305
306 if ($this->_dataSourceIsValid) {
307 // Setup the params array
308 $this->_params = $this->controller->exportValues($this->_name);
309
310 $storeParams = array(
311 'onDuplicate' => 'onDuplicate',
312 'dedupe' => 'dedupe',
313 'contactType' => 'contactType',
314 'contactSubType' => 'subType',
315 'dateFormats' => 'dateFormats',
316 'savedMapping' => 'savedMapping',
317 );
318
319 foreach ($storeParams as $storeName => $storeValueName) {
320 $$storeName = $this->exportValue($storeValueName);
321 $this->set($storeName, $$storeName);
322 }
323
324 $this->set('dataSource', $this->_params['dataSource']);
325 $this->set('skipColumnHeader', CRM_Utils_Array::value('skipColumnHeader', $this->_params));
326
327 $session = CRM_Core_Session::singleton();
328 $session->set('dateTypes', $dateFormats);
329
330 // Get the PEAR::DB object
331 $dao = new CRM_Core_DAO();
332 $db = $dao->getDatabaseConnection();
333
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));
338 }
339
481a74f4 340 $this->_dataSourceClass->postProcess($this->_params, $db, $this);
6a488035
TO
341
342 // We should have the data in the DB now, parse it
343 $importTableName = $this->get('importTableName');
353ffa53
TO
344 $fieldNames = $this->_prepareImportTable($db, $importTableName);
345 $mapper = array();
6a488035 346
719a6fec 347 $parser = new CRM_Contact_Import_Parser_Contact($mapper);
6a488035
TO
348 $parser->setMaxLinesToProcess(100);
349 $parser->run($importTableName,
350 $mapper,
a05662ef 351 CRM_Import_Parser::MODE_MAPFIELD,
6a488035
TO
352 $contactType,
353 $fieldNames['pk'],
354 $fieldNames['status'],
a05662ef 355 CRM_Import_Parser::DUPLICATE_SKIP,
6a488035 356 NULL, NULL, FALSE,
719a6fec 357 CRM_Contact_Import_Parser::DEFAULT_TIMEOUT,
6a488035
TO
358 $contactSubType,
359 $dedupe
360 );
361
362 // add all the necessary variables to the form
363 $parser->set($this);
364 }
365 else {
366 CRM_Core_Error::fatal("Invalid DataSource on form post. This shouldn't happen!");
367 }
368 }
369
370 /**
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
373 *
77b97be7 374 * @param $db
100fef9d 375 * @param string $importTableName
77b97be7 376 *
6a488035 377 * @return array
6a488035
TO
378 */
379 private function _prepareImportTable($db, $importTableName) {
380 /* TODO: Add a check for an existing _status field;
e70a7fc0
TO
381 * if it exists, create __status instead and return that
382 */
6a488035
TO
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.
e70a7fc0
TO
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 */
6a488035
TO
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
6a488035
TO
414 */
415 public function getTitle() {
416 return ts('Choose Data Source');
417 }
96025800 418
6a488035 419}