all(); $entry = array( 'type' => $type, 'summary' => $summary, 'payload' => $payload, 'recorded_at' => time(), 'recorded_gmt' => gmdate( 'c' ), 'site_url' => home_url( '/' ), 'version' => AIBLACKOUT_VERSION, ); $last = $this->last_of_type( $entries, $type ); if ( null !== $last && $this->is_same_declaration( $last, $entry ) ) { return true; } $entries[] = $entry; // Entries run oldest first, so the cap drops from the front. if ( count( $entries ) > self::MAX_ENTRIES ) { $entries = array_slice( $entries, -self::MAX_ENTRIES ); } return update_option( self::OPTION, $entries, false ); } /** * Removes entries that only repeat what the same type already said. * * @return int Number of entries removed. */ public function prune_duplicates(): int { $entries = $this->all(); if ( count( $entries ) < 2 ) { return 0; } $kept = array(); foreach ( $entries as $entry ) { if ( ! is_array( $entry ) || ! isset( $entry['type'] ) ) { continue; } $last = $this->last_of_type( $kept, (string) $entry['type'] ); if ( null !== $last && $this->is_same_declaration( $last, $entry ) ) { continue; } $kept[] = $entry; } $removed = count( $entries ) - count( $kept ); if ( $removed > 0 ) { update_option( self::OPTION, $kept, false ); } return $removed; } /** * Returns the most recent entry of one type, or null when there is none. * * @param array $entries Entries to search, oldest first. * @param string $type Declaration type. * @return array|null The entry, or null. */ private function last_of_type( array $entries, string $type ): ?array { for ( $i = count( $entries ) - 1; $i >= 0; $i-- ) { if ( isset( $entries[ $i ]['type'] ) && $entries[ $i ]['type'] === $type ) { return $entries[ $i ]; } } return null; } /** * Returns when a declaration type was last changed. * * @param string $type Declaration type. * @return int|null Unix timestamp, or null when never published. */ public function last_changed( string $type ): ?int { $last = $this->last_of_type( $this->all(), $type ); return isset( $last['recorded_at'] ) ? (int) $last['recorded_at'] : null; } /** * Returns entries of one declaration type, oldest first. * * @param string $type Declaration type. * @return array Matching entries. */ public function for_type( string $type ): array { return array_values( array_filter( $this->all(), static function ( $entry ) use ( $type ) { return isset( $entry['type'] ) && $entry['type'] === $type; } ) ); } /** * Returns when a declaration type was first published. * * @param string $type Declaration type. * @return int|null Unix timestamp, or null when never published. */ public function first_published( string $type ): ?int { $entries = $this->for_type( $type ); if ( empty( $entries ) ) { return null; } return isset( $entries[0]['recorded_at'] ) ? (int) $entries[0]['recorded_at'] : null; } /** * Whether two entries describe the same declaration. * * @param array $a First entry. * @param array $b Second entry. * @return bool True when type and payload match. */ private function is_same_declaration( array $a, array $b ): bool { $type_a = isset( $a['type'] ) ? $a['type'] : ''; $type_b = isset( $b['type'] ) ? $b['type'] : ''; if ( $type_a !== $type_b ) { return false; } $payload_a = isset( $a['payload'] ) ? $a['payload'] : array(); $payload_b = isset( $b['payload'] ) ? $b['payload'] : array(); return wp_json_encode( $payload_a ) === wp_json_encode( $payload_b ); } }