????
Current Path : /proc/self/cwd/wp-content/plugins/ewww-image-optimizer/classes/ |
Current File : //proc/self/cwd/wp-content/plugins/ewww-image-optimizer/classes/class-background-process-media.php |
<?php /** * Class for Background processing of Media Library images. * * @link https://ewww.io * @package EWWW_Image_Optimizer */ namespace EWWW; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Processes media uploads in background/async mode. * * Uses a db queue system to track uploads to be optimized, handling them one at a time. * * @see EWWW\Background_Process */ class Background_Process_Media extends Background_Process { /** * The action name used to trigger this class extension. * * @access protected * @var string $action */ protected $action = 'ewwwio_media_optimize'; /** * The queue name for this class extension. * * @access protected * @var string $action */ protected $active_queue = 'media-async'; /** * Batch size limit. * * @var int * @access protected */ protected $limit = 500; /** * Handle * * @global string|array $optimized_list A list of all images that have been optimized, or a string * indicating why that is not a good idea. * * Wrapper around parent::handle() to verify that background processing isn't paused. */ protected function handle() { if ( \get_option( 'ewww_image_optimizer_pause_queues' ) ) { \ewwwio_debug_message( 'all queues paused' ); return; } global $optimized_list; if ( empty( $optimized_list ) && $this->count_queue() > 100 ) { \ewww_image_optimizer_optimized_list(); } else { \ewwwio_debug_message( 'less than 100 attachments, not running _optimized_list()' ); $optimized_list = 'small_scan'; } parent::handle(); } /** * Runs task for an item from the Media Library queue. * * Makes sure an image upload has finished processing and has been stored in the database. * Then runs the usual media optimization routine on the specified item. * * @access protected * * @param array $item The id of the attachment, how many attempts have been made to process * the item, the type of attachment, and whether it is a new upload. * @return bool|array If the item is not complete, return it. False indicates completion. */ protected function task( $item ) { session_write_close(); \ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' ); ewwwio()->defer = false; $max_attempts = 15; $attachment_id = $item['attachment_id']; if ( empty( $item['attempts'] ) && ! empty( $item['new'] ) ) { ewwwio_debug_message( 'first attempt on new upload, going to sleep for a second' ); $item['attempts'] = 0; sleep( 1 ); // On the first attempt, hold off and wait for the db to catch up. } $type = get_post_mime_type( $attachment_id ); if ( empty( $type ) ) { ewwwio_debug_message( "mime is missing, requeueing {$item['attempts']}" ); sleep( 4 ); return $item; } ewwwio_debug_message( "background processing $attachment_id, type: " . $type ); $supported_types = ewwwio()->get_supported_types(); if ( in_array( $type, $supported_types, true ) && $item['new'] && class_exists( 'wpCloud\StatelessMedia\EWWW' ) ) { $meta = wp_get_attachment_metadata( $attachment_id ); } else { // This is unfiltered for performance, because we don't often need filtered meta. $meta = wp_get_attachment_metadata( $attachment_id, true ); } if ( in_array( $type, $supported_types, true ) && empty( $meta ) ) { ewwwio_debug_message( "metadata is missing, requeueing {$item['attempts']}" ); sleep( 4 ); return $item; } $this->process_attachment( $meta, $item, $attachment_id ); return false; } /** * Check if an individual size from a media attachment should be (re)optimized. * * @param string $file_path The filesystem path for the image. * @param string $size The thumb size (name). * @param array $item The async data for this attachment. * @param array $already_optimized The previous image results (if any) for this attachment. * @return bool True if the image should be optimized, false otherwise. */ protected function should_optimize_size( $file_path, $size, $item, $already_optimized ) { \ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' ); if ( \apply_filters( 'ewww_image_optimizer_bypass', false, $file_path ) ) { \ewwwio_debug_message( "skipping $file_path as instructed" ); return false; } if ( ! \is_file( $file_path ) ) { \ewwwio_debug_message( "file does not exist: $file_path" ); return false; } $image_size = filesize( $file_path ); if ( ! $image_size ) { \ewwwio_debug_message( "file skipped due to no size: $file_path" ); return false; } if ( $image_size < \ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_size' ) ) { \ewwwio_debug_message( "file skipped due to filesize: $file_path" ); return false; } $mime = ewww_image_optimizer_quick_mimetype( $file_path ); if ( 'image/png' === $mime && \ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_png_size' ) && $image_size > \ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_png_size' ) ) { \ewwwio_debug_message( "file skipped due to PNG filesize: $file_path" ); return false; } if ( 'image/bmp' === $mime && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_bmp_convert' ) && empty( $item['convert_once'] ) ) { \ewwwio_debug_message( "BMP skipped, no conversion enabled: $file_path" ); return false; } $compression_level = \ewww_image_optimizer_get_level( $mime ); $smart_reopt = false; if ( ! empty( $item['force_smart'] ) && ! \ewww_image_optimizer_level_mismatch( $already_optimized['level'], $compression_level ) ) { $item['force_smart'] = false; } if ( 'full' === $size && \ewww_image_optimizer_should_resize( $file_path, true ) ) { $item['force_smart'] = true; } if ( ! empty( $already_optimized['id'] ) && (int) $image_size === (int) $already_optimized['image_size'] ) { if ( empty( $item['force_reopt'] ) && empty( $item['force_smart'] ) && empty( $item['webp_only'] ) && empty( $item['new'] ) && empty( $item['convert_once'] ) ) { return false; } } return true; } /** * Queue an individual size for a media attachment. * * @global object $wpdb * @global string|array $optimized_list A list of all images that have been optimized, or a string * indicating why that is not a good idea. * * @param int $id The attachment ID number. * @param string $size The thumb size (name). * @param string $file_path The filesystem path for the image. * @param array $item The async data for this attachment. * @return int 1 if the image was queued, 0 if not. */ protected function queue_single_size( $id, $size, $file_path, $item ) { \ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' ); global $wpdb; global $optimized_list; if ( is_array( $optimized_list ) && isset( $optimized_list[ $file_path ] ) ) { $already_optimized = $optimized_list[ $file_path ]; } else { $already_optimized = \ewww_image_optimizer_find_already_optimized( $file_path ); } $image_size = \ewww_image_optimizer_filesize( $file_path ); \ewwwio_debug_message( "(maybe) queuing optimization for $id/$size" ); if ( ! $this->should_optimize_size( $file_path, $size, $item, $already_optimized ) ) { \ewwwio_debug_message( 'already optimized, not forcing or webp-only, so skipping' ); return 0; } elseif ( ! empty( $already_optimized['id'] ) ) { $wpdb->update( $wpdb->ewwwio_images, array( 'pending' => 1, 'attachment_id' => $id, 'gallery' => 'media', 'resize' => $size, 'updated' => $already_optimized['updated'], ), array( 'id' => $already_optimized['id'], ) ); $id_to_queue = $already_optimized['id']; \ewwwio_debug_message( 'toggled db record' ); } else { $wpdb->insert( $wpdb->ewwwio_images, array( 'path' => \ewww_image_optimizer_relativize_path( $file_path ), 'converted' => '', 'gallery' => 'media', 'orig_size' => $image_size, 'attachment_id' => $id, 'resize' => $size, 'pending' => 1, ) ); $id_to_queue = $wpdb->insert_id; \ewwwio_debug_message( 'inserted db record' ); } if ( ! $id_to_queue ) { \ewwwio_debug_message( 'failed to update/insert record, no ID to queue' ); return 0; } ewwwio()->background_image->push_to_queue( array( 'id' => $id_to_queue, 'new' => $item['new'], 'convert_once' => $item['convert_once'], 'force_reopt' => $item['force_reopt'], 'force_smart' => $item['force_smart'], 'webp_only' => $item['webp_only'], ) ); return 1; } /** * Find image paths from an attachment's meta data and process each image. * * Called after `wp_generate_attachment_metadata` is completed (async), it also searches for retina images, * and a few custom theme resizes. * * @global array $ewww_attachment { * Stores the ID and meta for later use with W3TC. * * @type int $id The attachment ID number. * @type array $meta The attachment metadata from the postmeta table. * } * * @param array $meta The attachment metadata generated by WordPress. * @param array $item The async data for this attachment. * @param int $id The attachment ID number. */ protected function process_attachment( $meta, $item, $id ) { \ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' ); if ( ! is_array( $meta ) && empty( $meta ) ) { $meta = array(); } elseif ( ! is_array( $meta ) ) { ewwwio_debug_message( 'attachment meta is not a usable array' ); return; } $gallery = 'media'; $size = 'full'; $queued = 0; ewwwio_debug_message( "attachment id: $id" ); session_write_close(); if ( $item['new'] ) { ewwwio_debug_message( 'this is a newly uploaded image from the async queue' ); $new_image = true; } else { ewwwio_debug_message( 'this image is not a new upload' ); $new_image = false; } list( $file_path, $upload_path ) = ewww_image_optimizer_attachment_path( $meta, $id ); /** * Allow altering the metadata or performing other actions before the plugin processes an attachement. * * @param array $meta The attachment metadata. * @param string $file_path The file path to the image. * @param bool $new_image True if this is a newly uploaded image, false otherwise. */ $meta = apply_filters( 'ewww_image_optimizer_resize_from_meta_data', $meta, $file_path, $new_image ); if ( ! $new_image && class_exists( 'Amazon_S3_And_CloudFront' ) && ewww_image_optimizer_stream_wrapped( $file_path ) ) { ewww_image_optimizer_check_table_as3cf( $meta, $id, $file_path ); } if ( ! ewwwio_is_file( $file_path ) && class_exists( 'wpCloud\StatelessMedia\EWWW' ) && ! empty( $meta['gs_link'] ) ) { $file_path = ewww_image_optimizer_remote_fetch( $id, $meta ); } // If the local file is missing and we have valid metadata, see if we can fetch via CDN. if ( ! ewwwio_is_file( $file_path ) || ewww_image_optimizer_stream_wrapped( $file_path ) ) { $file_path = ewww_image_optimizer_remote_fetch( $id, $meta ); if ( ! $file_path ) { ewwwio_debug_message( 'could not retrieve path' ); return; } } ewwwio_debug_message( "retrieved file path: $file_path" ); $supported_types = ewwwio()->get_supported_types(); $type = ewww_image_optimizer_mimetype( $file_path, 'i' ); if ( ! in_array( $type, $supported_types, true ) ) { ewwwio_debug_message( "mimetype not supported: $id" ); return; } // Queue the full-size image. $queued += $this->queue_single_size( $id, $size, $file_path, $item ); // Then disable the conversion flag for all sub-sizes and derivatives. $item['convert_once'] = false; $hidpi_path = ewww_image_optimizer_get_hidpi_path( $file_path, true ); $size .= '-retina'; if ( $hidpi_path ) { $queued += $this->queue_single_size( $id, $size, $hidpi_path, $item ); } $base_dir = trailingslashit( dirname( $file_path ) ); // Resized versions, so we can continue. if ( isset( $meta['sizes'] ) && ewww_image_optimizer_iterable( $meta['sizes'] ) ) { $disabled_sizes = ewww_image_optimizer_get_option( 'ewww_image_optimizer_disable_resizes_opt', false, true ); ewwwio_debug_message( 'processing resizes' ); // Process each resized version. $processed = array(); foreach ( $meta['sizes'] as $size => $data ) { ewwwio_debug_message( "processing size: $size" ); if ( strpos( $size, 'webp' ) === 0 ) { continue; } if ( ! empty( $disabled_sizes[ $size ] ) ) { continue; } if ( ! empty( $disabled_sizes['pdf-full'] ) && 'full' === $size ) { continue; } if ( empty( $data['file'] ) ) { continue; } ewwwio_debug_message( "maybe optimizing {$data['file']}" ); // Check through all the sizes we've processed so far. foreach ( $processed as $proc => $scan ) { // If a previous resize had identical dimensions. if ( $scan['height'] === $data['height'] && $scan['width'] === $data['width'] ) { // We found a duplicate resize, so... // Point this resize at the same image as the previous one. $meta['sizes'][ $size ]['file'] = $meta['sizes'][ $proc ]['file']; if ( isset( $meta['sizes'][ $proc ]['mime-type'] ) ) { $meta['sizes'][ $size ]['mime-type'] = $meta['sizes'][ $proc ]['mime-type']; } continue( 2 ); } } // If this is a unique size. $resize_path = str_replace( wp_basename( $file_path ), $data['file'], $file_path ); if ( empty( $resize_path ) ) { ewwwio_debug_message( 'strange... $resize_path was empty' ); continue; } if ( 'application/pdf' === $type && 'full' === $size ) { $size = 'pdf-full'; } // Because some SVG plugins populate the resizes with the original path (since SVG is "scalable", of course). // Though it could happen for other types perhaps... if ( $resize_path === $file_path ) { continue; } $queued += $this->queue_single_size( $id, $size, $resize_path, $item ); // Optimize retina images, if they exist. if ( function_exists( 'wr2x_get_retina' ) ) { $retina_path = wr2x_get_retina( $resize_path ); } else { $retina_path = false; } if ( $retina_path && ewwwio_is_file( $retina_path ) ) { $queued += $this->queue_single_size( $id, $size . '-retina', $retina_path, $item ); } else { $hidpi_path = ewww_image_optimizer_get_hidpi_path( $resize_path, true ); if ( $hidpi_path ) { $queued += $this->queue_single_size( $id, $size . '-retina', $hidpi_path, $item ); } } // Store info on the sizes we've processed, so we can check the list for duplicate sizes. $processed[ $size ]['width'] = $data['width']; $processed[ $size ]['height'] = $data['height']; } // End foreach(). } // End if(). // Original image detected. if ( isset( $meta['original_image'] ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_include_originals' ) ) { // Meta sizes don't contain a path, so we calculate one. $resize_path = trailingslashit( dirname( $file_path ) ) . $meta['original_image']; $queued += $this->queue_single_size( $id, 'original_image', $resize_path, $item ); } // End if(). // Process size from a custom theme. if ( isset( $meta['image_meta']['resized_images'] ) && ewww_image_optimizer_iterable( $meta['image_meta']['resized_images'] ) ) { $imagemeta_resize_pathinfo = pathinfo( $file_path ); $imagemeta_resize_path = ''; foreach ( $meta['image_meta']['resized_images'] as $imagemeta_resize ) { $imagemeta_resize_path = $imagemeta_resize_pathinfo['dirname'] . '/' . $imagemeta_resize_pathinfo['filename'] . '-' . $imagemeta_resize . '.' . $imagemeta_resize_pathinfo['extension']; $queued += $this->queue_single_size( $id, '', $imagemeta_resize_path, $item ); } } // And another custom theme. if ( isset( $meta['custom_sizes'] ) && ewww_image_optimizer_iterable( $meta['custom_sizes'] ) ) { $custom_sizes_pathinfo = pathinfo( $file_path ); $custom_size_path = ''; foreach ( $meta['custom_sizes'] as $custom_size ) { $custom_size_path = $custom_sizes_pathinfo['dirname'] . '/' . $custom_size['file']; $queued += $this->queue_single_size( $id, '', $custom_size_path, $item ); } } if ( $queued && ! ewwwio()->background_image->is_process_running() && ! \get_option( 'ewww_image_optimizer_pause_image_queue' ) ) { ewwwio()->background_image->dispatch(); } } /** * Runs failure routine for an item from the Media Library queue. * * @access protected * * @param array $item The id of the attachment, how many attempts have been made to process * the item and whether it is a new upload. */ protected function failure( $item ) { if ( empty( $item['attachment_id'] ) ) { return; } \ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' ); $file_path = false; $meta = \wp_get_attachment_metadata( $item['attachment_id'] ); if ( ! empty( $meta ) ) { list( $file_path, $upload_path ) = \ewww_image_optimizer_attachment_path( $meta, $item['attachment_id'] ); } if ( $file_path ) { \ewww_image_optimizer_add_file_exclusion( $file_path ); } } /** * Complete. */ protected function complete() { \ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' ); parent::complete(); if ( 'scanning' === \get_option( 'ewww_image_optimizer_bulk_resume' ) ) { if ( ! \ewww_image_optimizer_get_option( 'ewww_image_optimizer_auto' ) && ! get_option( 'ewwwio_stop_scheduled_scan' ) ) { \ewwwio_debug_message( 'starting async scan' ); \update_option( 'ewww_image_optimizer_aux_resume', 'scanning' ); \ewwwio()->async_scan->data( array( 'ewww_scan' => 'scheduled', ) )->dispatch(); } \ewwwio_debug_message( 'finished media queue' ); \update_option( 'ewww_image_optimizer_bulk_resume', '' ); } } }