v1.0.0
This commit is contained in:
commit
e1f5a47c96
22 changed files with 3155 additions and 0 deletions
211
includes/class-fontdelbosc-combustible-api.php
Normal file
211
includes/class-fontdelbosc-combustible-api.php
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue