dolibarr 21.0.0-alpha
import_csv.modules.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2006-2012 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2009-2012 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2012 Christophe Battarel <christophe.battarel@altairis.fr>
5 * Copyright (C) 2012-2016 Juanjo Menent <jmenent@2byte.es>
6 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
7 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 * or see https://www.gnu.org/
22 */
23
30require_once DOL_DOCUMENT_ROOT.'/core/modules/import/modules_import.php';
31
32
37{
41 public $db;
42
46 public $id;
47
52 public $version = 'dolibarr';
53
57 public $label_lib;
58
62 public $version_lib;
63
67 public $separator;
68
72 public $file; // Path of file
73
77 public $handle; // File handle
78
79 public $cacheconvert = array(); // Array to cache list of value found after a conversion
80
81 public $cachefieldtable = array(); // Array to cache list of value found into fields@tables
82
83 public $nbinsert = 0; // # of insert done during the import
84
85 public $nbupdate = 0; // # of update done during the import
86
87 public $charset = '';
88
89 public $col;
90
91
98 public function __construct($db, $datatoimport)
99 {
100 global $langs;
101
102 parent::__construct();
103 $this->db = $db;
104
105 $this->separator = (GETPOST('separator') ? GETPOST('separator') : getDolGlobalString('IMPORT_CSV_SEPARATOR_TO_USE', ','));
106 $this->enclosure = '"';
107 $this->escape = '"';
108
109 $this->id = 'csv'; // Same value then xxx in file name export_xxx.modules.php
110 $this->label = 'Csv'; // Label of driver
111 $this->desc = $langs->trans("CSVFormatDesc", $this->separator, $this->enclosure, $this->escape);
112 $this->extension = 'csv'; // Extension for generated file by this driver
113 $this->picto = 'mime/other'; // Picto
114 $this->version = '1.34'; // Driver version
115 $this->phpmin = array(7, 0); // Minimum version of PHP required by module
116
117 require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
118 if (versioncompare($this->phpmin, versionphparray()) > 0) {
119 dol_syslog("Module need a higher PHP version");
120 $this->error = "Module need a higher PHP version";
121 return;
122 }
123
124 // If driver use an external library, put its name here
125 $this->label_lib = 'Dolibarr';
126 $this->version_lib = DOL_VERSION;
127
128 $this->datatoimport = $datatoimport;
129 if (preg_match('/^societe_/', $datatoimport)) {
130 $this->thirdpartyobject = new Societe($this->db);
131 }
132 }
133
134
135 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
142 public function write_header_example($outputlangs)
143 {
144 // phpcs:enable
145 return '';
146 }
147
148 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
156 public function write_title_example($outputlangs, $headerlinefields)
157 {
158 // phpcs:enable
159 $s = implode($this->separator, array_map('cleansep', $headerlinefields));
160 return $s."\n";
161 }
162
163 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
171 public function write_record_example($outputlangs, $contentlinevalues)
172 {
173 // phpcs:enable
174 $s = implode($this->separator, array_map('cleansep', $contentlinevalues));
175 return $s."\n";
176 }
177
178 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
185 public function write_footer_example($outputlangs)
186 {
187 // phpcs:enable
188 return '';
189 }
190
191
192 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
199 public function import_open_file($file)
200 {
201 // phpcs:enable
202 global $langs;
203 $ret = 1;
204
205 dol_syslog(get_class($this)."::open_file file=".$file);
206
207 ini_set('auto_detect_line_endings', 1); // For MAC compatibility
208
209 $handle = fopen(dol_osencode($file), "r");
210 if (!$this->handle) {
211 $langs->load("errors");
212 $this->error = $langs->trans("ErrorFailToOpenFile", $file);
213 $ret = -1;
214 } else {
215 $this->handle = $handle;
216 $this->file = $file;
217 }
218
219 return $ret;
220 }
221
222
223 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
230 public function import_get_nb_of_lines($file)
231 {
232 // phpcs:enable
233 return dol_count_nb_of_line($file);
234 }
235
236
237 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
243 public function import_read_header()
244 {
245 // phpcs:enable
246 return 0;
247 }
248
249
250 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
256 public function import_read_record()
257 {
258 // phpcs:enable
259 global $conf;
260
261 $arrayres = fgetcsv($this->handle, 100000, $this->separator, $this->enclosure, $this->escape);
262
263 // End of file
264 if ($arrayres === false) {
265 return false;
266 }
267
268 //var_dump($this->handle);
269 //var_dump($arrayres);exit;
270 $newarrayres = array();
271 if ($arrayres && is_array($arrayres)) {
272 foreach ($arrayres as $key => $val) {
273 if (getDolGlobalString('IMPORT_CSV_FORCE_CHARSET')) { // Forced charset
274 if (strtolower($conf->global->IMPORT_CSV_FORCE_CHARSET) == 'utf8') {
275 $newarrayres[$key]['val'] = $val;
276 $newarrayres[$key]['type'] = (dol_strlen($val) ? 1 : -1); // If empty we consider it's null
277 } else {
278 $newarrayres[$key]['val'] = mb_convert_encoding($val, 'UTF-8', 'ISO-8859-1');
279 $newarrayres[$key]['type'] = (dol_strlen($val) ? 1 : -1); // If empty we consider it's null
280 }
281 } else { // Autodetect format (UTF8 or ISO)
282 if (utf8_check($val)) {
283 $newarrayres[$key]['val'] = $val;
284 $newarrayres[$key]['type'] = (dol_strlen($val) ? 1 : -1); // If empty we consider it's null
285 } else {
286 $newarrayres[$key]['val'] = mb_convert_encoding($val, 'UTF-8', 'ISO-8859-1');
287 $newarrayres[$key]['type'] = (dol_strlen($val) ? 1 : -1); // If empty we consider it's null
288 }
289 }
290 }
291
292 $this->col = count($newarrayres);
293 }
294
295 return $newarrayres;
296 }
297
298 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
304 public function import_close_file()
305 {
306 // phpcs:enable
307 fclose($this->handle);
308 return 0;
309 }
310
311
312 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
324 public function import_insert($arrayrecord, $array_match_file_to_database, $objimport, $maxfields, $importid, $updatekeys)
325 {
326 // phpcs:enable
327 global $langs, $conf, $user;
328 global $thirdparty_static; // Specific to thirdparty import
329 global $tablewithentity_cache; // Cache to avoid to call desc at each rows on tables
330
331 $error = 0;
332 $warning = 0;
333 $this->errors = array();
334 $this->warnings = array();
335
336 //dol_syslog("import_csv.modules maxfields=".$maxfields." importid=".$importid);
337
338 //var_dump($array_match_file_to_database);
339 //var_dump($arrayrecord); exit;
340
341 $array_match_database_to_file = array_flip($array_match_file_to_database);
342 $sort_array_match_file_to_database = $array_match_file_to_database;
343 ksort($sort_array_match_file_to_database);
344
345 //var_dump($sort_array_match_file_to_database);
346
347 if (count($arrayrecord) == 0 || (count($arrayrecord) == 1 && empty($arrayrecord[0]['val']))) {
348 //print 'W';
349 $this->warnings[$warning]['lib'] = $langs->trans('EmptyLine');
350 $this->warnings[$warning]['type'] = 'EMPTY';
351 $warning++;
352 } else {
353 $last_insert_id_array = array(); // store the last inserted auto_increment id for each table, so that dependent tables can be inserted with the appropriate id (eg: extrafields fk_object will be set with the last inserted object's id)
354 $updatedone = false;
355 $insertdone = false;
356 // For each table to insert, me make a separate insert
357 foreach ($objimport->array_import_tables[0] as $alias => $tablename) {
358 // Build sql request
359 $sql = '';
360 $listfields = array();
361 $listvalues = array();
362 $i = 0;
363 $errorforthistable = 0;
364
365 // Define $tablewithentity_cache[$tablename] if not already defined
366 if (!isset($tablewithentity_cache[$tablename])) { // keep this test with "isset"
367 dol_syslog("Check if table ".$tablename." has an entity field");
368 $resql = $this->db->DDLDescTable($tablename, 'entity');
369 if ($resql) {
370 $obj = $this->db->fetch_object($resql);
371 if ($obj) {
372 $tablewithentity_cache[$tablename] = 1; // table contains entity field
373 } else {
374 $tablewithentity_cache[$tablename] = 0; // table does not contain entity field
375 }
376 } else {
377 dol_print_error($this->db);
378 }
379 } else {
380 //dol_syslog("Table ".$tablename." check for entity into cache is ".$tablewithentity_cache[$tablename]);
381 }
382
383 // Define an array to convert fields ('c.ref', ...) into column index (1, ...)
384 $arrayfield = array();
385 foreach ($sort_array_match_file_to_database as $key => $val) {
386 $arrayfield[$val] = ($key - 1);
387 }
388
389 // $arrayrecord start at key 0
390 // $sort_array_match_file_to_database start at key 1
391
392 // Loop on each fields in the match array: $key = 1..n, $val=alias of field (s.nom)
393 foreach ($sort_array_match_file_to_database as $key => $val) {
394 $fieldalias = preg_replace('/\..*$/i', '', $val);
395 $fieldname = preg_replace('/^.*\./i', '', $val);
396
397 if ($alias != $fieldalias) {
398 continue; // Not a field of current table
399 }
400
401 if ($key <= $maxfields) {
402 // Set $newval with value to insert and set $listvalues with sql request part for insert
403 $newval = '';
404 if ($arrayrecord[($key - 1)]['type'] > 0) {
405 $newval = $arrayrecord[($key - 1)]['val']; // If type of field into input file is not empty string (so defined into input file), we get value
406 }
407
408 //var_dump($newval);var_dump($val);
409 //var_dump($objimport->array_import_convertvalue[0][$val]);
410
411 // Make some tests on $newval
412
413 // Is it a required field ?
414 if (preg_match('/\*/', $objimport->array_import_fields[0][$val]) && ((string) $newval == '')) {
415 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
416 $this->errors[$error]['lib'] = $langs->trans('ErrorMissingMandatoryValue', $key);
417 $this->errors[$error]['type'] = 'NOTNULL';
418 $errorforthistable++;
419 $error++;
420 } else {
421 // Test format only if field is not a missing mandatory field (field may be a value or empty but not mandatory)
422 // We convert field if required
423 if (!empty($objimport->array_import_convertvalue[0][$val])) {
424 //print 'Must convert '.$newval.' with rule '.join(',',$objimport->array_import_convertvalue[0][$val]).'. ';
425 if ($objimport->array_import_convertvalue[0][$val]['rule'] == 'fetchidfromcodeid'
426 || $objimport->array_import_convertvalue[0][$val]['rule'] == 'fetchidfromref'
427 || $objimport->array_import_convertvalue[0][$val]['rule'] == 'fetchidfromcodeorlabel'
428 ) {
429 // New val can be an id or ref. If it start with id: it is forced to id, if it start with ref: it is forced to ref. It not, we try to guess.
430 $isidorref = 'id';
431 if (!is_numeric($newval) && $newval != '' && !preg_match('/^id:/i', $newval)) {
432 $isidorref = 'ref';
433 }
434
435 $newval = preg_replace('/^(id|ref):/i', '', $newval); // Remove id: or ref: that was used to force if field is id or ref
436 //print 'Newval is now "'.$newval.'" and is type '.$isidorref."<br>\n";
437
438 if ($isidorref == 'ref') { // If value into input import file is a ref, we apply the function defined into descriptor
439 $file = (empty($objimport->array_import_convertvalue[0][$val]['classfile']) ? $objimport->array_import_convertvalue[0][$val]['file'] : $objimport->array_import_convertvalue[0][$val]['classfile']);
440 $class = $objimport->array_import_convertvalue[0][$val]['class'];
441 $method = $objimport->array_import_convertvalue[0][$val]['method'];
442 if ($this->cacheconvert[$file.'_'.$class.'_'.$method.'_'][$newval] != '') {
443 $newval = $this->cacheconvert[$file.'_'.$class.'_'.$method.'_'][$newval];
444 } else {
445 $resultload = dol_include_once($file);
446 if (empty($resultload)) {
447 dol_print_error(null, 'Error trying to call file='.$file.', class='.$class.', method='.$method);
448 break;
449 }
450 $classinstance = new $class($this->db);
451 if ($class == 'CGenericDic') {
452 $classinstance->element = $objimport->array_import_convertvalue[0][$val]['element'];
453 $classinstance->table_element = $objimport->array_import_convertvalue[0][$val]['table_element'];
454 }
455
456 // Try the fetch from code or ref
457 $param_array = array('', $newval);
458 if ($class == 'AccountingAccount') {
459 //var_dump($arrayrecord[0]['val']);
460 /*include_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountancysystem.class.php';
461 $tmpchartofaccount = new AccountancySystem($this->db);
462 $tmpchartofaccount->fetch(getDolGlobalInt('CHARTOFACCOUNTS'));
463 //var_dump($tmpchartofaccount->ref.' - '.$arrayrecord[0]['val']);
464 if ((! (getDolGlobalInt('CHARTOFACCOUNTS') > 0)) || $tmpchartofaccount->ref != $arrayrecord[0]['val'])
465 {
466 $this->errors[$error]['lib']=$langs->trans('ErrorImportOfChartLimitedToCurrentChart', $tmpchartofaccount->ref);
467 $this->errors[$error]['type']='RESTRICTONCURRENCTCHART';
468 $errorforthistable++;
469 $error++;
470 }*/
471 $param_array = array('', $newval, 0, $arrayrecord[0]['val']); // Param to fetch parent from account, in chart.
472 }
473 if ($class == 'CActionComm') {
474 $param_array = array($newval); // CActionComm fetch method have same parameter for id and code
475 }
476 $result = call_user_func_array(array($classinstance, $method), $param_array);
477
478 // If duplicate record found
479 if (!($classinstance->id != '') && $result == -2) {
480 $this->errors[$error]['lib'] = $langs->trans('ErrorMultipleRecordFoundFromRef', $newval);
481 $this->errors[$error]['type'] = 'FOREIGNKEY';
482 $errorforthistable++;
483 $error++;
484 }
485
486 // If not found, try the fetch from label
487 if (!($classinstance->id != '') && $objimport->array_import_convertvalue[0][$val]['rule'] == 'fetchidfromcodeorlabel') {
488 $param_array = array('', '', $newval);
489 call_user_func_array(array($classinstance, $method), $param_array);
490 }
491 $this->cacheconvert[$file.'_'.$class.'_'.$method.'_'][$newval] = $classinstance->id;
492
493 //print 'We have made a '.$class.'->'.$method.' to get id from code '.$newval.'. ';
494 if ($classinstance->id != '') { // id may be 0, it is a found value
495 $newval = $classinstance->id;
496 } elseif (! $error) {
497 if (!empty($objimport->array_import_convertvalue[0][$val]['dict'])) {
498 $this->errors[$error]['lib'] = $langs->trans('ErrorFieldValueNotIn', num2Alpha($key - 1), $newval, 'code', $langs->transnoentitiesnoconv($objimport->array_import_convertvalue[0][$val]['dict']));
499 } elseif (!empty($objimport->array_import_convertvalue[0][$val]['element'])) {
500 $this->errors[$error]['lib'] = $langs->trans('ErrorFieldRefNotIn', num2Alpha($key - 1), $newval, $langs->transnoentitiesnoconv($objimport->array_import_convertvalue[0][$val]['element']));
501 } else {
502 $this->errors[$error]['lib'] = 'ErrorBadDefinitionOfImportProfile';
503 }
504 $this->errors[$error]['type'] = 'FOREIGNKEY';
505 $errorforthistable++;
506 $error++;
507 }
508 }
509 }
510 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'fetchidfromcodeandlabel') {
511 $isidorref = 'id';
512 if (!is_numeric($newval) && $newval != '' && !preg_match('/^id:/i', $newval)) {
513 $isidorref = 'ref';
514 }
515 $newval = preg_replace('/^(id|ref):/i', '', $newval);
516
517 if ($isidorref == 'ref') {
518 $file = (empty($objimport->array_import_convertvalue[0][$val]['classfile']) ? $objimport->array_import_convertvalue[0][$val]['file'] : $objimport->array_import_convertvalue[0][$val]['classfile']);
519 $class = $objimport->array_import_convertvalue[0][$val]['class'];
520 $method = $objimport->array_import_convertvalue[0][$val]['method'];
521 $codefromfield = $objimport->array_import_convertvalue[0][$val]['codefromfield'];
522 $code = $arrayrecord[$arrayfield[$codefromfield]]['val'];
523 if ($this->cacheconvert[$file.'_'.$class.'_'.$method.'_'.$code][$newval] != '') {
524 $newval = $this->cacheconvert[$file.'_'.$class.'_'.$method.'_'.$code][$newval];
525 } else {
526 $resultload = dol_include_once($file);
527 if (empty($resultload)) {
528 dol_print_error(null, 'Error trying to call file='.$file.', class='.$class.', method='.$method.', code='.$code);
529 break;
530 }
531 $classinstance = new $class($this->db);
532 // Try the fetch from code and ref
533 $param_array = array('', $newval, $code);
534 call_user_func_array(array($classinstance, $method), $param_array);
535 $this->cacheconvert[$file.'_'.$class.'_'.$method.'_'.$code][$newval] = $classinstance->id;
536 if ($classinstance->id > 0) { // we found record
537 $newval = $classinstance->id;
538 } else {
539 if (!empty($objimport->array_import_convertvalue[0][$val]['dict'])) {
540 $this->errors[$error]['lib'] = $langs->trans('ErrorFieldValueNotIn', num2Alpha($key - 1), $newval, 'scale', $langs->transnoentitiesnoconv($objimport->array_import_convertvalue[0][$val]['dict']));
541 } else {
542 $this->errors[$error]['lib'] = 'ErrorFieldValueNotIn';
543 }
544 $this->errors[$error]['type'] = 'FOREIGNKEY';
545 $errorforthistable++;
546 $error++;
547 }
548 }
549 }
550 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'zeroifnull') {
551 if (empty($newval)) {
552 $newval = '0';
553 }
554 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'fetchidfromcodeunits' || $objimport->array_import_convertvalue[0][$val]['rule'] == 'fetchscalefromcodeunits') {
555 $file = (empty($objimport->array_import_convertvalue[0][$val]['classfile']) ? $objimport->array_import_convertvalue[0][$val]['file'] : $objimport->array_import_convertvalue[0][$val]['classfile']);
556 $class = $objimport->array_import_convertvalue[0][$val]['class'];
557 $method = $objimport->array_import_convertvalue[0][$val]['method'];
558 $units = $objimport->array_import_convertvalue[0][$val]['units'];
559 if ($this->cacheconvert[$file.'_'.$class.'_'.$method.'_'.$units][$newval] != '') {
560 $newval = $this->cacheconvert[$file.'_'.$class.'_'.$method.'_'.$units][$newval];
561 } else {
562 $resultload = dol_include_once($file);
563 if (empty($resultload)) {
564 dol_print_error(null, 'Error trying to call file='.$file.', class='.$class.', method='.$method.', units='.$units);
565 break;
566 }
567 $classinstance = new $class($this->db);
568 // Try the fetch from code or ref
569 call_user_func_array(array($classinstance, $method), array('', '', $newval, $units));
570 $scaleorid = (($objimport->array_import_convertvalue[0][$val]['rule'] == 'fetchidfromcodeunits') ? $classinstance->id : $classinstance->scale);
571 $this->cacheconvert[$file.'_'.$class.'_'.$method.'_'.$units][$newval] = $scaleorid;
572 //print 'We have made a '.$class.'->'.$method." to get a value from key '".$newval."' and we got '".$scaleorid."'.";exit;
573 if ($classinstance->id > 0) { // we found record
574 $newval = $scaleorid ? $scaleorid : 0;
575 } else {
576 if (!empty($objimport->array_import_convertvalue[0][$val]['dict'])) {
577 $this->errors[$error]['lib'] = $langs->trans('ErrorFieldValueNotIn', num2Alpha($key - 1), $newval, 'scale', $langs->transnoentitiesnoconv($objimport->array_import_convertvalue[0][$val]['dict']));
578 } else {
579 $this->errors[$error]['lib'] = 'ErrorFieldValueNotIn';
580 }
581 $this->errors[$error]['type'] = 'FOREIGNKEY';
582 $errorforthistable++;
583 $error++;
584 }
585 }
586 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'getcustomercodeifauto') {
587 if (strtolower($newval) == 'auto') {
588 $this->thirdpartyobject->get_codeclient(0, 0);
589 $newval = $this->thirdpartyobject->code_client;
590 //print 'code_client='.$newval;
591 }
592 if (empty($newval)) {
593 $arrayrecord[($key - 1)]['type'] = -1; // If we get empty value, we will use "null"
594 }
595 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'getsuppliercodeifauto') {
596 if (strtolower($newval) == 'auto') {
597 $this->thirdpartyobject->get_codefournisseur(0, 1);
598 $newval = $this->thirdpartyobject->code_fournisseur;
599 //print 'code_fournisseur='.$newval;
600 }
601 if (empty($newval)) {
602 $arrayrecord[($key - 1)]['type'] = -1; // If we get empty value, we will use "null"
603 }
604 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'getcustomeraccountancycodeifauto') {
605 if (strtolower($newval) == 'auto') {
606 $this->thirdpartyobject->get_codecompta('customer');
607 $newval = $this->thirdpartyobject->code_compta_client;
608 //print 'code_compta='.$newval;
609 }
610 if (empty($newval)) {
611 $arrayrecord[($key - 1)]['type'] = -1; // If we get empty value, we will use "null"
612 }
613 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'getsupplieraccountancycodeifauto') {
614 if (strtolower($newval) == 'auto') {
615 $this->thirdpartyobject->get_codecompta('supplier');
616 $newval = $this->thirdpartyobject->code_compta_fournisseur;
617 if (empty($newval)) {
618 $arrayrecord[($key - 1)]['type'] = -1; // If we get empty value, we will use "null"
619 }
620 //print 'code_compta_fournisseur='.$newval;
621 }
622 if (empty($newval)) {
623 $arrayrecord[($key - 1)]['type'] = -1; // If we get empty value, we will use "null"
624 }
625 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'getrefifauto') {
626 if (strtolower($newval) == 'auto') {
627 $defaultref = '';
628
629 $classModForNumber = $objimport->array_import_convertvalue[0][$val]['class'];
630 $pathModForNumber = $objimport->array_import_convertvalue[0][$val]['path'];
631
632 if (!empty($classModForNumber) && !empty($pathModForNumber) && is_readable(DOL_DOCUMENT_ROOT.$pathModForNumber)) {
633 require_once DOL_DOCUMENT_ROOT.$pathModForNumber;
634 $modForNumber = new $classModForNumber();
635 '@phan-var-force ModeleNumRefMembers|ModeleNumRefCommandes|ModeleNumRefSuppliersInvoices|ModeleNumRefSuppliersOrders|ModeleNumRefProjects|ModeleNumRefTask|ModeleNumRefPropales $modForNumber';
636
637 $tmpobject = null;
638 // Set the object with the date property when we can
639 if (!empty($objimport->array_import_convertvalue[0][$val]['classobject'])) {
640 $pathForObject = $objimport->array_import_convertvalue[0][$val]['pathobject'];
641 require_once DOL_DOCUMENT_ROOT.$pathForObject;
642 $tmpclassobject = $objimport->array_import_convertvalue[0][$val]['classobject'];
643 $tmpobject = new $tmpclassobject($this->db);
644 foreach ($arrayfield as $tmpkey => $tmpval) { // $arrayfield is array('c.ref'=>0, ...)
645 if (in_array($tmpkey, array('t.date', 'c.date_commande'))) {
646 $tmpobject->date = dol_stringtotime($arrayrecord[$arrayfield[$tmpkey]]['val'], 1);
647 }
648 }
649 }
650
651 $defaultref = $modForNumber->getNextValue(null, $tmpobject);
652 }
653 if (is_numeric($defaultref) && $defaultref <= 0) { // If error
654 $defaultref = '';
655 }
656 $newval = $defaultref;
657 }
658 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'compute') {
659 $file = (empty($objimport->array_import_convertvalue[0][$val]['classfile']) ? $objimport->array_import_convertvalue[0][$val]['file'] : $objimport->array_import_convertvalue[0][$val]['classfile']);
660 $class = $objimport->array_import_convertvalue[0][$val]['class'];
661 $method = $objimport->array_import_convertvalue[0][$val]['method'];
662 $resultload = dol_include_once($file);
663 if (empty($resultload)) {
664 dol_print_error(null, 'Error trying to call file='.$file.', class='.$class.', method='.$method);
665 break;
666 }
667 $classinstance = new $class($this->db);
668 $res = call_user_func_array(array($classinstance, $method), array(&$arrayrecord, $arrayfield, ($key - 1)));
669 if (empty($classinstance->error) && empty($classinstance->errors)) {
670 $newval = $res; // We get new value computed.
671 } else {
672 $this->errors[$error]['type'] = 'CLASSERROR';
673 $this->errors[$error]['lib'] = implode(
674 "\n",
675 array_merge([$classinstance->error], $classinstance->errors)
676 );
677 $errorforthistable++;
678 $error++;
679 }
680 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'numeric') {
681 $newval = price2num($newval);
682 } elseif ($objimport->array_import_convertvalue[0][$val]['rule'] == 'accountingaccount') {
683 if (!getDolGlobalString('ACCOUNTING_MANAGE_ZERO')) {
684 $newval = rtrim(trim($newval), "0");
685 } else {
686 $newval = trim($newval);
687 }
688 }
689
690 //print 'Val to use as insert is '.$newval.'<br>';
691 }
692
693 // Test regexp
694 if (!empty($objimport->array_import_regex[0][$val]) && ($newval != '')) {
695 // If test regex string is "field@table" or "field@table:..." (means must exists into table ...)
696 $reg = array();
697 if (preg_match('/^(.+)@([^:]+)(:.+)?$/', $objimport->array_import_regex[0][$val], $reg)) {
698 $field = $reg[1];
699 $table = $reg[2];
700 $filter = !empty($reg[3]) ? substr($reg[3], 1) : '';
701
702 $cachekey = $field.'@'.$table;
703 if (!empty($filter)) {
704 $cachekey .= ':'.$filter;
705 }
706
707 // Load content of field@table into cache array
708 if (!is_array($this->cachefieldtable[$cachekey])) { // If content of field@table not already loaded into cache
709 $sql = "SELECT ".$field." as aliasfield FROM ".$table;
710 if (!empty($filter)) {
711 $sql .= ' WHERE '.$filter;
712 }
713
714 $resql = $this->db->query($sql);
715 if ($resql) {
716 $num = $this->db->num_rows($resql);
717 $i = 0;
718 while ($i < $num) {
719 $obj = $this->db->fetch_object($resql);
720 if ($obj) {
721 $this->cachefieldtable[$cachekey][] = $obj->aliasfield;
722 }
723 $i++;
724 }
725 } else {
726 dol_print_error($this->db);
727 }
728 }
729
730 // Now we check cache is not empty (should not) and key is in cache
731 if (!is_array($this->cachefieldtable[$cachekey]) || !in_array($newval, $this->cachefieldtable[$cachekey])) {
732 $tableforerror = $table;
733 if (!empty($filter)) {
734 $tableforerror .= ':'.$filter;
735 }
736 $this->errors[$error]['lib'] = $langs->transnoentitiesnoconv('ErrorFieldValueNotIn', num2Alpha($key - 1), $newval, $field, $tableforerror);
737 $this->errors[$error]['type'] = 'FOREIGNKEY';
738 $errorforthistable++;
739 $error++;
740 }
741 } elseif (!preg_match('/'.$objimport->array_import_regex[0][$val].'/i', $newval)) {
742 // If test is just a static regex
743 //if ($key == 19) print "xxx".$newval."zzz".$objimport->array_import_regex[0][$val]."<br>";
744 $this->errors[$error]['lib'] = $langs->transnoentitiesnoconv('ErrorWrongValueForField', num2Alpha($key - 1), $newval, $objimport->array_import_regex[0][$val]);
745 $this->errors[$error]['type'] = 'REGEX';
746 $errorforthistable++;
747 $error++;
748 }
749 }
750
751 // Check HTML injection
752 $inj = testSqlAndScriptInject($newval, 0);
753 if ($inj) {
754 $this->errors[$error]['lib'] = $langs->transnoentitiesnoconv('ErrorHtmlInjectionForField', num2Alpha($key - 1), dol_trunc($newval, 100));
755 $this->errors[$error]['type'] = 'HTMLINJECTION';
756 $errorforthistable++;
757 $error++;
758 }
759
760 // Other tests
761 // ...
762 }
763
764 // Define $listfields and $listvalues to build the SQL request
765 if (isModEnabled("socialnetworks") && strpos($fieldname, "socialnetworks") !== false) {
766 if (!in_array("socialnetworks", $listfields)) {
767 $listfields[] = "socialnetworks";
768 $socialkey = array_search("socialnetworks", $listfields); // Return position of 'socialnetworks' key in array
769 $listvalues[$socialkey] = '';
770 }
771 //var_dump($newval); var_dump($arrayrecord[($key - 1)]['type']);
772 if (!empty($newval) && $arrayrecord[($key - 1)]['type'] > 0) {
773 $socialkey = array_search("socialnetworks", $listfields); // Return position of 'socialnetworks' key in array
774 //var_dump('sk='.$socialkey); // socialkey=19
775 $socialnetwork = explode("_", $fieldname)[1];
776 if (empty($listvalues[$socialkey]) || $listvalues[$socialkey] == "null") {
777 $json = new stdClass();
778 $json->$socialnetwork = $newval;
779 $listvalues[$socialkey] = json_encode($json);
780 } else {
781 $jsondata = $listvalues[$socialkey];
782 $json = json_decode($jsondata);
783 $json->$socialnetwork = $newval;
784 $listvalues[$socialkey] = json_encode($json);
785 }
786 }
787 } else {
788 $listfields[] = $fieldname;
789 // Note: arrayrecord (and 'type') is filled with ->import_read_record called by import.php page before calling import_insert
790 if (empty($newval) && $arrayrecord[($key - 1)]['type'] < 0) {
791 $listvalues[] = ($newval == '0' ? (int) $newval : "null");
792 } elseif (empty($newval) && $arrayrecord[($key - 1)]['type'] == 0) {
793 $listvalues[] = "''";
794 } else {
795 $listvalues[] = "'".$this->db->escape($newval)."'";
796 }
797 }
798 }
799 $i++;
800 }
801
802 // We add hidden fields (but only if there is at least one field to add into table)
803 // We process here all the fields that were declared into the array $this->import_fieldshidden_array of the descriptor file.
804 // Previously we processed the ->import_fields_array.
805 if (!empty($listfields) && is_array($objimport->array_import_fieldshidden[0])) {
806 // Loop on each hidden fields to add them into listfields/listvalues
807 foreach ($objimport->array_import_fieldshidden[0] as $tmpkey => $tmpval) {
808 if (!preg_match('/^' . preg_quote($alias, '/') . '\./', $tmpkey)) {
809 continue; // Not a field of current table
810 }
811 $keyfield = preg_replace('/^' . preg_quote($alias, '/') . '\./', '', $tmpkey);
812
813 if (in_array($keyfield, $listfields)) { // avoid duplicates in insert
814 continue;
815 } elseif ($tmpval == 'user->id') {
816 $listfields[] = $keyfield;
817 $listvalues[] = ((int) $user->id);
818 } elseif (preg_match('/^lastrowid-/', $tmpval)) {
819 $tmp = explode('-', $tmpval);
820 $lastinsertid = (isset($last_insert_id_array[$tmp[1]])) ? $last_insert_id_array[$tmp[1]] : 0;
821 $listfields[] = $keyfield;
822 $listvalues[] = (int) $lastinsertid;
823 //print $tmpkey."-".$tmpval."-".$listfields."-".$listvalues."<br>";exit;
824 } elseif (preg_match('/^const-/', $tmpval)) {
825 $tmp = explode('-', $tmpval, 2);
826 $listfields[] = $keyfield;
827 $listvalues[] = "'".$this->db->escape($tmp[1])."'";
828 } elseif (preg_match('/^rule-/', $tmpval)) {
829 $fieldname = $tmpkey;
830 if (!empty($objimport->array_import_convertvalue[0][$fieldname])) {
831 if ($objimport->array_import_convertvalue[0][$fieldname]['rule'] == 'compute') {
832 $file = (empty($objimport->array_import_convertvalue[0][$fieldname]['classfile']) ? $objimport->array_import_convertvalue[0][$fieldname]['file'] : $objimport->array_import_convertvalue[0][$fieldname]['classfile']);
833 $class = $objimport->array_import_convertvalue[0][$fieldname]['class'];
834 $method = $objimport->array_import_convertvalue[0][$fieldname]['method'];
835 $type = $objimport->array_import_convertvalue[0][$fieldname]['type'];
836 $resultload = dol_include_once($file);
837 if (empty($resultload)) {
838 dol_print_error(null, 'Error trying to call file=' . $file . ', class=' . $class . ', method=' . $method);
839 break;
840 }
841 $classinstance = new $class($this->db);
842 $res = call_user_func_array(array($classinstance, $method), array(&$arrayrecord, $arrayfield, ($key - 1)));
843 if (empty($classinstance->error) && empty($classinstance->errors)) {
844 $fieldArr = explode('.', $fieldname);
845 if (count($fieldArr) > 0) {
846 $fieldname = $fieldArr[1];
847 }
848
849 // Set $listfields and $listvalues
850 $listfields[] = $fieldname;
851 if ($type == 'int') {
852 $listvalues[] = (int) $res;
853 } elseif ($type == 'double') {
854 $listvalues[] = (float) $res;
855 } else {
856 $listvalues[] = "'".$this->db->escape($res)."'";
857 }
858 } else {
859 $this->errors[$error]['type'] = 'CLASSERROR';
860 $this->errors[$error]['lib'] = implode(
861 "\n",
862 array_merge([$classinstance->error], $classinstance->errors)
863 );
864 $errorforthistable++;
865 $error++;
866 }
867 }
868 }
869 } else {
870 $this->errors[$error]['lib'] = 'Bad value of profile setup '.$tmpval.' for array_import_fieldshidden';
871 $this->errors[$error]['type'] = 'Import profile setup';
872 $error++;
873 }
874 }
875 }
876 //print 'listfields='.$listfields.'<br>listvalues='.$listvalues.'<br>';
877
878 // If no error for this $alias/$tablename, we have a complete $listfields and $listvalues that are defined
879 // so we can try to make the insert or update now.
880 if (!$errorforthistable) {
881 //print "$alias/$tablename/$listfields/$listvalues<br>";
882 if (!empty($listfields)) {
883 $updatedone = false;
884 $insertdone = false;
885
886 $is_table_category_link = false;
887 $fname = 'rowid';
888 if (strpos($tablename, '_categorie_') !== false) {
889 $is_table_category_link = true;
890 $fname = '*';
891 }
892
893 if (!empty($updatekeys)) {
894 // We do SELECT to get the rowid, if we already have the rowid, it's to be used below for related tables (extrafields)
895
896 if (empty($lastinsertid)) { // No insert done yet for a parent table
897 $sqlSelect = "SELECT ".$fname." FROM ".$tablename;
898 $data = array_combine($listfields, $listvalues);
899 $where = array(); // filters to forge SQL request
900 $filters = array(); // filters to forge output error message
901 foreach ($updatekeys as $key) {
902 $col = $objimport->array_import_updatekeys[0][$key];
903 $key = preg_replace('/^.*\./i', '', $key);
904 if (isModEnabled("socialnetworks") && strpos($key, "socialnetworks") !== false) {
905 $tmp = explode("_", $key);
906 $key = $tmp[0];
907 $socialnetwork = $tmp[1];
908 $jsondata = $data[$key];
909 $json = json_decode($jsondata);
910 $stringtosearch = json_encode($socialnetwork).':'.json_encode($json->$socialnetwork);
911 //var_dump($stringtosearch);
912 //var_dump($this->db->escape($stringtosearch)); // This provide a value for sql string (but not for a like)
913 $where[] = $key." LIKE '%".$this->db->escape($this->db->escapeforlike($stringtosearch))."%'";
914 $filters[] = $col." LIKE '%".$this->db->escape($this->db->escapeforlike($stringtosearch))."%'";
915 //var_dump($where[1]); // This provide a value for sql string inside a like
916 } else {
917 $where[] = $key.' = '.$data[$key];
918 $filters[] = $col.' = '.$data[$key];
919 }
920 }
921 if (!empty($tablewithentity_cache[$tablename])) {
922 $where[] = "entity IN (".getEntity($this->getElementFromTableWithPrefix($tablename)).")";
923 $filters[] = "entity IN (".getEntity($this->getElementFromTableWithPrefix($tablename)).")";
924 }
925 $sqlSelect .= " WHERE ".implode(' AND ', $where);
926
927 $resql = $this->db->query($sqlSelect);
928 if ($resql) {
929 $num_rows = $this->db->num_rows($resql);
930 if ($num_rows == 1) {
931 $res = $this->db->fetch_object($resql);
932 $lastinsertid = $res->rowid;
933 if ($is_table_category_link) {
934 $lastinsertid = 'linktable';
935 } // used to apply update on tables like llx_categorie_product and avoid being blocked for all file content if at least one entry already exists
936 $last_insert_id_array[$tablename] = $lastinsertid;
937 } elseif ($num_rows > 1) {
938 $this->errors[$error]['lib'] = $langs->trans('MultipleRecordFoundWithTheseFilters', implode(', ', $filters));
939 $this->errors[$error]['type'] = 'SQL';
940 $error++;
941 } else {
942 // No record found with filters, insert will be tried below
943 }
944 } else {
945 //print 'E';
946 $this->errors[$error]['lib'] = $this->db->lasterror();
947 $this->errors[$error]['type'] = 'SQL';
948 $error++;
949 }
950 } else {
951 // We have a last INSERT ID (got by previous pass), so we check if we have a row referencing this foreign key.
952 // This is required when updating table with some extrafields. When inserting a record in parent table, we can make
953 // a direct insert into subtable extrafields, but when me wake an update, the insertid is defined and the child record
954 // may already exists. So we rescan the extrafield table to know if record exists or not for the rowid.
955 // Note: For extrafield tablename, we have in importfieldshidden_array an entry 'extra.fk_object'=>'lastrowid-tableparent' so $keyfield is 'fk_object'
956 $sqlSelect = "SELECT rowid FROM ".$tablename;
957
958 if (empty($keyfield)) {
959 $keyfield = 'rowid';
960 }
961 $sqlSelect .= " WHERE ".$keyfield." = ".((int) $lastinsertid);
962
963 if (!empty($tablewithentity_cache[$tablename])) {
964 $sqlSelect .= " AND entity IN (".getEntity($this->getElementFromTableWithPrefix($tablename)).")";
965 }
966
967 $resql = $this->db->query($sqlSelect);
968 if ($resql) {
969 $res = $this->db->fetch_object($resql);
970 if ($this->db->num_rows($resql) == 1) {
971 // We have a row referencing this last foreign key, continue with UPDATE.
972 } else {
973 // No record found referencing this last foreign key,
974 // force $lastinsertid to 0 so we INSERT below.
975 $lastinsertid = 0;
976 }
977 } else {
978 //print 'E';
979 $this->errors[$error]['lib'] = $this->db->lasterror();
980 $this->errors[$error]['type'] = 'SQL';
981 $error++;
982 }
983 }
984
985 if (!empty($lastinsertid)) {
986 // We db escape social network field because he isn't in field creation
987 if (in_array("socialnetworks", $listfields)) {
988 $socialkey = array_search("socialnetworks", $listfields);
989 $tmpsql = $listvalues[$socialkey];
990 $listvalues[$socialkey] = "'".$this->db->escape($tmpsql)."'";
991 }
992
993 // Build SQL UPDATE request
994 $sqlstart = "UPDATE ".$tablename;
995
996 $data = array_combine($listfields, $listvalues);
997 $set = array();
998 foreach ($data as $key => $val) {
999 $set[] = $key." = ".$val; // $val was escaped/sanitized previously
1000 }
1001 $sqlstart .= " SET ".implode(', ', $set).", import_key = '".$this->db->escape($importid)."'";
1002
1003 if (empty($keyfield)) {
1004 $keyfield = 'rowid';
1005 }
1006 $sqlend = " WHERE ".$keyfield." = ".((int) $lastinsertid);
1007
1008 if ($is_table_category_link) {
1009 '@phan-var-force string[] $where';
1010 $sqlend = " WHERE " . implode(' AND ', $where);
1011 }
1012
1013 if (!empty($tablewithentity_cache[$tablename])) {
1014 $sqlend .= " AND entity IN (".getEntity($this->getElementFromTableWithPrefix($tablename)).")";
1015 }
1016
1017 $sql = $sqlstart.$sqlend;
1018
1019 // Run update request
1020 $resql = $this->db->query($sql);
1021 if ($resql) {
1022 // No error, update has been done. $this->db->db->affected_rows can be 0 if data hasn't changed
1023 $updatedone = true;
1024 } else {
1025 //print 'E';
1026 $this->errors[$error]['lib'] = $this->db->lasterror();
1027 $this->errors[$error]['type'] = 'SQL';
1028 $error++;
1029 }
1030 }
1031 }
1032
1033 // Update not done, we do insert
1034 if (!$error && !$updatedone) {
1035 // We db escape social network field because he isn't in field creation
1036 if (in_array("socialnetworks", $listfields)) {
1037 $socialkey = array_search("socialnetworks", $listfields);
1038 $tmpsql = $listvalues[$socialkey];
1039 $listvalues[$socialkey] = "'".$this->db->escape($tmpsql)."'";
1040 }
1041
1042 // Build SQL INSERT request
1043 $sqlstart = "INSERT INTO ".$tablename."(".implode(", ", $listfields).", import_key";
1044 $sqlend = ") VALUES(".implode(', ', $listvalues).", '".$this->db->escape($importid)."'";
1045 if (!empty($tablewithentity_cache[$tablename])) {
1046 $sqlstart .= ", entity";
1047 $sqlend .= ", ".$conf->entity;
1048 }
1049 if (!empty($objimport->array_import_tables_creator[0][$alias])) {
1050 $sqlstart .= ", ".$objimport->array_import_tables_creator[0][$alias];
1051 $sqlend .= ", ".$user->id;
1052 }
1053 $sql = $sqlstart.$sqlend.")";
1054 //dol_syslog("import_csv.modules", LOG_DEBUG);
1055
1056 // Run insert request
1057 if ($sql) {
1058 $resql = $this->db->query($sql);
1059 if ($resql) {
1060 if (!$is_table_category_link) {
1061 $last_insert_id_array[$tablename] = $this->db->last_insert_id($tablename); // store the last inserted auto_increment id for each table, so that child tables can be inserted with the appropriate id. This must be done just after the INSERT request, else we risk losing the id (because another sql query will be issued somewhere in Dolibarr).
1062 }
1063 $insertdone = true;
1064 } else {
1065 //print 'E';
1066 $this->errors[$error]['lib'] = $this->db->lasterror();
1067 $this->errors[$error]['type'] = 'SQL';
1068 $error++;
1069 }
1070 }
1071 }
1072 }
1073 /*else
1074 {
1075 dol_print_error(null,'ErrorFieldListEmptyFor '.$alias."/".$tablename);
1076 }*/
1077 }
1078
1079 if ($error) {
1080 break;
1081 }
1082 }
1083
1084 if ($updatedone) {
1085 $this->nbupdate++;
1086 }
1087 if ($insertdone) {
1088 $this->nbinsert++;
1089 }
1090 }
1091
1092 return 1;
1093 }
1094}
1095
1102function cleansep($value)
1103{
1104 return str_replace(array(',', ';'), '/', $value);
1105}
versionphparray()
Return version PHP.
versioncompare($versionarray1, $versionarray2)
Compare 2 versions (stored into 2 arrays).
Definition admin.lib.php:69
Class to import CSV files.
write_header_example($outputlangs)
Output header of an example file for this format.
import_get_nb_of_lines($file)
Return nb of records.
import_insert($arrayrecord, $array_match_file_to_database, $objimport, $maxfields, $importid, $updatekeys)
Insert a record into database.
__construct($db, $datatoimport)
Constructor.
import_read_record()
Return array of next record in input file.
write_record_example($outputlangs, $contentlinevalues)
Output record of an example file for this format.
write_footer_example($outputlangs)
Output footer of an example file for this format.
import_close_file()
Close file handle.
import_read_header()
Input header line from file.
write_title_example($outputlangs, $headerlinefields)
Output title line of an example file for this format.
import_open_file($file)
Open input file.
Parent class for import file readers.
getElementFromTableWithPrefix($tableNameWithPrefix)
Get element from table name with prefix.
Class to manage third parties objects (customers, suppliers, prospects...)
dol_stringtotime($string, $gm=1)
Convert a string date into a GM Timestamps date Warning: YYYY-MM-DDTHH:MM:SS+02:00 (RFC3339) is not s...
Definition date.lib.php:426
dol_count_nb_of_line($file)
Count number of lines in a file.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
num2Alpha($n)
Return a numeric value into an Excel like column number.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
utf8_check($str)
Check if a string is in UTF8.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
cleansep($value)
Clean a string from separator.
testSqlAndScriptInject($val, $type)
Security: WAF layer for SQL Injection and XSS Injection (scripts) protection (Filters on GET,...
Definition main.inc.php:123