vtenext/modules/Settings/DataImporter/DataImporterErp.php
2021-04-28 20:10:26 +02:00

2539 lines
84 KiB
PHP

<?php
/*************************************
* SPDX-FileCopyrightText: 2009-2020 Vtenext S.r.l. <info@vtenext.com>
* SPDX-License-Identifier: AGPL-3.0-only
************************************/
/* crmv@65455 - reviewed version of the erpConnector classes */
/* crmv@90287 - support for same-module reference fields */
/*
If you want to test an import directly, use this code:
require_once('modules/Settings/DataImporter/DataImporterCron.php');
$dcron = new DataImporterCron(3); // 3 is the importid
$r = $dcron->run();
inside a standard script file
*/
require_once('include/BaseClasses.php');
require_once('include/VTEBaseLogger.php');
require_once('modules/Settings/DataImporter/DataImporterUtils.php');
require_once('modules/Settings/DataImporter/DataImporterErpClasses.php');
class DataImporterErp extends SDKExtendableClass {
public $logConfig = array(
'level' => 5, // 5 = debug, 4 = info, 3 = warning, 2 = error, 1 = fatal
'name' => 'DataImporter',
);
public $config = array(
'log_memory_rows' => 1000, // log memory usage statistics every N rows, only if debug level >= 5, set to 0 do deactivate
'trim_csv_values' => true, // if true, values read from CSV are trimmed (space characters are removed from beginning and end)
'trim_db_values' => false, // if true, values read from database are trimmed
'on_invalid_cell' => 'skip', // "skip", "blank" or "ignore": when a cell contains an invalid value, you can skip the whole line, empty the cell
// or ignore it and try to continue (there may be problems later)
'log_statistics' => true, // if true, print out some statistics about the import process
'keep_import_files' => false, // if true, import files are kept (warning: they might use a lot of space!!)
'autoclean_import_files' => 30, // number of days after which automatically delete old import files (0 to disable)
'autoclean_log_files' => 90, // number of days after which automatically delete old log files (0 to disable)
'import_user' => 'ADMIN', // during the import, impersonate this user. Can be a userid, a username or "ADMIN" to use the first valid admin user.
// Empty to disable the switch. Using a non-admin user can give problems.
'abort_interval' => 30, // Every X seconds, check if the import should be aborted, only used in the first reading phase
'auto_create_index' => true, // If true, automatically creates indexes for columns used as external codes
'fast_mysql_insert' => true, // If true, when the VTE database is MySql, insert records really fast by using local-infile. Disabled when importing the product block
);
public $simulate = false; // if true, no changes are made to VTE (only some crmid increments might happen)
//crmv@136410
public $launch_workflows = false;
public $launch_processes = false;
public $enable_process_modify_action = true; // if false dataimport do not trigger the event "on change" but only "on create", "on create / change" and "every time the condition is true", if true all triggers are enabled
//crmv@136410e
public $pass;
public $module;
public $invmodule;
public $logTable;
public $erpdir;
public $sqldir;
public $logdir;
public $object;
public $log;
public $entity_created = array();
public $entity_updated = array();
public $entity_deleted = array();
public $entity_2ndpass = array();
// some internal vars
protected $importId;
protected $importInfo;
protected $time_start;
protected $diutils;
protected $iutils;
protected $allFormats;
protected $allFormulas;
// mapping related stuff
protected $mapping;
protected $mapping_inverse;
protected $external_code;
protected $fields = Array();
protected $fields_functions = Array();
protected $sequence_field = null;
protected $fields_auto_create = Array();
protected $fields_auto_update = Array();
protected $fields_runtime = Array();
protected $selfReferencies = array();
// vars used for database connection
private $extdb;
private $extdb_charset; // crmv@109628
private $qresult;
private $query = '';
private $selfRefUpdateQueries = '';
private $prodAttrCreated = false; // crmv@203591
private $prodAttrValues = []; // crmv@203591
// vars used by csv
private $csvFiles = array();
private $currentFileIdx = -1;
private $fhandle;
private $csvreader;
private $csv_rows = 0;
// statistics vars
private $row_retrieved = 0;
private $row_discarded = 0;
private $time_readsrc = '';
private $time_saverecords = '';
private $time_total = '';
// other internal vars, for product block
private $currentInventoryId = null;
private $currentTotals = array();
public function __construct($importid, $importInfo) {
global $table_prefix, $root_directory;
$this->importId = $importid;
$this->importInfo = $importInfo;
$this->logTable = $table_prefix.'_dataimporter_log';
$this->module = $importInfo['module'];
$this->invmodule = $importInfo['invmodule'];
// data importer utils
$this->diutils = new DataImporterUtils();
// inventory utils
if ($this->module == 'ProductRows') {
$this->config['fast_mysql_insert'] = false;
$this->iutils = InventoryUtils::getInstance();
$this->iutils->workingPrecision = $this->iutils->outputPrecision = 3;
}
$this->erpdir = rtrim($root_directory, '/').'/'.$this->diutils->working_folder;
$this->sqldir = $this->erpdir.'/import_'.$importid;
$this->logdir = $this->sqldir.'/logs';
// log object
$logCfg = array(
'file_global' => array_merge($this->logConfig, array('type' => 'file', 'rotate_size' => 5)),
'file_last' => array_merge($this->logConfig, array('type' => 'file', 'clean_on_start' => true)),
'std' => $this->logConfig,
);
$logCfg['file_global']['file'] = $this->logdir.'/general.log';
$logCfg['file_last']['file'] = $this->logdir.'/last.log';
$this->log = new DataImporterLogger($logCfg);
$this->allFormats = $this->diutils->getAvailableFormats();
$this->allFormulas = $this->diutils->getAvailableFormulas();
$this->initModuleObject();
$this->prepareMapping();
$this->getFieldsDesc();
}
public function __destruct() {
if ($this->fhandle) fclose($this->fhandle);
}
public function initModuleObject() {
global $table_prefix;
if ($this->module == 'ProductRows' && $this->invmodule) {
$this->object = CRMEntity::getInstance($this->invmodule);
} else {
$this->object = CRMEntity::getInstance($this->module);
}
if ($this->module == 'Users') {
$this->object->tab_name = Array($table_prefix.'_users');
$this->object->tab_name_index = Array($table_prefix.'_users'=>'id');
}
//check table: rimuovere tabelle che non fanno parte del "core" del modulo (tipo ticketcomments)
foreach ($this->object->tab_name_index as $tablename=>$index){
if (!in_array($tablename,$this->object->tab_name))
unset($this->object->tab_name_index[$tablename]);
}
}
public function prepareMapping() {
$this->external_code = $this->diutils->fixSpaceNames($this->importInfo['mapping']['dimport_mapping_keycol'], false); // crmv@93582 crmv@105144
foreach ($this->importInfo['mapping']['fields'] as $colname => $map) {
if ($map['field']) {
$this->mapping[$colname] = $map['field'];
}
}
if (is_array($this->mapping)) {
$this->mapping_inverse = array_flip($this->mapping);
}
// autocreate/update defaults. THe importer expect this to be a table->column array, but I have field names, so I need to translate them
if (is_array($this->importInfo['mapping']['deffields'])) {
foreach ($this->importInfo['mapping']['deffields']['create'] as $fld) {
if (!empty($fld['field']) && isset($fld['default'])) {
$finfo = $this->getFieldColumn($this->module, array($fld['field']));
$finfo = $finfo[$fld['field']];
$this->fields_auto_create[$finfo['tablename']][$finfo['columnname']] = $fld['default'];
}
}
foreach ($this->importInfo['mapping']['deffields']['update'] as $fld) {
if (!empty($fld['field']) && isset($fld['default'])) {
$finfo = $this->getFieldColumn($this->module, array($fld['field']));
$finfo = $finfo[$fld['field']];
$this->fields_auto_update[$finfo['tablename']][$finfo['columnname']] = $fld['default'];
}
}
}
}
// add some default for creation and update in case they are missing
protected function addAutoFields() {
global $table_prefix, $current_user;
if (empty($this->time_start)) {
$this->time_start = date('Y-m-d H:i:s'); // this is not right, since the import might require a long time... but ok let's keep it this way
}
$crmTable = $table_prefix.'_crmentity';
$inventoryTable = $table_prefix.'_inventoryproductrel';
if ($this->module == 'ProductRows') {
$this->fields_auto_create[$inventoryTable]['relmodule'] = $this->invmodule;
$this->fields_auto_update[$inventoryTable]['relmodule'] = $this->invmodule;
$this->fields_auto_create[$inventoryTable]['incrementondel'] = 0;
return;
}
if (in_array($crmTable,$this->object->tab_name)){
if (!array_key_exists('creator', $this->mapping_inverse) && empty($this->fields_auto_create[$crmTable]['smcreatorid'])) {
$this->fields_auto_create[$crmTable]['smcreatorid'] = $current_user->id;
}
if (!array_key_exists('assigned_user_id', $this->mapping_inverse) && empty($this->fields_auto_create[$crmTable]['smownerid'])) {
$this->fields_auto_create[$crmTable]['smownerid'] = $current_user->id;
}
if (!array_key_exists('modifiedby', $this->mapping_inverse)) {
if (empty($this->fields_auto_create[$crmTable]['modifiedby'])) {
$this->fields_auto_create[$crmTable]['modifiedby'] = $current_user->id;
}
if (empty($this->fields_auto_update[$crmTable]['modifiedby'])) {
$this->fields_auto_update[$crmTable]['modifiedby'] = $current_user->id;
}
}
if (!array_key_exists('createdtime', $this->mapping_inverse) && empty($this->fields_auto_create[$crmTable]['createdtime'])) {
$this->fields_auto_create[$crmTable]['createdtime'] = $this->time_start;
}
if (!array_key_exists('modifiedtime', $this->mapping_inverse)) {
if (empty($this->fields_auto_create[$crmTable]['modifiedtime'])) {
$this->fields_auto_create[$crmTable]['modifiedtime'] = $this->time_start;
}
if (empty($this->fields_auto_update[$crmTable]['modifiedtime'])) {
$this->fields_auto_update[$crmTable]['modifiedtime'] = $this->time_start;
}
}
$this->fields_auto_create[$crmTable]['setype'] = $this->module;
}
}
// get the table, column and uitype for the specified field from the database
// fieldname can be an array, in that case, all matching fields are returned
// an array of fields is alwasy returned
protected function getFieldColumn($module, $fieldname) {
global $adb, $table_prefix;
if (!is_array($fieldname)) $fieldname = array($fieldname);
if (!is_array($this->field_cache)) $this->field_cache = array();
if (!is_array($this->field_cache[$module])) $this->field_cache[$module] = array();
$fieldsInCache = array_intersect_key($this->field_cache[$module], array_fill_keys($fieldname, 0));
if (count($fieldsInCache) != count($fieldname)) {
// some fields are missing, I need to retrieve them
$fieldsToRetrieve = array_diff($fieldname, array_keys($fieldsInCache));
// populate the cache
if ($module == 'ProductRows') {
$flds = $this->diutils->getMappableFields($this->importInfo);
$flds = array_intersect_key($flds, array_flip($fieldsToRetrieve));
$this->field_cache[$module] = array_merge($this->field_cache[$module], $flds);
$fieldsInCache = array_merge($fieldsInCache, $flds);
} else {
// crmv@203591
$hasProdInfo = false;
if ($module == 'Products') {
// check for attributes
$prodInfoFields = [];
foreach ($fieldsToRetrieve as $fieldname) {
if (substr($fieldname, 0, 9) === 'prodattr_' || substr($fieldname, -8) === 'prodattr') {
$hasProdInfo = true;
$fieldsToRetrieve[] = 'confprodinfo';
$prodInfoFields[] = $fieldname;
}
}
$fieldsToRetrieve = array_unique($fieldsToRetrieve);
}
// crmv@203591e
$sql = "SELECT tablename,columnname,fieldname,uitype FROM {$table_prefix}_field WHERE fieldname IN (".generateQuestionMarks($fieldsToRetrieve).") AND tabid = ?";
$params = $fieldsToRetrieve;
$params[] = getTabid($module);
$res = $adb->pquery($sql,$params);
if ($res){
while ($row = $adb->fetchByAssoc($res,-1,false)){
$this->field_cache[$module][$row['fieldname']] = $row;
$fieldsInCache[$row['fieldname']] = $row;
}
}
// crmv@203591
if ($hasProdInfo) {
foreach ($prodInfoFields as $prodAttrField) {
$prodAttr = [
'tablename' => $this->field_cache[$module]['confprodinfo']['tablename'],
'columnname' => $prodAttrField,
'fieldname' => $prodAttrField,
'uitype' => 1,
];
$this->field_cache[$module][$prodAttrField] = $prodAttr;
$fieldsInCache[$prodAttrField] = $prodAttr;
}
}
// crmv@203591e
}
}
return $fieldsInCache;
}
public function getFieldsDesc() {
$fields = $this->getFieldColumn($this->module, array_values($this->mapping));
foreach ($fields as $fieldname=>$row) {
$this->fields[$row['tablename']][] = $row['columnname'];
$this->fields_name[$row['tablename']][$row['columnname']] = $row['fieldname']; // removed strtolower
// TODO: sistema un po' questa roba
switch($row['uitype']){
case 99: //password
$this->fields_functions[$row['fieldname']] = 'set_password';
break;
case 5: //data
$this->fields_functions[$row['fieldname']] = 'get_date_short_value';
break;
case 117: //valuta
$this->fields_functions[$row['fieldname']] = 'get_valuta';
break;
/*
case 56: //checkbox
$this->fields_functions[$row['fieldname']] = 'get_checkbox_value';
break;
case 53: //assegnatario
$this->fields_functions[$row['fieldname']] = 'get_assigned_user';
break;
case 70: //data e ora
$this->fields_functions[$row['fieldname']] = 'get_date_value';
break;
case 10: //uitype10 field
$this->fields_functions[$row['fieldname']] = 'get_ui10_value';
break;
*/
default:
break;
}
}
$this->getSequenceField();
}
// catches only fatal errors
static function errorHandler($logger = null) { // crmv@155585
$error = error_get_last();
$catchTypes = array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
// NOTICE: in case of die() or exit(), it's not possible to detect if there was an error or not, so the next code won't be executed
if ($logger && $error !== null && in_array($error['type'], $catchTypes)) {
$logger->fatal('A fatal error was encountered during the execution: '.print_r($error, true));
}
}
public function run() {
$this->log->info('Starting import...', true);
// catch fatal errors
register_shutdown_function(array($this, 'errorHandler'), $this->log);
if ($this->config['autoclean_log_files'] > 0) $this->autocleanLogFiles();
$r = $this->checkTables();
if (!$r) return $r;
$r = $this->checkDirectory();
if (!$r) return $r;
$r = $this->checkSource();
if (!$r) return $r;
if ($this->config['auto_create_index']) {
$r = $this->checkKeyIndexes();
}
$this->pass = 1;
// for statistics
$this->log->tic('stat_main');
$this->log->tic('stat_read');
$this->importLog('IMPORT_START', 'Import started');
// impersonate the import user
if (!empty($this->config['import_user'])) {
$r = $this->impersonateUser($this->config['import_user']);
if (!$r) return $this->log->fatal("Unable to switch to user {$this->config['import_user']}");
}
// add special fields for the import
$this->addAutoFields();
// check for same-module referencies
$this->checkSelfReferencies();
$r = $this->preprocess();
if (!$r) {
if (!empty($this->config['import_user'])) $this->restoreUser();
$this->log->info("Preprocess method returned false, so the import has been aborted");
return true;
}
// create the files
if ($this->config['autoclean_import_files'] > 0) $this->autocleanFiles();
$this->make_create_files();
// read the rows
while ($row = $this->getNextRow()) {
// apply early transformations
$row = $this->preprocessRow($row);
// validate it
$valid = $this->validateRow($row);
if (!$valid) {
$this->log->debug("Row #{$this->row_retrieved} has been discarded");
$this->row_discarded++;
continue;
}
$row = $this->applyRowTransformations($row);
// apply other transformations after the formulas
$row = $this->postprocessRow($row);
// populate the files
$recordid = null;
$exists = $this->checkExistance($row, $recordid);
if ($exists) {
$this->update($row,$recordid);
} else {
$this->create($row);
}
}
if ($this->module == 'ProductRows') {
// we have to update the last entity
$this->updateInventoryTotals();
}
// close the files
$this->close_files();
// check again for abort
if ($this->checkImportAbort()) {
$this->row_retrieved = 0;
$this->entity_created = 0;
$this->entity_updated = 0;
$this->entity_deleted = 0;
$this->log->warn('Import has been aborted upon user request');
}
$this->importLog('IMPORT_MIDDLE', 'Import files populated');
$this->time_readsrc = $this->log->tac('stat_read');
$this->log->tic('stat_write');
// now insert them in the vte
if ($this->row_retrieved > 0) {
$r = $this->execute();
if (!$r) $this->log->error("Errors during the import");
} else {
$this->log->info('No rows for import');
}
// delete the files
if (!$this->config['keep_import_files']) $this->delete_files();
// execute the second pass, if needed
if ($this->needSecondPass()) {
$r = $this->processSecondPass();
if (!$r) $this->log->error("Errors during the second pass");
}
// call the postprocess method
$r = $this->postprocess();
// restore the old user
if (!empty($this->config['import_user'])) $this->restoreUser();
$this->time_saverecords = $this->log->tac('stat_write');
$this->time_total = $this->log->tac('stat_main');
$logData = array(
'records_created' => count($this->entity_created),
'records_updated' => count($this->entity_updated),
'records_deleted' => count($this->entity_deleted),
'records_2ndpass' => count($this->entity_2ndpass),
);
$this->importLog('IMPORT_END', 'Import finished', $logData);
$this->log->info('Import completed in {tac}');
if ($this->config['log_statistics']) $this->logStatistics();
CronManager::requestCronTermination(); // crmv@194059
return true;
}
public function needSecondPass() {
return (!empty($this->selfReferencies));
}
public function processSecondPass() {
// only the self referencies are used for a second pass
$this->pass++;
$this->log->info('Starting second pass');
$this->resetRowPointer();
// read the rows
while ($row = $this->getNextRow()) {
// apply early transformations
$row = $this->preprocessRow($row);
// validate it
$valid = $this->validateRow($row);
if (!$valid) {
$this->log->debug("Row #{$this->row_retrieved} has been discarded");
continue;
}
$row = $this->applyRowTransformations($row);
// apply other transformations after the formulas
$row = $this->postprocessRow($row);
$r = $this->updateSelfReferencies($row);
if (!$r) {
return $this->log->error('Error during update of same-module reference fields');
}
}
$this->log->info('Second pass terminated');
return true;
}
public function updateSelfReferencies($row) {
global $adb, $table_prefix;
$recordid = null;
$exists = $this->checkExistance($row, $recordid);
if (!$exists) {
$name = $row[$this->external_code];
$this->log->warning("The record '$name' was not found during the second pass, skipped.");
} else {
// get or generate the queries
$qlist = $this->selfRefUpdateQueries;
if (empty($qlist)) {
$this->generateSelfRefUpdateQueries();
$qlist = $this->selfRefUpdateQueries;
if (empty($qlist)) {
$this->log->error('The query to update the same-module reference fields couldn\'t be generated');
return false;
}
}
// execute them!
foreach ($qlist as $qinfo) {
$sql = $qinfo['sql'];
$params = array();
// intersect and sort
foreach ($qinfo['columns'] as $col) {
if ($col == 'RECORDID') {
$params[] = $recordid;
} elseif (array_key_exists($col, $row)) {
$params[] = $row[$col];
}
}
if ($sql && count($params) >= 2) {
$res = $adb->pquery($sql, $params);
if (!$res) {
$this->log->error('Query error while updating same-module reference field');
return false;
} else {
$this->entity_2ndpass[$recordid] = $recordid;
}
}
}
}
return true;
}
// check if there are reference fields in the mapping pointing to the
// same module we are importing. In this case, these fields have to
// be imported with a second pass, when all the other external keys
// have been saved
protected function checkSelfReferencies() {
$map = $this->importInfo['mapping']['fields'];
if (is_array($map)) {
foreach ($map as $field) {
$ref = $field['reference'];
if (!empty($ref)) {
list($refmod, $refField) = explode(':', $ref);
if ($refmod == $this->module) {
$this->selfReferencies[] = $field;
}
}
}
}
if (count($this->selfReferencies) > 0) {
$this->log->info('Found same-module reference fields, there will be a second pass');
}
}
public function checkKeyIndexes() {
$map = $this->importInfo['mapping']['fields'];
if (is_array($map)) {
foreach ($map as $field) {
$ref = $field['reference'];
if (!empty($ref)) {
list($refmod, $refField) = explode(':', $ref);
$finfo = $this->getFieldColumn($refmod, $refField);
if ($finfo[$refField]['tablename'] && $finfo[$refField]['columnname']) {
// check for indexes on that column
$has = $this->tableHasIndexColumn($finfo[$refField]['tablename'], $finfo[$refField]['columnname']);
if (!$has) $this->createSimpleIndex($finfo[$refField]['tablename'], $finfo[$refField]['columnname']);
}
}
}
}
return true;
}
protected function createSimpleIndex($table, $column) {
global $adb;
$this->log->info("Creating an index for {$table}.{$column}...", true);
$idxname = $table.'_'.$column.'_idx';
$sql = $adb->datadict->CreateIndexSQL($idxname, $table, $column);
if ($sql) $adb->datadict->ExecuteSQLArray($sql);
$this->log->info('Index created in {tac}');
}
// check if there's an index with it's first column as the specified one
protected function tableHasIndexColumn($table, $column) {
global $adb;
$idxs = $adb->database->MetaIndexes($table);
if (is_array($idxs)) {
foreach ($idxs as $idxname => $idx) {
if (is_array($idx['columns']) && $idx['columns'][0] == $column) return true;
}
}
return false;
}
public function impersonateUser($user) {
global $current_user, $adb, $table_prefix;
if (empty($user)) return true;
if (is_int($user) || is_numeric($user)) {
$newid = intval($user);
// check if it's a valid id
$res = $adb->pquery("SELECT id FROM {$table_prefix}_users WHERE status = 'Active' AND deleted = 0 AND id = ?", array($user));
if (!$res || $adb->num_rows($res) == 0) {
return $this->log->error("No valid users found with ID $user");
}
} elseif (is_string($user)) {
// find userid
if ($user == 'ADMIN') {
// find first admin
$res = $adb->limitQuery("SELECT id FROM {$table_prefix}_users WHERE is_admin = 'on' AND status = 'Active' AND deleted = 0 ORDER BY id", 0, 1);
if ($res && $adb->num_rows($res) > 0) {
$newid = $adb->query_result_no_html($res, 0, 'id');
} else {
return $this->log->error("No valid admin users found");
}
} else {
// match username
$userFocus = CRMEntity::getInstance('Users');
$newid = $userFocus->retrieve_user_id($user);
}
if (empty($newid)) {
return $this->log->error("No users found with name $user");
}
}
if ($newid > 0) {
$this->oldUser = $current_user;
$current_user = CRMEntity::getInstance('Users');
$current_user->id = $newid;
$current_user->retrieveCurrentUserInfoFromFile($newid);
$this->log->debug("User switched to {$current_user->column_fields['user_name']}");
}
return true;
}
public function restoreUser() {
global $current_user;
$current_user = $this->oldUser;
return true;
}
public function checkDirectory() {
$this->log->debug('Checking directories');
if (!is_dir($this->erpdir)) {
return $this->log->fatal("Directory {$this->erpdir} doesn't exist, please provide the correct path");
}
if (!is_writable($this->erpdir)) {
return $this->log->fatal("Directory {$this->erpdir} is not writable, please check permissions");
}
if (!is_dir($this->sqldir)) {
mkdir($this->sqldir, 0755, true);
}
if (!is_dir($this->sqldir) || !is_writable($this->sqldir)) {
return $this->log->fatal("Directory {$this->sqldir} is not writable, please check permissions");
}
if (!is_dir($this->logdir)) {
mkdir($this->logdir, 0755, true);
}
if (!is_dir($this->logdir) || !is_writable($this->logdir)) {
return $this->log->fatal("Directory {$this->logdir} is not writable, please check permissions");
}
return true;
}
public function checkTables() {
global $adb;
$this->log->debug('Checking tables');
if(!Vtecrm_Utils::CheckTable($this->logTable)) {
$schema = '<?xml version="1.0"?>
<schema version="0.3">
<table name="'.$this->logTable.'">
<opt platform="mysql">ENGINE=InnoDB</opt>
<field name="id" type="I" size="19">
<KEY/>
</field>
<field name="importid" type="I" size="19">
<NOTNULL/>
</field>
<field name="module" type="C" size="63">
<NOTNULL/>
</field>
<field name="logdate" type="T">
<NOTNULL/>
<DEFAULT value="0000-00-00 00:00:00"/>
</field>
<field name="action" type="C" size="63">
<NOTNULL/>
</field>
<field name="logtext" type="C" size="255"/>
<field name="logdata" type="C" size="255"/>
<index name="importid_idx">
<col>importid</col>
</index>
<index name="logdate_idx">
<col>logdate</col>
</index>
</table>
</schema>';
$schema_obj = new adoSchema($adb->database);
$schema_obj->ExecuteSchema($schema_obj->ParseSchemaString($schema));
}
return true;
}
public function autocleanFiles() {
$days = intval($this->config['autoclean_import_files']);
if ($days > 0 && is_dir($this->sqldir)) {
$this->log->debug('Starting autocleaning of old files');
$list = glob($this->sqldir.'/*.{sql,csv}', GLOB_BRACE);
$deleted = 0;
$now = time();
if (is_array($list)) {
foreach ($list as $f) {
$ftime = filemtime($f);
if ($now - $ftime > ($days*3600*24)) {
if (unlink($f)) ++$deleted;
}
}
}
$this->log->debug("Autoclean removed $deleted old files");
}
}
public function autocleanLogFiles() {
$days = intval($this->config['autoclean_log_files']);
if ($days > 0 && is_dir($this->logdir)) {
$this->log->debug('Starting autocleaning of old log files');
$list = glob($this->logdir.'/*.log') ?: array();
$list = array_merge($list, glob($this->logdir.'/*.log.*'));
$deleted = 0;
$now = time();
if (is_array($list)) {
foreach ($list as $f) {
$ftime = filemtime($f);
if ($now - $ftime > ($days*3600*24)) {
if (unlink($f)) ++$deleted;
}
}
}
$this->log->debug("Autoclean removed $deleted old log files");
}
}
public function importLog($action, $text = "", $data = null) {
global $adb;
$now = date('Y-m-d H:i:s');
if (empty($action)) $action = 'UNKNOWN';
$id = $adb->getUniqueID($this->logTable);
$params = array(
'id'=>$id,
'importid' => $this->importId,
'module' => $this->module,
'logdate' => $now,
'action' => $action
);
if (!empty($text)) $params['logtext'] = $text;
if (!empty($data)) $params['logdata'] = Zend_Json::encode($data);
$q = "INSERT INTO {$this->logTable} (".implode(',', array_keys($params)).") VALUES(".generateQuestionMarks($params).")";
$res = $adb->pquery($q, $params);
}
public function checkSource() {
$this->log->debug('Checking source availability');
$srcinfo = $this->importInfo['srcinfo'];
$type = $srcinfo['dimport_sourcetype'];
if ($type == 'database') {
$db = $this->connectToDb($srcinfo);
if (!$db) return $this->log->fatal("No database connection available");
} elseif ($type == 'csv') {
$file = $srcinfo['dimport_csvpath'];
$file = $this->diutils->getOneCSVFile($file);
if (!$file) return $this->log->fatal("No CSV file found for $file");
$csvFolder = $this->diutils->import_folder;
if (substr($csvFolder, -1) != '/') $csvFolder .= '/';
$path = $csvFolder.$file;
if (!is_readable($path)) return $this->log->fatal("The CSV file $path is not readable");
if (filesize($path) == 0) return $this->log->fatal("The CSV file $path is empty");
} else {
return $this->log->fatal("Source is of unknown type: $type");
}
return true;
}
protected function connectToDb($srcinfo) {
if ($this->extdb && $this->extdb->database->isConnected()) return $this->extdb;
$dbtype = $srcinfo['dimport_dbtype'];
$dbhost = trim($srcinfo['dimport_dbhost']);
$dbport = trim($srcinfo['dimport_dbport']);
$dbuser = trim($srcinfo['dimport_dbuser']);
$dbpass = $srcinfo['dimport_dbpass'];
$dbname = trim($srcinfo['dimport_dbname']);
$host_separator = ':'; // crmv@166852
// remove port for mysqli (PearDatabase doesn't support it)
if ($dbtype == 'mysqli' && $dbport == '3306') $dbport = ''; // crmv@87579
if (($dbtype == 'mssqlnative' || $dbtype == 'mssql') && $dbport == '1433') $dbport = ''; // crmv@155585 crmv@178746
if ($dbtype == 'mssqlnative') $host_separator = ','; // crmv@166852
//crmv@83676
if (strpos($dbhost,'\\') !== false) {
list($dbhost_ip,$instance) = explode('\\',$dbhost);
$host = $dbhost_ip.(empty($dbport) ? '' : $host_separator.$dbport).'\\'.$instance; // crmv@166852
} else {
//crmv@83676e
$host = $dbhost.(empty($dbport) ? '' : $host_separator.$dbport); // crmv@166852
}
// crmv@193619
$ssl_connection = false;
if (substr($host, 0, 6) === 'ssl://') {
$ssl_connection = true;
$host = substr($host,6);
}
// crmv@193619e
$this->log->debug("Connecting to database ($dbtype) $host");
$this->extdb = new PearDatabase($dbtype, $host, $dbname, $dbuser, $dbpass);
$this->extdb->usePersistent = false; // force a new connection, in case the host, user and pwd is the same
$this->extdb->setDieOnError(false); // disable die on error
$this->extdb->setExceptOnError(true); // but enable exception on error
if ($ssl_connection) $this->extdb->setSSLconnection(); // crmv@193619
@$this->extdb->connect();
if (!$this->extdb->database->isConnected()) {
$this->extdb = null;
return $this->log->error("Unable to connect to database");
}
$this->log->debug('Connection successful');
// crmv@109628
// detect the external charset (only for mssql)
$charset = $this->getDbCharset();
if ($charset) $this->extdb_charset = $charset;
// crmv@109628e
return $this->extdb;
}
// crmv@109628
protected function getDbCharset() {
$charset = 'UTF8';
if ($this->extdb->isMssql()) {
$res = $this->extdb->pquery("SELECT DATABASEPROPERTYEX(?, 'Collation') as sql_collation", array($this->extdb->dbName));
if ($res) {
$collation = $this->extdb->query_result_no_html($res, 0, 'sql_collation'); // crmv@108648
if (strpos($collation, 'Latin1') !== false) {
$charset = 'Latin1';
}
}
}
// mysql doesn't need detection, since the mysql driver automatically converts the data
return $charset;
}
// crmv@109628e
protected function generateQuery() {
if ($this->pass == 2) return $this->generateQuerySelfRef();
$this->query = '';
$table = $this->importInfo['srcinfo']['dimport_dbtable'];
$query = $this->diutils->cleanImportQuery($this->importInfo['srcinfo']['dimport_dbquery']);
if (!empty($table)) {
// use the mapping to generate the query
$select = array();
$mapping = $this->importInfo['mapping'];
foreach ($mapping['fields'] as $column => $map) {
if ($map['field']) $select[] = $column;
}
$this->extdb->format_columns($select);
$this->extdb->format_columns($table);
$this->query = 'SELECT '.implode(',', $select)." FROM $table";
// now add a orderby for products rows
if ($this->module == 'ProductRows') {
$orders = array();
if (array_key_exists('inventoryid', $this->mapping_inverse)) {
$orders[] = $this->mapping_inverse['inventoryid']." ASC";
}
if (array_key_exists('productid', $this->mapping_inverse)) {
$orders[] = $this->mapping_inverse['productid']." ASC";
}
if (count($orders) > 0) {
$this->query .= " ORDER BY ".implode(', ', $orders);
}
}
} elseif (!empty($query)) {
$this->query = $query;
}
$this->log->debug('Query being executed: '.$this->query);
return true;
}
protected function generateQuerySelfRef() {
$this->query = '';
$table = $this->importInfo['srcinfo']['dimport_dbtable'];
$query = $this->diutils->cleanImportQuery($this->importInfo['srcinfo']['dimport_dbquery']);
if (!empty($table)) {
// use the mapping to generate the query
$select = array($this->external_code);
$mapping = $this->importInfo['mapping'];
foreach ($mapping['fields'] as $column => $map) {
if ($map['field'] && $map['reference']) {
list ($refmod, $reffield) = explode(':', $map['reference']);
if ($refmod == $this->module) {
$select[] = $column;
}
}
}
$this->extdb->format_columns($select);
$this->extdb->format_columns($table);
$this->query = 'SELECT '.implode(',', $select)." FROM $table";
} elseif (!empty($query)) {
$this->query = $query;
}
$this->log->debug('Query being executed: '.$this->query);
return true;
}
protected function generateSelfRefUpdateQueries() {
global $adb, $table_prefix;
$this->selfRefUpdateQueries = array();
$updateFields = array();
$mapping = $this->importInfo['mapping'];
if (is_array($mapping['fields'])) {
foreach ($mapping['fields'] as $column => $map) {
if ($map['field'] && $map['reference']) {
list ($refmod, $reffield) = explode(':', $map['reference']);
if ($refmod == $this->module) {
$updateFields[$column] = $map['field'];
}
}
}
}
$tables = array();
$scols = array();
foreach ($updateFields as $scol => $fieldname) {
$finfo = $this->getFieldColumn($this->module, $fieldname);
$ftable = $finfo[$fieldname]['tablename'];
$fcol = $finfo[$fieldname]['columnname'];
if ($ftable && $fcol) {
$tables[$ftable][] = $fcol;
$scols[$ftable][] = $scol;
}
}
// now find the indexes
$queries = array();
foreach ($tables as $ftable => $columns) {
if (!in_array($ftable, $this->object->tab_name) || !array_key_exists($ftable, $this->object->tab_name_index)) {
$this->log->warn("Unable to find the table $ftable in the module class");
continue;
}
$tableidx = $this->object->tab_name_index[$ftable];
$colsql = array();
foreach ($columns as $col) {
$colsql[] = "$col = ?";
}
if (count($colsql) > 0) {
$sql = "UPDATE {$ftable} SET ".implode(', ', $colsql)." WHERE {$tableidx} = ?";
$cols = $scols[$ftable];
$cols[] = 'RECORDID';
$queries[] = array('sql' => $sql, 'columns' => $cols, 'tableidx' => $tableidx);
}
}
$this->selfRefUpdateQueries = $queries;
return true;
}
protected function openNextCsv() {
if ($this->fhandle) fclose($this->fhandle);
$this->fhandle = null;
if (empty($this->csvFiles)) {
// i have to retrieve the list of files
$file = $this->importInfo['srcinfo']['dimport_csvpath'];
$this->csvFiles = $this->diutils->getAllCSVFiles($file);
if (empty($this->csvFiles)) return $this->log->error('No CSV files found');
$this->currentFileIdx = -1;
}
// check if last one
if ($this->currentFileIdx >= count($this->csvFiles)-1) return true;
$newfile = $this->csvFiles[++$this->currentFileIdx];
if (is_readable($newfile) && filesize($newfile) > 0) {
FSUtils::removeBOM($newfile); // crmv@138011
$this->fhandle = fopen($newfile, 'r');
if (!$this->fhandle) {
$this->log->error("Unable to open $newfile, skipped");
return $this->openNextCsv();
} else {
$this->log->info("Opened CSV file $newfile");
}
} else {
$this->log->error("The file $newfile is not readable or empty, skipped");
return $this->openNextCsv();
}
return true;
}
protected function resetRowPointer() {
$this->qresult = null;
if ($this->fhandle) fclose($this->fhandle);
$this->csv_rows = 0;
$this->currentFileIdx = -1;
}
protected function getNextRow() {
$type = $this->importInfo['srcinfo']['dimport_sourcetype'];
$row = false;
if ($type == 'database') {
if (!$this->qresult) {
$r = $this->generateQuery();
if (!$r) return $r;
// execute the query
try {
$this->qresult = $this->extdb->query($this->query);
} catch (Exception $e) {
return $this->log->error('Error while executing the query: '.$e->getMessage());
}
if (!$this->qresult) $this->log->error('There was an error executing the query');
$this->log->info('Query returned '.$this->extdb->num_rows($this->qresult).' rows');
}
$row = $this->extdb->FetchByAssoc($this->qresult, -1, false);
if (!empty($row) && $this->config['trim_db_values']) {
$row = array_map('trim', $row);
}
} elseif ($type == 'csv') {
if (!$this->fhandle) {
// open next csv file
$this->csv_rows = 0;
$r = $this->openNextCsv();
if (!$r) return $r;
}
// now if the file has been opened
if ($this->fhandle) {
$row = $this->getNextCsvRow($this->fhandle);
// close the handle at file end
if (empty($row)) {
$this->log->info('Read '.$this->csv_rows.' rows from CSV file');
fclose($this->fhandle);
$this->fhandle = null;
// re-iterate over the next file
return $this->getNextRow();
} else {
$this->csv_rows++;
}
}
}
// lgo memory usage, before the increment, so log also at the beginning
$logEvery = intval($this->config['log_memory_rows']);
if ($logEvery > 0 && $this->row_retrieved % $logEvery == 0) {
$this->logMemoryStats();
}
$abortEvery = intval($this->config['abort_interval']);
if ($abortEvery > 0 && (time() - $this->last_abort_check) > $abortEvery) {
$this->last_abort_check = time();
if ($this->checkImportAbort()) {
if ($this->pass == 1) {
$this->entity_created = 0;
$this->entity_updated = 0;
$this->entity_deleted = 0;
$this->row_retrieved = 0;
}
$this->entity_2ndpass = 0;
$this->log->warn('Import has been aborted upon user request');
return null;
}
}
// increment row count
if ($row) $this->row_retrieved++;
return $row;
}
protected function checkImportAbort() {
return $this->diutils->isAborted($this->importId);
}
protected function getNextCsvRow($fh) {
global $current_user;
$row = null;
if (!$fh) return $row;
if (!$this->csvreader) {
$cfg = array(
'fhandle' => $fh,
'delimiter' => $this->importInfo['srcinfo']['dimport_csvdelimiter'],
'file_encoding' => $this->importInfo['srcinfo']['dimport_csvencoding'],
'has_header' => !!$this->importInfo['srcinfo']['dimport_csvhasheader'],
'trim_values' => $this->config['trim_csv_values'],
);
$this->csvreader = new DataImporterErpCSVReader($current_user, $cfg);
}
$this->csvreader->setFHandle($fh);
$row = $this->csvreader->readRow();
return $row;
}
protected function logMemoryStats() {
$mb = $this->formatBytes(memory_get_usage());
$mpb = $this->formatBytes(memory_get_peak_usage());
$str = "Memory usage: $mb (peak $mpb)";
$this->log->debug($str);
}
protected function logStatistics() {
$totrecords = count($this->entity_created) + count($this->entity_updated) + count($this->entity_deleted);
$time = $this->time_total;
if ($totrecords > 0 && $time > 0) {
$speed = strval(round($totrecords/$time)).' record/s';
} else {
$speed = 'N/A';
}
$stats = "Statistics:\n\n";
$stats .= "Total rows read: ".$this->row_retrieved."\n";
$stats .= "Rows discarded: ".$this->row_discarded."\n";
$stats .= "Records created: ".count($this->entity_created)."\n";
$stats .= "Records updated: ".count($this->entity_updated)."\n";
$stats .= "Records deleted: ".count($this->entity_deleted)."\n";
if ($this->pass > 1) {
$stats .= "Records affected by second pass: ".count($this->entity_2ndpass)."\n";
}
$stats .= "Time to read source: ".$this->log->formatTac($this->time_readsrc)."\n";
$stats .= "Time to insert records: ".$this->log->formatTac($this->time_saverecords)."\n";
$stats .= "Total time: ".$this->log->formatTac($this->time_total)."\n";
$stats .= "Average speed: $speed\n";
$this->log->info($stats);
}
private function formatBytes($size, $precision = 2) {
$base = log($size, 1024);
$suffixes = array('', 'K', 'M', 'G', 'T');
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}
// called before the beginning of the import, if it returns false, the import is not executed
public function preprocess() {
// this method can be overloaded
$this->log->debug('Executing preprocess method');
return true;
}
// called at the end of the import
public function postprocess() {
// this method can be overloaded
$this->log->debug('Executing postprocess method');
$this->fixCurrencyRelTable(); //crmv@113210
$this->updateEntityNameCache(); //crmv@144125
// crmv@203591
if ($this->prodAttrCreated || $this->module == 'ModLightProdAttr') {
$confProdFocus = CRMEntity::getInstance('ConfProducts');
$confProdFocus->updateFieldIds();
} elseif ($this->module == 'Products' && count($this->prodAttrValues) > 0) {
$this->insertProdAttrValues();
}
// crmv@203591e
//crmv@136410
if ($this->module != 'ProductRows') {
if ($this->launch_workflows) $this->launch_workflows();
if ($this->launch_processes) $this->launch_processes();
}
//crmv@136410e
return true;
}
//crmv@113210
public function fixCurrencyRelTable() {
global $adb, $table_prefix;
// TODO: do the real price conversion
if (isProductModule($this->module)) {
$modTable = $this->object->table_name;
$modIndex = $this->object->table_index;
$sql =
"SELECT
{$table_prefix}_crmentity.crmid, {$modTable}.unit_price, {$modTable}.currency_id
FROM
{$modTable}
INNER JOIN {$table_prefix}_crmentity ON {$table_prefix}_crmentity.crmid = {$modTable}.{$modIndex}
LEFT JOIN {$table_prefix}_productcurrencyrel ON {$table_prefix}_productcurrencyrel.productid = {$modTable}.{$modIndex}
WHERE {$table_prefix}_crmentity.deleted = 0 AND {$table_prefix}_productcurrencyrel.productid IS NULL";
$res = $adb->query($sql);
while($row = $adb->fetchByAssoc($res, -1, false)){
$sql_update = "INSERT INTO {$table_prefix}_productcurrencyrel (productid,currencyid,converted_price,actual_price) VALUES (?,?,?,?)";
$params_update = array($row['crmid'],$row['currency_id'],$row['unit_price'],$row['unit_price']);
$adb->pquery($sql_update,$params_update);
}
// crmv@185823
// and now fix updated products
if (is_array($this->entity_updated) && count($this->entity_updated) > 0) {
$chunkSize = 100;
$chunks = array_chunk($this->entity_updated, $chunkSize);
foreach ($chunks as $chunk) {
if ($adb->isMysql()) {
$adb->pquery(
"UPDATE {$table_prefix}_productcurrencyrel pcr
INNER JOIN $modTable p ON p.{$modIndex} = pcr.productid
SET pcr.currencyid = p.currency_id, converted_price = p.unit_price, actual_price = p.unit_price
WHERE pcr.productid IN (".generateQuestionMarks($chunk).")",
$chunk
);
} else {
$res = $adb->pquery(
"SELECT {$modIndex} as crmid, unit_price, currency_id
FROM {$modTable}
WHERE {$modIndex} IN (".generateQuestionMarks($chunk).")",
$chunk
);
while ($row = $adb->fetchByAssoc($res, -1, false)) {
$sql = "UPDATE {$table_prefix}_productcurrencyrel SET currencyid = ?, converted_price = ?, actual_price = ? WHERE productid = ?";
$params = array($row['currency_id'],$row['unit_price'],$row['unit_price'], $row['crmid']);
$adb->pquery($sql,$params);
}
}
}
}
// crmv@185823
}
}
//crmv@113210e
//crmv@144125
public function updateEntityNameCache() {
$ENU = EntityNameUtils::getInstance();
// crmv@177787
if (is_array($this->entity_created) && count($this->entity_created) > 0) {
$ENU->rebuildForRecords($this->module, $this->entity_created);
}
if (is_array($this->entity_updated) && count($this->entity_updated) > 0) {
$ENU->rebuildForRecords($this->module, $this->entity_updated);
}
// crmv@177787e
// deletion not supported yet
//$ENU->removeCachedNames($this->module, $this->entity_deleted);
}
//crmv@144125e
//crmv@136410
protected function launch_workflows(){
require_once('include/events/SqlResultIterator.inc');
require_once('modules/com_workflow/VTWorkflowManager.inc');//crmv@207901
require_once('modules/com_workflow/VTTaskManager.inc');//crmv@207901
require_once('modules/com_workflow/VTTaskQueue.inc');//crmv@207901
require_once('modules/com_workflow/VTEntityCache.inc');//crmv@207901
require_once('include/Webservices/Utils.php');
require_once("include/Webservices/VtenextCRMObject.php");//crmv@207871
require_once("include/Webservices/VtenextCRMObjectMeta.php");//crmv@207871
require_once("include/Webservices/DataTransform.php");
require_once("include/Webservices/WebServiceError.php");
require_once('include/Webservices/ModuleTypes.php');
require_once('include/Webservices/Retrieve.php');
require_once('include/Webservices/Update.php');
require_once('include/Webservices/WebserviceField.php');
require_once('include/Webservices/EntityMeta.php');
require_once('include/Webservices/VtenextWebserviceObject.php');//crmv@207871
require_once('modules/com_workflow/VTWorkflowUtils.php');//crmv@207901
require_once('modules/com_workflow/VTEventHandler.inc');//crmv@207901
$wfh = new VTWorkflowEventHandler();
// now save the workflows with a special optimized method
// note: workflow with the clause ("has changed into") won't work.
if (is_array($this->entity_created) && count($this->entity_created) > 0) {
$wfh->massWorkflows('create', $this->module, $this->entity_created);
}
if (is_array($this->entity_updated) && count($this->entity_updated) > 0) {
$wfh->massWorkflows('update', $this->module, $this->entity_updated);
}
}
protected function launch_processes() {
global $adb; //crmv@185058
$entities = array_merge($this->entity_created,$this->entity_updated);
if (!empty($entities)) {
foreach($entities as $id) {
$focus = CRMEntity::getInstance($this->module);
$focus->retrieve_entity_info_no_html($id,$this->module);
(in_array($id,$this->entity_created)) ? $focus->mode = '' : $focus->mode = 'edit';
require_once("include/events/include.inc");
require_once("modules/Settings/ProcessMaker/ProcessMakerHandler.php");
$em = new VTEventsManager($adb);
// Initialize Event trigger cache
$em->initTriggerCache();
$entityData = VTEntityData::fromCRMEntity($focus);
if (in_array($id,$this->entity_created)) $entityData->setNew(true);
//crmv@185058
require_once('data/VTEntityDelta.php');
$entityDelta = new VTEntityDelta();
$entityDelta->handleEvent('vte.entity.aftersave',$entityData);//crmv@207852
//crmv@185058e
$processMakerHandler = new ProcessMakerHandler();
if (!$this->enable_process_modify_action && in_array($id,$this->entity_updated)) $processMakerHandler->real_save = false;
$processMakerHandler->handleEvent('vte.entity.aftersave.processes', $entityData); // crmv@177677 crmv@207852
}
}
}
//crmv@136410e
// apply transformations right after the row has been read from the source
public function preprocessRow($row) {
// this method can be overloaded
return $row;
}
public function validateRow(&$row) {
$valid = true;
$mapping = $this->importInfo['mapping'];
$invalidCase = $this->config['on_invalid_cell'];
foreach ($row as $column => &$value) {
$map = $mapping['fields'][$column];
if (!empty($map) && !empty($map['field'])) {
$vc = $this->validateCell($column, $value, $map, $row);
if (!$vc) {
if ($invalidCase == 'skip') {
// skip all
$valid = false;
} elseif ($invalidCase == 'blank') {
// empty the value
$value = '';
} elseif ($invalidCase == 'ignore') {
// ignore the error
}
}
}
}
return $valid;
}
public function validateCell($colname, $value, $map, $row) {
$valid = true;
if (!empty($map['srcformat'])) {
// do the validation
$format = $this->allFormats[$map['srcformat']];
$formatval = $map['srcformatval'];
$formatval2 = $map['srcformatlist']; // crmv@117880
if (empty($format)) {
$this->log->warn("The format {$map['srcformat']} is unknown");
return $valid;
}
// use filter_var
if ($value !== '' && !empty($format['phpfilter'])) { // crmv@113215
$r = filter_var($value, $format['phpfilter']['filter'], $format['phpfilter']['options']);
if (!$r) {
$this->log->debug("The value $value doesn't match the format for {$map['srcformat']}");
return false;
}
}
// use regexp
if ($value !== '' && !empty($format['regex'])) { // crmv@113215
// regex validation
if (!preg_match($format['regex'], $value)) {
$this->log->debug("The value $value doesn't match the format for {$map['srcformat']}");
return false;
}
}
// other cases
switch ($map['srcformat']) {
case 'BOOL_NULL_REGEX':
// change the value
$row[$colname] = (empty($value) ? 0 : 1);
break;
case 'DATE_TIME_REGEX':
if ($value != '') {
$date = DateTime::createFromFormat($formatval, $value);
if ($date) {
$row[$colname] = $date->format('Y-m-d H:i:s');
} else {
$this->log->debug("The date '$value' doesn't match the specified format format '$formatval'");
return false;
}
}
break;
// crmv@117880
case 'NUMBER_REGEX':
if ($value !== '' && $formatval2 != '') {
// check number format
$newvalue = $this->checkNumbeFormat($value, $formatval2);
if ($newvalue === false) {
$this->log->debug("The number '$value' doesn't match the specified format format '$formatval2'");
return false;
} else {
// change the format!
$row[$colname] = $newvalue;
}
}
break;
// crmv@117880e
}
}
return $valid;
}
// crmv@117880
// check and convert the number using the specified format!
public function checkNumbeFormat($number, $format) {
list($ts,$ds) = explode(':', $format);
$ts = str_replace(array('EMPTY', 'PERIOD', 'COMMA', 'SPACE', 'QUOTE'), array('', '.', ',', ' ', "'"), $ts);
$ds = str_replace(array('EMPTY', 'PERIOD', 'COMMA', 'SPACE', 'QUOTE'), array('', '.', ',', ' ', "'"), $ds);
// add a simple check, otherwise it might go through
$cn = $number;
if ($ts != '') $cn = str_replace($ts, '', $number);
if ($ds !== '.' && strpos($cn, '.') !== false) return false;
// instantiate a single class, used only for converting numbers
if (!$this->iutils_check) {
$this->iutils_check = InventoryUtils::getInstance();
}
$this->iutils_check->decimalSeparator = $ds;
$this->iutils_check->thousandsSeparator = $ts;
$this->iutils_check->invalidNumber = NaN;
$number = $this->iutils_check->parseUserNumber($number);
if ($number === NaN) return false;
return $number;
}
// crmv@117880e
public function applyRowTransformations($row) {
$mapping = $this->importInfo['mapping'];
foreach ($row as $column => &$value) {
$map = $mapping['fields'][$column];
if (!empty($map) && !empty($map['field'])) {
// apply defaults
$value = $this->applyDefaultToCell($column, $value, $map, $row);
// crmv@203591
// apply formats
$value = $this->applyFormatToCell($column, $value, $map, $row);
// crmv@203591e
// apply formulas
$value = $this->applyFormulaToCell($column, $value, $map, $row);
// reverse lookup for uitype10 and reference fields
$value = $this->applyReferenciesToCell($column, $value, $map, $row);
}
}
return $row;
}
public function applyFormulas($row) {
$mapping = $this->importInfo['mapping'];
foreach ($row as $column => &$value) {
$map = $mapping['fields'][$column];
if (!empty($map) && !empty($map['field'])) {
$value = $this->applyFormulaToCell($column, $value, $map, $row);
}
}
return $row;
}
public function applyFormulaToCell($colname, $value, $map, $row) {
if (!empty($map['formula'])) {
// do the validation
$formula = $this->allFormulas[$map['formula']];
if (empty($formula)) {
$this->log->warn("The formula {$map['formula']} is unknown");
return $value;
}
$formulaVal = $map['formulaval'];
switch ($map['formula']) {
case 'PREPEND':
$value = $formulaVal.$value;
break;
case 'APPEND':
$value .= $formulaVal;
break;
case 'ADD':
$value += $formulaVal;
break;
case 'SUBTRACT':
$value -= $formulaVal;
break;
case 'YEAR':
// date is already in a valid format
$value = substr($value, 0, 4);
break;
case 'YEARMONTH':
// idem
$value = substr($value, 0, 7);
break;
}
}
return $value;
}
public function applyDefaults($row) {
$mapping = $this->importInfo['mapping'];
foreach ($row as $column => &$value) {
$map = $mapping['fields'][$column];
if (!empty($map) && !empty($map['field'])) {
$value = $this->applyDefaultToCell($column, $value, $map, $row);
}
}
return $row;
}
public function applyDefaultToCell($colname, $value, $map, $row) {
$default = $map['default'];
if ($default !== '' && $value == '') {
$value = $default;
}
return $value;
}
// crmv@203591
public function applyFormatToCell($colname, $value, $map, $row) {
$format = $map['srcformat'];
$charMap = array(
'SPACE' => ' ',
'COMMA' => ',',
'COLON' => ':',
'SEMICOLON' => ';',
);
// for attributes, values is an array with names for keys and an array for values
// if only names are requested, the values array is null
if ($format === 'ATTRIBUTES_SEPARATOR' && $map['srcformatlist'] && $value != '') {
$value = explode($charMap[$map['srcformatlist']], $value);
$value = array_combine($value, array_fill(0, count($value), null));
} elseif ($format === 'ATTRIBUTES_TABLE' && $map['srcformatlist'] && $value != '') {
if ($map['srcformatlist'] == 'JSON') {
$value = Zend_Json::decode($value);
}
}
return $value;
}
// crmv@203591e
public function applyReferencies($row) {
$mapping = $this->importInfo['mapping'];
foreach ($row as $column => &$value) {
$map = $mapping['fields'][$column];
if (!empty($map) && !empty($map['field'])) {
$value = $this->applyReferenciesToCell($column, $value, $map, $row);
}
}
return $row;
}
public function applyReferenciesToCell($colname, $value, $map, $row) {
global $adb, $table_prefix;
// TODO: cache ids ??
$ref = $map['reference'];
if (!empty($ref) && $value != '') {
if (!$this->cache['ref_sql'][$colname]) {
// generates the query to do the lookup by external code
list($refmod, $refField) = explode(':', $ref);
// skip the id, it will be imported with the second pass
if ($refmod == $this->module && $this->pass == 1) return 0;
$focus = CRMEntity::getInstance($refmod);
$finfo = $this->getFieldColumn($refmod, $refField);
$ftable = $finfo[$refField]['tablename'];
$fcol = $finfo[$refField]['columnname'];
if (!in_array($ftable, $focus->tab_name) || !array_key_exists($ftable, $focus->tab_name_index)) {
$this->log->warn("The link field $refmod::$refField is in a table not declared in module class ($ftable), value left untouched");
return $value;
}
$tableidx = $focus->tab_name_index[$ftable];
$mainJoin = "";
$fieldJoin = "INNER JOIN {$ftable} ON {$ftable}.{$tableidx} = {$table_prefix}_crmentity.crmid";
if ($ftable != $focus->table_name) {
$mainJoin = "INNER JOIN {$focus->table_name} ON {$focus->table_name}.{$focus->table_index} = {$table_prefix}_crmentity.crmid";
}
$query = "SELECT {$table_prefix}_crmentity.crmid
FROM {$table_prefix}_crmentity
$mainJoin
$fieldJoin
WHERE {$table_prefix}_crmentity.deleted = 0 AND {$ftable}.{$fcol} = ?";
$this->cache['ref_sql'][$colname] = $query;
}
$sql = $this->cache['ref_sql'][$colname];
if ($sql) {
$res = $adb->pquery($sql, array($value));
if ($res && $adb->num_rows($res) > 0) {
$value = $adb->query_result_no_html($res, 0, 'crmid');
}
}
}
return $value;
}
// apply transformations after the formulas have been calculated and right before being saved in the vte
public function postprocessRow($row) {
// this method can be overloaded
return $row;
}
public function notifyUser() {
// not done year
}
public function checkExistance($row, &$recordid = null) {
// if no external key defined, consider always the entity not existing
// also for the product rows, the lines are always deleted and re-added
if ($this->external_code == '' || empty($row) || $this->module == 'ProductRows') return false; // crmv@117237
// TODO: what is this? array??
// NOT USED NOW
/*if (is_array($this->external_code)){
$func = 'get_existing_entity_runtime_unique';
} else{
$func = 'get_existing_entity_runtime';
}
$recordid = $this->$func($row);
*/
$recordid = $this->get_existing_entity_runtime($row);
return (!empty($recordid));
}
protected function writeToFile($fh, $str, $filename = '') {
$r = fwrite($fh, $str);
if ($r === false) {
$this->log->warn("There was an error while writing to a file");
}
// this method is useful for debugging, to see what is being written to the files
//echo basename($filename).': '.$str;
}
// ---------------------------- STUFF from old classes file
private function make_create_files() {
global $table_prefix;
$time = time();
if ($this->module == 'ProductRows') {
$table = $table_prefix.'_inventoryproductrel';
$this->sql_file_name_create[$table] = $this->sqldir."/".$this->module."_sql_create_".$table."_".$time.".csv";
@unlink($this->sql_file_name_create[$table]);
$this->sql_file_create[$table] = fopen($this->sql_file_name_create[$table] , 'w+');
}
foreach ($this->object->tab_name_index as $t=>$k) {
//file creazione
$this->sql_file_name_create[$t] = $this->sqldir."/".$this->module."_sql_create_".$t."_".$time.".csv";
@unlink($this->sql_file_name_create[$t]);
$this->sql_file_create[$t] = fopen($this->sql_file_name_create[$t] , 'w+');
//file aggiornamento
$this->sql_file_name_update[$t] = $this->sqldir."/".$this->module."_sql_update_".$t."_".$time.".sql";
@unlink($this->sql_file_name_update[$t]);
$this->sql_file_update[$t] = fopen($this->sql_file_name_update[$t] , 'w+');
}
@unlink($this->file_create);
$this->file_create = $this->sqldir."/".$this->module."_sql_create_global_".$time.".sql";
}
private function close_files() {
if (is_array($this->sql_file_create)) {
foreach ($this->sql_file_create as $fl){
fclose($fl);
}
}
if (is_array($this->sql_file_update)) {
foreach ($this->sql_file_update as $fl){
fclose($fl);
}
}
}
private function delete_files() {
if (is_array($this->sql_file_name_create)) {
foreach ($this->sql_file_name_create as $fl){
@unlink($fl);
}
}
if (is_array($this->sql_file_name_update)) {
foreach ($this->sql_file_name_update as $fl){
@unlink($fl);
}
}
@unlink($this->file_create);
$this->log->debug('Temporary files removed');
}
protected function create($data) {
global $adb,$table_prefix;
// product block
if ($this->module == 'ProductRows') return $this->createInventory($data);
// crmentity
$table = $table_prefix.'_crmentity';
if (!in_array($table,$this->object->tab_name)) {
$table = $this->object->tab_name[0];
}
$id = $adb->getUniqueID($table);
$prodattr = $subprodattr = array(); // crmv@203591
$create = $this->getcached_create_arr();
if (!empty($this->mapping_entity)){
foreach ($this->mapping_entity as $f){
$f = strtolower($f);
if (trim($data[$f]) != '')
$create[$table][$this->mapping[$f]] = $data[$f];
}
}
// other tables
foreach ($this->fields as $table => $arr){
foreach ($arr as $field){
// crmv@203591
if ($this->fields_functions[$this->fields_name[$table][$field]] != '' && method_exists($this,$this->fields_functions[$this->fields_name[$table][$field]])) {
$readyData = $this->{$this->fields_functions[$this->fields_name[$table][$field]]}($this->fields_name[$table][$field],$data,$table,$field,'create');
} else {
$readyData = $data[$this->mapping_inverse[$this->fields_name[$table][$field]]];
}
if ($field == 'mlProdAttr') {
$prodattr[$id] = $readyData;
continue;
} elseif (substr($field, 0, 9) === 'prodattr_') {
$subprodattr[$field] = $readyData;
continue;
} elseif (substr($field, -8) === 'prodattr') {
$subprodattr[$field] = $readyData;
continue;
}
$create[$table][$field] = $readyData;
// crmv@203591e
}
$create[$table][$this->object->tab_name_index[$table]] = $id;
}
foreach ($this->object->tab_name_index as $t=>$k){
if (!$create[$t][$k]) $create[$t][$k] = $id;
}
if (is_array($this->fields_runtime)) {
foreach ($this->fields_runtime as $table=>$arr){
foreach ($arr as $column=>$value){
$create[$table][$column] = $value;
}
}
}
if ($this->sequence_field){
$create[$this->sequence_field[0]][$this->sequence_field[1]] = $this->getModuleSeqNumber($data); //crmv@95507
}
foreach ($this->object->tab_name_index as $t=>$k){
$this->insert_into_create_file($t,$create);
}
$this->entity_created[] = $id;
// users case, not used now
if ($this->module == 'Users') {
$t = $table_prefix.'_users';
$this->writeToFile($this->sql_file_update[$t],"insert into ".$table_prefix."_user2role values ($id,'".$this->cache[$this->module]['roleid']."');\n", $this->sql_file_name_update[$t]);
}
$this->createProdAttributes($prodattr, $table); // crmv@203591
$this->setProdAttributes($id, $subprodattr, $create,$table); // crmv@203591
return true;
}
//crmv@95507
//we do not really need $data param but is usefull when overrided
protected function getModuleSeqNumber($data){
return $this->object->setModuleSeqNumber("increment",$this->module);
}
//crmv@95507e
protected function createInventory($data) {
global $adb,$table_prefix;
// crmentity
$table = $table_prefix.'_inventoryproductrel';
$idx = 'lineitem_id';
$id = $adb->getUniqueID($table);
$deleteProducts = false;
$inventoryId = $data[$this->mapping_inverse['inventoryid']];
if (empty($inventoryId)) {
$this->log->warn('No '.$this->invmodule.' record found for this product line, skipped');
return true;
}
if (is_null($this->currentInventoryId)) {
// first row
$this->currentInventoryId = $inventoryId;
$deleteProducts = true;
} elseif ($this->currentInventoryId != $inventoryId) {
// this is a new record, so output the totals
$this->updateInventoryTotals();
$this->currentInventoryId = $inventoryId;
$this->currentTotals = array();
$deleteProducts = true;
}
// the delete query
if ($inventoryId > 0 && $deleteProducts) {
$sql = "DELETE FROM {$table} WHERE id = '$inventoryId'\r\n";
$this->writeToFile($this->sql_file_create[$table], $sql, $this->sql_file_name_create[$table]);
}
$create = $this->getcached_create_arr();
// other tables
foreach ($this->fields as $ftable => $arr){
foreach ($arr as $field){
if ($this->fields_functions[$this->fields_name[$ftable][$field]] != '' && method_exists($this,$this->fields_functions[$this->fields_name[$ftable][$field]])) {
$create[$ftable][$field] = $this->{$this->fields_functions[$this->fields_name[$ftable][$field]]}($this->fields_name[$ftable][$field],$data,$ftable,$field,'create');
}
else{
$create[$ftable][$field] = $data[$this->mapping_inverse[$this->fields_name[$ftable][$field]]];
}
}
// no id, it's already done in the fields_runtime
}
// calculate all the prices
$prodPrices = $this->iutils->calcProductTotals($create[$table]);
$prodFieldsToAdd = array(
'price_discount' => 'total_notaxes',
'price_taxes' => 'linetotal'
);
// add the calculated fields
foreach ($prodFieldsToAdd as $k => $fld) {
if (array_key_exists($k, $prodPrices) && !isset($create[$table][$fld])) {
$this->fields_runtime[$table][$fld] = $prodPrices[$k];
}
}
// add the lineitemid
$this->fields_runtime[$table][$idx] = $id;
if (is_array($this->fields_runtime)) {
foreach ($this->fields_runtime as $ftable=>$arr){
foreach ($arr as $column=>$value){
$create[$ftable][$column] = $value;
}
}
}
foreach ($create as $t=>$k){
$this->insert_into_create_file($t,$create);
}
$this->entity_created[] = $id;
// save the totals
$this->currentTotals[] = $prodPrices;
return true;
}
protected function updateInventoryTotals() {
global $adb, $table_prefix;
if (empty($this->currentInventoryId) || count($this->currentTotals) == 0) {
// nothing to calculate
return true;
}
$table = $this->object->table_name;
$index = $this->object->table_index;
$subtotal = 0;
foreach($this->currentTotals as $value) {
$subtotal += $value['price_taxes'];
}
$grandTotal = $subtotal;
// get other prices
$res = $adb->pquery("select discount_percent, discount_amount, adjustment, s_h_amount from {$table} where {$index} = ?", array($this->currentInventoryId));
$row = $adb->FetchByAssoc($res, -1, false);
if (empty($row)) {
$this->log->warn('It was not possible to calculate the totals, since the main '.$this->invmodule.'record was not found');
return true;
}
$totalinfo = array(
'nettotal' => floatval($subtotal),
's_h_amount' => floatval($row['s_h_amount']),
'discount_percent' => $row['discount_percent'],
'discount_amount' => $row['discount_amount'],
'adjustment' => floatval($row['adjustment']),
'taxes' => array(),
'shtaxes' => array(),
);
// calculate totals
$totalPrices = $this->iutils->calcInventoryTotals($totalinfo);
if ($totalPrices) {
$grandTotal = $totalPrices['price_adjustment'];
}
$sql = "UPDATE {$table} set subtotal = ?, total = ? where {$index} = ?;\r\n";
$params = array($subtotal,$grandTotal,$this->currentInventoryId);
$sql = $adb->convert2Sql($sql, $adb->flatten_array($params));
$this->writeToFile($this->sql_file_update[$table], $sql, $this->sql_file_name_update[$table]);
return true;
}
protected function update($data,$id) {
global $adb, $table_prefix;
if ($this->module == 'ProductRows') {
$this->log->warning('The update is not supported for products rows, something went wrong. Update skipped');
return true;
}
//crmv@185058
if ($this->launch_processes) {
require_once('data/VTEntityDelta.php');
$entityDelta = new VTEntityDelta();
$entityDelta->setOldEntity($this->module,$id);
}
//crmv@185058e
$prodattr = $subprodattr = array(); // crmv@203591
$update = $this->getcached_update_arr();
foreach ($this->fields as $table => $arr){
foreach ($arr as $field){
// should i skip the field in update?
if (is_array($this->fields_jump_update) && in_array($this->fields_name[$table][$field],$this->fields_jump_update)) continue;
// crmv@203591
// which funciton to use for the value
if ($this->fields_functions[$this->fields_name[$table][$field]] != '' && method_exists($this,$this->fields_functions[$this->fields_name[$table][$field]])) {
$readyData = $this->{$this->fields_functions[$this->fields_name[$table][$field]]}($this->fields_name[$table][$field],$data,$table,$field,'update');
} else {
$readyData = $data[$this->mapping_inverse[$this->fields_name[$table][$field]]];
}
if ($field == 'mlProdAttr') {
$prodattr[$id] = $readyData;
continue;
} elseif (substr($field, 0, 9) === 'prodattr_') {
$subprodattr[$field] = $readyData;
continue;
} elseif (substr($field, -8) === 'prodattr') {
$subprodattr[$field] = $readyData;
continue;
}
$update[$table][$field] = $readyData;
// crmv@203591e
}
}
if (is_array($this->fields_runtime)) {
foreach ($this->fields_runtime as $table=>$arr){
foreach ($arr as $column=>$value){
$update[$table][$column] = $value;
}
}
}
foreach ($update as $table=>$arr){
$sql = "UPDATE $table SET ";
$first = true;
$params = Array();
foreach ($arr as $field=>$value){
if (!$first)
$sql .=",";
$sql .= " $field = ?";
$first = false;
$params[] = $value;
}
$sql .= " WHERE ".$this->getkey('full',$table)." = ?";
$params[] = $id;
$sql = $adb->convert2Sql($sql,$adb->flatten_array($params));
if ($id){
$this->writeToFile($this->sql_file_update[$table],$sql.";\n", $this->sql_file_name_update[$table]);
}
}
$this->entity_updated[] = $id;
$this->createProdAttributes($prodattr, $table); // crmv@203591
$this->setProdAttributes($id, $subprodattr, $update,$table); // crmv@203591
return true;
}
// crmv@203591
protected function createProdAttributes($prodattr, $table) {
global $adb, $table_prefix, $current_user;
if (is_array($prodattr) && count($prodattr) > 0) {
$confProdFocus = CRMEntity::getInstance('ConfProducts');
$queries = [];
foreach ($prodattr as $confprodid => $attributes) {
// get all attributes
$attributes = array_keys($attributes);
$attrs = $confProdFocus->getAttributes($confprodid);
// extract just names for comparisons
$attrNames = array_column($attrs, 'fieldlabel');
$toAdd = array_diff($attributes, $attrNames);
$toDel = array_diff($attrNames, $attributes);
if (count($toDel) > 0) {
$sqlDel = "DELETE FROM {$table_prefix}_modlightprodattr WHERE parent_id = ? AND attr_name IN (".generateQuestionMarks($toDel).");\n";
$sqlDel = $adb->convert2Sql($sqlDel,$adb->flatten_array(array(intval($confprodid), $toDel)));
$queries[] = $sqlDel;
}
if (count($toAdd) > 0) {
$seq = 1;
foreach ($toAdd as $addAttr) {
$id = $adb->getUniqueID($table_prefix.'_crmentity');
$params = [$id, $current_user->id, $current_user->id, 'ModLightProdAttr', $this->time_start, $this->time_start, 1, 0];
$sqlIns = "INSERT INTO {$table_prefix}_crmentity (crmid, smcreatorid, smownerid, setype, createdtime, modifiedtime, presence, deleted) VALUES (".generateQuestionMarks($params).");\n";
$sqlIns = $adb->convert2Sql($sqlIns,$adb->flatten_array($params));
$queries[] = $sqlIns;
$params = [$id, $addAttr, '', $confprodid, $seq++];
$sqlIns = "INSERT INTO {$table_prefix}_modlightprodattr (modlightprodattrid, attr_name, attr_values, parent_id, seq) VALUES (".generateQuestionMarks($params).");\n";
$sqlIns = $adb->convert2Sql($sqlIns,$adb->flatten_array($params));
$queries[] = $sqlIns;
}
}
}
if (count($queries) > 0) $this->prodAttrCreated = true;
// now set values, if present
foreach ($prodattr as $confprodid => $attributes) {
foreach ($attributes as $attrName => $values) {
if (is_array($values)) {
// ok, values are passed
$params = [implode("\r\n", $values), $confprodid, $attrName];
$sql = "UPDATE {$table_prefix}_modlightprodattr SET attr_values = ? WHERE parent_id = ? AND attr_name = ?;\n";
$sql = $adb->convert2Sql($sql,$adb->flatten_array($params));
$queries[] = $sql;
}
}
}
foreach ($queries as $q) {
$this->writeToFile($this->sql_file_update[$table],$q, $this->sql_file_name_update[$table]);
}
}
}
protected function setProdAttributes($id, $subprodattr, $update, $table) {
global $adb, $table_prefix;
if (is_array($subprodattr) && count($subprodattr) > 0) {
$confprodid = $update[$table]['confproductid'];
if ($confprodid > 0) {
// take only the attributes matching the configurable product
$toUpdate = [];
foreach ($subprodattr as $attrName => $value) {
if (substr($attrName, -8) === 'prodattr') {
// I only have the label, find the matching field
$attrName = $this->findMatchingAttribute($confprodid, str_replace('prodattr', '', $attrName));
if (!$attrName) continue;
}
list($xx, $confid, $mlid) = explode('_', $attrName);
if ($confprodid == $confid) {
$toUpdate[$attrName] = $value;
// save all the values for later
$this->prodAttrValues[$confprodid][$mlid][$value] = 1;
}
}
$params = [Zend_Json::encode($toUpdate), intval($id)];
$sql = "UPDATE $table SET confprodinfo = ? WHERE productid = ?;\n";
$sql = $adb->convert2Sql($sql,$adb->flatten_array($params));
$this->writeToFile($this->sql_file_update[$table],$sql, $this->sql_file_name_update[$table]);
} else {
// delete the conf
$sql = "UPDATE $table SET confprodinfo = NULL WHERE productid = ".intval($id).";\n";
$this->writeToFile($this->sql_file_update[$table],$sql, $this->sql_file_name_update[$table]);
}
}
}
/**
* Find a matching attribute name using the label
*/
protected function findMatchingAttribute($confprodid, $label) {
static $prodAttrNamesCache = array();
$key = $confprodid.'_'.$label;
if (!array_key_exists($key, $prodAttrNamesCache)) {
$field = null;
$confProdFocus = CRMEntity::getInstance('ConfProducts');
$attrs = $confProdFocus->getAttributes($confprodid);
foreach ($attrs as $attrinfo) {
if ($attrinfo['fieldlabel'] == $label) {
$field = $attrinfo['fieldname'];
break;
}
}
$prodAttrNamesCache[$key] = $field;
}
return $prodAttrNamesCache[$key];
}
protected function insertProdAttrValues() {
global $adb, $table_prefix;
foreach ($this->prodAttrValues as $confprodid => $attributes) {
foreach ($attributes as $mlid => $values) {
$values = array_keys($values);
// get existing values
$res = $adb->pquery("SELECT attr_values FROM {$table_prefix}_modlightprodattr WHERE modlightprodattrid = ?", array($mlid));
if ($res && $adb->num_rows($res) > 0) {
$existingValues = $adb->query_result_no_html($res, 0, 'attr_values');
$existingValues = explode("\n", str_replace("\r", "", $existingValues));
$allValues = array_unique(array_filter(array_merge($existingValues, $values)));
$adb->pquery(
"UPDATE {$table_prefix}_modlightprodattr SET attr_values = ? WHERE modlightprodattrid = ?",
[implode("\r\n", $allValues), $mlid]
);
}
}
}
}
// crmv@203591e
protected function getkey($mode = '',$table = false){
global $table_prefix;
if (!$table){
$table = $table_prefix.'_crmentity';
if(!in_array($table,$this->object->tab_name)){
$table = $this->object->tab_name[0];
}
}
if ($mode == 'full')
return $table.".".$this->object->tab_name_index[$table];
else
return $this->object->tab_name_index[$table];
}
protected function executeSystemCommand($cmd) {
if ($this->simulate) {
$this->log->debug('SIMULATION: Execute command: '.$cmd);
} else {
// crmv@155585
$lastRow = system($cmd,$result);
if ($result != '0'){
$this->log->error('Command error: '.$lastRow);
return false;
}
// crmv@155585e
}
return true;
}
protected function executeUpdateQuery($q) {
global $adb;
if ($this->simulate) {
$this->log->debug('SIMULATION: Execute query: '.$q);
} else {
$r = $adb->query($q);
if (!$r) return false;
}
return true;
}
protected function execute(){
global $dbconfig, $adb, $table_prefix;
$this->log->debug('Reading files and executing query now...');
if ($this->config['fast_mysql_insert'] && $adb->isMySQL()) {
$port = str_replace(":","",$dbconfig['db_port']);
$mysqlOpts = "--local-infile -h {$dbconfig['db_server']} -u {$dbconfig['db_username']} --password='{$dbconfig['db_password']}' -P {$port} {$dbconfig['db_name']}"; // crmv@169696
$create_file = fopen($this->file_create,'w+');
$pre_create = "/*!40101 SET NAMES utf8 */;\n/*!40101 SET SQL_MODE=''*/;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n";
$this->writeToFile($create_file,$pre_create, $this->file_create);
foreach ($this->sql_file_name_create as $t=>$file){
//faccio le create
$fields = $this->get_column_create($t);
if (filesize($file)>0){
// crmv@109628
$csvCharset = $this->extdb_charset ?: 'UTF8';
$sql_load = 'LOAD DATA LOCAL INFILE \''.$file.'\' INTO TABLE '.$t.' CHARACTER SET '.$csvCharset.' FIELDS ESCAPED BY \'\' TERMINATED BY \',\' OPTIONALLY ENCLOSED BY \'"\' LINES TERMINATED BY \'\n\' ('.implode(",",$fields).');'."\n";
$this->writeToFile($create_file,$sql_load, $this->file_create);
// crmv@109628e
}
}
$post_create = "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;";
$this->writeToFile($create_file,$post_create, $this->file_create);
fclose($create_file);
// now execute the commands
if (filesize($this->file_create)>0){
$filename = $this->file_create;
$string_create = "mysql $mysqlOpts 2>&1 < {$filename}"; // crmv@155585
$r = $this->executeSystemCommand($string_create);
if (!$r) return $this->log->error("Error loading create file {$this->file_create}");
}
// now the updates
foreach ($this->sql_file_name_update as $t=>$file){
//faccio le insert
if (filesize($file)>0){
$string_update = "mysql $mysqlOpts 2>&1 < {$file}"; // crmv@155585
$r = $this->executeSystemCommand($string_update);
if (!$r) return $this->log->error("Error loading updating file {$file}");
}
}
} else {
// TODO: le query sembrano errate
//faccio le create
foreach ($this->sql_file_name_create as $t=>$file){
$create_file = fopen($file,'rb');
while (($buffer = fgets($create_file, 4096)) !== false) {
if (trim($buffer) != ''){
$this->executeUpdateQuery($buffer);
}
}
fclose($create_file);
}
//faccio le insert
foreach ($this->sql_file_name_update as $t=>$file){
if (filesize($file)>0) {
$update_file = fopen($file,'rb');
while (($buffer = fgets($update_file, 4096)) !== false) {
if (trim($buffer) != ''){
$this->executeUpdateQuery($buffer);
}
}
fclose($update_file);
}
}
}
return true;
}
protected function get_existing_entity_runtime($row){
global $adb, $table_prefix;
$LVU = ListViewUtils::getInstance();
// prepare the query
if (!isset($this->cache[$this->module]['query_unique'])){
$sql = "select tablename,columnname from ".$table_prefix."_field where tabid = ? and fieldname = ?"; //crmv@75695
$params[] = getTabid($this->module);
$params[] = $this->mapping[$this->external_code];
$res = $adb->pquery($sql,$params);
if ($res){
$table = $adb->query_result_no_html($res,0,'tablename');
$columnname = $adb->query_result_no_html($res,0,'columnname'); //crmv@75695
if (empty($table)) {
$this->log->warn("The external code is not configured properly");
return false;
}
$external_code = $table.".".$columnname; //crmv@75695
}
$qry = $LVU->getListQuery($this->module,"and $external_code = ?");
if ($this->module == 'Users'){
$qry = replaceSelectQuery($qry,'id');
} else {
$qry = replaceSelectQuery($qry,$table_prefix.'_crmentity.crmid');
}
$this->cache[$this->module]['query_unique'] = $qry;
}
// execute it
$extcode = $row[$this->external_code];
$qry = $this->cache[$this->module]['query_unique'];
$res = $adb->pquery($qry,Array($extcode));
$id = false;
if ($res && $adb->num_rows($res)>0) {
if ($this->module == 'Users'){
$id = $adb->query_result_no_html($res,0,'id');
} else {
$id = $adb->query_result_no_html($res,0,'crmid');
}
}
// log disabled, since can log every row, but kept here for future use
//$this->log->debug("The external code '$extcode' ".($id ? "has been found. crmid = $id" : "has not been found"));
return $id;
}
private function get_column_create($table_name) {
$create = $this->getcached_create_arr(false);
foreach ($this->fields as $table => $arr){
foreach ($arr as $field){
if (substr($field, 0, 9) === 'prodattr_' || substr($field, -8) === 'prodattr') continue; // crmv@203591
$create[$table][$field] = '';
}
$idx = $this->object->tab_name_index[$table];
if ($idx) $create[$table][$idx] = '';
}
foreach ($this->object->tab_name_index as $t=>$k){
if (!$create[$t][$k]) $create[$t][$k] = '';
}
if (is_array($this->fields_runtime)) {
foreach ($this->fields_runtime as $table=>$arr){
foreach ($arr as $column=>$value){
$create[$table][$column] = '';
}
}
}
if ($this->sequence_field){
$create[$this->sequence_field[0]][$this->sequence_field[1]] = '';
}
return array_keys($create[$table_name]);
}
private function getcached_create_arr($data = true) {
if ($this->create_arr) return $this->create_arr;
if (is_array($this->fields_auto_create)) {
foreach ($this->fields_auto_create as $table => $arr){
foreach ($arr as $field=>$def_value){
if (!$data) $def_value = '';
$this->create_arr[$table][$field] = $def_value;
}
}
}
return $this->create_arr;
}
private function getcached_update_arr() {
if ($this->update_arr) return $this->update_arr;
if (is_array($this->fields_auto_update)){
foreach ($this->fields_auto_update as $table => $arr){
foreach ($arr as $field=>$def_value){
$this->update_arr[$table][$field] = $def_value;
}
}
}
return $this->update_arr;
}
private function insert_into_create_file($table,$create){
global $adb;
if ($this->config['fast_mysql_insert'] && $adb->isMySQL()){
$this->fputcsv2($this->sql_file_create[$table],$create[$table], ',', '"', true, $this->sql_file_name_create[$table]);
}
else{
array_walk($create[$table],array($this, 'sanitize_array_sql'));
$columns = array_keys($create[$table]);
$adb->format_columns($columns);
$sql = "INSERT INTO $table (".implode(",",$columns).") VALUES (".implode(",",$create[$table]).")\r\n";
$this->writeToFile($this->sql_file_create[$table], $sql, $this->sql_file_name_create[$table]);
}
}
// special method to save a csv in a way msql like it
function fputcsv2 ($fh, array $fields, $delimiter = ',', $enclosure = '"', $mysql_null = true, $filename = null) {
$delimiter_esc = preg_quote($delimiter, '/');
$enclosure_esc = preg_quote($enclosure, '/');
$output = array();
foreach ($fields as $field) {
if ($field === null && $mysql_null) {
$output[] = 'NULL';
continue;
}
$output[] = preg_match("/(?:${delimiter_esc}|${enclosure_esc}|\s)/", $field) ? (
$enclosure . str_replace($enclosure, $enclosure . $enclosure, $field) . $enclosure
) : $field;
}
$this->writeToFile($fh, join($delimiter, $output) . "\n", $filename);
}
function sanitize_array_sql(&$item,&$key){
global $adb;
if (is_string($item)) {
if($item == '') {
$item = $adb->database->Quote($item);
}
else {
$item = "'".$adb->sql_escape_string($item). "'";
}
}
if ($item === null) {
$item = "NULL";
}
}
// get the field containing the sequence for the module and cache this information
protected function getSequenceField(){
global $adb,$table_prefix;
if ($this->sequence_field) return $this->sequence_field;
$sql = "select tablename,columnname,uitype from {$table_prefix}_field where tabid = ? and uitype = 4";
$res = $adb->limitpQuery($sql, 0, 1, Array(getTabid($this->module)));
if ($res && $adb->num_rows($res) > 0){
$this->sequence_field = Array($adb->query_result_no_html($res,0,'tablename'),$adb->query_result_no_html($res,0,'columnname'));
}
return $this->sequence_field;
}
protected function delete($data,$id){
// NOT IMPLEMENTED!!
}
}