2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
4 /*********************************************************************************
5 * SugarCRM Community Edition is a customer relationship management program developed by
6 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU Affero General Public License version 3 as published by the
10 * Free Software Foundation with the addition of the following permission added
11 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
12 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
13 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
20 * You should have received a copy of the GNU Affero General Public License along with
21 * this program; if not, see http://www.gnu.org/licenses or write to the Free
22 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
26 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
28 * The interactive user interfaces in modified source and object code versions
29 * of this program must display Appropriate Legal Notices, as required under
30 * Section 5 of the GNU Affero General Public License version 3.
32 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
33 * these Appropriate Legal Notices must retain the display of the "Powered by
34 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
35 * technical reasons, the Appropriate Legal Notices must display the words
36 * "Powered by SugarCRM".
37 ********************************************************************************/
39 /*********************************************************************************
41 * Description: view handler for step 4 of the import process
42 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
43 * All Rights Reserved.
44 ********************************************************************************/
46 require_once('include/MVC/View/SugarView.php');
47 require_once('modules/Import/ImportFile.php');
48 require_once('modules/Import/ImportFileSplitter.php');
49 require_once('modules/Import/ImportCacheFiles.php');
50 require_once('modules/Import/ImportFieldSanitize.php');
51 require_once('modules/Import/ImportDuplicateCheck.php');
53 class ImportViewStep4 extends SugarView
56 * @see SugarView::display()
58 public function display()
62 // Increase the max_execution_time since this step can take awhile
63 ini_set("max_execution_time", max($sugar_config['import_max_execution_time'],3600));
66 TrackerManager::getInstance()->pause();
68 // use our own error handler
69 set_error_handler(array('ImportViewStep4','handleImportErrors'),E_ALL);
71 global $mod_strings, $app_strings, $current_user, $import_bean_map;
72 global $app_list_strings, $timedate;
74 $update_only = ( isset($_REQUEST['import_type']) && $_REQUEST['import_type'] == 'update' );
75 $firstrow = unserialize(base64_decode($_REQUEST['firstrow']));
77 // All the Look Up Caches are initialized here
78 $enum_lookup_cache=array();
80 // setup the importable fields array.
81 $importable_fields = $this->bean->get_importable_fields();
83 // loop through all request variables
84 $importColumns = array();
85 foreach ($_REQUEST as $name => $value) {
86 // only look for var names that start with "fieldNum"
87 if (strncasecmp($name, "colnum_", 7) != 0) {
91 // pull out the column position for this field name
92 $pos = substr($name, 7);
94 if ( isset($importable_fields[$value]) ) {
95 // now mark that we've seen this field
96 $importColumns[$pos] = $value;
101 // set the default locale settings
102 $ifs = new ImportFieldSanitize();
103 $ifs->dateformat = $_REQUEST['importlocale_dateformat'];
104 $ifs->timeformat = $_REQUEST['importlocale_timeformat'];
105 $ifs->timezone = $_REQUEST['importlocale_timezone'];
106 $currency = new Currency();
107 $currency->retrieve($_REQUEST['importlocale_currency']);
108 $ifs->currency_symbol = $currency->symbol;
109 $ifs->default_currency_significant_digits
110 = $_REQUEST['importlocale_default_currency_significant_digits'];
112 = $_REQUEST['importlocale_num_grp_sep'];
113 $ifs->dec_sep = $_REQUEST['importlocale_dec_sep'];
114 $ifs->default_locale_name_format
115 = $_REQUEST['importlocale_default_locale_name_format'];
117 // Check to be sure we are getting an import file that is in the right place
118 if ( realpath(dirname($_REQUEST['tmp_file']).'/') != realpath($sugar_config['upload_dir']) )
119 trigger_error($mod_strings['LBL_CANNOT_OPEN'],E_USER_ERROR);
121 // Open the import file
122 $importFile = new ImportFile(
123 $_REQUEST['tmp_file'],
124 $_REQUEST['custom_delimiter'],
125 html_entity_decode($_REQUEST['custom_enclosure'],ENT_QUOTES)
128 if ( !$importFile->fileExists() ) {
129 trigger_error($mod_strings['LBL_CANNOT_OPEN'],E_USER_ERROR);
132 $fieldDefs = $this->bean->getFieldDefinitions();
134 while ( $row = $importFile->getNextRow() ) {
135 $focus = clone $this->bean;
136 $focus->unPopulateDefaultValues();
137 $focus->save_from_post = false;
138 $focus->team_id = null;
139 $ifs->createdBeans = array();
143 for ( $fieldNum = 0; $fieldNum < $_REQUEST['columncount']; $fieldNum++ ) {
144 // loop if this column isn't set
145 if ( !isset($importColumns[$fieldNum]) ) {
149 // get this field's properties
150 $field = $importColumns[$fieldNum];
151 $fieldDef = $focus->getFieldDefinition($field);
152 $fieldTranslated = translate((isset($fieldDef['vname'])?$fieldDef['vname']:$fieldDef['name']),
153 $_REQUEST['module'])." (".$fieldDef['name'].")";
155 // Bug 37241 - Don't re-import over a field we already set during the importing of another field
156 if ( !empty($focus->$field) ) {
160 //DETERMINE WHETHER OR NOT $fieldDef['name'] IS DATE_MODIFIED AND SET A VAR, USE DOWN BELOW
165 $locale = new Localization();
167 if ( isset($row[$fieldNum]) )
168 $rowValue = $locale->translateCharset(
169 strip_tags(trim($row[$fieldNum])),
170 $_REQUEST['importlocale_charset'],
171 $sugar_config['default_charset']
176 // If there is an default value then use it instead
177 if ( !empty($_REQUEST[$field]) ) {
178 if ( is_array($_REQUEST[$field]) )
179 $defaultRowValue = encodeMultienumValue($_REQUEST[$field]);
181 $defaultRowValue = $_REQUEST[$field];
182 // translate default values to the date/time format for the import file
183 if ( $fieldDef['type'] == 'date'
184 && $ifs->dateformat != $timedate->get_date_format() )
185 $defaultRowValue = $timedate->swap_formats(
186 $defaultRowValue, $ifs->dateformat, $timedate->get_date_format());
187 if ( $fieldDef['type'] == 'time'
188 && $ifs->timeformat != $timedate->get_time_format() )
189 $defaultRowValue = $timedate->swap_formats(
190 $defaultRowValue, $ifs->timeformat, $timedate->get_time_format());
191 if ( ($fieldDef['type'] == 'datetime' || $fieldDef['type'] == 'datetimecombo')
192 && $ifs->dateformat.' '.$ifs->timeformat != $timedate->get_date_time_format() )
193 $defaultRowValue = $timedate->swap_formats(
194 $defaultRowValue, $ifs->dateformat.' '.$ifs->timeformat,
195 $timedate->get_date_time_format());
196 if ( in_array($fieldDef['type'],array('currency','float','int','num'))
197 && $ifs->num_grp_sep != $current_user->getPreference('num_grp_sep') )
198 $defaultRowValue = str_replace($current_user->getPreference('num_grp_sep'),
199 $ifs->num_grp_sep,$defaultRowValue);
200 if ( in_array($fieldDef['type'],array('currency','float'))
201 && $ifs->dec_sep != $current_user->getPreference('dec_sep') )
202 $defaultRowValue = str_replace($current_user->getPreference('dec_sep'),
203 $ifs->dec_sep,$defaultRowValue);
204 $currency->retrieve('-99');
205 $user_currency_symbol = $currency->symbol;
206 if ( $fieldDef['type'] == 'currency'
207 && $ifs->currency_symbol != $user_currency_symbol )
208 $defaultRowValue = str_replace($user_currency_symbol,
209 $ifs->currency_symbol,$defaultRowValue);
212 if ( empty($rowValue) ) {
213 $rowValue = $defaultRowValue;
214 unset($defaultRowValue);
218 // Bug 22705 - Don't update the First Name or Last Name value if Full Name is set
219 if ( in_array($field, array('first_name','last_name')) && !empty($focus->full_name) )
222 // loop if this value has not been set
223 if ( !isset($rowValue) )
226 // If the field is required and blank then error out
227 if ( array_key_exists($field,$focus->get_import_required_fields())
230 $importFile->writeError(
231 $mod_strings['LBL_REQUIRED_VALUE'],
238 // Handle the special case "Sync to Outlook"
239 if ( $focus->object_name == "Contacts" && $field == 'sync_contact' ) {
240 $bad_names = array();
241 $returnValue = $ifs->synctooutlook(
245 // try the default value on fail
246 if ( !$returnValue && !empty($defaultRowValue) )
247 $returnValue = $ifs->synctooutlook(
251 if ( !$returnValue ) {
252 $importFile->writeError(
253 $mod_strings['LBL_ERROR_SYNC_USERS'],
255 explode(",",$bad_names));
260 // Handle email1 and email2 fields ( these don't have the type of email )
261 if ( $field == 'email1' || $field == 'email2' ) {
262 $returnValue = $ifs->email($rowValue, $fieldDef, $focus);
263 // try the default value on fail
264 if ( !$returnValue && !empty($defaultRowValue) )
265 $returnValue = $ifs->email(
268 if ( $returnValue === FALSE ) {
270 $importFile->writeError(
271 $mod_strings['LBL_ERROR_INVALID_EMAIL'],
276 $rowValue = $returnValue;
277 // check for current opt_out and invalid email settings for this email address
278 // if we find any, set them now
279 $emailres = $focus->db->query(
280 "SELECT opt_out, invalid_email FROM email_addresses
281 WHERE email_address = '".$focus->db->quote($rowValue)."'");
282 if ( $emailrow = $focus->db->fetchByAssoc($emailres) ) {
283 $focus->email_opt_out = $emailrow['opt_out'];
284 $focus->invalid_email = $emailrow['invalid_email'];
289 // Handle splitting Full Name into First and Last Name parts
290 if ( $field == 'full_name' && !empty($rowValue) ) {
297 // to maintain 451 compatiblity
298 if(!isset($fieldDef['module']) && $fieldDef['type']=='relate')
299 $fieldDef['module'] = ucfirst($fieldDef['table']);
301 if(isset($fieldDef['custom_type']) && !empty($fieldDef['custom_type']))
302 $fieldDef['type'] = $fieldDef['custom_type'];
304 // If the field is empty then there is no need to check the data
305 if( !empty($rowValue) ) {
306 switch ($fieldDef['type']) {
309 if ( isset($fieldDef['type']) && $fieldDef['type'] == "multienum" )
310 $returnValue = $ifs->multienum($rowValue,$fieldDef);
312 $returnValue = $ifs->enum($rowValue,$fieldDef);
313 // try the default value on fail
314 if ( !$returnValue && !empty($defaultRowValue) )
315 if ( isset($fieldDef['type']) && $fieldDef['type'] == "multienum" )
316 $returnValue = $ifs->multienum($defaultRowValue,$fieldDef);
318 $returnValue = $ifs->enum($defaultRowValue,$fieldDef);
319 if ( $returnValue === FALSE ) {
320 $importFile->writeError(
321 $mod_strings['LBL_ERROR_NOT_IN_ENUM']
322 . implode(",",$app_list_strings[$fieldDef['options']]),
328 $rowValue = $returnValue;
333 $returnValue = $ifs->relate(
337 empty($defaultRowValue));
338 if ( !$returnValue && !empty($defaultRowValue) )
339 $returnValue = $ifs->relate(
343 // Bug 33623 - Set the id value found from the above method call as an importColumn
344 if ( $returnValue !== false )
345 $importColumns[] = $fieldDef['id_name'];
348 $returnValue = $ifs->teamset(
352 $importColumns[] = 'team_set_id';
353 $importColumns[] = 'team_id';
358 $fieldtype = $fieldDef['type'];
359 $returnValue = $ifs->$fieldtype($rowValue, $fieldDef, $focus);
360 // try the default value on fail
361 if ( !$returnValue && !empty($defaultRowValue) )
362 $returnValue = $ifs->$fieldtype(
366 if ( !$returnValue ) {
368 $importFile->writeError(
369 $mod_strings['LBL_ERROR_INVALID_'.strtoupper($fieldDef['type'])],
375 $rowValue = $returnValue;
379 $focus->$field = $rowValue;
380 unset($defaultRowValue);
383 // Now try to validate flex relate fields
384 if ( isset($focus->field_defs['parent_name'])
385 && isset($focus->parent_name)
386 && ($focus->field_defs['parent_name']['type'] == 'parent') ) {
387 // populate values from the picker widget if the import file doesn't have them
388 $parent_idField = $focus->field_defs['parent_name']['id_name'];
389 if ( empty($focus->$parent_idField) && !empty($_REQUEST[$parent_idField]) )
390 $focus->$parent_idField = $_REQUEST[$parent_idField];
391 $parent_typeField = $focus->field_defs['parent_name']['type_name'];
392 if ( empty($focus->$parent_typeField) && !empty($_REQUEST[$parent_typeField]) )
393 $focus->$parent_typeField = $_REQUEST[$parent_typeField];
395 $returnValue = $ifs->parent(
397 $focus->field_defs['parent_name'],
399 empty($_REQUEST['parent_name']));
400 if ( !$returnValue && !empty($_REQUEST['parent_name']) )
401 $returnValue = $ifs->parent(
402 $_REQUEST['parent_name'],
403 $focus->field_defs['parent_name'],
407 // check to see that the indexes being entered are unique.
408 if (isset($_REQUEST['display_tabs_def']) && $_REQUEST['display_tabs_def'] != ""){
409 $idc = new ImportDuplicateCheck($focus);
410 if ( $idc->isADuplicateRecord(explode('&', $_REQUEST['display_tabs_def'])) ){
411 $importFile->markRowAsDuplicate();
412 $this->_undoCreatedBeans($ifs->createdBeans);
417 // if the id was specified
419 if ( !empty($focus->id) ) {
420 $focus->id = $this->_convertId($focus->id);
422 // check if it already exists
423 $query = "SELECT * FROM {$focus->table_name} WHERE id='".$focus->db->quote($focus->id)."'";
424 $result = $focus->db->query($query)
425 or sugar_die("Error selecting sugarbean: ");
427 $dbrow = $focus->db->fetchByAssoc($result);
429 if (isset ($dbrow['id']) && $dbrow['id'] != -1) {
430 // if it exists but was deleted, just remove it
431 if (isset ($dbrow['deleted']) && $dbrow['deleted'] == 1 && $update_only==false) {
432 $query2 = "DELETE FROM {$focus->table_name} WHERE id='".$focus->db->quote($focus->id)."'";
433 $result2 = $focus->db->query($query2) or sugar_die($mod_strings['LBL_ERROR_DELETING_RECORD']." ".$focus->id);
434 if ($focus->hasCustomFields()) {
435 $query3 = "DELETE FROM {$focus->table_name}_cstm WHERE id_c='".$focus->db->quote($focus->id)."'";
436 $result2 = $focus->db->query($query3);
438 $focus->new_with_id = true;
441 if( !$update_only ) {
443 $importFile->writeError($mod_strings['LBL_ID_EXISTS_ALREADY'],'ID',$focus->id);
444 $this->_undoCreatedBeans($ifs->createdBeans);
447 $existing_focus = clone $this->bean;
449 if ( !( $existing_focus->retrieve($dbrow['id']) instanceOf SugarBean ) ) {
451 $importFile->writeError($mod_strings['LBL_RECORD_CANNOT_BE_UPDATED'],'ID',$focus->id);
452 $this->_undoCreatedBeans($ifs->createdBeans);
456 $newData = $focus->toArray();
457 foreach ( $newData as $focus_key => $focus_value )
458 if ( in_array($focus_key,$importColumns) )
459 $existing_focus->$focus_key = $focus_value;
461 $focus = $existing_focus;
462 $focus->update_date_entered = true;
464 unset($existing_focus);
468 $focus->new_with_id = true;
473 // Populate in any default values to the bean
474 $focus->populateDefaultValues();
476 if ( !isset($focus->assigned_user_id) || $focus->assigned_user_id == '' && $newRecord ) {
477 $focus->assigned_user_id = $current_user->id;
480 * Bug 34854: Added all conditions besides the empty check on date modified. Currently, if
481 * we do an update to a record, it doesn't update the date_modified value.
482 * Hack note: I'm doing a to_display and back to_db on the fetched row to make sure that any truncating that happens
483 * when $focus->date_modified goes to_display and back to_db also happens on the fetched db value. Otherwise,
484 * in some cases we truncate the seconds on one and not the other, and the comparison fails when it should pass
486 if ( ( !empty($focus->new_with_id) && !empty($focus->date_modified) ) ||
487 ( empty($focus->new_with_id) && $timedate->to_db($focus->date_modified) != $timedate->to_db($timedate->to_display_date_time($focus->fetched_row['date_modified'])) )
489 $focus->update_date_modified = false;
491 $focus->optimistic_lock = false;
492 if ( $focus->object_name == "Contacts" && isset($focus->sync_contact) ) {
493 //copy the potential sync list to another varible
494 $list_of_users=$focus->sync_contact;
495 //and set it to false for the save
496 $focus->sync_contact=false;
497 } else if($focus->object_name == "User" && !empty($current_user) && $current_user->isAdminForModule( 'Users')) {
498 sugar_die($GLOBALS['mod_strings']['ERR_IMPORT_SYSTEM_ADMININSTRATOR']);
500 //bug# 40260 setting it true as the module in focus is involved in an import
501 $focus->in_import=true;
502 // call any logic needed for the module preSave
503 $focus->beforeImportSave();
508 // call any logic needed for the module postSave
509 $focus->afterImportSave();
511 if ( $focus->object_name == "Contacts" && isset($list_of_users) )
512 $focus->process_sync_to_outlook($list_of_users);
514 // Update the created/updated counter
515 $importFile->markRowAsImported($newRecord);
517 // Add ID to User's Last Import records
519 ImportFile::writeRowToLastImport(
520 $_REQUEST['import_module'],
521 ($focus->object_name == 'Case' ? 'aCase' : $focus->object_name),
525 $this->_undoCreatedBeans($ifs->createdBeans);
527 unset($defaultRowValue);
530 // save mapping if requested
531 if ( isset($_REQUEST['save_map_as']) && $_REQUEST['save_map_as'] != '' ) {
532 $mapping_file = new ImportMap();
533 if ( isset($_REQUEST['has_header']) && $_REQUEST['has_header'] == 'on') {
534 $header_to_field = array ();
535 foreach ($importColumns as $pos => $field_name) {
536 if (isset($firstrow[$pos]) && isset($field_name)) {
537 $header_to_field[$firstrow[$pos]] = $field_name;
540 $mapping_file->setMapping($header_to_field);
543 $mapping_file->setMapping($importColumns);
546 // save default fields
547 $defaultValues = array();
548 for ( $i = 0; $i < $_REQUEST['columncount']; $i++ )
550 if (isset($importColumns[$i]) && !empty($_REQUEST[$importColumns[$i]])) {
551 $field = $importColumns[$i];
552 $fieldDef = $focus->getFieldDefinition($field);
553 if(!empty($fieldDef['custom_type']) && $fieldDef['custom_type'] == 'teamset') {
554 require_once('include/SugarFields/Fields/Teamset/SugarFieldTeamset.php');
555 $sugar_field = new SugarFieldTeamset('Teamset');
556 $teams = $sugar_field->getTeamsFromRequest($field);
557 if(isset($_REQUEST['primary_team_name_collection'])) {
558 $primary_index = $_REQUEST['primary_team_name_collection'];
561 //If primary_index was selected, ensure that the first Array entry is the primary team
562 if(isset($primary_index)) {
564 $new_teams = array();
565 foreach($teams as $id=>$name) {
566 if($primary_index == $count++) {
567 $new_teams[$id] = $name;
573 foreach($teams as $id=>$name) {
574 $new_teams[$id] = $name;
579 $json = getJSONobj();
580 $defaultValues[$field] = $json->encode($teams);
582 $defaultValues[$field] = $_REQUEST[$importColumns[$i]];
586 $mapping_file->setDefaultValues($defaultValues);
587 $result = $mapping_file->save(
589 $_REQUEST['save_map_as'],
590 $_REQUEST['import_module'],
592 ( isset($_REQUEST['has_header']) && $_REQUEST['has_header'] == 'on'),
593 $_REQUEST['custom_delimiter'],
594 html_entity_decode($_REQUEST['custom_enclosure'],ENT_QUOTES)
598 $importFile->writeStatus();
602 * If a bean save is not done for some reason, this method will undo any of the beans that were created
604 * @param array $ids ids of user_last_import records created
606 protected function _undoCreatedBeans(
610 $focus = new UsersLastImport();
611 foreach ($ids as $id)
612 $focus->undoById($id);
616 * clean id's when being imported
618 * @param string $string
621 protected function _convertId(
625 return preg_replace_callback(
628 // single quotes are essential here,
629 // or alternative escape all $ as \$
631 'return ord($matches[0]);'
637 * Replaces PHP error handler in Step4
640 * @param string $errstr
641 * @param string $errfile
642 * @param string $errline
644 public static function handleImportErrors(
651 if ( !defined('E_DEPRECATED') )
652 define('E_DEPRECATED','8192');
653 if ( !defined('E_USER_DEPRECATED') )
654 define('E_USER_DEPRECATED','16384');
656 // check to see if current reporting level should be included based upon error_reporting() setting, if not
658 if ( !(error_reporting() & $errno) )
663 echo "ERROR: [$errno] $errstr on line $errline in file $errfile<br />\n";
668 echo "WARNING: [$errno] $errstr on line $errline in file $errfile<br />\n";
672 echo "NOTICE: [$errno] $errstr on line $errline in file $errfile<br />\n";
676 case E_USER_DEPRECATED:
677 // don't worry about these
678 //echo "STRICT ERROR: [$errno] $errstr on line $errline in file $errfile<br />\n";
681 echo "Unknown error type: [$errno] $errstr on line $errline in file $errfile<br />\n";