2025-02-21 09:32:54 +01:00
< ? php
/*
* This file is part of Part - DB ( https :// github . com / Part - DB / Part - DB - symfony ) .
*
* Copyright ( C ) 2019 - 2023 Jan Böhmer ( https :// github . com / jbtronics )
* Copyright ( C ) 2025 Marc Kreidler ( https :// github . com / mkne )
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*/
declare ( strict_types = 1 );
namespace App\Services\InfoProviderSystem\Providers ;
use App\Services\InfoProviderSystem\DTOs\FileDTO ;
use App\Services\InfoProviderSystem\DTOs\ParameterDTO ;
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO ;
use App\Services\InfoProviderSystem\DTOs\PriceDTO ;
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO ;
2025-12-11 14:36:19 +01:00
use App\Settings\InfoProviderSystem\BuerklinSettings ;
2025-02-24 15:24:56 +01:00
use App\Services\OAuth\OAuthTokenManager ;
use Psr\Cache\CacheItemPoolInterface ;
use Symfony\Component\DependencyInjection\Attribute\Autowire ;
2025-02-24 09:38:42 +01:00
use Symfony\Component\HttpClient\HttpOptions ;
2025-02-21 09:32:54 +01:00
use Symfony\Contracts\HttpClient\HttpClientInterface ;
class BuerklinProvider implements InfoProviderInterface
{
2025-12-06 23:53:14 +01:00
private const ENDPOINT_URL = 'https://www.buerklin.com/buerklinws/v2/buerklin' ;
2025-02-21 09:32:54 +01:00
public const DISTRIBUTOR_NAME = 'Buerklin' ;
2025-02-24 09:38:42 +01:00
private const OAUTH_APP_NAME = 'ip_buerklin_oauth' ;
2025-12-07 20:09:32 +01:00
private const CACHE_TTL = 600 ;
/**
* Local in - request cache to avoid hitting the PSR cache repeatedly for the same product .
* @ var array < string , array >
*/
private array $productCache = [];
2025-02-21 09:32:54 +01:00
2025-12-06 23:53:14 +01:00
public function __construct (
private readonly HttpClientInterface $client ,
2025-02-24 15:24:56 +01:00
private readonly OAuthTokenManager $authTokenManager ,
private readonly CacheItemPoolInterface $partInfoCache ,
2025-12-11 14:50:53 +01:00
private readonly BuerklinSettings $settings ,
2025-12-11 16:14:11 +01:00
) {
2025-02-21 09:32:54 +01:00
}
2025-02-24 09:38:42 +01:00
/**
* Gets the latest OAuth token for the Buerklin API , or creates a new one if none is available
* @ return string
*/
private function getToken () : string
{
2025-12-06 23:53:14 +01:00
// Cache token to avoid hammering the auth server on every request
$cacheKey = 'buerklin.oauth.token' ;
$item = $this -> partInfoCache -> getItem ( $cacheKey );
if ( $item -> isHit ()) {
$token = $item -> get ();
if ( is_string ( $token ) && $token !== '' ) {
return $token ;
}
}
// Bürklin OAuth2 password grant (ROPC)
$resp = $this -> client -> request ( 'POST' , 'https://www.buerklin.com/authorizationserver/oauth/token/' , [
'headers' => [
'Accept' => 'application/json' ,
'Content-Type' => 'application/x-www-form-urlencoded' ,
],
'body' => [
'grant_type' => 'password' ,
2025-12-11 14:36:19 +01:00
'client_id' => $this -> settings -> clientId ,
'client_secret' => $this -> settings -> secret ,
'username' => $this -> settings -> username ,
'password' => $this -> settings -> password ,
2025-12-06 23:53:14 +01:00
],
]);
$data = $resp -> toArray ( false );
if ( ! isset ( $data [ 'access_token' ])) {
throw new \RuntimeException (
'Invalid token response from Bürklin: HTTP ' . $resp -> getStatusCode () . ' body=' . $resp -> getContent ( false )
);
2025-02-24 09:38:42 +01:00
}
2025-12-06 23:53:14 +01:00
$token = ( string ) $data [ 'access_token' ];
// Cache for (expires_in - 30s) if available
$ttl = 300 ;
if ( isset ( $data [ 'expires_in' ]) && is_numeric ( $data [ 'expires_in' ])) {
$ttl = max ( 60 , ( int ) $data [ 'expires_in' ] - 30 );
2025-02-24 09:38:42 +01:00
}
2025-12-06 23:53:14 +01:00
$item -> set ( $token );
$item -> expiresAfter ( $ttl );
$this -> partInfoCache -> save ( $item );
2025-02-25 16:40:20 +01:00
return $token ;
2025-12-06 23:53:14 +01:00
}
2025-12-07 20:09:32 +01:00
private function getDefaultQueryParams () : array
{
return [
2025-12-11 14:36:19 +01:00
'curr' => $this -> settings -> currency ? : 'EUR' ,
'language' => $this -> settings -> language ? : 'en' ,
2025-12-07 20:09:32 +01:00
];
}
private function getProduct ( string $code ) : array
{
$code = strtoupper ( trim ( $code ));
if ( $code === '' ) {
throw new \InvalidArgumentException ( 'Product code must not be empty.' );
}
$cacheKey = sprintf (
'buerklin.product.%s' ,
2025-12-11 14:50:53 +01:00
md5 ( $code . '|' . $this -> settings -> language . '|' . $this -> settings -> currency )
2025-12-07 20:09:32 +01:00
);
if ( isset ( $this -> productCache [ $cacheKey ])) {
return $this -> productCache [ $cacheKey ];
}
$item = $this -> partInfoCache -> getItem ( $cacheKey );
if ( $item -> isHit () && is_array ( $cached = $item -> get ())) {
return $this -> productCache [ $cacheKey ] = $cached ;
}
$product = $this -> makeAPICall ( '/products/' . rawurlencode ( $code ) . '/' );
$item -> set ( $product );
$item -> expiresAfter ( self :: CACHE_TTL );
$this -> partInfoCache -> save ( $item );
return $this -> productCache [ $cacheKey ] = $product ;
}
2025-02-24 09:38:42 +01:00
2025-02-25 16:40:20 +01:00
private function makeAPICall ( string $endpoint , array $queryParams = []) : array
2025-02-24 15:24:56 +01:00
{
2025-02-25 16:40:20 +01:00
try {
$response = $this -> client -> request ( 'GET' , self :: ENDPOINT_URL . $endpoint , [
'auth_bearer' => $this -> getToken (),
2025-12-06 23:53:14 +01:00
'headers' => [ 'Accept' => 'application/json' ],
2025-12-07 20:09:32 +01:00
'query' => array_merge ( $this -> getDefaultQueryParams (), $queryParams ),
2025-02-25 16:40:20 +01:00
]);
return $response -> toArray ();
} catch ( \Exception $e ) {
2025-12-06 23:53:14 +01:00
throw new \RuntimeException ( " Buerklin API request failed: " .
" Endpoint: " . $endpoint .
" Token: [redacted] " .
" QueryParams: " . json_encode ( $queryParams , JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) . " " .
" Exception message: " . $e -> getMessage ());
2025-02-24 15:24:56 +01:00
}
}
2025-02-25 16:40:20 +01:00
2025-02-21 09:32:54 +01:00
public function getProviderInfo () : array
{
return [
'name' => 'Buerklin' ,
'description' => 'This provider uses the Buerklin API to search for parts.' ,
'url' => 'https://www.buerklin.com/' ,
2025-12-06 23:53:14 +01:00
'oauth_app_name' => self :: OAUTH_APP_NAME ,
2025-02-24 09:38:42 +01:00
'disabled_help' => 'Set the environment variables PROVIDER_BUERKLIN_CLIENT_ID, PROVIDER_BUERKLIN_SECRET, PROVIDER_BUERKLIN_USERNAME and PROVIDER_BUERKLIN_PASSWORD.'
2025-02-21 09:32:54 +01:00
];
}
public function getProviderKey () : string
{
return 'buerklin' ;
}
// This provider is always active
public function isActive () : bool
{
2025-02-24 09:38:42 +01:00
//The client ID has to be set and a token has to be available (user clicked connect)
2025-12-11 14:36:19 +01:00
return $this -> settings -> clientId !== ''
&& $this -> settings -> secret !== ''
&& $this -> settings -> username !== ''
&& $this -> settings -> password !== '' ;
2025-02-21 09:32:54 +01:00
}
/**
* @ param string $id
* @ return PartDetailDTO
*/
private function queryDetail ( string $id ) : PartDetailDTO
{
2025-12-07 20:09:32 +01:00
$product = $this -> getProduct ( $id );
2025-02-21 09:32:54 +01:00
if ( $product === null ) {
throw new \RuntimeException ( 'Could not find product code: ' . $id );
}
return $this -> getPartDetail ( $product );
}
/**
* Sanitizes a field by removing any HTML tags and other unwanted characters
* @ param string | null $field
* @ return string | null
*/
private function sanitizeField ( ? string $field ) : ? string
{
if ( $field === null ) {
return null ;
}
return strip_tags ( $field );
}
/**
* Takes a deserialized json object of the product and returns a PartDetailDTO
* @ param array $product
* @ return PartDetailDTO
*/
private function getPartDetail ( array $product ) : PartDetailDTO
{
2025-12-06 23:53:14 +01:00
// If this is a search-result object, it may not contain prices/features/images -> reload full detail.
if (( ! isset ( $product [ 'price' ]) && ! isset ( $product [ 'volumePrices' ])) && isset ( $product [ 'code' ])) {
try {
2025-12-07 20:09:32 +01:00
$product = $this -> getProduct (( string ) $product [ 'code' ]);
2025-12-06 23:53:14 +01:00
} catch ( \Throwable $e ) {
// If reload fails, keep the partial product data and continue.
}
2025-02-21 09:32:54 +01:00
}
2025-12-07 20:09:32 +01:00
// Extract Images from API response
2025-12-06 23:53:14 +01:00
$productImages = $this -> getProductImages ( $product [ 'images' ] ? ? null );
2025-12-07 20:09:32 +01:00
// Set Preview image
2025-12-06 23:53:14 +01:00
$preview = $productImages [ 0 ] -> url ? ? null ;
2025-12-07 20:09:32 +01:00
// Extract features (parameters) from classifications[0].features of Bürklin JSON response
2025-12-06 23:53:14 +01:00
$features = $product [ 'classifications' ][ 0 ][ 'features' ] ? ? [];
2025-12-07 20:09:32 +01:00
// Feature Parameters (from classifications->features)
$featureParams = $this -> attributesToParameters ( $features , '' ); //leave group empty for normal parameters
2025-12-06 23:53:14 +01:00
2025-12-07 20:09:32 +01:00
// Compliance-Parameter (from Top-Level fields like RoHS/SVHC/…)
2025-12-06 23:53:14 +01:00
$complianceParams = $this -> complianceToParameters ( $product , 'Compliance' );
2025-12-07 20:09:32 +01:00
// Merge all parameters
2025-12-06 23:53:14 +01:00
$allParams = array_merge ( $featureParams , $complianceParams );
2025-12-07 20:09:32 +01:00
// Assign Footprint: "Design" (en) / "Bauform" (de) / "Enclosure" (en) / "Gehäuse" (de)
2025-12-06 23:53:14 +01:00
$footprint = null ;
if ( is_array ( $features )) {
foreach ( $features as $feature ) {
$name = $feature [ 'name' ] ? ? null ;
2025-12-07 20:09:32 +01:00
if ( $name === 'Design' || $name === 'Bauform' || $name === 'Enclosure' || $name === 'Gehäuse' ) {
2025-12-06 23:53:14 +01:00
$footprint = $feature [ 'featureValues' ][ 0 ][ 'value' ] ? ? null ;
2025-02-25 16:40:20 +01:00
break ;
}
2025-02-24 15:24:56 +01:00
}
2025-02-21 09:32:54 +01:00
}
2025-12-06 23:53:14 +01:00
// Prices: prefer volumePrices, fallback to single price
$code = ( string ) ( $product [ 'orderNumber' ] ? ? $product [ 'code' ] ? ? '' );
$prices = $product [ 'volumePrices' ] ? ? null ;
if ( ! is_array ( $prices ) || count ( $prices ) === 0 ) {
$pVal = $product [ 'price' ][ 'value' ] ? ? null ;
2025-12-11 14:50:53 +01:00
$pCur = $product [ 'price' ][ 'currencyIso' ] ? ? ( $this -> settings -> currency ? : 'EUR' );
2025-12-06 23:53:14 +01:00
if ( is_numeric ( $pVal )) {
$prices = [
[
'minQuantity' => 1 ,
'value' => ( float ) $pVal ,
'currencyIso' => ( string ) $pCur ,
]
];
} else {
$prices = [];
}
}
2025-02-21 09:32:54 +01:00
return new PartDetailDTO (
provider_key : $this -> getProviderKey (),
2025-12-06 23:53:14 +01:00
provider_id : ( string ) ( $product [ 'code' ] ? ? $code ),
name : ( string ) ( $product [ 'manufacturerProductId' ] ? ? $code ),
description : $this -> sanitizeField ( $product [ 'description' ] ? ? null ),
category : $this -> sanitizeField ( $product [ 'classifications' ][ 0 ][ 'name' ] ? ? ( $product [ 'categories' ][ 0 ][ 'name' ] ? ? null )),
2025-02-24 15:24:56 +01:00
manufacturer : $this -> sanitizeField ( $product [ 'manufacturer' ] ? ? null ),
mpn : $this -> sanitizeField ( $product [ 'manufacturerProductId' ] ? ? null ),
2025-12-06 23:53:14 +01:00
preview_image_url : $preview ,
2025-02-21 09:32:54 +01:00
manufacturing_status : null ,
2025-12-06 23:53:14 +01:00
provider_url : $this -> getProductShortURL (( string ) ( $product [ 'code' ] ? ? $code )),
footprint : $footprint ,
2025-12-07 20:09:32 +01:00
datasheets : null , // not found in JSON response, the Buerklin website however has links to datasheets
2025-12-06 23:53:14 +01:00
images : $productImages ,
parameters : $allParams ,
vendor_infos : $this -> pricesToVendorInfo (
sku : $code ,
url : $this -> getProductShortURL ( $code ),
prices : $prices
),
2025-02-21 09:32:54 +01:00
mass : $product [ 'weight' ] ? ? null ,
);
}
/**
* Converts the price array to a VendorInfoDTO array to be used in the PartDetailDTO
* @ param string $sku
* @ param string $url
* @ param array $prices
* @ return array
*/
private function pricesToVendorInfo ( string $sku , string $url , array $prices ) : array
{
2025-12-06 23:53:14 +01:00
$priceDTOs = array_map ( function ( $price ) {
$val = $price [ 'value' ] ? ? null ;
$valStr = is_numeric ( $val )
? number_format (( float ) $val , 6 , '.' , '' ) // 6 Nachkommastellen, trailing zeros ok
: ( string ) $val ;
// Optional: weich kürzen (z.B. 75.550000 -> 75.55)
$valStr = rtrim ( rtrim ( $valStr , '0' ), '.' );
return new PriceDTO (
minimum_discount_amount : ( float ) ( $price [ 'minQuantity' ] ? ? 1 ),
price : $valStr ,
2025-12-11 14:36:19 +01:00
currency_iso_code : ( string ) ( $price [ 'currencyIso' ] ? ? $this -> settings -> currency ? ? 'EUR' ),
2025-12-06 23:53:14 +01:00
includes_tax : false
);
}, $prices );
2025-02-21 09:32:54 +01:00
return [
new PurchaseInfoDTO (
distributor_name : self :: DISTRIBUTOR_NAME ,
order_number : $sku ,
2025-02-25 16:40:20 +01:00
prices : $priceDTOs ,
2025-02-21 09:32:54 +01:00
product_url : $url ,
)
];
}
2025-02-25 16:40:20 +01:00
2025-02-21 09:32:54 +01:00
/**
* Returns a valid Buerklin product short URL from product code
* @ param string $product_code
* @ return string
*/
private function getProductShortURL ( string $product_code ) : string
{
2025-12-06 23:53:14 +01:00
return 'https://www.buerklin.com/de/p/' . $product_code . '/' ;
2025-02-21 09:32:54 +01:00
}
/**
2025-12-06 23:53:14 +01:00
* Returns a deduplicated list of product images as FileDTOs .
*
2025-12-07 20:09:32 +01:00
* - takes only real image arrays ( with 'url' field )
* - makes relative URLs absolut
* - deduplicates using URL
* - prefers 'zoom' format , then 'product' format , then all others
2025-12-06 23:53:14 +01:00
*
* @ param array | null $images
* @ return \App\Services\InfoProviderSystem\DTOs\FileDTO []
2025-02-21 09:32:54 +01:00
*/
private function getProductImages ( ? array $images ) : array
{
2025-12-06 23:53:14 +01:00
if ( ! is_array ( $images ))
return [];
2025-12-07 20:09:32 +01:00
// 1) Only real image entries with URL
2025-12-06 23:53:14 +01:00
$imgs = array_values ( array_filter ( $images , fn ( $i ) => is_array ( $i ) && ! empty ( $i [ 'url' ])));
2025-12-07 20:09:32 +01:00
// 2) Prefer zoom images
2025-12-06 23:53:14 +01:00
$zoom = array_values ( array_filter ( $imgs , fn ( $i ) => ( $i [ 'format' ] ? ? null ) === 'zoom' ));
$chosen = count ( $zoom ) > 0
? $zoom
: array_values ( array_filter ( $imgs , fn ( $i ) => ( $i [ 'format' ] ? ? null ) === 'product' ));
2025-12-07 20:09:32 +01:00
// 3) If still none, take all
2025-12-06 23:53:14 +01:00
if ( count ( $chosen ) === 0 ) {
$chosen = $imgs ;
}
2025-12-07 20:09:32 +01:00
// 4) Deduplicate by URL (after making absolute)
2025-12-06 23:53:14 +01:00
$byUrl = [];
foreach ( $chosen as $img ) {
$url = ( string ) $img [ 'url' ];
if ( ! str_starts_with ( $url , 'http://' ) && ! str_starts_with ( $url , 'https://' )) {
$url = 'https://www.buerklin.com' . $url ;
}
if ( ! filter_var ( $url , FILTER_VALIDATE_URL ))
continue ;
$byUrl [ $url ] = $url ;
}
return array_map (
fn ( $url ) => new \App\Services\InfoProviderSystem\DTOs\FileDTO ( $url ),
array_values ( $byUrl )
);
2025-02-21 09:32:54 +01:00
}
2025-12-06 23:53:14 +01:00
2025-02-21 09:32:54 +01:00
/**
* @ param array | null $attributes
* @ return ParameterDTO []
*/
2025-12-06 23:53:14 +01:00
private function attributesToParameters ( array $features , ? string $group = null ) : array
2025-02-21 09:32:54 +01:00
{
2025-12-06 23:53:14 +01:00
if ( ! is_array ( $features )) {
return [];
}
$out = [];
foreach ( $features as $f ) {
if ( ! is_array ( $f ))
continue ;
$name = $f [ 'name' ] ? ? null ;
if ( ! is_string ( $name ) || trim ( $name ) === '' )
2025-02-25 16:40:20 +01:00
continue ;
2025-12-06 23:53:14 +01:00
$vals = [];
foreach (( $f [ 'featureValues' ] ? ? []) as $fv ) {
if ( is_array ( $fv ) && isset ( $fv [ 'value' ]) && is_string ( $fv [ 'value' ]) && trim ( $fv [ 'value' ]) !== '' ) {
$vals [] = trim ( $fv [ 'value' ]);
}
2025-02-21 09:32:54 +01:00
}
2025-12-06 23:53:14 +01:00
if ( count ( $vals ) === 0 )
2025-02-25 16:40:20 +01:00
continue ;
2025-12-06 23:53:14 +01:00
2025-12-07 20:09:32 +01:00
// Multiple values: join with comma
2025-12-06 23:53:14 +01:00
$value = implode ( ', ' , array_values ( array_unique ( $vals )));
2025-12-07 20:09:32 +01:00
// Unit/Symbol from Buerklin feature
2025-12-06 23:53:14 +01:00
$unit = $f [ 'featureUnit' ][ 'symbol' ] ? ? null ;
if ( ! is_string ( $unit ) || trim ( $unit ) === '' ) {
$unit = null ;
2025-02-25 16:40:20 +01:00
}
2025-12-06 23:53:14 +01:00
2025-12-07 20:09:32 +01:00
// ParameterDTO parses value field (handles value+unit)
2025-12-06 23:53:14 +01:00
$out [] = ParameterDTO :: parseValueField (
name : $name ,
2025-02-25 16:40:20 +01:00
value : $value ,
2025-12-06 23:53:14 +01:00
unit : $unit ,
symbol : null ,
group : $group
2025-02-25 16:40:20 +01:00
);
2025-02-21 09:32:54 +01:00
}
2025-12-06 23:53:14 +01:00
2025-12-07 20:09:32 +01:00
// deduplicate by name
2025-12-06 23:53:14 +01:00
$byName = [];
foreach ( $out as $p ) {
$byName [ $p -> name ] ? ? = $p ;
}
return array_values ( $byName );
2025-02-21 09:32:54 +01:00
}
2025-12-06 23:53:14 +01:00
2025-02-21 09:32:54 +01:00
public function searchByKeyword ( string $keyword ) : array
{
2025-12-06 23:53:14 +01:00
$keyword = strtoupper ( trim ( $keyword ));
if ( $keyword === '' ) {
return [];
}
$response = $this -> makeAPICall ( '/products/search/' , [
'pageSize' => 50 ,
'currentPage' => 1 ,
'query' => $keyword ,
'sort' => 'relevance' ,
]);
$products = $response [ 'products' ] ? ? [];
2025-12-07 20:09:32 +01:00
// Normal case: products found in search results
2025-12-06 23:53:14 +01:00
if ( is_array ( $products ) && count ( $products ) > 0 ) {
return array_map ( fn ( $p ) => $this -> getPartDetail ( $p ), $products );
}
2025-12-07 20:09:32 +01:00
// Fallback: try direct lookup by code
2025-12-06 23:53:14 +01:00
try {
2025-12-07 20:09:32 +01:00
$product = $this -> getProduct ( $keyword );
2025-12-06 23:53:14 +01:00
return [ $this -> getPartDetail ( $product )];
} catch ( \Throwable $e ) {
return [];
}
2025-02-21 09:32:54 +01:00
}
2025-12-06 23:53:14 +01:00
2025-02-21 09:32:54 +01:00
public function getDetails ( string $id ) : PartDetailDTO
{
2025-12-06 23:53:14 +01:00
// Detail endpoint is /products/{code}/
2025-12-07 20:09:32 +01:00
$response = $this -> getProduct ( $id );
2025-12-06 23:53:14 +01:00
return $this -> getPartDetail ( $response );
2025-02-21 09:32:54 +01:00
}
public function getCapabilities () : array
{
return [
ProviderCapabilities :: BASIC ,
ProviderCapabilities :: PICTURE ,
2025-12-11 16:14:11 +01:00
//ProviderCapabilities::DATASHEET, //currently not implemented
2025-02-21 09:32:54 +01:00
ProviderCapabilities :: PRICE ,
2025-12-06 23:53:14 +01:00
ProviderCapabilities :: FOOTPRINT ,
2025-02-21 09:32:54 +01:00
];
}
2025-12-06 23:53:14 +01:00
private function complianceToParameters ( array $product , ? string $group = 'Compliance' ) : array
{
$params = [];
$add = function ( string $name , $value ) use ( & $params , $group ) {
if ( $value === null )
return ;
if ( is_bool ( $value )) {
$value = $value ? 'Yes' : 'No' ;
} elseif ( is_array ( $value ) || is_object ( $value )) {
// avoid dumping huge structures
return ;
} else {
$value = trim (( string ) $value );
if ( $value === '' )
return ;
}
$params [] = ParameterDTO :: parseValueField (
name : $name ,
value : ( string ) $value ,
unit : null ,
symbol : null ,
group : $group
);
};
2025-12-11 16:14:11 +01:00
$add ( 'RoHS conform' , $product [ 'labelRoHS' ] ? ? null ); // "yes"/"no"
$rawRoHsDate = $product [ 'dateRoHS' ] ? ? null ;
2025-12-11 16:42:37 +01:00
// Try to parse and reformat date to Y-m-d (don't use language-dependent formats)
2025-12-11 16:14:11 +01:00
if ( is_string ( $rawRoHsDate ) && $rawRoHsDate !== '' ) {
try {
$dt = new \DateTimeImmutable ( $rawRoHsDate );
2025-12-11 16:42:37 +01:00
$formatted = $dt -> format ( 'Y-m-d' );
2025-12-11 16:14:11 +01:00
} catch ( \Exception $e ) {
$formatted = $rawRoHsDate ;
}
2025-12-11 16:42:37 +01:00
// Use always the same parameter name (don't use language-dependent names)
2025-12-11 16:14:11 +01:00
$add ( 'RoHS date' , $formatted );
}
$add ( 'SVHC free' , $product [ 'SVHC' ] ? ? null ); // bool
2025-12-06 23:53:14 +01:00
$add ( 'Hazardous good' , $product [ 'hazardousGood' ] ? ? null ); // bool
$add ( 'Hazardous materials' , $product [ 'hazardousMaterials' ] ? ? null ); // bool
2025-12-11 16:14:11 +01:00
2025-12-06 23:53:14 +01:00
$add ( 'Country of origin' , $product [ 'countryOfOrigin' ] ? ? null );
2025-12-11 16:14:11 +01:00
// Customs tariffs code/Zolltarifnummer always as string otherwise "85411000" is stored as "8.5411e+7"
if ( isset ( $product [ 'articleCustomsCode' ])) {
2025-12-11 16:42:37 +01:00
// Rohwert als String
$codeRaw = ( string ) $product [ 'articleCustomsCode' ];
// Optional: nur Ziffern behalten (falls mal Leerzeichen o.ä. drin sind)
$code = preg_replace ( '/\D/' , '' , $codeRaw ) ? ? $codeRaw ;
$code = trim ( $code );
if ( $code !== '' ) {
$params [] = new ParameterDTO (
name : 'Customs code' ,
value_text : $code ,
value_typ : null ,
value_min : null ,
value_max : null ,
unit : null ,
symbol : null ,
group : $group
);
}
2025-12-11 16:14:11 +01:00
}
2025-12-06 23:53:14 +01:00
return $params ;
}
2025-02-21 09:32:54 +01:00
}