This commit is contained in:
Javier Casares 2026-10-12 15:30:04 +00:00
commit 1ef08bd731
27 changed files with 4854 additions and 0 deletions

View file

@ -0,0 +1,209 @@
<?php
/**
* Orchestration: caching, stale-while-error, fetch status.
*
* @package Fontdelbosc_Transit
*/
defined( 'ABSPATH' ) || exit;
/**
* Coordinates API access with transients.
*/
class Fontdelbosc_Transit_Data_Manager {
private const INCIDENTS_TRANSIENT = 'fontdelbosc_transit_incidents';
private const INCIDENTS_STALE = 'fontdelbosc_transit_incidents_stale';
private const ZBE_INDEX_TRANSIENT = 'fontdelbosc_transit_zbe_index';
private const STALE_WINDOW = 6 * HOUR_IN_SECONDS;
private const ZBE_STALE_WINDOW = 7 * DAY_IN_SECONDS;
/**
* Get incidents (cached, stale-while-error).
*
* @return array{incidents: array<int, array<mixed>>, source: string, publication_time: string}|null
*/
public static function get_incidents(): ?array {
$cached = get_transient( self::INCIDENTS_TRANSIENT );
if ( is_array( $cached ) ) {
return self::shape_incidents( $cached );
}
$fresh = Fontdelbosc_Transit_Api_Sct::get_incidents();
if ( null === $fresh ) {
$stale = get_transient( self::INCIDENTS_STALE );
if ( is_array( $stale ) ) {
return self::shape_incidents( $stale );
}
return null;
}
$options = Fontdelbosc_Transit_Plugin::get_options();
$ttl = max( 60, Fontdelbosc_Transit_Payload::int( $options['incident_cache_ttl'] ?? null ) ?? 300 );
set_transient( self::INCIDENTS_TRANSIENT, $fresh, $ttl );
set_transient( self::INCIDENTS_STALE, $fresh, self::STALE_WINDOW );
self::record_status(
'incidents',
array(
'last_fetch' => time(),
'source' => $fresh['source'],
'publication_time' => $fresh['publication_time'],
'count' => count( $fresh['incidents'] ),
)
);
return $fresh;
}
/**
* Coerce a cached incidents payload back into its typed shape.
*
* @param array<mixed> $raw Cached value.
* @return array{incidents: array<int, array<mixed>>, source: string, publication_time: string}
*/
private static function shape_incidents( array $raw ): array {
$incidents = array();
foreach ( Fontdelbosc_Transit_Payload::arr( Fontdelbosc_Transit_Payload::get( $raw, 'incidents' ) ) as $incident ) {
if ( is_array( $incident ) ) {
$incidents[] = $incident;
}
}
return array(
'incidents' => $incidents,
'source' => Fontdelbosc_Transit_Payload::str( Fontdelbosc_Transit_Payload::get( $raw, 'source' ) ) ?? '',
'publication_time' => Fontdelbosc_Transit_Payload::str( Fontdelbosc_Transit_Payload::get( $raw, 'publication_time' ) ) ?? '',
);
}
/**
* Get the zones selected in the options (cached, stale-while-error).
*
* @return array<int, array<mixed>>
*/
public static function get_zbes(): array {
$options = Fontdelbosc_Transit_Plugin::get_options();
$selected = Fontdelbosc_Transit_Payload::arr( $options['zbe_municipalities'] ?? Fontdelbosc_Transit_Api_Zbe::DEFAULT_CATALAN_ZONES );
$ttl = max( HOUR_IN_SECONDS, Fontdelbosc_Transit_Payload::int( $options['zbe_cache_ttl'] ?? null ) ?? DAY_IN_SECONDS );
$zones = array();
foreach ( $selected as $slug ) {
$slug = Fontdelbosc_Transit_Api_Zbe::sanitize_slug( Fontdelbosc_Transit_Payload::str( $slug ) ?? '' );
if ( '' === $slug ) {
continue;
}
$key = 'fontdelbosc_transit_zbe_' . $slug;
$cache = get_transient( $key );
if ( is_array( $cache ) ) {
$cache['slug'] = $slug;
$zones[] = $cache;
continue;
}
$zone = Fontdelbosc_Transit_Api_Zbe::get_zone( $slug );
if ( null === $zone ) {
$stale = get_transient( $key . '_stale' );
if ( is_array( $stale ) ) {
$stale['slug'] = $slug;
$zones[] = $stale;
}
continue;
}
$zone['slug'] = $slug;
set_transient( $key, $zone, $ttl );
set_transient( $key . '_stale', $zone, self::ZBE_STALE_WINDOW );
$zones[] = $zone;
}
self::record_status(
'zbe',
array(
'last_fetch' => time(),
'zones' => count( $zones ),
)
);
return $zones;
}
/**
* Get the full zone index for the admin (cached 24 h).
*
* @return array<int, string>|null
*/
public static function get_zbe_index(): ?array {
$cached = get_transient( self::ZBE_INDEX_TRANSIENT );
if ( is_array( $cached ) ) {
$slugs = array();
foreach ( $cached as $slug ) {
$slug = Fontdelbosc_Transit_Payload::str( $slug );
if ( null !== $slug ) {
$slugs[] = $slug;
}
}
return $slugs;
}
$index = Fontdelbosc_Transit_Api_Zbe::get_index();
if ( null !== $index ) {
set_transient( self::ZBE_INDEX_TRANSIENT, $index, DAY_IN_SECONDS );
}
return $index;
}
/**
* Flush every data transient.
*/
public static function flush(): void {
delete_transient( self::INCIDENTS_TRANSIENT );
delete_transient( self::INCIDENTS_STALE );
delete_transient( self::ZBE_INDEX_TRANSIENT );
$options = Fontdelbosc_Transit_Plugin::get_options();
$slugs = array_map(
static fn( mixed $slug ): string => Fontdelbosc_Transit_Payload::str( $slug ) ?? '',
array_merge(
Fontdelbosc_Transit_Payload::arr( $options['zbe_municipalities'] ?? array() ),
self::get_zbe_index() ?? array()
)
);
foreach ( array_unique( $slugs ) as $slug ) {
$key = 'fontdelbosc_transit_zbe_' . Fontdelbosc_Transit_Api_Zbe::sanitize_slug( Fontdelbosc_Transit_Payload::str( $slug ) ?? '' );
delete_transient( $key );
delete_transient( $key . '_stale' );
}
}
/**
* Record a fetch status entry.
*
* @param string $stream Stream name (incidents, zbe).
* @param array<string, mixed> $data Status payload.
*/
private static function record_status( string $stream, array $data ): void {
$status = Fontdelbosc_Transit_Payload::arr( get_option( 'fontdelbosc_transit_status', array() ) );
$status[ $stream ] = $data;
update_option( 'fontdelbosc_transit_status', $status, false );
}
}