2024-05-13 14:43:33 +02:00
< ? php
/*
* SPDX - FileCopyrightText : 2022 Andreas Palm
2024-05-24 10:10:59 +02:00
* SPDX - FileCopyrightText : 2024 OpenXE project
2024-05-13 14:43:33 +02:00
*
* SPDX - License - Identifier : LicenseRef - EGPL - 3.1
*/
2024-05-22 11:25:35 +02:00
class Shopimporter_Mirakl extends ShopimporterBase {
private $app ;
private $intern ;
private $shopid ;
private $protocol ;
private $apiKey ;
private $shopUrl ;
2024-05-24 10:53:49 +02:00
private $mirakl_shopid ;
2024-05-22 11:25:35 +02:00
private $createManufacturerAllowed = false ;
private $idsabholen ;
private $idbearbeitung ;
private $idabgeschlossen ;
public $data ;
// TODO
private $langidToIso = [ 3 => 'de' , 1 => 'en' ];
private $taxationByDestinationCountry ;
private $orderSearchLimit ;
2024-06-13 15:51:30 +02:00
private $category_identifier ;
private $offer_field_map ;
2024-05-22 11:25:35 +02:00
public function __construct ( $app , $intern = false ) {
$this -> app = $app ;
$this -> intern = $intern ;
if ( $intern )
return ;
2024-05-13 14:43:33 +02:00
}
2024-05-22 11:25:35 +02:00
/*
2024-06-13 15:51:30 +02:00
* See widget . shopexport . php , ShowExtraeinstellungen ()
2024-05-22 11:25:35 +02:00
*/
public function EinstellungenStruktur () {
2024-06-13 15:51:30 +02:00
$einstellungen = [
2024-05-22 11:25:35 +02:00
'ausblenden' => [ 'abholmodus' => [ 'ab_nummer' , 'zeitbereich' ]],
'functions' => [ 'getarticlelist' ],
'felder' => [
'protokoll' => [
'typ' => 'checkbox' ,
'bezeichnung' => '{|Protokollierung im Logfile|}:'
],
'apikey' => [
'typ' => 'text' ,
'bezeichnung' => '{|API Key|}:' ,
'size' => 40 ,
],
'shopurl' => [
'typ' => 'text' ,
'bezeichnung' => '{|Shop URL|}:' ,
'size' => 40 ,
],
2024-05-24 10:53:49 +02:00
'mirakl_shopid' => [
2024-05-22 11:25:35 +02:00
'typ' => 'text' ,
'bezeichnung' => '{|Shop ID des Shops|}:' ,
'size' => 40 ,
'info' => 'optional, int64'
],
2024-06-13 15:51:30 +02:00
'category_identifier_source' => [
2024-05-22 11:25:35 +02:00
'typ' => 'select' ,
2024-06-13 15:51:30 +02:00
'bezeichnung' => '{|Katalogkategorie-Typ|}:' ,
2024-05-22 11:25:35 +02:00
'size' => 40 ,
2024-06-13 15:51:30 +02:00
'info' => 'Woher soll die Katalogkategorie des jeweiligen Artikels bezogen werden?' ,
'default' => 'feld' ,
'optionen' => [ 'feld' => '{|Feld|}' , 'freifeld' => '{|Freifeld|}' , 'eigenschaft' => '{|Eigenschaft|}' , 'wert' => '{|Fester Wert|}' ]
2024-05-22 11:25:35 +02:00
],
2024-06-13 15:51:30 +02:00
'category_identifier_source_value' => [
2024-05-22 11:25:35 +02:00
'typ' => 'text' ,
2024-06-13 15:51:30 +02:00
'bezeichnung' => '{|Katalogkategorie-Wert|}:' ,
2024-05-22 11:25:35 +02:00
'size' => 40 ,
2024-06-13 15:51:30 +02:00
'info' => '' ,
'default' => 'kategoriename'
2024-05-22 11:25:35 +02:00
],
2024-06-13 15:51:30 +02:00
'offer_field_map' => [
2024-05-22 11:25:35 +02:00
'typ' => 'textarea' ,
'bezeichnung' => '{|Zuordnung Angebots-Felder je Kategorie (JSON)|}:' ,
2024-06-13 15:51:30 +02:00
'cols' => 80 ,
'rows' => 20 ,
'info' => 'Die Felder werden vom Mirakl-Betreiber vorgegeben. Ist keine Kategorie definiert, gilt der Eintrag für alle Artikel. Jedes Feld kann wie folgt zugeordnet werden:<br>Artikelfeld: "Mirakel-Feldname": {"feld": "xyz"} oder kurz "Mirakel-Feldname": "xyz",<br>Freifeld: "Mirakel-Feldname": {"freifeld": "Bezeichnung in Shop"} (Siehe Reiter "Freifelder"),<br>Eigenschaft: "Mirakel-Feldname": {"eigenschaft": "Eigenschaftenname xyz"},<br>Fester Wert: "Mirakel-Feldname": {"wert": "xyz"}<br><br>Zusatzfelder zusätzlich mit der Eigenschaft "zusatzfeld": true versehen: z.B. "Mirakel-Feldname": {"feld": "name_de", "zusatzfeld": true}' ,
'placeholder' => ' [
{
& quot ; kategorien & quot ; : [
& quot ; Schuhe & quot ;, & quot ; Hosen & quot ;
],
& quot ; felder & quot ; : {
& quot ; product_id_type & quot ; : { & quot ; wert & quot ; : & quot ; SHOP_SKU & quot ;},
& quot ; product_id & quot ; : { & quot ; feld & quot ; : & quot ; nummer & quot ;},
& quot ; shop_sku & quot ; : { & quot ; feld & quot ; : & quot ; nummer & quot ;},
& quot ; price & quot ; : { & quot ; feld & quot ; : & quot ; preis & quot ;},
& quot ; description & quot ; : { & quot ; freifeld & quot ; : & quot ; Kategorie & quot ;},
& quot ; internal_description & quot ; : { & quot ; eigenschaft & quot ; : & quot ; Mirakl Steuertext & quot ;},
& quot ; reversecharge & quot ; : { & quot ; wert & quot ; : & quot ; false & quot ;, & quot ; zusatzfeld & quot ; : true },
& quot ; warehouse & quot ; : { & quot ; wert & quot ; : & quot ; 1 & quot ;, & quot ; zusatzfeld & quot ; : true },
& quot ; quantity & quot ; : { & quot ; feld & quot ; : & quot ; anzahl_lager & quot ;}
}
}
] '
2024-05-22 11:25:35 +02:00
],
2024-06-13 15:51:30 +02:00
'Artikelfelder' => [
'heading' => 'Zusatzinformationen' ,
'typ' => 'info' ,
'text' => 'Folgende Artikelfelder stehen zur Verfügung:' ,
'bezeichnung' => null ,
'info' => 'artikel, artikelid, nummer, inaktiv, name_de, name_en, einheit, hersteller, herstellernummer, ean, artikelnummer_fremdnummern, kurztext_de, kurztext_en, anabregs_text, anabregs_text_en, beschreibung_de, beschreibung_en, uebersicht_de, uebersicht_en, herkunftsland, texteuebertragen, metadescription_de, metadescription_en, metakeywords_de, metakeywords_en, metatitle_de, metatitle_en, links_de, altersfreigabe, links_en, startseite_de, startseite_en, restmenge, startseite, standardbild, herstellerlink, lieferzeit, lieferzeitmanuell, gewicht, laenge, breite, hoehe, wichtig, porto, gesperrt, sperrgrund, gueltigbis, umsatzsteuer, ausverkauft, variante, variante_von_id, variantevon, pseudopreis, keinrabatterlaubt, einkaufspreis, pseudolager, downloadartikel, zolltarifnummer, freifeld_Kategorie, typ, kategoriename, steuer_art_produkt, steuer_art_produkt_download, anzahl_bilder, anzahl_lager, lagerkorrekturwert, autolagerlampe, waehrung, preis, steuersatz, bruttopreis, checksum, variantevorhanden'
]
2024-05-22 11:25:35 +02:00
]
];
2024-06-13 15:51:30 +02:00
return ( $einstellungen );
2024-05-13 14:43:33 +02:00
}
2024-05-22 11:25:35 +02:00
public function getKonfig ( $shopid , $data ) {
2024-06-13 15:51:30 +02:00
2024-05-22 11:25:35 +02:00
$this -> shopid = $shopid ;
$this -> data = $data ;
$importerSettings = $this -> app -> DB -> SelectArr ( " SELECT `einstellungen_json` FROM `shopexport` WHERE `id` = ' $shopid ' LIMIT 1 " );
$importerSettings = reset ( $importerSettings );
$einstellungen = [];
if ( ! empty ( $importerSettings [ 'einstellungen_json' ])) {
$einstellungen = json_decode ( $importerSettings [ 'einstellungen_json' ], true );
}
$this -> protocol = $einstellungen [ 'felder' ][ 'protokoll' ];
$this -> apiKey = $einstellungen [ 'felder' ][ 'apikey' ];
$this -> shopUrl = rtrim ( $einstellungen [ 'felder' ][ 'shopurl' ], '/' ) . '/' ;
2024-05-24 10:53:49 +02:00
$this -> mirakl_shopid = $einstellungen [ 'felder' ][ 'mirakl_shopid' ];
2024-05-22 11:25:35 +02:00
if ( $einstellungen [ 'felder' ][ 'autoerstellehersteller' ] === '1' ) {
$this -> createManufacturerAllowed = true ;
}
$this -> idsabholen = $einstellungen [ 'felder' ][ 'abholen' ];
$this -> idbearbeitung = $einstellungen [ 'felder' ][ 'bearbeitung' ];
$this -> idabgeschlossen = $einstellungen [ 'felder' ][ 'abgeschlossen' ];
$query = sprintf ( 'SELECT `steuerfreilieferlandexport` FROM `shopexport` WHERE `id` = %d' , $this -> shopid );
$this -> taxationByDestinationCountry = ! empty ( $this -> app -> DB -> Select ( $query ));
2024-06-13 15:51:30 +02:00
$this -> category_identifier = array ( $einstellungen [ 'felder' ][ 'category_identifier_source' ] => $einstellungen [ 'felder' ][ 'category_identifier_source_value' ]);
$this -> offer_field_map = json_decode ( $einstellungen [ 'felder' ][ 'offer_field_map' ], true , flags : JSON_THROW_ON_ERROR );
2024-05-14 11:00:56 +02:00
}
2024-05-22 11:25:35 +02:00
private function miraklRequest ( string $endpoint , $postdata = null , array $getdata = null , string $content_type = null , bool $raw = false ) {
$ch = curl_init ();
$url_addition = " " ;
$headers = array ( " Authorization: " . $this -> apiKey );
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
2024-05-24 10:53:49 +02:00
if ( ! empty ( $this -> mirakl_shopid )) {
$getdata [ 'shop_id' ] = $this -> mirakl_shopid ;
}
2024-05-22 11:25:35 +02:00
if ( ! empty ( $getdata )) {
$url_addition = " ? " ;
$ampersand = " " ;
foreach ( $getdata as $key => $value ) {
$url_addition .= $ampersand . $key . " = " . $value ;
$ampersand = " & " ;
}
2024-05-24 10:53:49 +02:00
}
if ( ! empty ( $postdata )) {
2024-05-22 11:25:35 +02:00
curl_setopt ( $ch , CURLOPT_CUSTOMREQUEST , 'POST' );
curl_setopt ( $ch , CURLOPT_POSTFIELDS , $postdata );
$headers [] = 'Content-Type: ' . $content_type ;
}
curl_setopt ( $ch , CURLOPT_URL , $this -> shopUrl . $endpoint . $url_addition );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , $headers );
curl_setopt ( $ch , CURLINFO_HEADER_OUT , true );
curl_setopt ( $ch , CURLOPT_VERBOSE , true );
$response = curl_exec ( $ch );
if ( curl_error ( $ch )) {
$this -> error [] = curl_error ( $ch );
}
curl_close ( $ch );
$information = curl_getinfo ( $ch );
2024-05-24 10:53:49 +02:00
/* print_r ( $information );
print_r ( $postdata );
print_r ( $response );
exit ();
*/
2024-05-22 11:25:35 +02:00
if ( $raw )
return $response ;
return simplexml_load_string ( $response );
2024-05-14 11:00:56 +02:00
}
2024-05-22 11:25:35 +02:00
public function ImportAuth () {
$ch = curl_init ( $this -> shopUrl . " version " );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , array ( " Authorization: " . $this -> apiKey ));
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$code = curl_getinfo ( $ch , CURLINFO_RESPONSE_CODE );
if ( $code == 200 ) {
return 'success ' . print_r ( $response , true );
}
return $response ;
}
/*
* Fetches article list from the shop , puts them into table shopexport_getarticles , starts the prozessstarter getarticles which fetches details for each article via ImportGetArticle ()
*/
2024-05-17 16:50:19 +02:00
2024-05-22 11:25:35 +02:00
public function ImportGetArticleList () {
$result = [];
2024-05-14 11:00:56 +02:00
2024-05-22 11:25:35 +02:00
$response = $this -> miraklRequest ( 'offers' , raw : true );
2024-05-13 14:43:33 +02:00
2024-05-22 11:25:35 +02:00
$result_array = json_decode ( $response );
foreach ( $result_array -> offers as $offer ) {
$result [] = $offer -> shop_sku ;
}
2024-05-14 11:00:56 +02:00
2024-05-22 11:25:35 +02:00
array_unique ( $result );
return $result ;
2024-05-14 11:00:56 +02:00
}
2024-05-13 14:43:33 +02:00
2024-05-22 11:25:35 +02:00
/*
* Fetches article details from the shop
*/
2024-05-17 16:50:19 +02:00
2024-05-22 11:25:35 +02:00
public function ImportGetArticle () {
2024-05-14 11:00:56 +02:00
2024-05-22 11:25:35 +02:00
$articleList = $this -> CatchRemoteCommand ( 'data' );
2024-05-14 11:00:56 +02:00
2024-05-22 11:25:35 +02:00
$parameters = array ( 'product_references' => 'productID' , 'product_' );
$response = $this -> miraklRequest ( 'products?' , raw : true );
throw new Exception ( " Not implemented " );
2024-05-13 14:43:33 +02:00
}
2024-05-23 20:12:05 +02:00
/*
* Gets a flexible mapped fieldvalue from feld , wert or eigenschaft
*/
public function GetFieldValue ( $article , $field_map_entry ) {
foreach ( $field_map_entry as $key => $value ) {
switch ( $key ) {
case 'feld' :
2024-06-13 15:51:30 +02:00
if ( isset ( $article [ $value ])) {
return ( $article [ $value ]);
} else {
throw new Exception ( " Artikelfeld existiert nicht: \" " . $value . " \" " );
}
break ;
case 'freifeld' :
if ( isset ( $article [ 'freifelder' ][ 'DE' ][ $value ])) {
return ( $article [ 'freifelder' ][ 'DE' ][ $value ]);
} else {
throw new Exception ( " Freifeld existiert nicht: \" " . $value . " \" " );
}
2024-05-23 20:12:05 +02:00
break ;
case 'eigenschaft' :
$sql = " SELECT wert FROM artikeleigenschaften ae INNER JOIN artikeleigenschaftenwerte aew ON aew.artikeleigenschaften = ae.id WHERE aew.artikel = ' " . $article [ 'artikelid' ] . " ' AND ae.name = ' " . $value . " ' LIMIT 1 " ;
return ( $this -> app -> DB -> Select ( $sql ));
break ;
case 'wert' :
return ( $value );
break ;
}
2024-06-13 15:51:30 +02:00
}
2024-05-23 20:12:05 +02:00
return ( null );
}
2024-05-24 18:57:37 +02:00
public function ImportSendListLager () {
return ( $this -> ImportSendList ());
}
2024-05-22 11:25:35 +02:00
/*
* Send articles to shop
*/
public function ImportSendList () {
2024-05-23 20:12:05 +02:00
2024-05-22 11:25:35 +02:00
$articleList = $this -> CatchRemoteCommand ( 'data' );
2024-05-23 20:12:05 +02:00
2024-05-22 11:25:35 +02:00
// First gather all articles as offers and send them
// Wait for import to finish
// Evaluate import
// Unimplemented (needed?)
// Select offers with no product
// Create products and send
// Wait for import to finish
// Evaluate import
foreach ( $articleList as $article ) {
/*
* Export offer
*/
2024-06-13 15:51:30 +02:00
$category_found = false ;
2024-05-22 11:25:35 +02:00
$additional_fields = array ();
$offer_for_mirakl = array (
'state_code' => '11' , // ?!?!
'update_delete' => null // Update delete flag. Could be empty (means "update"), "update" or "delete".
);
2024-05-23 20:12:05 +02:00
2024-06-13 15:51:30 +02:00
$kategorie = $this -> GetFieldValue ( $article , $this -> category_identifier );
foreach ( $this -> offer_field_map as $offer_field_entry ) {
2024-05-24 10:10:59 +02:00
if ( $offer_field_entry [ 'kategorien' ] != null ) {
if ( ! in_array ( $kategorie , $offer_field_entry [ 'kategorien' ])) {
continue ;
}
2024-06-13 15:51:30 +02:00
}
$category_found = true ;
2024-05-23 20:12:05 +02:00
// Check Required attributes
$required = [
'product_id_type' ,
'product_id' ,
'shop_sku' ,
'price'
];
$missing = null ;
foreach ( $required as $key ) {
if ( ! isset ( $offer_field_entry [ 'felder' ][ $key ])) {
$missing [] = $key ;
}
}
if ( $missing ) {
return ( array ( 'status' => false , 'message' => " Pflichtfelder fehlen in Angebotskonfiguration von Kategorie \" " . $offer_field_entry [ 'kategorie' ] . " \" : " . implode ( ', ' , $missing )));
2024-05-22 13:37:30 +02:00
}
2024-05-23 20:12:05 +02:00
// Check Required attributes
foreach ( $offer_field_entry [ 'felder' ] as $offer_field => $offer_field_source ) {
if ( ! is_array ( $offer_field_source )) {
$offer_field_source = array ( 'feld' => $offer_field_source );
}
$offer_field_value = null ;
$is_additional_field = false ;
$offer_field_value = $this -> GetFieldValue ( $article , $offer_field_source );
if ( in_array ( 'zusatzfeld' , $offer_field_source )) {
$is_additional_field = true ;
}
if ( $is_additional_field ) {
$additional_field = array (
" code " => $offer_field ,
" value " => $offer_field_value
);
$additional_fields [] = $additional_field ;
} else {
$offer_for_mirakl [ $offer_field ] = $offer_field_value ;
}
}
}
2024-05-22 13:37:30 +02:00
2024-06-13 15:51:30 +02:00
if ( ! $category_found ) {
return ( array ( 'status' => false , 'message' => " Angebotskonfiguration für Artikel " . $article [ 'nummer' ] . " , Kategorie \" " . $kategorie . " \" nicht gefunden " ));
}
2024-05-22 11:25:35 +02:00
if ( ! empty ( $additional_fields )) {
$offer_for_mirakl [ 'offer_additional_fields' ] = $additional_fields ;
}
$offers_for_mirakl [] = $offer_for_mirakl ;
}
$data_for_mirakl = array ();
$data_for_mirakl [ 'offers' ] = $offers_for_mirakl ;
$json_for_mirakl = json_encode ( $data_for_mirakl );
$result = [];
$response = $this -> miraklRequest ( 'offers' , postdata : $json_for_mirakl , content_type : 'application/json' , raw : true );
$result = json_decode ( $response );
if ( ! isset ( $result -> import_id )) {
2024-05-23 20:12:05 +02:00
return ( array ( 'status' => false , 'message' => " Angebotsimport in Mirakl abgelehnt: " . print_r ( $response , true )));
2024-05-22 11:25:35 +02:00
}
2024-05-17 16:50:19 +02:00
2024-05-22 11:25:35 +02:00
$import_id = $result -> import_id ;
// Wait for import to finish
2024-05-17 16:50:19 +02:00
2024-05-22 11:25:35 +02:00
$status = null ;
/*
WAITING_SYNCHRONIZATION_PRODUCT , WAITING , RUNNING , COMPLETE , FAILED
*/
while ( $status != 'COMPLETE' && $status != 'FAILED' ) {
sleep ( 5 );
$response = $this -> miraklRequest ( 'offers/imports/' . $import_id , raw : true );
$result = json_decode ( $response );
$status = $result -> status ;
}
if ( $status == 'FAILED' ) {
2024-05-23 20:12:05 +02:00
return ( array ( 'status' => false , 'message' => " Angebotsimport in Mirakl fehlgeschlagen: " . print_r ( $response , true )));
2024-05-22 11:25:35 +02:00
}
if ( $result -> lines_in_error == 0 ) {
return ( $result -> lines_in_success );
}
// Check errors
2024-05-24 18:57:37 +02:00
$response = $this -> miraklRequest ( 'offers/imports/' . $import_id . '/error_report' , raw : true );
2024-05-23 20:12:05 +02:00
return ( array ( 'status' => false , 'message' => " Angebotsimport in Mirakl hat Fehler: " . print_r ( $response , true )));
2024-05-13 14:43:33 +02:00
}
2024-05-22 11:25:35 +02:00
private function getOrdersToProcess ( int $limit ) {
2024-05-13 14:43:33 +02:00
}
2024-05-22 11:25:35 +02:00
private function Log ( $message , $dump = '' ) {
if ( $this -> protocol ) {
$this -> app -> erp -> Logfile ( $message , print_r ( $dump , true ));
2024-05-13 14:43:33 +02:00
}
2024-05-22 11:25:35 +02:00
}
2024-05-13 14:43:33 +02:00
2024-05-22 11:25:35 +02:00
public function ImportDeleteAuftrag () {
}
2024-05-13 14:43:33 +02:00
2024-05-22 11:25:35 +02:00
public function ImportUpdateAuftrag () {
2024-05-13 14:43:33 +02:00
}
2024-05-22 11:25:35 +02:00
public function ImportGetAuftraegeAnzahl () {
}
2024-05-13 14:43:33 +02:00
2024-05-22 11:25:35 +02:00
public function ImportGetAuftrag () {
}
2024-05-13 14:43:33 +02:00
}
2024-05-22 11:25:35 +02:00