211 lines
6.1 KiB
PHP
211 lines
6.1 KiB
PHP
<?php
|
|
/**
|
|
* MITECO / MINETUR ServiciosRestCarburantes API client.
|
|
*
|
|
* @package FontdelboscCombustible
|
|
*/
|
|
|
|
declare( strict_types=1 );
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
/**
|
|
* Fetches and normalizes province station data and the product catalog.
|
|
*/
|
|
final class Fontdelbosc_Combustible_Api {
|
|
|
|
/**
|
|
* Hardcoded province ID: Barcelona (08).
|
|
*/
|
|
public const PROVINCE_ID = '08';
|
|
|
|
/**
|
|
* Primary API base URL.
|
|
*/
|
|
private const BASE_PRIMARY = 'https://energia.serviciosmin.gob.es/ServiciosRestCarburantes/PreciosCarburantes/';
|
|
|
|
/**
|
|
* Fallback API base URL.
|
|
*/
|
|
private const BASE_FALLBACK = 'https://sedeaplicaciones.minetur.gob.es/ServiciosRESTCarburantes/PreciosCarburantes/';
|
|
|
|
/**
|
|
* Per-request timeout in seconds. Worst case (primary + fallback) stays bounded.
|
|
*/
|
|
private const TIMEOUT = 8;
|
|
|
|
/**
|
|
* Fetches all stations of the province, normalized with distance.
|
|
*
|
|
* @return array{fecha:string,stations:array<int,array<string,mixed>>,fetched_at:int,duration:float}|null Null on total failure.
|
|
*/
|
|
public static function fetch_province(): ?array {
|
|
$started = microtime( true );
|
|
$body = self::get( 'EstacionesTerrestres/FiltroProvincia/' . self::PROVINCE_ID );
|
|
|
|
if ( null === $body ) {
|
|
return null;
|
|
}
|
|
|
|
$decoded = json_decode( $body, true );
|
|
|
|
if ( ! is_array( $decoded ) ) {
|
|
return null;
|
|
}
|
|
|
|
$fecha = Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $decoded, 'Fecha' ) ) ?? '';
|
|
$raw_list = Fontdelbosc_Combustible_Payload::arr( Fontdelbosc_Combustible_Payload::get( $decoded, 'ListaEESSPrecio' ) );
|
|
$stations = array();
|
|
|
|
foreach ( $raw_list as $raw ) {
|
|
$station = self::normalize_station( $raw );
|
|
|
|
if ( null !== $station ) {
|
|
$stations[] = $station;
|
|
}
|
|
}
|
|
|
|
return array(
|
|
'fecha' => $fecha,
|
|
'stations' => $stations,
|
|
'fetched_at' => time(),
|
|
'duration' => microtime( true ) - $started,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Fetches the petroleum product catalog.
|
|
*
|
|
* @return array<int,array{id:int,name:string,abbr:string}>|null Null on total failure.
|
|
*/
|
|
public static function fetch_products(): ?array {
|
|
$body = self::get( 'Listados/ProductosPetroliferos/' );
|
|
|
|
if ( null === $body ) {
|
|
return null;
|
|
}
|
|
|
|
$decoded = json_decode( $body, true );
|
|
|
|
if ( ! is_array( $decoded ) ) {
|
|
return null;
|
|
}
|
|
|
|
$products = array();
|
|
|
|
foreach ( $decoded as $entry ) {
|
|
$id = Fontdelbosc_Combustible_Payload::int( Fontdelbosc_Combustible_Payload::get( $entry, 'IDProducto' ) );
|
|
|
|
if ( null === $id ) {
|
|
continue;
|
|
}
|
|
|
|
$products[] = array(
|
|
'id' => $id,
|
|
'name' => Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $entry, 'NombreProducto' ) ) ?? (string) $id,
|
|
'abbr' => Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $entry, 'NombreProductoAbreviatura' ) ) ?? '',
|
|
);
|
|
}
|
|
|
|
return $products;
|
|
}
|
|
|
|
/**
|
|
* GETs a path against the primary base URL, falling back to the legacy host.
|
|
*
|
|
* @param string $path API path relative to the base URL.
|
|
* @return string|null Response body, or null when both hosts fail.
|
|
*/
|
|
private static function get( string $path ): ?string {
|
|
foreach ( array( self::BASE_PRIMARY, self::BASE_FALLBACK ) as $base ) {
|
|
$response = wp_remote_get(
|
|
$base . $path,
|
|
array(
|
|
'timeout' => self::TIMEOUT,
|
|
'headers' => array( 'Accept' => 'application/json' ),
|
|
)
|
|
);
|
|
|
|
if ( is_wp_error( $response ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
|
continue;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body( $response );
|
|
|
|
if ( '' !== $body ) {
|
|
return $body;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Normalizes one raw station entry; null when coordinates are unusable.
|
|
*
|
|
* @param mixed $raw Raw station.
|
|
* @return array<string,mixed>|null
|
|
*/
|
|
private static function normalize_station( mixed $raw ): ?array {
|
|
if ( ! is_array( $raw ) ) {
|
|
return null;
|
|
}
|
|
|
|
$lat = self::coord( Fontdelbosc_Combustible_Payload::get( $raw, 'Latitud' ) );
|
|
$lon = self::coord( Fontdelbosc_Combustible_Payload::get( $raw, 'Longitud (WGS84)' ) );
|
|
|
|
if ( null === $lat || null === $lon ) {
|
|
return null;
|
|
}
|
|
|
|
$prices = array();
|
|
|
|
foreach ( $raw as $key => $value ) {
|
|
if ( ! is_string( $key ) || ! str_starts_with( $key, 'Precio ' ) ) {
|
|
continue;
|
|
}
|
|
|
|
$text = Fontdelbosc_Combustible_Payload::str( $value );
|
|
$price = null === $text ? null : Fontdelbosc_Combustible_Payload::float( str_replace( ',', '.', $text ) );
|
|
|
|
if ( null !== $price && $price > 0.0 ) {
|
|
$prices[ substr( $key, 7 ) ] = $price;
|
|
}
|
|
}
|
|
|
|
return array(
|
|
'id' => Fontdelbosc_Combustible_Payload::int( Fontdelbosc_Combustible_Payload::get( $raw, 'IDEESS' ) ) ?? 0,
|
|
'brand' => Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $raw, 'Rótulo' ) ) ?? '',
|
|
'address' => Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $raw, 'Dirección' ) ) ?? '',
|
|
'locality' => Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $raw, 'Localidad' ) ) ?? '',
|
|
'municipality' => Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $raw, 'Municipio' ) ) ?? '',
|
|
'cp' => Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $raw, 'C.P.' ) ) ?? '',
|
|
'schedule' => Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $raw, 'Horario' ) ) ?? '',
|
|
'tipo_venta' => Fontdelbosc_Combustible_Payload::str( Fontdelbosc_Combustible_Payload::get( $raw, 'Tipo Venta' ) ) ?? '',
|
|
'lat' => $lat,
|
|
'lon' => $lon,
|
|
'distance' => Fontdelbosc_Combustible_Geo::haversine( $lat, $lon ),
|
|
'prices' => $prices,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Parses a comma-decimal coordinate string.
|
|
*
|
|
* @param mixed $value Raw coordinate value.
|
|
*/
|
|
private static function coord( mixed $value ): ?float {
|
|
$text = Fontdelbosc_Combustible_Payload::str( $value );
|
|
$float = null === $text ? null : Fontdelbosc_Combustible_Payload::float( str_replace( ',', '.', $text ) );
|
|
|
|
if ( null === $float || abs( $float ) > 180.0 ) {
|
|
return null;
|
|
}
|
|
|
|
return $float;
|
|
}
|
|
}
|