cache = $cache; } /** * Builds the public payload. * * @param Dashboard $dashboard Dashboard. * @param int $now Current Unix time. * @return array */ public function build( Dashboard $dashboard, int $now ): array { $settings = $dashboard->settings; $interval = (int) $settings['refresh_interval'] * 60; $items = array(); $seen = array(); $statuses = array(); $updated = 0; foreach ( $dashboard->feeds as $feed ) { $entry = $this->cache->get( $feed['url'] ); $statuses[] = Refresh_Policy::status( $entry, $interval, $now ); if ( null === $entry ) { continue; } $updated = max( $updated, $entry['fetched_at'] ); foreach ( $entry['items'] as $item ) { if ( ! is_array( $item ) || ! is_string( $item['title'] ?? null ) ) { continue; } $dedupe = ( is_string( $item['url'] ?? null ) && '' !== $item['url'] ) ? $item['url'] : (string) ( $item['id'] ?? $item['title'] ); if ( isset( $seen[ $dedupe ] ) ) { continue; } $seen[ $dedupe ] = true; $items[] = array( 'id' => (string) ( $item['id'] ?? '' ), 'title' => $item['title'], 'summary' => (string) ( $item['summary'] ?? '' ), 'url' => (string) ( $item['url'] ?? '' ), 'image' => (string) ( $item['image'] ?? '' ), 'source' => '' !== $feed['label'] ? $feed['label'] : (string) ( $item['source'] ?? '' ), 'color' => Feed_List::sanitize_color( $feed['color'] ?? '' ), 'published' => (int) ( $item['published'] ?? 0 ), ); }//end foreach }//end foreach // Newest first; undated items last. usort is stable. usort( $items, static function ( array $a, array $b ): int { if ( 0 === $a['published'] || 0 === $b['published'] ) { return ( 0 === $a['published'] ) <=> ( 0 === $b['published'] ); } return $b['published'] <=> $a['published']; } ); $has_items = array() !== $items; $items = array_slice( $items, 0, (int) $settings['max_items'] ); foreach ( $items as $index => $item ) { $items[ $index ]['published'] = $item['published'] > 0 ? self::iso( $item['published'] ) : null; } return array( 'v' => self::VERSION, 'status' => self::status( $has_items, $statuses ), 'feed_count' => count( $dashboard->feeds ), 'updated' => $updated > 0 ? self::iso( $updated ) : null, 'settings' => Settings_Schema::public_settings( $settings ), 'items' => $items, ); } /** * Overall status: pending until there is content, stale if any feed is stale or failing. * * @param bool $has_items Whether any item is cached. * @param array $statuses Per-feed status. */ private static function status( bool $has_items, array $statuses ): string { if ( ! $has_items ) { return 'pending'; } foreach ( $statuses as $status ) { if ( Refresh_Policy::STATUS_STALE === $status || Refresh_Policy::STATUS_FAILING === $status ) { return 'stale'; } } return 'ok'; } /** * ISO 8601 UTC time. * * @param int $timestamp Unix time. */ private static function iso( int $timestamp ): string { return gmdate( 'Y-m-d\TH:i:s\Z', $timestamp ); } }