ldc-boutique-cnc/app/Jobs/XmlIncoReader.php
2023-02-24 11:44:32 +01:00

578 lines
20 KiB
PHP

<?php
namespace App\Jobs;
use App\Exceptions\AbortXmlImportException;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\XmlInco;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
class XmlIncoReader extends XmlJobs
{
/**
* The Laravel disk name
*
* @var string
*/
protected $disk = 'inco';
/**
* The columns to take from the XML
* subcolumns can be added with a dot
*
* @var array
*
* éléments à la racine : nom du noeud
* sous éléments d'un noeud PARENT :
* d'abord:
* 'PARENT' => 'parent', // Consider this sub array
* puis:
* "PARENT.LEAF" => 'leaf', // extract this data in the line
* Note : Xmljobs iterates the PARENT with RecursiveIteratorIterator::LEAVES_ONLY
* intermediate nodes are therefore ignored
* example : nodes : <EXPLOITANT><SOCIETE><ID_SOCIETE /><NOM_SOCIETE /></SOCIETE></EXPLOITANT>
* how to extract leaves :
* 'EXPLOITANT' => 'exploitant', // Consider this sub array
* 'EXPLOITANT.ID_SOCIETE' => 'id_societe',
* 'EXPLOITANT.NOM_SOCIETE' => 'nom_societe',
*/
protected $takeColumns = [
'NO_ART' => 'id',
// Primary key on model.
// This wil not return anything cf doc above.
'ID_SOCIETE' => 'id_societe',
'NOM_SOCIETE' => 'nom_societe',
'PROD_SAISON' => 'prod_saison',
'NOM' => 'nom',
'NUMERO' => 'numero',
'TAILLE_REF' => 'taille_ref',
// $poidsRef censé être dans INCO_COMPO (et généralement 100g)
'ALLEGATIONS' => 'allegations',
'CONSEIL_PREPA' => 'conseil_prepa',
'ASTUCES' => 'astuces',
'DESCRIPTIF' => 'descriptif',
// $descriptif
'ARG_VENTE' => 'arg_vente',
'NB_PORTION' => 'nb_portion',
'TYP_PORTION' => 'typ_portion',
'PDS_MOY_THEO' => 'pds_moy_theo',
'LIB_CONSO' => 'lib_conso',
'LST_ING' => 'lst_ing',
// $ingredients
// List alergene.
'LST_ALLERGENE' => 'lst_allergene',
// Consider this sub array.
'LST_ALLERGENE.LIB_ALLERGENE' => 'lib_allergene',
// Allegations.
'ALLEGAT_SANTE' => 'allegat_sante',
'ALLEGAT_NUTRIT' => 'allegat_nutrit',
'ALLEGAT_AUTRE' => 'allegat_autre',
'INCO_COMPO' => 'inco_compo',
// Consider this sub array.
'INCO_COMPO.NOM' => 'nom',
'INCO_COMPO.TAILLE_REF' => 'taille_ref',
'INCO_COMPO.VAL_ENERG_KJ_NUT' => 'val_energ_kj_nut',
'INCO_COMPO.VAL_ENERG_KJ_R' => 'val_energ_kj_r',
// _R apports de la taille_ref
'INCO_COMPO.VAL_ENERG_KCAL_R' => 'val_energ_kcal_r',
'INCO_COMPO.VAL_ENERG_KCAL_NUT' => 'val_energ_kcal_nut',
'INCO_COMPO.MAT_GRASS_R' => 'mat_grass_r',
'INCO_COMPO.MAT_GRASS_NUT' => 'mat_grass_nut',
'INCO_COMPO.AGS_R' => 'ags_r',
'INCO_COMPO.AGS_NUT' => 'ags_nut',
'INCO_COMPO.GLUCIDES_R' => 'glucides_r',
'INCO_COMPO.GLUCIDES_NUT' => 'glucides_nut',
'INCO_COMPO.SUCRE_NUT' => 'sucre_nut',
'INCO_COMPO.SUCRE_R' => 'sucre_r',
'INCO_COMPO.FIB_ALIM_NUT' => 'fib_alim_nut',
'INCO_COMPO.FIB_ALIM_R' => 'fib_alim_r',
'INCO_COMPO.PROTEINE_R' => 'proteine_r',
'INCO_COMPO.PROTEINE_NUT' => 'proteine_nut',
'INCO_COMPO.SEL_R' => 'sel_r',
'INCO_COMPO.SEL_NUT' => 'sel_nut',
'INCO_COMPO.SODIUM_R' => 'sodium_r',
'INCO_COMPO.ENER_E14_R' => 'ener_e14_r',
'INCO_COMPO.FRUITS_R' => 'fruits_r',
'INCO_COMPO.FRUITS_COQUE_SECH_R' => 'fruits_coque_sech_r',
'INCO_COMPO.POURCENT_APPORT_REF_E14_R' => 'pourcent_apport_ref_e14_r',
// POURCENT_*_P apports journaliers par portion.
'INCO_COMPO.ENER_KJO_R' => 'ener_kjo_r',
'INCO_COMPO.POURCENT_APPORT_REF_KJO_R' => 'pourcent_apport_ref_kjo_r',
'INCO_COMPO.POURCENT_APPORT_REF_FAT_R' => 'pourcent_apport_ref_fat_r',
'INCO_COMPO.FATSAT_R' => 'fatsat_r',
'INCO_COMPO.POURCENT_APPORT_REF_FATSAT_R' => 'pourcent_apport_ref_fatsat_r',
'INCO_COMPO.CHOVAL_R' => 'choval_r',
'INCO_COMPO.POURCENT_APPORT_REF_CHOVAL_R' => 'pourcent_apport_ref_choval_r',
'INCO_COMPO.SUGAR_R' => 'sugar_r',
'INCO_COMPO.POURCENT_APPORT_REF_SUGAR_R' => 'pourcent_apport_ref_sugar_r',
'INCO_COMPO.FIBTG_R' => 'fibtg_r',
'INCO_COMPO.POURCENT_APPORT_REF_FIBTG_R' => 'pourcent_apport_ref_fibtg_r',
'INCO_COMPO.PRO_R' => 'pro_r',
'INCO_COMPO.POURCENT_APPORT_REF_PRO_R' => 'pourcent_apport_ref_pro_r',
'INCO_COMPO.SALTEQ_R' => 'salteq_r',
'INCO_COMPO.POURCENT_APPORT_REF_SALTEQ_R' => 'pourcent_apport_ref_salteq_r',
'INCO_COMPO.APPORT_REF' => 'apport_ref',
'INCO_COMPO.TAILLE_PORT' => 'taille_port',
// $poidsPortion
'INCO_COMPO.VAL_ENERG_KJ_P' => 'val_energ_kj_p',
// _P apports par portion
'INCO_COMPO.VAL_ENERG_KCAL_P' => 'val_energ_kcal_p',
'INCO_COMPO.MAT_GRASS_P' => 'mat_grass_p',
'INCO_COMPO.AGS_P' => 'ags_p',
'INCO_COMPO.GLUCIDES_P' => 'glucides_p',
'INCO_COMPO.SUCRE_P' => 'sucre_p',
'INCO_COMPO.FIB_ALIM_P' => 'fib_alim_p',
'INCO_COMPO.PROTEINE_P' => 'proteine_p',
'INCO_COMPO.SODIUM_P' => 'sodium_p',
'INCO_COMPO.FRUITS_P' => 'fruits_p',
'INCO_COMPO.SEL_P' => 'sel_p',
'INCO_COMPO.FRUITS_COQUE_SECH_P' => 'fruits_coque_sech_p',
'INCO_COMPO.ENER_E14_P' => 'ener_e14_p',
'INCO_COMPO.POURCENT_APPORT_REF_E14_P' => 'pourcent_apport_ref_e14_p',
'INCO_COMPO.ENER_KJO_P' => 'ener_kjo_p',
'INCO_COMPO.POURCENT_APPORT_REF_KJO_P' => 'pourcent_apport_ref_kjo_p',
'INCO_COMPO.FAT_P' => 'fat_p',
'INCO_COMPO.POURCENT_APPORT_REF_FAT_P' => 'pourcent_apport_ref_fat_p',
'INCO_COMPO.FATSAT_P' => 'fatsat_p',
'INCO_COMPO.POURCENT_APPORT_REF_FATSAT_P' => 'pourcent_apport_ref_fatsat_p',
'INCO_COMPO.CHOVAL_P' => 'CHOVAL_P',
'INCO_COMPO.POURCENT_APPORT_REF_CHOVAL_P' => 'pourcent_apport_ref_choval_p',
'INCO_COMPO.SUGAR_P' => 'SUGAR_P',
'INCO_COMPO.POURCENT_APPORT_REF_SUGAR_P' => 'pourcent_apport_ref_sugar_p',
'INCO_COMPO.FIBTG_P' => 'fibtg_p',
'INCO_COMPO.POURCENT_APPORT_REF_FIBTG_P' => 'pourcent_apport_ref_fibtg_p',
'INCO_COMPO.PRO_P' => 'pro_p',
'INCO_COMPO.POURCENT_APPORT_REF_PRO_P' => 'pourcent_apport_ref_pro_p',
'INCO_COMPO.SALTEQ_P' => 'salteq_p',
'INCO_COMPO.POURCENT_APPORT_REF_SALTEQ_P' => 'pourcent_apport_ref_salteq_p',
'INCO_COMPO.APPORT_PORT' => 'apport_port',
'INCO_COMPO.PART_P' => 'part_p',
'INCO_COMPO.PART_G' => 'part_g',
'INCO_COMPO.PART_L' => 'part_l',
'INCO_COMPO.P_SUR_L' => 'p_sur_l',
'INCO_COMPO.POURCENT_AGS' => 'pourcent_ags',
'INCO_COMPO.POURCENT_AGI' => 'pourcent_agi',
'INCO_COMPO.POURCENT_SS' => 'pourcent_ss',
'INCO_COMPO.POURCENT_AMIDON' => 'pourcent_amidon',
'INCO_COMPO.COMPOSANTE_N' => 'composante_n',
'INCO_COMPO.COMPOSANTE_P' => 'composante_p',
'INCO_COMPO.POURCENT_FRUITS' => 'pourcent_fruits',
'INCO_COMPO.SCORE_NUTRI' => 'score_nutri',
// Du coup nutriscore ?.
'INCO_COMPO.CLASSEMENT' => 'classement',
// $nutriscore mais pas censé être dans INCO_COMPO
'INCO_COMPO.MAT_GRASS_R_PREC' => 'mat_grass_r_prec',
'INCO_COMPO.AGS_R_PREC' => 'ags_r_prec',
'INCO_COMPO.GLUCIDES_R_PREC' => 'glucides_r_prec',
'INCO_COMPO.SUCRE_R_PREC' => 'sucre_r_prec',
'INCO_COMPO.FIB_ALIM_R_PREC' => 'fib_alim_r_prec',
'INCO_COMPO.PROTEINE_R_PREC' => 'proteine_r_prec',
'INCO_COMPO.SEL_R_PREC' => 'sel_r_prec',
'INCO_COMPO.SODIUM_R_PREC' => 'sodium_r_prec',
'INCO_COMPO.FRUITS_R_PREC' => 'fruits_r_prec',
'INCO_COMPO.FRUITS_COQUE_SECH_R_PREC' => 'fruits_coque_sech_r_prec',
];
/**
* The columns allowed to be null
*
* @var array
*/
protected $nullableFields = [
'taille_ref',
'conseil_prepa',
'typ_portion',
'nb_portion',
'descriptif',
'prod_saison',
'allegations',
'astuces', 'arg_vente',
'pds_moy_theo',
'lib_conso', 'lst_ing', 'lst_allergene',
'allegat_sante', 'allegat_nutrit', 'allegat_autre',
'inco_compo',
'inco_compo.nom',
'inco_compo.numero',
'inco_compo.taille',
'inco_compo.taille_ref',
'inco_compo.val_energ_kj_nut',
'inco_compo.val_energ_kj_r',
'inco_compo.val_energ_kcal_r',
'inco_compo.val_energ_kcal_nut',
'inco_compo.mat_grass_r',
'inco_compo.mat_grass_nut',
'inco_compo.ags_r',
'inco_compo.ags_nut',
'inco_compo.glucides_r',
'inco_compo.glucides_nut',
'inco_compo.sucre_nut',
'inco_compo.sucre_r',
'inco_compo.fib_alim_nut',
'inco_compo.fib_alim_r',
'inco_compo.proteine_r',
'inco_compo.proteine_nut',
'inco_compo.sel_r',
'inco_compo.sel_nut',
'inco_compo.sodium_r',
'inco_compo.ener_e14_r',
'inco_compo.fruits_r',
'inco_compo.fruits_coque_sech_r',
'inco_compo.pourcent_apport_ref_e14_r',
'inco_compo.ener_kjo_r',
'inco_compo.pourcent_apport_ref_kjo_r',
'inco_compo.pourcent_apport_ref_fat_r',
'inco_compo.fatsat_r',
'inco_compo.pourcent_apport_ref_fatsat_r',
'inco_compo.choval_r',
'inco_compo.pourcent_apport_ref_choval_r',
'inco_compo.sugar_r',
'inco_compo.pourcent_apport_ref_sugar_r',
'inco_compo.fibtg_r',
'inco_compo.pourcent_apport_ref_fibtg_r',
'inco_compo.pro_r',
'inco_compo.pourcent_apport_ref_pro_r',
'inco_compo.salteq_r',
'inco_compo.pourcent_apport_ref_salteq_r',
'inco_compo.apport_ref',
'inco_compo.taille_port',
'inco_compo.val_energ_kj_p',
'inco_compo.val_energ_kcal_p',
'inco_compo.mat_grass_p',
'inco_compo.ags_p',
'inco_compo.glucides_p',
'inco_compo.sucre_p',
'inco_compo.fib_alim_p',
'inco_compo.proteine_p',
'inco_compo.sodium_p',
'inco_compo.fruits_p',
'inco_compo.sel_p',
'inco_compo.fruits_coque_sech_p',
'inco_compo.ener_e14_p',
'inco_compo.pourcent_apport_ref_e14_p',
'inco_compo.ener_kjo_p',
'inco_compo.pourcent_apport_ref_kjo_p',
'inco_compo.fat_p',
'inco_compo.pourcent_apport_ref_fat_p',
'inco_compo.fatsat_p',
'inco_compo.pourcent_apport_ref_fatsat_p',
'inco_compo.choval_p',
'inco_compo.pourcent_apport_ref_choval_p',
'inco_compo.sugar_p',
'inco_compo.pourcent_apport_ref_sugar_p',
'inco_compo.fibtg_p',
'inco_compo.pourcent_apport_ref_fibtg_p',
'inco_compo.pro_p',
'inco_compo.pourcent_apport_ref_pro_p',
'inco_compo.salteq_p',
'inco_compo.pourcent_apport_ref_salteq_p',
'inco_compo.apport_port',
'inco_compo.part_p',
'inco_compo.part_g',
'inco_compo.part_l',
'inco_compo.p_sur_l',
'inco_compo.pourcent_ags',
'inco_compo.pourcent_agi',
'inco_compo.pourcent_ss',
'inco_compo.pourcent_amidon',
'inco_compo.composante_n',
'inco_compo.composante_p',
'inco_compo.pourcent_fruits',
'inco_compo.score_nutri',
'inco_compo.classement',
'inco_compo.mat_grass_r_prec',
'inco_compo.ags_r_prec',
'inco_compo.glucides_r_prec',
'inco_compo.sucre_r_prec',
'inco_compo.fib_alim_r_prec',
'inco_compo.proteine_r_prec',
'inco_compo.sel_r_prec',
'inco_compo.sodium_r_prec',
'inco_compo.fruits_r_prec',
'inco_compo.fruits_coque_sech_r_prec'
];
/**
* Unique keys for updating rows
*
* @var array
*/
protected $uniqueKeyField = ['id'];
/**
* Columns to merge in
*
* @var array
*/
protected $mergeColumns = [
'allegations' => [
'allegat_sante',
'allegat_nutrit',
'allegat_autre'
],
'nutritional_values' => [
'taille_ref',
'val_energ_kj_nut',
'val_energ_kj_r',
'val_energ_kcal_r',
'val_energ_kcal_nut',
'mat_grass_r',
'mat_grass_nut',
'ags_r',
'ags_nut',
'glucides_r',
'glucides_nut',
'sucre_nut',
'sucre_r',
'fib_alim_nut',
'fib_alim_r',
'proteine_r',
'proteine_nut',
'sel_r',
'sel_nut',
'sodium_r',
'ener_e14_r',
'fruits_r',
'fruits_coque_sech_r',
'pourcent_apport_ref_e14_r',
'ener_kjo_r',
'pourcent_apport_ref_kjo_r',
'pourcent_apport_ref_fat_r',
'fatsat_r',
'pourcent_apport_ref_fatsat_r',
'choval_r',
'pourcent_apport_ref_choval_r',
'sugar_r',
'pourcent_apport_ref_sugar_r',
'fibtg_r',
'pourcent_apport_ref_fibtg_r',
'pro_r',
'pourcent_apport_ref_pro_r',
'salteq_r',
'pourcent_apport_ref_salteq_r',
'apport_ref',
'taille_port',
'val_energ_kj_p',
'val_energ_kcal_p',
'mat_grass_p',
'ags_p',
'glucides_p',
'sucre_p',
'fib_alim_p',
'proteine_p',
'sodium_p',
'fruits_p',
'sel_p',
'fruits_coque_sech_p',
'ener_e14_p',
'pourcent_apport_ref_e14_p',
'ener_kjo_p',
'pourcent_apport_ref_kjo_p',
'fat_p',
'pourcent_apport_ref_fat_p',
'fatsat_p',
'pourcent_apport_ref_fatsat_p',
'choval_p',
'pourcent_apport_ref_choval_p',
'sugar_p',
'pourcent_apport_ref_sugar_p',
'fibtg_p',
'pourcent_apport_ref_fibtg_p',
'pro_p',
'pourcent_apport_ref_pro_p',
'salteq_p',
'pourcent_apport_ref_salteq_p',
'apport_port',
'part_p',
'part_g',
'part_l',
'p_sur_l',
'pourcent_ags',
'pourcent_agi',
'pourcent_ss',
'pourcent_amidon',
'composante_n',
'composante_p',
'pourcent_fruits',
'score_nutri',
'classement',
'mat_grass_r_prec',
'ags_r_prec',
'glucides_r_prec',
'sucre_r_prec',
'fib_alim_r_prec',
'proteine_r_prec',
'sel_r_prec',
'sodium_r_prec',
'fruits_r_prec',
'fruits_coque_sech_r_prec',
]
];
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->parseData('INCO', XmlInco::class, function (\SimpleXMLElement $xmlData, array &$line) {
// Merge columns.
foreach ($this->mergeColumns as $key => $values) {
$line[$key] = [];
// Merge columns and remove merged columns from line.
array_map(function ($kValue) use (&$line, $key) {
if (array_key_exists($kValue, $line)) {
$line[$key][$kValue] = $line[$kValue];
unset($line[$kValue]);
}
}, $values);
if ($key == 'allegations') {
$line[$key] = implode(',', array_filter($line[$key]));
$line[$key] = strlen($line[$key]) ? $line[$key] : null;
}
}
// Boolean cast prod_saison.
$line['prod_saison'] = isset($line['prod_saison']) ?
(strtolower($line['prod_saison']) == 'non' ? false : true) : false;
$arImgPaths = $this->parseImage($xmlData);
$line['pictures'] = $arImgPaths;
});
}
/**
* Extract Image form XML file
*
* @param \SimpleXMLElement $xmlData
* @return array
* @throws AbortXmlImportException When media tag does not contain an image.
*/
private function parseImage(\SimpleXMLElement $xmlData)
{
/** @var \Illuminate\Support\Collection<\SimpleXMLElement> */
$list_media = collect($xmlData->INCO->MEDIA_VISUEL_EAI->MEDIA_VISUEL_EAI);
$arImgPaths = \collect();
$i = -1;
$list_media->each(function ($media) use ($xmlData, $arImgPaths, &$i) {
$i++;
/** @var \SimpleXMLElement|null */
$image = $media->img;
$imageData = trim((string)$image);
if (!strlen($imageData)) {
return;
}
$imageData = \strval(base64_decode($imageData));
$imgName = strval($xmlData->INCO->INCO_COMPO->TBL_INCO_COMPO->ID_INCO ?? $xmlData->INCO->NO_ART);
$imgNamePath = "{$imgName}_" . \uniqid();
$info = getimagesizefromstring($imageData);
if ($info === false) {
Log::debug("Skipping file {$imgNamePath} on index {$i} as could not determine picture info.");
return;
}
$mime = $info['mime'];
switch ($mime) {
case 'image/jpeg':
$imgExtension = 'jpg';
break;
case 'image/png':
$imgExtension = 'png';
break;
case 'image/gif':
$imgExtension = 'gif';
break;
default:
throw new AbortXmlImportException(sprintf('Unknown image type %s', $imgName));
}
$imgNamePath = FileStorageHelper::sanitizeFileName("{$imgNamePath}-{$i}.{$imgExtension}");
if (!($file = tempnam(sys_get_temp_dir(), $imgNamePath))) {
throw new \RuntimeException('Could not initiate a php temp file');
}
if (!File::put($file, $imageData)) {
throw new \RuntimeException('Could not write to the php temp file');
}
$arImgPaths->push(FileStorageHelper::storeFile(
new XmlInco(),
new \SplFileInfo($file),
true,
$imgNamePath,
true
));
});
return $arImgPaths->all();
}
/**
* Recursive extract to array
* introduce sort of Hook to transform
* <TYP_PORTION>
* <code><![CDATA[2]]></code>
* <libelle><![CDATA[Approximatif]]></libelle>
* </TYP_PORTION>
* extracted as "TYP_PORTION" => array:1 [
* 0 => array:2 [
* "code" => "2"
* "libelle" => "Approximatif"
* ]
* ]
* into this
* <TYP_PORTION><![CDATA[Approximatif]]></TYP_PORTION>
* extracted as "TYP_PORTION" => "Approximatif"
*
*
* @param \SimpleXMLElement|false $sxe
* @return array
*/
protected function extract($sxe = null): array
{
if (!$sxe instanceof \SimpleXMLElement) {
return array();
}
$extract = array();
foreach ($sxe->children() as $key => $value) {
if ($value->count()) {
$extraction = $this->extract($value);
if (
count($extraction)
&& array_key_exists("code", $extraction)
&& array_key_exists("libelle", $extraction)
) {
$val = strval($extraction['libelle']);
$extract[$key] = strlen($val) ? $val : null;
continue;
}
$extract[$key][] = $extraction;
continue;
}
$value = strval($value);
if (array_key_exists($key, $extract) and is_array($extract[$key])) {
$extract[$key][] = strlen($value) ? $value : null;
} else {
$extract[$key] = strlen($value) ? $value : null;
}
}//end foreach
return $extract;
}
}