maybe_handle_option_upgrade(); $this->init_hooks(); } /** * Initialize hooks - simplified approach. */ private function init_hooks() { // Load admin settings page only if needed. if ( is_admin() ) { require_once QUEUE_OPTIMIZER_PLUGIN_DIR . 'admin/class-settings-page.php'; Queue_Optimizer_Settings_Page::get_instance(); } // Apply the three essential ActionScheduler optimization filters. // This is the core functionality that makes image processing fast. add_filter( 'action_scheduler_queue_runner_time_limit', array( $this, 'set_time_limit' ) ); add_filter( 'action_scheduler_queue_runner_concurrent_batches', array( $this, 'set_concurrent_batches' ) ); add_filter( 'wp_image_editors', array( $this, 'set_image_editor' ) ); // Add hooks for post-upload processing. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); add_action( 'wp_ajax_queue_optimizer_upload_complete', array( $this, 'handle_upload_complete_ajax' ) ); add_filter( 'wp_generate_attachment_metadata', array( $this, 'on_attachment_metadata_generated' ), 999, 2 ); // Plugin activation hook. register_activation_hook( QUEUE_OPTIMIZER_PLUGIN_DIR . '365i-queue-optimizer.php', array( __CLASS__, 'activate' ) ); } /** * Display WordPress version compatibility notice. */ public function display_version_notice() { echo '

' . sprintf( /* translators: %s: minimum required WordPress version */ esc_html__( '365i Queue Optimizer requires WordPress version %s or higher.', '365i-queue-optimizer' ), esc_html( QUEUE_OPTIMIZER_MIN_WP_VERSION ) ) . '

'; } /** * Enqueue admin scripts for media uploader detection. */ public function enqueue_admin_scripts() { // Only enqueue on media-related pages. $screen = get_current_screen(); if ( ! $screen || ! in_array( $screen->id, array( 'upload', 'post', 'page' ), true ) ) { return; } wp_enqueue_script( 'queue-optimizer-upload-complete', plugin_dir_url( QUEUE_OPTIMIZER_PLUGIN_DIR . '365i-queue-optimizer.php' ) . 'assets/js/upload-complete-trigger.js', array( 'jquery', 'media-views' ), '1.2.0', true ); wp_localize_script( 'queue-optimizer-upload-complete', 'QueueOptimizer', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'queue-optimizer-upload-complete' ), ) ); } /** * Handle the AJAX request when uploads complete. */ public function handle_upload_complete_ajax() { // Verify nonce. check_ajax_referer( 'queue-optimizer-upload-complete', 'nonce' ); // Check if post-upload processing is enabled. $enabled = get_option( 'queue_optimizer_post_upload_processing', true ); if ( ! $enabled ) { wp_send_json_success( 'Processing disabled by settings.' ); return; } // Trigger ActionScheduler to run now. if ( class_exists( 'ActionScheduler' ) ) { do_action( 'action_scheduler_run_queue', 'upload-complete-trigger' ); do_action( 'queue_optimizer_processing_triggered' ); } wp_send_json_success( 'Queue processing triggered.' ); } /** * Maybe handle option name upgrade for backward compatibility. */ private function maybe_handle_option_upgrade() { // Check if the old option exists but the new one doesn't. if ( get_option( 'queue_optimizer_immediate_processing' ) !== false && get_option( 'queue_optimizer_post_upload_processing' ) === false ) { // Copy value from old option to new option. $old_value = get_option( 'queue_optimizer_immediate_processing' ); add_option( 'queue_optimizer_post_upload_processing', $old_value ); // Remove old option. delete_option( 'queue_optimizer_immediate_processing' ); } } /** * Set ActionScheduler time limit. * * @param int $time_limit The default time limit. * @return int Modified time limit. */ public function set_time_limit( $time_limit ) { $value = get_option( 'queue_optimizer_time_limit', 60 ); $validated = $this->validate_int_option( $value, 10, 300, 60 ); /** * Filter the ActionScheduler time limit. * * @param int $validated The validated time limit value. * @param int $value The original option value. * @param int $time_limit The default time limit. */ return apply_filters( 'queue_optimizer_time_limit', $validated, $value, $time_limit ); } /** * Set ActionScheduler concurrent batches. * * @param int $batches The default number of concurrent batches. * @return int Modified number of concurrent batches. */ public function set_concurrent_batches( $batches ) { $value = get_option( 'queue_optimizer_concurrent_batches', 4 ); $validated = $this->validate_int_option( $value, 1, 10, 4 ); /** * Filter the ActionScheduler concurrent batches. * * @param int $validated The validated concurrent batches value. * @param int $value The original option value. * @param int $batches The default number of batches. */ return apply_filters( 'queue_optimizer_concurrent_batches', $validated, $value, $batches ); } /** * Set image editor priority. * * @param array $editors Array of image editor class names. * @return array Modified array with preferred editor prioritized. */ public function set_image_editor( $editors ) { $allowed = array( 'WP_Image_Editor_GD', 'WP_Image_Editor_Imagick' ); $preferred = get_option( 'queue_optimizer_image_engine', 'WP_Image_Editor_GD' ); if ( ! in_array( $preferred, $allowed, true ) ) { $preferred = 'WP_Image_Editor_GD'; } $result = array_merge( array( $preferred ), array_diff( $editors, array( $preferred ) ) ); /** * Filter the image editor priority array. * * @param array $result The reordered editors array. * @param string $preferred The preferred editor. * @param array $editors The original editors array. */ return apply_filters( 'queue_optimizer_image_editors', $result, $preferred, $editors ); } /** * Process attachment metadata for single uploads (fallback). * * @param array $metadata Attachment metadata. * @param int $attachment_id Attachment ID. * @return array Unmodified metadata. */ public function on_attachment_metadata_generated( $metadata, $attachment_id ) { // Check if it's an image. if ( ! wp_attachment_is_image( $attachment_id ) ) { return $metadata; } // Check if post-upload processing is enabled. $enabled = get_option( 'queue_optimizer_post_upload_processing', true ); if ( ! $enabled ) { return $metadata; } // Only trigger for non-AJAX contexts or when not already handled by our JavaScript. // This serves as a fallback for uploads that don't use the media modal. if ( ! defined( 'DOING_AJAX' ) || ! $this->is_our_ajax_action() ) { // Trigger ActionScheduler for this single upload. if ( class_exists( 'ActionScheduler' ) ) { do_action( 'action_scheduler_run_queue', 'single-upload-trigger' ); do_action( 'queue_optimizer_processing_triggered' ); } } return $metadata; } /** * Check if we're in our own AJAX action context. * * @return bool True if we're in our AJAX action. */ private function is_our_ajax_action() { // Safely check if we're in our own AJAX action without accessing $_POST directly. return defined( 'DOING_AJAX' ) && wp_doing_ajax() && current_action() === 'wp_ajax_queue_optimizer_upload_complete'; } /** * Validate integer option within range. * * @param mixed $value The value to validate. * @param int $min Minimum allowed value. * @param int $max Maximum allowed value. * @param int $default Default value if validation fails. * @return int Validated value. */ private function validate_int_option( $value, $min, $max, $default ) { $value = intval( $value ); return ( $value < $min || $value > $max ) ? $default : $value; } /** * Plugin activation. */ public static function activate() { // Set default options. add_option( 'queue_optimizer_time_limit', 60 ); add_option( 'queue_optimizer_concurrent_batches', 4 ); add_option( 'queue_optimizer_image_engine', 'WP_Image_Editor_GD' ); add_option( 'queue_optimizer_post_upload_processing', true ); add_option( 'queue_optimizer_activated', time() ); // Clear any caches. wp_cache_flush(); } }