0, 'cache_hits' => 0, 'lazy_loads' => 0, 'memory_saved' => 0, 'time_saved' => 0, ); /** * Query cache * * @var array. */ private array $query_cache = array(); /** * Optimization settings * * @var array. */ private array $settings = array( 'enable_query_cache' => true, 'enable_lazy_loading' => true, 'enable_object_cache' => true, 'cache_expiration' => 3600, 'max_memory_usage' => 128, // MB. ); /** * Constructor * * @param Cache_Manager|null $cache_manager Cache manager instance. */ public function __construct( ?Cache_Manager $cache_manager = null ) { $this->cache_manager = $cache_manager ?? new Cache_Manager(); $this->lazy_loader = new Lazy_Loader(); $this->load_settings(); $this->init_optimizations(); } /** * Initialize performance optimizations * * @return void. */ public function init_optimizations(): void { // Database query optimization. if ( $this->settings['enable_query_cache'] ) { add_filter( 'posts_pre_query', array( $this, 'optimize_posts_query' ), 10, 2 ); add_filter( 'pre_get_posts', array( $this, 'optimize_post_queries' ) ); } // Memory optimization. add_action( 'wp_loaded', array( $this, 'optimize_memory_usage' ) ); // Asset optimization hooks. add_action( 'wp_enqueue_scripts', array( $this, 'optimize_frontend_assets' ), 5 ); add_action( 'admin_enqueue_scripts', array( $this, 'optimize_admin_assets' ), 5 ); // Cleanup hooks. add_action( 'wp_scheduled_delete', array( $this, 'cleanup_performance_data' ) ); // Performance monitoring. if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { add_action( 'shutdown', array( $this, 'log_performance_stats' ) ); } } /** * Optimize posts query with caching * * @param array|null $posts Posts array. * @param \WP_Query $query WordPress query object. * @return array|null. */ public function optimize_posts_query( $posts, \WP_Query $query ): ?array { // Only optimize our plugin's queries. if ( ! $this->is_plugin_query( $query ) ) { return $posts; } $cache_key = $this->generate_query_cache_key( $query ); // Try to get from cache. $cached_posts = $this->cache_manager->get( $cache_key, null, $this->settings['cache_expiration'], 'posts' ); if ( false !== $cached_posts ) { ++$this->stats['cache_hits']; accessibility_audio_tts_debug_log( "Posts query cache hit: {$cache_key}" ); return $cached_posts; } return $posts; // Let WordPress handle the query. } /** * Optimize post queries * * @param \WP_Query $query WordPress query object. * @return void. */ public function optimize_post_queries( \WP_Query $query ): void { if ( ! $this->is_plugin_query( $query ) ) { return; } // Optimize meta queries. $meta_query = $query->get( 'meta_query' ); if ( ! empty( $meta_query ) ) { $optimized_meta_query = $this->optimize_meta_query( $meta_query ); $query->set( 'meta_query', $optimized_meta_query ); ++$this->stats['queries_optimized']; } // Limit fields if possible. if ( ! $query->get( 'fields' ) ) { $query->set( 'fields', 'ids' ); // Only get IDs if full posts not needed. } // Add caching for the query results. add_action( 'posts_selection', function ( $selection ) use ( $query ) { $cache_key = $this->generate_query_cache_key( $query ); $this->cache_manager->set( $cache_key, $selection, $this->settings['cache_expiration'], 'posts' ); } ); } /** * Optimize memory usage * * @return void. */ public function optimize_memory_usage(): void { $current_memory = memory_get_usage( true ) / 1024 / 1024; // MB. if ( $current_memory > $this->settings['max_memory_usage'] ) { // Clear non-essential caches. $this->cache_manager->invalidate_group( 'assets' ); // Force garbage collection. if ( function_exists( 'gc_collect_cycles' ) ) { $collected = gc_collect_cycles(); $this->stats['memory_saved'] += $collected; accessibility_audio_tts_debug_log( "Garbage collected {$collected} cycles" ); } } } /** * Optimize frontend assets * * @return void. */ public function optimize_frontend_assets(): void { // Only load assets where needed. if ( ! $this->should_load_frontend_assets() ) { return; } // Use lazy loading for non-critical assets. if ( $this->settings['enable_lazy_loading'] ) { $this->setup_lazy_asset_loading( 'frontend' ); } } /** * Optimize admin assets * * @param string $hook Hook name. * @return void. */ public function optimize_admin_assets( string $hook ): void { // Only load on plugin pages. if ( ! $this->is_plugin_admin_page( $hook ) ) { return; } // Use lazy loading for admin assets. if ( $this->settings['enable_lazy_loading'] ) { $this->setup_lazy_asset_loading( 'admin' ); } } /** * Setup lazy asset loading * * @param string $context 'frontend' or 'admin'. * @return void. */ private function setup_lazy_asset_loading( string $context ): void { $assets = $this->get_lazy_loadable_assets( $context ); foreach ( $assets as $asset ) { $this->lazy_loader::register_component( $asset['key'], $asset['class'], $asset['args'] ?? array(), array( 'singleton' => true ) ); } $this->stats['lazy_loads'] += count( $assets ); } /** * Get assets that can be lazy loaded * * @param string $context Context identifier. * @return array. */ private function get_lazy_loadable_assets( string $context ): array { $assets = array(); if ( 'frontend' === $context ) { $assets = array( array( 'key' => 'player_renderer', 'class' => '\Accessibility_Audio_TTS\Frontend\Player_Renderer', ), array( 'key' => 'listening_tracker', 'class' => '\Accessibility_Audio_TTS\Frontend\Listening_Tracker', ), ); } elseif ( 'admin' === $context ) { $assets = array( array( 'key' => 'conversion_manager', 'class' => '\Accessibility_Audio_TTS\Admin\Conversion_Manager', ), array( 'key' => 'statistics_calculator', 'class' => '\Accessibility_Audio_TTS\Admin\Statistics_Calculator', ), ); } return $assets; } /** * Optimize meta query * * @param array $meta_query Meta query array. * @return array. */ private function optimize_meta_query( array $meta_query ): array { // Add indexes hints if supported. foreach ( $meta_query as &$clause ) { if ( is_array( $clause ) && isset( $clause['key'] ) ) { // Ensure we're using the most efficient comparison. if ( ! isset( $clause['compare'] ) ) { $clause['compare'] = '='; } // Use EXISTS for checking meta key existence. if ( '=' === $clause['compare'] && empty( $clause['value'] ) ) { $clause['compare'] = 'EXISTS'; unset( $clause['value'] ); } } } return $meta_query; } /** * Generate cache key for query * * @param \WP_Query $query WordPress query object. * @return string. */ private function generate_query_cache_key( \WP_Query $query ): string { $key_parts = array( 'post_type' => $query->get( 'post_type' ), 'meta_query' => $query->get( 'meta_query' ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- only used to compose cache key 'posts_per_page' => $query->get( 'posts_per_page' ), 'paged' => $query->get( 'paged' ), 'orderby' => $query->get( 'orderby' ), 'order' => $query->get( 'order' ), ); return 'query_' . md5( serialize( $key_parts ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Safe use for cache key generation only. } /** * Check if query is related to our plugin * * @param \WP_Query $query WordPress query object. * @return bool. */ private function is_plugin_query( \WP_Query $query ): bool { $meta_query = $query->get( 'meta_query' ); if ( empty( $meta_query ) ) { return false; } // Check if any meta query is for our plugin. foreach ( $meta_query as $clause ) { if ( is_array( $clause ) && isset( $clause['key'] ) ) { if ( strpos( $clause['key'], 'accessibility_audio_tts_' ) === 0 ) { return true; } } } return false; } /** * Check if should load frontend assets * * @return bool. */ private function should_load_frontend_assets(): bool { global $post; // Check if current post has audio content. if ( $post && has_shortcode( $post->post_content, 'accessibility_audio_tts_player' ) ) { return true; } // Check if any post on the page has audio. if ( is_home() || is_archive() ) { $cache_key = 'has_audio_' . get_queried_object_id(); return $this->cache_manager->get( $cache_key, function () { global $wp_query; if ( ! $wp_query->posts ) { return false; } foreach ( $wp_query->posts as $post ) { if ( has_shortcode( $post->post_content, 'accessibility_audio_tts_player' ) ) { return true; } // Check meta for audio files. if ( get_post_meta( $post->ID, 'accessibility_audio_tts_file', true ) ) { return true; } } return false; }, 1800, 'posts' ); } return false; } /** * Check if current admin page is plugin page * * @param string $hook Hook name. * @return bool. */ private function is_plugin_admin_page( string $hook ): bool { return strpos( $hook, 'accessibility-audio-tts' ) !== false; } /** * Cleanup performance data * * @return void. */ public function cleanup_performance_data(): void { // Clear old cache entries. $this->cache_manager->cleanup_expired(); // Clear query cache. $this->query_cache = array(); accessibility_audio_tts_debug_log( 'Performance data cleanup completed' ); } /** * Log performance statistics * * @return void. */ public function log_performance_stats(): void { if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) { return; } $stats = $this->get_performance_stats(); accessibility_audio_tts_debug_log( 'Performance Stats: ' . wp_json_encode( $stats ) ); } /** * Get performance statistics * * @return array. */ public function get_performance_stats(): array { $memory_usage = memory_get_usage( true ) / 1024 / 1024; // MB. $peak_memory = memory_get_peak_usage( true ) / 1024 / 1024; // MB. return array_merge( $this->stats, array( 'memory_usage_mb' => round( $memory_usage, 2 ), 'peak_memory_mb' => round( $peak_memory, 2 ), 'cache_stats' => $this->cache_manager->get_stats(), 'lazy_loader_stats' => Lazy_Loader::get_stats(), 'settings' => $this->settings, ) ); } /** * Load optimization settings * * @return void. */ private function load_settings(): void { $saved_settings = get_option( 'accessibility_audio_tts_performance_settings', array() ); $this->settings = array_merge( $this->settings, $saved_settings ); } /** * Update optimization settings * * @param array $new_settings New settings array. * @return bool. */ public function update_settings( array $new_settings ): bool { $this->settings = array_merge( $this->settings, $new_settings ); return update_option( 'accessibility_audio_tts_performance_settings', $this->settings ); } /** * Enable/disable query caching * * @param bool $enabled Enable flag. * @return void. */ public function set_query_cache_enabled( bool $enabled ): void { $this->settings['enable_query_cache'] = $enabled; $this->update_settings( array( 'enable_query_cache' => $enabled ) ); } /** * Enable/disable lazy loading * * @param bool $enabled Enable flag. * @return void. */ public function set_lazy_loading_enabled( bool $enabled ): void { $this->settings['enable_lazy_loading'] = $enabled; $this->update_settings( array( 'enable_lazy_loading' => $enabled ) ); } /** * Set cache expiration time * * @param int $seconds Expiration in seconds. * @return void. */ public function set_cache_expiration( int $seconds ): void { $this->settings['cache_expiration'] = $seconds; $this->update_settings( array( 'cache_expiration' => $seconds ) ); } /** * Set maximum memory usage * * @param int $mb Max memory in MB. * @return void. */ public function set_max_memory_usage( int $mb ): void { $this->settings['max_memory_usage'] = $mb; $this->update_settings( array( 'max_memory_usage' => $mb ) ); } /** * Preload critical data * * @return void. */ public function preload_critical_data(): void { $critical_keys = array( 'plugin_settings', 'active_voices', ); foreach ( $critical_keys as $key ) { $this->cache_manager->get( $key, function () use ( $key ) { return $this->get_critical_data( $key ); }, $this->settings['cache_expiration'] ); } accessibility_audio_tts_debug_log( 'Preloaded critical data' ); } /** * Get critical data by key * * @param string $key Cache key. * @return mixed. */ private function get_critical_data( string $key ): mixed { switch ( $key ) { case 'plugin_settings': return get_option( 'accessibility_audio_tts_settings', array() ); case 'active_voices': // This would get available voices from API. return array(); default: return null; } } /** * Reset performance statistics * * @return void. */ public function reset_stats(): void { $this->stats = array( 'queries_optimized' => 0, 'cache_hits' => 0, 'lazy_loads' => 0, 'memory_saved' => 0, 'time_saved' => 0, ); Cache_Manager::reset_stats(); Lazy_Loader::reset(); accessibility_audio_tts_debug_log( 'Performance statistics reset' ); } /** * Get optimization recommendations * * @return array. */ public function get_optimization_recommendations(): array { $recommendations = array(); $stats = $this->get_performance_stats(); // Memory usage recommendations. if ( $stats['memory_usage_mb'] > $this->settings['max_memory_usage'] ) { $recommendations[] = array( 'type' => 'memory', 'severity' => 'high', 'message' => 'Memory usage is high. Consider enabling more aggressive caching.', 'action' => 'increase_cache_expiration', ); } // Cache efficiency recommendations. $cache_stats = $stats['cache_stats']; if ( $cache_stats['hit_ratio'] < 50 ) { $recommendations[] = array( 'type' => 'cache', 'severity' => 'medium', 'message' => 'Cache hit ratio is low. Consider preloading critical data.', 'action' => 'preload_data', ); } // Query optimization recommendations. if ( 0 === $stats['queries_optimized'] && $this->settings['enable_query_cache'] ) { $recommendations[] = array( 'type' => 'queries', 'severity' => 'low', 'message' => 'No queries have been optimized. Check if query caching is working.', 'action' => 'check_query_cache', ); } return $recommendations; } }