209 lines
5.7 KiB
PHP
209 lines
5.7 KiB
PHP
<?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 );
|
|
}
|
|
}
|