>, 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 $raw Cached value. * @return array{incidents: array>, 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> */ 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|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 $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 ); } }