>,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|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|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; } }