'int', 'ok' => 'bool', 'kind' => 'word', 'http' => 'int', 'code' => 'word', 'model' => 'word', 'scope' => 'word', 'attempts' => 'int', 'waited' => 'float', 'ms' => 'int', 'in' => 'int', 'out' => 'int', 'chars' => 'int', 'request_id' => 'word', ); /** * Adds an entry; unknown fields are dropped. * * @param array $entry Entry. * @return array The entry as stored. */ public function add( array $entry ) { $clean = array( 'time' => time() ); foreach ( self::FIELDS as $key => $type ) { if ( ! array_key_exists( $key, $entry ) || null === $entry[ $key ] ) { continue; } $v = $entry[ $key ]; if ( ! is_scalar( $v ) ) { continue; } switch ( $type ) { case 'int': $clean[ $key ] = (int) $v; break; case 'float': $clean[ $key ] = round( (float) $v, 1 ); break; case 'bool': $clean[ $key ] = (bool) $v; break; default: // Short identifiers only (model IDs, error codes, request IDs); never free text. $clean[ $key ] = Text::substr( (string) preg_replace( '/[^A-Za-z0-9_\.\-:\/]/', '', HttpProvider::clean( (string) $v ) ), 0, 64 ); } } OptionUpdate::change( self::OPTION, static function ( array $log ) use ( $clean ) { $log[] = $clean; return array_slice( $log, -self::KEEP ); } ); if ( empty( $clean['ok'] ) && defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { $parts = array(); foreach ( $clean as $k => $v ) { $parts[] = $k . '=' . ( is_bool( $v ) ? ( $v ? '1' : '0' ) : $v ); } // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only with WP_DEBUG_LOG on; the entry holds codes and counts, never keys or client data. error_log( '[TurtleReport AI] ' . implode( ' ', $parts ) ); } return $clean; } /** * Entries, oldest first. * * @return array[] */ public function recent() { $log = get_option( self::OPTION, array() ); return is_array( $log ) ? array_values( array_filter( $log, 'is_array' ) ) : array(); } /** * Removes all entries. * * @return void */ public function clear() { delete_option( self::OPTION ); } }