importSource = $importSource;
//Vanilla copy of the bean object.
$this->bean = $bean;
// use our own error handler
set_error_handler(array('Importer','handleImportErrors'),E_ALL);
// Increase the max_execution_time since this step can take awhile
ini_set("max_execution_time", max($sugar_config['import_max_execution_time'],3600));
// stop the tracker
TrackerManager::getInstance()->pause();
// set the default locale settings
$this->ifs = $this->getFieldSanitizer();
//Get the default user currency
$this->defaultUserCurrency = new Currency();
$this->defaultUserCurrency->retrieve('-99');
//Get our import column definitions
$this->importColumns = $this->getImportColumns();
$this->isUpdateOnly = ( isset($_REQUEST['import_type']) && $_REQUEST['import_type'] == 'update' );
}
public function import()
{
foreach($this->importSource as $row)
{
$this->importRow($row);
}
// save mapping if requested
if ( isset($_REQUEST['save_map_as']) && $_REQUEST['save_map_as'] != '' )
{
$this->saveMappingFile();
}
$this->importSource->writeStatus();
//All done, remove file.
}
protected function importRow($row)
{
global $sugar_config, $mod_strings;
$focus = clone $this->bean;
$focus->unPopulateDefaultValues();
$focus->save_from_post = false;
$focus->team_id = null;
$this->ifs->createdBeans = array();
$this->importSource->resetRowErrorCounter();
$do_save = true;
for ( $fieldNum = 0; $fieldNum < $_REQUEST['columncount']; $fieldNum++ )
{
// loop if this column isn't set
if ( !isset($this->importColumns[$fieldNum]) )
continue;
// get this field's properties
$field = $this->importColumns[$fieldNum];
$fieldDef = $focus->getFieldDefinition($field);
$fieldTranslated = translate((isset($fieldDef['vname'])?$fieldDef['vname']:$fieldDef['name']), $focus->module_dir)." (".$fieldDef['name'].")";
$defaultRowValue = '';
// Bug 37241 - Don't re-import over a field we already set during the importing of another field
if ( !empty($focus->$field) )
continue;
// translate strings
global $locale;
if(empty($locale))
{
$locale = new Localization();
}
if ( isset($row[$fieldNum]) )
{
$rowValue = $locale->translateCharset(strip_tags(trim($row[$fieldNum])),$this->importSource->importlocale_charset,$sugar_config['default_charset']);
}
else if( isset($this->sugarToExternalSourceFieldMap[$field]) && isset($row[$this->sugarToExternalSourceFieldMap[$field]]) )
{
$rowValue = $locale->translateCharset(strip_tags(trim($row[$this->sugarToExternalSourceFieldMap[$field]])),$this->importSource->importlocale_charset,$sugar_config['default_charset']);
}
else
{
$rowValue = '';
}
// If there is an default value then use it instead
if ( !empty($_REQUEST[$field]) )
{
$defaultRowValue = $this->populateDefaultMapValue($field, $_REQUEST[$field], $fieldDef);
if( empty($rowValue))
{
$rowValue = $defaultRowValue;
//reset the default value to empty
$defaultRowValue='';
}
}
// Bug 22705 - Don't update the First Name or Last Name value if Full Name is set
if ( in_array($field, array('first_name','last_name')) && !empty($focus->full_name) )
continue;
// loop if this value has not been set
if ( !isset($rowValue) )
continue;
// If the field is required and blank then error out
if ( array_key_exists($field,$focus->get_import_required_fields()) && empty($rowValue) && $rowValue!='0')
{
$this->importSource->writeError( $mod_strings['LBL_REQUIRED_VALUE'],$fieldTranslated,'NULL');
$do_save = false;
}
// Handle the special case "Sync to Outlook"
if ( $focus->object_name == "Contacts" && $field == 'sync_contact' )
{
$bad_names = array();
$returnValue = $this->ifs->synctooutlook($rowValue,$fieldDef,$bad_names);
// try the default value on fail
if ( !$returnValue && !empty($defaultRowValue) )
$returnValue = $this->ifs->synctooutlook($defaultRowValue, $fieldDef, $bad_names);
if ( !$returnValue )
{
$this->importSource->writeError($mod_strings['LBL_ERROR_SYNC_USERS'], $fieldTranslated, explode(",",$bad_names));
$do_save = 0;
}
}
// Handle email1 and email2 fields ( these don't have the type of email )
if ( $field == 'email1' || $field == 'email2' )
{
$returnValue = $this->ifs->email($rowValue, $fieldDef, $focus);
// try the default value on fail
if ( !$returnValue && !empty($defaultRowValue) )
$returnValue = $this->ifs->email( $defaultRowValue, $fieldDef);
if ( $returnValue === FALSE )
{
$do_save=0;
$this->importSource->writeError( $mod_strings['LBL_ERROR_INVALID_EMAIL'], $fieldTranslated, $rowValue);
}
else
{
$rowValue = $returnValue;
// check for current opt_out and invalid email settings for this email address
// if we find any, set them now
$emailres = $focus->db->query( "SELECT opt_out, invalid_email FROM email_addresses WHERE email_address = '".$focus->db->quote($rowValue)."'");
if ( $emailrow = $focus->db->fetchByAssoc($emailres) )
{
$focus->email_opt_out = $emailrow['opt_out'];
$focus->invalid_email = $emailrow['invalid_email'];
}
}
}
// Handle splitting Full Name into First and Last Name parts
if ( $field == 'full_name' && !empty($rowValue) )
{
$this->ifs->fullname($rowValue,$fieldDef,$focus);
}
// to maintain 451 compatiblity
if(!isset($fieldDef['module']) && $fieldDef['type']=='relate')
$fieldDef['module'] = ucfirst($fieldDef['table']);
if(isset($fieldDef['custom_type']) && !empty($fieldDef['custom_type']))
$fieldDef['type'] = $fieldDef['custom_type'];
// If the field is empty then there is no need to check the data
if( !empty($rowValue) )
{
//Start
$rowValue = $this->sanitizeFieldValueByType($rowValue, $fieldDef, $defaultRowValue, $focus, $fieldTranslated);
if($rowValue === FALSE)
continue;
}
// if the parent type is in singular form, get the real module name for parent_type
if (isset($fieldDef['type']) && $fieldDef['type']=='parent_type') {
$rowValue = get_module_from_singular($rowValue);
}
$focus->$field = $rowValue;
unset($defaultRowValue);
}
// Now try to validate flex relate fields
if ( isset($focus->field_defs['parent_name']) && isset($focus->parent_name) && ($focus->field_defs['parent_name']['type'] == 'parent') )
{
// populate values from the picker widget if the import file doesn't have them
$parent_idField = $focus->field_defs['parent_name']['id_name'];
if ( empty($focus->$parent_idField) && !empty($_REQUEST[$parent_idField]) )
$focus->$parent_idField = $_REQUEST[$parent_idField];
$parent_typeField = $focus->field_defs['parent_name']['type_name'];
if ( empty($focus->$parent_typeField) && !empty($_REQUEST[$parent_typeField]) )
$focus->$parent_typeField = $_REQUEST[$parent_typeField];
// now validate it
$returnValue = $this->ifs->parent($focus->parent_name,$focus->field_defs['parent_name'],$focus, empty($_REQUEST['parent_name']));
if ( !$returnValue && !empty($_REQUEST['parent_name']) )
$returnValue = $this->ifs->parent( $_REQUEST['parent_name'],$focus->field_defs['parent_name'], $focus);
}
// check to see that the indexes being entered are unique.
if (isset($_REQUEST['enabled_dupes']) && $_REQUEST['enabled_dupes'] != "")
{
$toDecode = html_entity_decode ($_REQUEST['enabled_dupes'], ENT_QUOTES);
$enabled_dupes = json_decode($toDecode);
$idc = new ImportDuplicateCheck($focus);
if ( $idc->isADuplicateRecord($enabled_dupes) )
{
$this->importSource->markRowAsDuplicate($idc->_dupedFields);
$this->_undoCreatedBeans($this->ifs->createdBeans);
return;
}
}
//Allow fields to be passed in for dup check as well (used by external adapters)
else if( !empty($_REQUEST['enabled_dup_fields']) )
{
$toDecode = html_entity_decode ($_REQUEST['enabled_dup_fields'], ENT_QUOTES);
$enabled_dup_fields = json_decode($toDecode);
$idc = new ImportDuplicateCheck($focus);
if ( $idc->isADuplicateRecordByFields($enabled_dup_fields) )
{
$this->importSource->markRowAsDuplicate($idc->_dupedFields);
$this->_undoCreatedBeans($this->ifs->createdBeans);
return;
}
}
// if the id was specified
$newRecord = true;
if ( !empty($focus->id) )
{
$focus->id = $this->_convertId($focus->id);
// check if it already exists
$query = "SELECT * FROM {$focus->table_name} WHERE id='".$focus->db->quote($focus->id)."'";
$result = $focus->db->query($query)
or sugar_die("Error selecting sugarbean: ");
$dbrow = $focus->db->fetchByAssoc($result);
if (isset ($dbrow['id']) && $dbrow['id'] != -1)
{
// if it exists but was deleted, just remove it
if (isset ($dbrow['deleted']) && $dbrow['deleted'] == 1 && $this->isUpdateOnly ==false)
{
$this->removeDeletedBean($focus);
$focus->new_with_id = true;
}
else
{
if( ! $this->isUpdateOnly )
{
$this->importSource->writeError($mod_strings['LBL_ID_EXISTS_ALREADY'],'ID',$focus->id);
$this->_undoCreatedBeans($this->ifs->createdBeans);
return;
}
$clonedBean = $this->cloneExistingBean($focus);
if($clonedBean === FALSE)
{
$this->importSource->writeError($mod_strings['LBL_RECORD_CANNOT_BE_UPDATED'],'ID',$focus->id);
$this->_undoCreatedBeans($this->ifs->createdBeans);
return;
}
else
{
$focus = $clonedBean;
$newRecord = FALSE;
}
}
}
else
{
$focus->new_with_id = true;
}
}
if ($do_save)
{
$this->saveImportBean($focus, $newRecord);
// Update the created/updated counter
$this->importSource->markRowAsImported($newRecord);
}
else
$this->_undoCreatedBeans($this->ifs->createdBeans);
unset($defaultRowValue);
}
protected function sanitizeFieldValueByType($rowValue, $fieldDef, $defaultRowValue, $focus, $fieldTranslated)
{
global $mod_strings, $app_list_strings;
switch ($fieldDef['type'])
{
case 'enum':
case 'multienum':
if ( isset($fieldDef['type']) && $fieldDef['type'] == "multienum" )
$returnValue = $this->ifs->multienum($rowValue,$fieldDef);
else
$returnValue = $this->ifs->enum($rowValue,$fieldDef);
// try the default value on fail
if ( !$returnValue && !empty($defaultRowValue) )
{
if ( isset($fieldDef['type']) && $fieldDef['type'] == "multienum" )
$returnValue = $this->ifs->multienum($defaultRowValue,$fieldDef);
else
$returnValue = $this->ifs->enum($defaultRowValue,$fieldDef);
}
if ( $returnValue === FALSE )
{
$this->importSource->writeError($mod_strings['LBL_ERROR_NOT_IN_ENUM'] . implode(",",$app_list_strings[$fieldDef['options']]), $fieldTranslated,$rowValue);
return FALSE;
}
else
return $returnValue;
break;
case 'relate':
case 'parent':
$returnValue = $this->ifs->relate($rowValue, $fieldDef, $focus, empty($defaultRowValue));
if (!$returnValue && !empty($defaultRowValue))
$returnValue = $this->ifs->relate($defaultRowValue,$fieldDef, $focus);
// Bug 33623 - Set the id value found from the above method call as an importColumn
if ($returnValue !== false)
$this->importColumns[] = $fieldDef['id_name'];
return $rowValue;
break;
case 'teamset':
$this->ifs->teamset($rowValue,$fieldDef,$focus);
$this->importColumns[] = 'team_set_id';
$this->importColumns[] = 'team_id';
return $rowValue;
break;
case 'fullname':
return $rowValue;
break;
default:
$fieldtype = $fieldDef['type'];
$returnValue = $this->ifs->$fieldtype($rowValue, $fieldDef, $focus);
// try the default value on fail
if ( !$returnValue && !empty($defaultRowValue) )
$returnValue = $this->ifs->$fieldtype($defaultRowValue,$fieldDef, $focus);
if ( !$returnValue )
{
$this->importSource->writeError($mod_strings['LBL_ERROR_INVALID_'.strtoupper($fieldDef['type'])],$fieldTranslated,$rowValue,$focus);
return FALSE;
}
return $returnValue;
}
}
protected function cloneExistingBean($focus)
{
$existing_focus = clone $this->bean;
if ( !( $existing_focus->retrieve($focus->id) instanceOf SugarBean ) )
{
return FALSE;
}
else
{
$newData = $focus->toArray();
foreach ( $newData as $focus_key => $focus_value )
if ( in_array($focus_key,$this->importColumns) )
$existing_focus->$focus_key = $focus_value;
return $existing_focus;
}
}
protected function removeDeletedBean($focus)
{
global $mod_strings;
$query2 = "DELETE FROM {$focus->table_name} WHERE id='".$focus->db->quote($focus->id)."'";
$result2 = $focus->db->query($query2) or sugar_die($mod_strings['LBL_ERROR_DELETING_RECORD']." ".$focus->id);
if ($focus->hasCustomFields())
{
$query3 = "DELETE FROM {$focus->table_name}_cstm WHERE id_c='".$focus->db->quote($focus->id)."'";
$result2 = $focus->db->query($query3);
}
}
protected function saveImportBean($focus, $newRecord)
{
global $timedate, $current_user;
// Populate in any default values to the bean
$focus->populateDefaultValues();
if ( !isset($focus->assigned_user_id) || $focus->assigned_user_id == '' && $newRecord )
{
$focus->assigned_user_id = $current_user->id;
}
/*
* Bug 34854: Added all conditions besides the empty check on date modified.
*/
if ( ( !empty($focus->new_with_id) && !empty($focus->date_modified) ) ||
( empty($focus->new_with_id) && $timedate->to_db($focus->date_modified) != $timedate->to_db($timedate->to_display_date_time($focus->fetched_row['date_modified'])) )
)
$focus->update_date_modified = false;
$focus->optimistic_lock = false;
if ( $focus->object_name == "Contacts" && isset($focus->sync_contact) )
{
//copy the potential sync list to another varible
$list_of_users=$focus->sync_contact;
//and set it to false for the save
$focus->sync_contact=false;
}
else if($focus->object_name == "User" && !empty($current_user) && $focus->is_admin && !is_admin($current_user) && is_admin_for_module($current_user, 'Users')) {
sugar_die($GLOBALS['mod_strings']['ERR_IMPORT_SYSTEM_ADMININSTRATOR']);
}
//bug# 40260 setting it true as the module in focus is involved in an import
$focus->in_import=true;
// call any logic needed for the module preSave
$focus->beforeImportSave();
$focus->save(false);
// call any logic needed for the module postSave
$focus->afterImportSave();
if ( $focus->object_name == "Contacts" && isset($list_of_users) )
$focus->process_sync_to_outlook($list_of_users);
// Add ID to User's Last Import records
if ( $newRecord )
$this->importSource->writeRowToLastImport( $_REQUEST['import_module'],($focus->object_name == 'Case' ? 'aCase' : $focus->object_name),$focus->id);
}
protected function saveMappingFile()
{
global $current_user;
$firstrow = unserialize(base64_decode($_REQUEST['firstrow']));
$mappingValsArr = $this->importColumns;
$mapping_file = new ImportMap();
if ( isset($_REQUEST['has_header']) && $_REQUEST['has_header'] == 'on')
{
$header_to_field = array ();
foreach ($this->importColumns as $pos => $field_name)
{
if (isset($firstrow[$pos]) && isset($field_name))
{
$header_to_field[$firstrow[$pos]] = $field_name;
}
}
$mappingValsArr = $header_to_field;
}
//get array of values to save for duplicate and locale settings
$advMapping = $this->retrieveAdvancedMapping();
//merge with mappingVals array
if(!empty($advMapping) && is_array($advMapping))
{
$mappingValsArr = array_merge($mappingValsArr,$advMapping);
}
//set mapping
$mapping_file->setMapping($mappingValsArr);
// save default fields
$defaultValues = array();
for ( $i = 0; $i < $_REQUEST['columncount']; $i++ )
{
if (isset($this->importColumns[$i]) && !empty($_REQUEST[$this->importColumns[$i]]))
{
$field = $this->importColumns[$i];
$fieldDef = $this->bean->getFieldDefinition($field);
if(!empty($fieldDef['custom_type']) && $fieldDef['custom_type'] == 'teamset')
{
require_once('include/SugarFields/Fields/Teamset/SugarFieldTeamset.php');
$sugar_field = new SugarFieldTeamset('Teamset');
$teams = $sugar_field->getTeamsFromRequest($field);
if(isset($_REQUEST['primary_team_name_collection']))
{
$primary_index = $_REQUEST['primary_team_name_collection'];
}
//If primary_index was selected, ensure that the first Array entry is the primary team
if(isset($primary_index))
{
$count = 0;
$new_teams = array();
foreach($teams as $id=>$name)
{
if($primary_index == $count++)
{
$new_teams[$id] = $name;
unset($teams[$id]);
break;
}
}
foreach($teams as $id=>$name)
{
$new_teams[$id] = $name;
}
$teams = $new_teams;
} //if
$json = getJSONobj();
$defaultValues[$field] = $json->encode($teams);
}
else
{
$defaultValues[$field] = $_REQUEST[$this->importColumns[$i]];
}
}
}
$mapping_file->setDefaultValues($defaultValues);
$result = $mapping_file->save( $current_user->id, $_REQUEST['save_map_as'], $_REQUEST['import_module'], $_REQUEST['source'],
( isset($_REQUEST['has_header']) && $_REQUEST['has_header'] == 'on'), $_REQUEST['custom_delimiter'], html_entity_decode($_REQUEST['custom_enclosure'],ENT_QUOTES)
);
}
protected function populateDefaultMapValue($field, $fieldValue, $fieldDef)
{
global $timedate, $current_user;
if ( is_array($fieldValue) )
$defaultRowValue = encodeMultienumValue($fieldValue);
else
$defaultRowValue = $_REQUEST[$field];
// translate default values to the date/time format for the import file
if( $fieldDef['type'] == 'date' && $this->ifs->dateformat != $timedate->get_date_format() )
$defaultRowValue = $timedate->swap_formats($defaultRowValue, $this->ifs->dateformat, $timedate->get_date_format());
if( $fieldDef['type'] == 'time' && $this->ifs->timeformat != $timedate->get_time_format() )
$defaultRowValue = $timedate->swap_formats($defaultRowValue, $this->ifs->timeformat, $timedate->get_time_format());
if( ($fieldDef['type'] == 'datetime' || $fieldDef['type'] == 'datetimecombo') && $this->ifs->dateformat.' '.$this->ifs->timeformat != $timedate->get_date_time_format() )
$defaultRowValue = $timedate->swap_formats($defaultRowValue, $this->ifs->dateformat.' '.$this->ifs->timeformat,$timedate->get_date_time_format());
if ( in_array($fieldDef['type'],array('currency','float','int','num')) && $this->ifs->num_grp_sep != $current_user->getPreference('num_grp_sep') )
$defaultRowValue = str_replace($current_user->getPreference('num_grp_sep'), $this->ifs->num_grp_sep,$defaultRowValue);
if ( in_array($fieldDef['type'],array('currency','float')) && $this->ifs->dec_sep != $current_user->getPreference('dec_sep') )
$defaultRowValue = str_replace($current_user->getPreference('dec_sep'), $this->ifs->dec_sep,$defaultRowValue);
$user_currency_symbol = $this->defaultUserCurrency->symbol;
if ( $fieldDef['type'] == 'currency' && $this->ifs->currency_symbol != $user_currency_symbol )
$defaultRowValue = str_replace($user_currency_symbol, $this->ifs->currency_symbol,$defaultRowValue);
return $defaultRowValue;
}
protected function getImportColumns()
{
$importable_fields = $this->bean->get_importable_fields();
$importColumns = array();
foreach ($_REQUEST as $name => $value)
{
// only look for var names that start with "fieldNum"
if (strncasecmp($name, "colnum_", 7) != 0)
continue;
// pull out the column position for this field name
$pos = substr($name, 7);
if ( isset($importable_fields[$value]) )
{
// now mark that we've seen this field
$importColumns[$pos] = $value;
}
}
return $importColumns;
}
protected function getFieldSanitizer()
{
$ifs = new ImportFieldSanitize();
$copyFields = array('dateformat','timeformat','timezone','default_currency_significant_digits','num_grp_sep','dec_sep','default_locale_name_format');
foreach($copyFields as $field)
{
$fieldKey = "importlocale_$field";
$ifs->$field = $this->importSource->$fieldKey;
}
$currency = new Currency();
$currency->retrieve($this->importSource->importlocale_currency);
$ifs->currency_symbol = $currency->symbol;
return $ifs;
}
/**
* Sets a translation map from sugar field key to external source key used while importing a row. This allows external sources
* to return a data set that is an associative array rather than numerically indexed.
*
* @param $translator
* @return void
*/
public function setFieldKeyTranslator($translator)
{
$this->sugarToExternalSourceFieldMap = $translator;
}
/**
* If a bean save is not done for some reason, this method will undo any of the beans that were created
*
* @param array $ids ids of user_last_import records created
*/
protected function _undoCreatedBeans( array $ids )
{
$focus = new UsersLastImport();
foreach ($ids as $id)
$focus->undoById($id);
}
/**
* clean id's when being imported
*
* @param string $string
* @return string
*/
protected function _convertId($string)
{
return preg_replace_callback(
'|[^A-Za-z0-9\-\_]|',
create_function(
// single quotes are essential here,
// or alternative escape all $ as \$
'$matches',
'return ord($matches[0]);'
) ,
$string);
}
public function retrieveAdvancedMapping()
{
$advancedMappingSettings = array();
//harvest the dupe index settings
if( isset($_REQUEST['enabled_dupes']) )
{
$toDecode = html_entity_decode ($_REQUEST['enabled_dupes'], ENT_QUOTES);
$dupe_ind = json_decode($toDecode);
foreach($dupe_ind as $dupe)
{
$advancedMappingSettings['dupe_'.$dupe] = $dupe;
}
}
foreach($_REQUEST as $rk=>$rv)
{
//harvest the import locale settings
if(strpos($rk,'portlocale_')>0)
{
$advancedMappingSettings[$rk] = $rv;
}
}
return $advancedMappingSettings;
}
public static function getImportableModules()
{
global $beanList;
$importableModules = array();
foreach ($beanList as $moduleName => $beanName)
{
if( class_exists($beanName) )
{
$tmp = new $beanName();
if( isset($tmp->importable) && $tmp->importable )
{
$label = isset($GLOBALS['app_list_strings']['moduleList'][$moduleName]) ? $GLOBALS['app_list_strings']['moduleList'][$moduleName] : $moduleName;
$importableModules[$moduleName] = $label;
}
}
}
asort($importableModules);
return $importableModules;
}
/**
* Replaces PHP error handler in Step4
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param string $errline
*/
public static function handleImportErrors($errno, $errstr, $errfile, $errline)
{
$GLOBALS['log']->fatal("Caught error: $errstr");
if ( !defined('E_DEPRECATED') )
define('E_DEPRECATED','8192');
if ( !defined('E_USER_DEPRECATED') )
define('E_USER_DEPRECATED','16384');
// check to see if current reporting level should be included based upon error_reporting() setting, if not
// then just return
if ( !(error_reporting() & $errno) )
return true;
switch ($errno)
{
case E_USER_ERROR:
echo "ERROR: [$errno] $errstr on line $errline in file $errfile
\n";
exit(1);
break;
case E_USER_WARNING:
case E_WARNING:
echo "WARNING: [$errno] $errstr on line $errline in file $errfile
\n";
break;
case E_USER_NOTICE:
case E_NOTICE:
echo "NOTICE: [$errno] $errstr on line $errline in file $errfile
\n";
break;
case E_STRICT:
case E_DEPRECATED:
case E_USER_DEPRECATED:
// don't worry about these
//echo "STRICT ERROR: [$errno] $errstr on line $errline in file $errfile
\n";
break;
default:
echo "Unknown error type: [$errno] $errstr on line $errline in file $errfile
\n";
break;
}
return true;
}
}