Файловый менеджер - Редактировать - /home/bean7936/perfect-community.com/442aa3/classes.zip
Назад
PK ̜�\��i�+ + CLI/BulkOptimizeCommand.phpnu �[��� <?php declare(strict_types=1); namespace Imagify\CLI; use Imagify\Bulk\Bulk; /** * Command class for the bulk optimization */ class BulkOptimizeCommand extends AbstractCommand { /** * Executes the command. * * @param array $arguments Positional argument. * @param array $options Optional arguments. */ public function __invoke( $arguments, $options ) { $level = 2; if ( isset( $options['lossless'] ) ) { $level = 0; } foreach ( $arguments as $context ) { Bulk::get_instance()->run_optimize( $context, $level ); } \WP_CLI::log( 'Imagify bulk optimization triggered.' ); } /** * {@inheritdoc} */ protected function get_command_name(): string { return 'bulk-optimize'; } /** * {@inheritdoc} */ public function get_description(): string { return 'Run the bulk optimization'; } /** * {@inheritdoc} */ public function get_synopsis(): array { return [ [ 'type' => 'positional', 'name' => 'contexts', 'description' => 'The context(s) to run the bulk optimization for. Possible values are wp and custom-folders.', 'optional' => false, 'repeating' => true, ], [ 'type' => 'flag', 'name' => 'lossless', 'description' => 'Use lossless compression.', 'optional' => true, ], ]; } } PK ̜�\5. �� � % CLI/GenerateMissingNextgenCommand.phpnu �[��� <?php declare(strict_types=1); namespace Imagify\CLI; use Imagify\Bulk\Bulk; /** * Command class for the missing Nextgen generation */ class GenerateMissingNextgenCommand extends AbstractCommand { /** * Executes the command. * * @param array $arguments Positional argument. * @param array $options Optional arguments. */ public function __invoke( $arguments, $options ) { Bulk::get_instance()->run_generate_nextgen( $arguments ); \WP_CLI::log( 'Imagify missing next-gen images generation triggered.' ); } /** * {@inheritdoc} */ protected function get_command_name(): string { return 'generate-missing-nextgen'; } /** * {@inheritdoc} */ public function get_description(): string { return 'Run the generation of the missing next-gen images versions'; } /** * {@inheritdoc} */ public function get_synopsis(): array { return [ [ 'type' => 'positional', 'name' => 'contexts', 'description' => 'The context(s) to run the missing next-gen images generation for. Possible values are wp and custom-folders.', 'optional' => false, 'repeating' => true, ], ]; } } PK ̜�\å� � CLI/AbstractCommand.phpnu �[��� <?php declare(strict_types=1); namespace Imagify\CLI; /** * Abstrat class for CLI Command */ abstract class AbstractCommand implements CommandInterface { /** * {@inheritdoc} */ final public function get_name(): string { return sprintf( 'imagify %s', $this->get_command_name() ); } /** * Get the "imagify" command name. * * @return string */ abstract protected function get_command_name(): string; /** * {@inheritdoc} */ public function get_synopsis(): array { return []; } } PK ̜�\��n n CLI/CommandInterface.phpnu �[��� <?php declare(strict_types=1); namespace Imagify\CLI; interface CommandInterface { /** * Get the command name. * * @return string */ public function get_name(): string; /** * Executes the command. * * @param array $arguments Positional argument. * @param array $options Optional arguments. */ public function __invoke( $arguments, $options ); /** * Get the positional and associative arguments a command accepts. * * @return array */ public function get_synopsis(): array; /** * Get the command description. * * @return string */ public function get_description(): string; } PK ̜�\M;Pk� � Bulk/BulkInterface.phpnu �[��� <?php namespace Imagify\Bulk; /** * Interface to use for bulk. * * @since 1.9 */ interface BulkInterface { /** * Get all unoptimized media ids. * * @since 1.9 * * @param int $optimization_level The optimization level. * @return array A list of unoptimized media. Array keys are media IDs prefixed with an underscore character, array values are the main file’s URL. */ public function get_unoptimized_media_ids( $optimization_level ); /** * Get ids of all optimized media without Next gen versions. * * @since 2.2 * * @param string $format Format we are looking for. (webp|avif). * * @return array { * @type array $ids A list of media IDs. * @type array $errors { * @type array $no_file_path A list of media IDs. * @type array $no_backup A list of media IDs. * } * } */ public function get_optimized_media_ids_without_format( $format ); /** * Tell if there are optimized media without next-gen versions. * * @since 2.2 * * @return int The number of media. */ public function has_optimized_media_without_nextgen(); /** * Get the context data. * * @since 1.9 * * @return array { * The formated data. * The array keys corresponds to the table cell classes: "imagify-cell-{key}". * * @type string $count-optimized Number of media optimized. * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors. * @type string $optimized-size Optimized filesize. * @type string $original-size Original filesize. * } */ public function get_context_data(); } PK ̜�\*f&�\D \D Bulk/Bulk.phpnu �[��� <?php namespace Imagify\Bulk; use Exception; use Imagify\Traits\InstanceGetterTrait; use Imagify\Optimization\Process\ProcessInterface; use WP_Error; /** * Bulk optimization */ final class Bulk { use InstanceGetterTrait; /** * Class init: launch hooks. * * @since 2.1 */ public function init() { add_action( 'imagify_optimize_media', [ $this, 'optimize_media' ], 10, 3 ); add_action( 'imagify_convert_next_gen', [ $this, 'generate_nextgen_versions' ], 10, 2 ); // @phpstan-ignore-line add_action( 'wp_ajax_imagify_bulk_optimize', [ $this, 'bulk_optimize_callback' ] ); add_action( 'wp_ajax_imagify_missing_nextgen_generation', [ $this, 'missing_nextgen_callback' ] ); add_action( 'wp_ajax_imagify_get_folder_type_data', [ $this, 'get_folder_type_data_callback' ] ); add_action( 'wp_ajax_imagify_bulk_info_seen', [ $this, 'bulk_info_seen_callback' ] ); add_action( 'wp_ajax_imagify_bulk_get_stats', [ $this, 'bulk_get_stats_callback' ] ); add_action( 'imagify_after_optimize', [ $this, 'check_optimization_status' ], 10, 2 ); add_action( 'imagify_deactivation', [ $this, 'delete_transients_data' ] ); add_action( 'update_option_imagify_settings', [ $this, 'maybe_generate_missing_nextgen' ], 10, 2 ); } /** * Delete transients data on deactivation * * @return void */ public function delete_transients_data() { delete_transient( 'imagify_custom-folders_optimize_running' ); delete_transient( 'imagify_wp_optimize_running' ); delete_transient( 'imagify_bulk_optimization_complete' ); delete_transient( 'imagify_missing_next_gen_total' ); } /** * Checks bulk optimization status after each optimization task * * @param ProcessInterface $process The optimization process. * @param array $item The item being processed. * * @return void */ public function check_optimization_status( $process, $item ) { $custom_folders = get_transient( 'imagify_custom-folders_optimize_running' ); $library_wp = get_transient( 'imagify_wp_optimize_running' ); if ( ! $custom_folders && ! $library_wp ) { return; } $data = $process->get_data(); if ( ! $data ) { return; } $progress = get_transient( 'imagify_bulk_optimization_result' ); if ( $data->is_optimized() ) { $size_data = $data->get_size_data(); if ( false === $progress ) { $progress = [ 'total' => 0, 'original_size' => 0, 'optimized_size' => 0, ]; } ++$progress['total']; $progress['original_size'] += $size_data['original_size']; $progress['optimized_size'] += $size_data['optimized_size']; set_transient( 'imagify_bulk_optimization_result', $progress, DAY_IN_SECONDS ); } $remaining = 0; if ( false !== $custom_folders ) { if ( false !== strpos( $item['process_class'], 'CustomFolders' ) ) { --$custom_folders['remaining']; set_transient( 'imagify_custom-folders_optimize_running', $custom_folders, DAY_IN_SECONDS ); $remaining += $custom_folders['remaining']; } } if ( false !== $library_wp ) { if ( false !== strpos( $item['process_class'], 'WP' ) ) { --$library_wp['remaining']; set_transient( 'imagify_wp_optimize_running', $library_wp, DAY_IN_SECONDS ); $remaining += $library_wp['remaining']; } } if ( 0 >= $remaining ) { delete_transient( 'imagify_custom-folders_optimize_running' ); delete_transient( 'imagify_wp_optimize_running' ); set_transient( 'imagify_bulk_optimization_complete', 1, DAY_IN_SECONDS ); } } /** * Decrease optimization running counter for the given context * * @param string $context Context to update. * * @return void */ private function decrease_counter( string $context ) { $counter = get_transient( "imagify_{$context}_optimize_running" ); if ( false === $counter ) { return; } $counter['total'] = $counter['total'] - 1; $counter['remaining'] = $counter['remaining'] - 1; if ( 0 === $counter['total'] && 0 >= $counter['remaining'] ) { delete_transient( "imagify_{$context}_optimize_running" ); } set_transient( "imagify_{$context}_optimize_running", $counter, DAY_IN_SECONDS ); } /** * Process a media with the requested imagify bulk action. * * @since 2.1 * * @param int $media_id Media ID. * @param string $context Current context. * @param int $optimization_level Optimization level. */ public function optimize_media( int $media_id, string $context, int $optimization_level ) { if ( ! $media_id || ! $context ) { $this->decrease_counter( $context ); return; } $this->force_optimize( $media_id, $context, $optimization_level ); } /** * Runs the bulk optimization * * @param string $context Current context (WP/Custom folders). * @param int $optimization_level Optimization level. * * @return array */ public function run_optimize( string $context, int $optimization_level ) { if ( ! $this->can_optimize() ) { return [ 'success' => false, 'message' => 'over-quota', ]; } $media_ids = $this->get_bulk_instance( $context )->get_unoptimized_media_ids( $optimization_level ); if ( empty( $media_ids ) ) { return [ 'success' => false, 'message' => 'no-images', ]; } foreach ( $media_ids as $media_id ) { try { as_enqueue_async_action( 'imagify_optimize_media', [ 'id' => $media_id, 'context' => $context, 'level' => $optimization_level, ], "imagify-{$context}-optimize-media" ); } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch // nothing to do. } } $data = [ 'total' => count( $media_ids ), 'remaining' => count( $media_ids ), ]; set_transient( "imagify_{$context}_optimize_running", $data, DAY_IN_SECONDS ); return [ 'success' => true, 'message' => 'success', ]; } /** * Runs the next-gen generation * * @param array $contexts An array of contexts (WP/Custom folders). * @param array $formats An array of format to generate. * * @return array */ public function run_generate_nextgen( array $contexts, array $formats ) { if ( ! $this->can_optimize() ) { return [ 'success' => false, 'message' => 'over-quota', ]; } delete_transient( 'imagify_stat_without_next_gen' ); $medias = []; foreach ( $contexts as $context ) { foreach ( $formats as $format ) { $media = $this->get_bulk_instance( $context )->get_optimized_media_ids_without_format( $format ); if ( ! $media['ids'] && $media['errors']['no_backup'] ) { // No backup, no next-gen. return [ 'success' => false, 'message' => 'no-backup', ]; } elseif ( ! $media['ids'] && $media['errors']['no_file_path'] ) { // Error. return [ 'success' => false, 'message' => __( 'The path to the selected files could not be retrieved.', 'imagify' ), ]; } $medias[ $context ] = $media['ids']; } } if ( empty( $medias ) ) { return [ 'success' => false, 'message' => 'no-images', ]; } $total = 0; foreach ( $medias as $context => $media_ids ) { $total += count( $media_ids ); foreach ( $media_ids as $media_id ) { try { as_enqueue_async_action( 'imagify_convert_next_gen', [ 'id' => $media_id, 'context' => $context, ], "imagify-{$context}-convert-nextgen" ); } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch // nothing to do. } } } set_transient( 'imagify_missing_next_gen_total', $total, HOUR_IN_SECONDS ); return [ 'success' => true, 'message' => $total, ]; } /** * Get the Bulk class name depending on a context. * * @since 2.1 * * @param string $context The context name. Default values are 'wp' and 'custom-folders'. * @return string The Bulk class name. */ private function get_bulk_class_name( string $context ): string { switch ( $context ) { case 'wp': $class_name = WP::class; break; case 'custom-folders': $class_name = CustomFolders::class; break; default: $class_name = Noop::class; } /** * Filter the name of the class to use for bulk process. * * @since 1.9 * * @param string $class_name The class name. * @param string $context The context name. */ $class_name = wpm_apply_filters_typed( 'string', 'imagify_bulk_class_name', $class_name, $context ); return '\\' . ltrim( $class_name, '\\' ); } /** * Get the Bulk instance depending on a context. * * @since 2.1 * * @param string $context The context name. Default values are 'wp' and 'custom-folders'. * * @return BulkInterface The optimization process instance. */ public function get_bulk_instance( string $context ): BulkInterface { $class_name = $this->get_bulk_class_name( $context ); return new $class_name(); } /** * Optimize all files from a media, whatever this media’s previous optimization status (will be restored if needed). * This is used by the bulk optimization page. * * @since 1.9 * * @param int $media_id The media ID. * @param string $context The context. * @param int $level The optimization level. * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ private function force_optimize( int $media_id, string $context, int $level ) { if ( ! $this->can_optimize() ) { $this->decrease_counter( $context ); return false; } $process = imagify_get_optimization_process( $media_id, $context ); $data = $process->get_data(); // Restore before re-optimizing. if ( $data->is_optimized() ) { $result = $process->restore(); if ( is_wp_error( $result ) ) { $this->decrease_counter( $context ); // Return an error message. return $result; } } return $process->optimize( $level ); } /** * Generate next-gen images if they are missing. * * @since 2.1 * * @param int $media_id Media ID. * @param string $context Current context. * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ public function generate_nextgen_versions( int $media_id, string $context ) { if ( ! $this->can_optimize() ) { return false; } return imagify_get_optimization_process( $media_id, $context )->generate_nextgen_versions(); } /** * Check if the user has a valid account and has quota. Die on failure. * * @since 2.1 */ public function can_optimize() { if ( ! \Imagify_Requirements::is_api_key_valid() ) { return false; } if ( \Imagify_Requirements::is_over_quota() ) { return false; } return true; } /** * Get the submitted context. * * @since 1.9 * * @param string $method The method used: 'GET' (default), or 'POST'. * @param string $parameter The name of the parameter to look for. * * @return string */ public function get_context( $method = 'GET', $parameter = 'context' ) { if ( empty( $_POST[ $parameter ] ) && empty( $_GET[ $parameter ] ) ) { // No context. return 'noop'; } $context = 'POST' === $method ? sanitize_text_field( wp_unslash( $_POST[ $parameter ] ) ) : sanitize_text_field( wp_unslash( $_GET[ $parameter ] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended return imagify_sanitize_context( $context ); } /** * Get the submitted optimization level. * * @since 1.7 * @since 1.9 Added $method and $parameter parameters. * @author Grégory Viguier * * @param string $method The method used: 'GET' (default), or 'POST'. * @param string $parameter The name of the parameter to look for. * @return int */ public function get_optimization_level( $method = 'GET', $parameter = 'optimization_level' ) { $method = 'POST' === $method ? INPUT_POST : INPUT_GET; $level = filter_input( $method, $parameter ); if ( ! is_numeric( $level ) || $level < 0 || $level > 2 ) { if ( get_imagify_option( 'lossless' ) ) { return 0; } return get_imagify_option( 'optimization_level' ); } return (int) $level; } /** * Launch the bulk optimization action * * @return void */ public function bulk_optimize_callback() { imagify_check_nonce( 'imagify-bulk-optimize' ); $context = $this->get_context(); $level = $this->get_optimization_level(); if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) { imagify_die(); } $data = $this->run_optimize( $context, $level ); if ( false === $data['success'] ) { wp_send_json_error( [ 'message' => $data['message'] ] ); } wp_send_json_success( [ 'total' => $data['message'] ] ); } /** * Launch the missing Next-gen versions generation * * @return void */ public function missing_nextgen_callback() { imagify_check_nonce( 'imagify-bulk-optimize' ); $contexts = $this->get_contexts(); foreach ( $contexts as $context ) { if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) { imagify_die(); } } $formats = imagify_nextgen_images_formats(); $data = $this->run_generate_nextgen( $contexts, $formats ); if ( false === $data['success'] ) { wp_send_json_error( [ 'message' => $data['message'] ] ); } wp_send_json_success( [ 'total' => $data['message'] ] ); } /** * Get stats data for a specific folder type. * * @since 1.7 */ public function get_folder_type_data_callback() { imagify_check_nonce( 'imagify-bulk-optimize' ); $context = $this->get_context(); if ( ! $context ) { imagify_die( __( 'Invalid request', 'imagify' ) ); } if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) { imagify_die(); } $bulk = $this->get_bulk_instance( $context ); wp_send_json_success( $bulk->get_context_data() ); } /** * Set the "bulk info" popup state as "seen". * * @since 1.7 */ public function bulk_info_seen_callback() { imagify_check_nonce( 'imagify-bulk-optimize' ); $context = $this->get_context(); if ( ! $context ) { imagify_die( __( 'Invalid request', 'imagify' ) ); } if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) { imagify_die(); } set_transient( 'imagify_bulk_optimization_infos', 1, WEEK_IN_SECONDS ); wp_send_json_success(); } /** * Get generic stats to display in the bulk page. * * @since 1.7.1 */ public function bulk_get_stats_callback() { imagify_check_nonce( 'imagify-bulk-optimize' ); $folder_types = filter_input( INPUT_GET, 'types', FILTER_REQUIRE_ARRAY ); $folder_types = is_array( $folder_types ) ? $folder_types : []; if ( ! $folder_types ) { imagify_die( __( 'Invalid request', 'imagify' ) ); } foreach ( $folder_types as $folder_type_data ) { $context = ! empty( $folder_type_data['context'] ) ? $folder_type_data['context'] : 'noop'; if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) { imagify_die(); } } wp_send_json_success( imagify_get_bulk_stats( array_flip( $folder_types ) ) ); } /** * Update Options callback to start bulk optimization. * * @since 2.2 * * @param array $old_value The old option value. * @param array $value The new option value. * * @return void */ public function maybe_generate_missing_nextgen( $old_value, $value ) { if ( ! isset( $old_value['optimization_format'], $value['optimization_format'] ) ) { return; } if ( $old_value['optimization_format'] === $value['optimization_format'] ) { // Old value = new value so do nothing. return; } if ( 'off' === $value['optimization_format'] ) { // No need to generate next-gen images. return; } $contexts = $this->get_contexts(); $formats = imagify_nextgen_images_formats(); $this->run_generate_nextgen( $contexts, $formats ); } /** * Get the context for the bulk optimization page. * * @since 2.2 * * @return array The array of unique contexts ('wp' or 'custom-folders'). */ public function get_contexts() { $contexts = []; $types = []; // Library: in each site. if ( ! is_network_admin() ) { $types['library|wp'] = 1; } // Custom folders: in network admin only if network activated, in each site otherwise. if ( imagify_can_optimize_custom_folders() && ( ( imagify_is_active_for_network() && is_network_admin() ) || ! imagify_is_active_for_network() ) ) { $types['custom-folders|custom-folders'] = 1; } /** * Filter the types to display in the bulk optimization page. * * @since 1.7.1 * * @param array $types The folder types displayed on the page. If a folder type is "library", the context should be suffixed after a pipe character. They are passed as array keys. */ $types = wpm_apply_filters_typed( 'array', 'imagify_bulk_page_types', $types ); $types = array_filter( (array) $types ); if ( isset( $types['library|wp'] ) ) { $contexts[] = 'wp'; } if ( isset( $types['custom-folders|custom-folders'] ) ) { $folders_instance = \Imagify_Folders_DB::get_instance(); if ( ! $folders_instance->has_items() ) { if ( ! in_array( 'wp', $contexts, true ) ) { $contexts[] = 'wp'; } } elseif ( $folders_instance->has_active_folders() ) { $contexts[] = 'custom-folders'; } } return $contexts; } } PK ̜�\���� Bulk/Noop.phpnu �[��� <?php namespace Imagify\Bulk; /** * Falback class for bulk. * * @since 1.9 */ class Noop extends AbstractBulk { /** * Get all unoptimized media ids. * * @since 1.9 * * @param int $optimization_level The optimization level. * @return array A list of unoptimized media. Array keys are media IDs prefixed with an underscore character, array values are the main file’s URL. */ public function get_unoptimized_media_ids( $optimization_level ) { return []; } /** * * Get ids of all optimized media without Next gen versions. * * @since 2.2 * * @param string $format Format we are looking for. (webp|avif). * * @return array { * @type array $ids A list of media IDs. * @type array $errors { * @type array $no_file_path A list of media IDs. * @type array $no_backup A list of media IDs. * } * } */ public function get_optimized_media_ids_without_format( $format ) { return [ 'ids' => [], 'errors' => [ 'no_file_path' => [], 'no_backup' => [], ], ]; } /** * Get the context data. * * @since 1.9 * * @return array { * The formated data. * * @type string $count-optimized Number of media optimized. * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors. * @type string $optimized-size Optimized filesize. * @type string $original-size Original filesize. * } */ public function get_context_data() { $data = [ 'count-optimized' => 0, 'count-errors' => 0, 'optimized-size' => 0, 'original-size' => 0, 'errors_url' => get_imagify_admin_url( 'folder-errors', 'noop' ), ]; return $this->format_context_data( $data ); } } PK ̜�\��9�� � Bulk/CustomFolders.phpnu �[��� <?php namespace Imagify\Bulk; use Imagify_Custom_Folders; use Imagify_Files_Scan; use Imagify_Files_DB; use Imagify_Folders_DB; use Imagify_DB; use Imagify_Files_Stats; /** * Class to use for bulk for custom folders. * * @since 1.9 */ class CustomFolders extends AbstractBulk { /** * Context "short name". * * @var string * @since 1.9 */ protected $context = 'custom-folders'; /** * Get all unoptimized media ids. * * @since 1.9 * * @param int $optimization_level The optimization level. * * @return array A list of unoptimized media IDs. */ public function get_unoptimized_media_ids( $optimization_level ) { $this->set_no_time_limit(); /** * Get the folders from DB. */ $folders = Imagify_Custom_Folders::get_folders( [ 'active' => true, ] ); if ( ! $folders ) { return []; } /** * Fires before getting file IDs. * * @since 1.7 * * @param array $folders An array of folders data. * @param int $optimization_level The optimization level that will be used for the optimization. */ do_action( 'imagify_bulk_optimize_files_before_get_files', $folders, $optimization_level ); /** * Get the files from DB, and from the folders. */ $files = Imagify_Custom_Folders::get_files_from_folders( $folders, [ 'optimization_level' => $optimization_level, ] ); if ( ! $files ) { return []; } foreach ( $files as $k => $file ) { $files[ $k ] = $file['file_id']; } return $files; } /** * Get ids of all optimized media without Next gen versions. * * @since 2.2 * * @param string $format Format we are looking for. (webp|avif). * * @return array { * @type array $ids A list of media IDs. * @type array $errors { * @type array $no_file_path A list of media IDs. * @type array $no_backup A list of media IDs. * } * } */ public function get_optimized_media_ids_without_format( $format ) { global $wpdb; $this->set_no_time_limit(); $files_table = Imagify_Files_DB::get_instance()->get_table_name(); $folders_table = Imagify_Folders_DB::get_instance()->get_table_name(); $mime_types = Imagify_DB::get_mime_types( 'image' ); // Remove single quotes and explode string into array. $mime_types_array = explode( ',', str_replace( "'", '', $mime_types ) ); // Iterate over array and check if string contains input. foreach ( $mime_types_array as $item ) { if ( strpos( $item, $format ) !== false ) { $mime = $item; break; } } if ( ! isset( $mime ) && empty( $mime ) ) { $mime = 'image/webp'; } $mime_types = str_replace( ",'" . $mime . "'", '', $mime_types ); $nextgen_suffix = constant( imagify_get_optimization_process_class_name( 'custom-folders' ) . '::' . strtoupper( $format ) . '_SUFFIX' ); $files = $wpdb->get_results( $wpdb->prepare( // WPCS: unprepared SQL ok. " SELECT fi.file_id, fi.path FROM $files_table as fi INNER JOIN $folders_table AS fo ON ( fi.folder_id = fo.folder_id ) WHERE fi.mime_type IN ( $mime_types ) AND ( fi.status = 'success' OR fi.status = 'already_optimized' ) AND ( fi.data NOT LIKE %s OR fi.data IS NULL ) ORDER BY fi.file_id DESC", '%' . $wpdb->esc_like( $nextgen_suffix . '";a:4:{s:7:"success";b:1;' ) . '%' ) ); $wpdb->flush(); unset( $mime_types, $files_table, $folders_table, $nextgen_suffix, $mime ); $data = [ 'ids' => [], 'errors' => [ 'no_file_path' => [], 'no_backup' => [], ], ]; if ( ! $files ) { return $data; } foreach ( $files as $file ) { $file_id = absint( $file->file_id ); if ( empty( $file->path ) ) { // Problem. $data['errors']['no_file_path'][] = $file_id; continue; } $file_path = Imagify_Files_Scan::remove_placeholder( $file->path ); $backup_path = Imagify_Custom_Folders::get_file_backup_path( $file_path ); if ( ! $this->filesystem->exists( $backup_path ) ) { // No backup, no WebP. $data['errors']['no_backup'][] = $file_id; continue; } $data['ids'][] = $file_id; } return $data; } /** * Get the context data. * * @since 1.9 * * @return array { * The formated data. * * @type string $count-optimized Number of media optimized. * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors. * @type string $optimized-size Optimized filesize. * @type string $original-size Original filesize. * } */ public function get_context_data() { $data = [ 'count-optimized' => Imagify_Files_Stats::count_optimized_files(), 'count-errors' => Imagify_Files_Stats::count_error_files(), 'optimized-size' => Imagify_Files_Stats::get_optimized_size(), 'original-size' => Imagify_Files_Stats::get_original_size(), 'errors_url' => get_imagify_admin_url( 'folder-errors', $this->context ), ]; return $this->format_context_data( $data ); } } PK ̜�\��$ �$ Bulk/WP.phpnu �[��� <?php namespace Imagify\Bulk; use Imagify_DB; /** * Class to use for bulk for WP attachments. * * @since 1.9 */ class WP extends AbstractBulk { /** * Context "short name". * * @var string * @since 1.9 */ protected $context = 'wp'; /** * Get all unoptimized media ids. * * @since 1.9 * * @param int $optimization_level The optimization level. * * @return array A list of unoptimized media IDs. */ public function get_unoptimized_media_ids( $optimization_level ) { global $wpdb; $this->set_no_time_limit(); $mime_types = Imagify_DB::get_mime_types(); $statuses = Imagify_DB::get_post_statuses(); $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause(); $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause( [ 'prepared' => true, ] ); $ids = $wpdb->get_col( $wpdb->prepare( // WPCS: unprepared SQL ok. " SELECT DISTINCT p.ID FROM $wpdb->posts AS p $nodata_join LEFT JOIN $wpdb->postmeta AS mt1 ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' ) LEFT JOIN $wpdb->postmeta AS mt2 ON ( p.ID = mt2.post_id AND mt2.meta_key = '_imagify_optimization_level' ) WHERE p.post_mime_type IN ( $mime_types ) AND ( mt1.meta_value = 'error' OR mt2.meta_value != %d OR mt2.post_id IS NULL ) AND p.post_type = 'attachment' AND p.post_status IN ( $statuses ) $nodata_where ORDER BY CASE mt1.meta_value WHEN 'already_optimized' THEN 2 ELSE 1 END ASC, p.ID DESC LIMIT 0, %d", $optimization_level, imagify_get_unoptimized_attachment_limit() ) ); $wpdb->flush(); unset( $mime_types ); $ids = array_filter( array_map( 'absint', $ids ) ); if ( ! $ids ) { return []; } $metas = Imagify_DB::get_metas( [ // Get attachments filename. 'filenames' => '_wp_attached_file', // Get attachments data. 'data' => '_imagify_data', // Get attachments optimization level. 'optimization_levels' => '_imagify_optimization_level', // Get attachments status. 'statuses' => '_imagify_status', ], $ids ); // First run. foreach ( $ids as $i => $id ) { $attachment_status = isset( $metas['statuses'][ $id ] ) ? $metas['statuses'][ $id ] : false; $attachment_optimization_level = isset( $metas['optimization_levels'][ $id ] ) ? $metas['optimization_levels'][ $id ] : false; $attachment_error = ''; if ( isset( $metas['data'][ $id ]['sizes']['full']['error'] ) ) { $attachment_error = $metas['data'][ $id ]['sizes']['full']['error']; } // Don't try to re-optimize if the optimization level is still the same. if ( $optimization_level === $attachment_optimization_level && is_string( $attachment_error ) ) { unset( $ids[ $i ] ); continue; } // Don't try to re-optimize images already compressed. if ( 'already_optimized' === $attachment_status && $attachment_optimization_level >= $optimization_level ) { unset( $ids[ $i ] ); continue; } $attachment_error = trim( $attachment_error ); // Don't try to re-optimize images with an empty error message. if ( 'error' === $attachment_status && empty( $attachment_error ) ) { unset( $ids[ $i ] ); } } if ( ! $ids ) { return []; } $ids = array_values( $ids ); /** * Fires before testing for file existence. * * @since 1.6.7 * * @param array $ids An array of attachment IDs. * @param array $metas An array of the data fetched from the database. * @param int $optimization_level The optimization level that will be used for the optimization. */ do_action( 'imagify_bulk_optimize_before_file_existence_tests', $ids, $metas, $optimization_level ); $data = []; foreach ( $ids as $i => $id ) { if ( empty( $metas['filenames'][ $id ] ) ) { // Problem. continue; } $file_path = get_imagify_attached_file( $metas['filenames'][ $id ] ); if ( ! $file_path || ! $this->filesystem->exists( $file_path ) ) { continue; } $attachment_backup_path = get_imagify_attachment_backup_path( $file_path ); $attachment_status = isset( $metas['statuses'][ $id ] ) ? $metas['statuses'][ $id ] : false; $attachment_optimization_level = isset( $metas['optimization_levels'][ $id ] ) ? $metas['optimization_levels'][ $id ] : false; // Don't try to re-optimize if there is no backup file. if ( 'success' === $attachment_status && $optimization_level !== $attachment_optimization_level && ! $this->filesystem->exists( $attachment_backup_path ) ) { continue; } $data[] = $id; } return $data; } /** * Get ids of all optimized media without Next gen versions. * * @since 2.2 * * @param string $format Format we are looking for. (webp|avif). * * @return array { * @type array $ids A list of media IDs. * @type array $errors { * @type array $no_file_path A list of media IDs. * @type array $no_backup A list of media IDs. * } * } */ public function get_optimized_media_ids_without_format( $format ) { global $wpdb; $this->set_no_time_limit(); $mime_types = Imagify_DB::get_mime_types( 'image' ); // Remove single quotes and explode string into array. $mime_types_array = explode( ',', str_replace( "'", '', $mime_types ) ); // Iterate over array and check if string contains input. foreach ( $mime_types_array as $item ) { if ( strpos( $item, $format ) !== false ) { $mime = $item; break; } } if ( ! isset( $mime ) && empty( $mime ) ) { $mime = 'image/webp'; } $mime_types = str_replace( ",'" . $mime . "'", '', $mime_types ); $statuses = Imagify_DB::get_post_statuses(); $nodata_join = ''; $nodata_where = ''; if ( ! imagify_has_attachments_without_required_metadata() ) { $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause(); $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause( [ 'prepared' => true, ] ); } $nextgen_suffix = constant( imagify_get_optimization_process_class_name( 'wp' ) . '::' . strtoupper( $format ) . '_SUFFIX' ); $ids = $wpdb->get_col( $wpdb->prepare( // WPCS: unprepared SQL ok. " SELECT p.ID FROM $wpdb->posts AS p $nodata_join LEFT JOIN $wpdb->postmeta AS mt1 ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' ) LEFT JOIN $wpdb->postmeta AS mt2 ON ( p.ID = mt2.post_id AND mt2.meta_key = '_imagify_data' ) WHERE p.post_mime_type IN ( $mime_types ) AND (mt1.meta_key IS NULL OR mt1.meta_value = 'success' OR mt1.meta_value = 'already_optimized' ) AND mt2.meta_value NOT LIKE %s AND p.post_type = 'attachment' AND p.post_status IN ( $statuses ) $nodata_where ORDER BY p.ID DESC LIMIT 0, %d", '%' . $wpdb->esc_like( $nextgen_suffix . '";a:4:{s:7:"success";b:1;' ) . '%', imagify_get_unoptimized_attachment_limit() ) ); $wpdb->flush(); unset( $mime_types, $statuses, $nextgen_suffix, $mime ); $ids = array_filter( array_map( 'absint', $ids ) ); $data = [ 'ids' => [], 'errors' => [ 'no_file_path' => [], 'no_backup' => [], ], ]; if ( ! $ids ) { return $data; } $metas = Imagify_DB::get_metas( [ // Get attachments filename. 'filenames' => '_wp_attached_file', ], $ids ); /** * Fires before testing for file existence. * * @since 1.9 * * @param array $ids An array of attachment IDs. * @param array $metas An array of the data fetched from the database. * @param string $context The context. */ do_action( 'imagify_bulk_generate_nextgen_before_file_existence_tests', $ids, $metas, 'wp' ); foreach ( $ids as $i => $id ) { if ( empty( $metas['filenames'][ $id ] ) ) { // Problem. Should not happen, thanks to the wpdb query. $data['errors']['no_file_path'][] = $id; continue; } $file_path = get_imagify_attached_file( $metas['filenames'][ $id ] ); if ( ! $file_path ) { // Main file not found. $data['errors']['no_file_path'][] = $id; continue; } $backup_path = get_imagify_attachment_backup_path( $file_path ); if ( ! $this->filesystem->exists( $backup_path ) ) { // No backup, no WebP. $data['errors']['no_backup'][] = $id; continue; } $data['ids'][] = $id; } return $data; } /** * Get the context data. * * @since 1.9 * * @return array { * The formated data. * * @type string $count-optimized Number of media optimized. * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors. * @type string $optimized-size Optimized filesize. * @type string $original-size Original filesize. * } */ public function get_context_data() { $total_saving_data = imagify_count_saving_data(); $data = [ 'count-optimized' => imagify_count_optimized_attachments(), 'count-errors' => imagify_count_error_attachments(), 'optimized-size' => $total_saving_data['optimized_size'], 'original-size' => $total_saving_data['original_size'], 'errors_url' => get_imagify_admin_url( 'folder-errors', $this->context ), ]; return $this->format_context_data( $data ); } } PK ̜�\�m� � Bulk/AbstractBulk.phpnu �[��� <?php namespace Imagify\Bulk; use Imagify_Filesystem; /** * Abstract class to use for bulk. * * @since 1.9 */ abstract class AbstractBulk implements BulkInterface { /** * Filesystem object. * * @var Imagify_Filesystem * @since 1.9 */ protected $filesystem; /** * The constructor. * * @since 1.9 */ public function __construct() { $this->filesystem = Imagify_Filesystem::get_instance(); } /** * Format context data (stats). * * @since 1.9 * * @param array $data { * The data to format. * * @type int $count-optimized Number of media optimized. * @type int $count-errors Number of media having an optimization error. * @type int $optimized-size Optimized filesize. * @type int $original-size Original filesize. * @type string $errors_url URL to the page listing the optimization errors. * } * @return array { * The formated data. * * @type string $count-optimized Number of media optimized. * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors. * @type string $optimized-size Optimized filesize. * @type string $original-size Original filesize. * } */ protected function format_context_data( $data ) { $defaults = [ 'count-optimized' => '', 'count-errors' => '', 'optimized-size' => '', 'original-size' => '', ]; $data = wp_parse_args( $data, $defaults ); $data = array_map( function ( $item ) { return empty( $item ) ? '' : $item; }, $data ); if ( ! empty( $data['count-optimized'] ) ) { // translators: %s is a formatted number, dont use %d. $data['count-optimized'] = sprintf( _n( '%s Media File Optimized', '%s Media Files Optimized', $data['count-optimized'], 'imagify' ), '<span>' . number_format_i18n( $data['count-optimized'] ) . '</span>' ); } if ( ! empty( $data['count-errors'] ) ) { /* translators: %s is a formatted number, dont use %d. */ $data['count-errors'] = sprintf( _n( '%s Error', '%s Errors', $data['count-errors'], 'imagify' ), '<span>' . number_format_i18n( $data['count-errors'] ) . '</span>' ); $data['count-errors'] .= ' <a href="' . esc_url( $data['errors_url'] ) . '">' . __( 'View Errors', 'imagify' ) . '</a>'; } if ( ! empty( $data['optimized-size'] ) ) { $data['optimized-size'] = '<span class="imagify-cell-label">' . __( 'Optimized Filesize', 'imagify' ) . '</span> ' . imagify_size_format( $data['optimized-size'], 2 ); } if ( ! empty( $data['original-size'] ) ) { $data['original-size'] = '<span class="imagify-cell-label">' . __( 'Original Filesize', 'imagify' ) . '</span> ' . imagify_size_format( $data['original-size'], 2 ); } unset( $data['errors_url'] ); return $data; } /** * Attempts to set no limit to the PHP timeout for time intensive processes. * * @return void */ protected function set_no_time_limit() { if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved ) { @set_time_limit( 0 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } } /** * Tell if there are optimized media without next-gen versions. * * @since 1.9 * * @return int The number of media. */ public function has_optimized_media_without_nextgen() { $format = get_imagify_option( 'optimization_format' ); if ( 'off' === $format ) { return 0; } return count( $this->get_optimized_media_ids_without_format( $format )['ids'] ); } } PK ̜�\E��?� � DB/DBInterface.phpnu �[��� <?php namespace Imagify\DB; defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' ); /** * Interface to interact with the database. * * @since 1.9 * @author Grégory Viguier */ interface DBInterface { /** * Get the main Instance. * * @since 1.9 * @access public * @author Grégory Viguier * * @return DBInterface Main instance. */ public static function get_instance(); /** * Retrieve a row by the primary key. * * @since 1.9 * @access public * @author Grégory Viguier * * @param int $row_id A primary key. * @return array */ public function get( $row_id ); /** * Update a row. * * @since 1.9 * @access public * @author Grégory Viguier * * @param int $row_id A primary key. * @param array $data New data. * @param string $where A column name. * @return bool */ public function update( $row_id, $data = [], $where = '' ); /** * Delete a row identified by the primary key. * * @since 1.9 * @access public * @author Grégory Viguier * * @param int $row_id A primary key. * @return bool */ public function delete( $row_id ); /** * Default column values. * * @since 1.9 * @access public * @author Grégory Viguier * * @return array */ public function get_column_defaults(); /** * Get the primary column name. * * @since 1.9 * @access public * @author Grégory Viguier * * @return string */ public function get_primary_key(); } PK ̜�\Y����) �) Job/MediaOptimization.phpnu �[��� <?php namespace Imagify\Job; use Imagify\Optimization\Process\ProcessInterface; use Imagify\Traits\InstanceGetterTrait; use WP_Error; /** * Job class for media optimization. * * @since 1.9 */ final class MediaOptimization extends \Imagify_Abstract_Background_Process { use InstanceGetterTrait; /** * Background process: the action to perform. * * @var string * @since 1.9 */ protected $action = 'optimize_media'; /** * The optimization process instance. * * @var ?ProcessInterface * @since 1.9 */ protected $optimization_process; /** * Handle job logic. * * @since 1.9 * * @param array $item { * The data to use for this job. * * @type string $task The task to perform. Optional: set it only if you know what you’re doing. * @type int $id The media ID. * @type array $sizes An array of media sizes (strings). Use "full" for the size of the main file. * @type array $sizes_done Used internally to store the media sizes that have been processed. * @type int $optimization_level The optimization level. Null for the level set in the settings. * @type string $process_class The name of the process class. The class must implement ProcessInterface. * @type array $data { * Can be used to pass any data. Keep it short, don’t forget it will be stored in the database. * It should contain the following though: * * @type string $hook_suffix Suffix used to trigger hooks before and after optimization. Should be always provided. * @type bool $delete_backup True to delete the backup file after the optimization process. This is used when a temporary backup of the original file has been created, but backup option is disabled. Default is false. * } * } * @return array|bool The modified item to put back in the queue. False to remove the item from the queue. */ protected function task( $item ) { $item = $this->validate_item( $item ); if ( ! $item ) { // Not valid. return false; } // Launch the task. $method = 'task_' . $item['task']; $item = $this->$method( $item ); if ( $item['task'] ) { // Next task. return $item; } // End of the queue. $this->optimization_process->unlock(); return false; } /** * Trigger hooks before the optimization job. * * @since 1.9 * * @param array $item See $this->task(). * @return array The item. */ private function task_before( $item ) { if ( ! empty( $item['error'] ) && is_wp_error( $item['error'] ) ) { $wp_error = $item['error']; } else { $wp_error = new WP_Error(); } /** * Fires before optimizing a media. * Any number of files can be optimized, not necessarily all of the media files. * If you want to return a WP_Error, use the existing $wp_error object. * * @since 1.9 * * @param array|WP_Error $data New data to pass along the item. A WP_Error object to stop the process. * @param WP_Error $wp_error Add errors to this object and return it to stop the process. * @param ProcessInterface $process The optimization process. * @param array $item The item being processed. See $this->task(). */ $data = apply_filters( 'imagify_before_optimize', [], $wp_error, $this->optimization_process, $item ); // @phpstan-ignore-line if ( is_wp_error( $data ) ) { $wp_error = $data; } elseif ( $data && is_array( $data ) ) { $item['data'] = array_merge( $data, $item['data'] ); } if ( $wp_error->get_error_codes() ) { // Don't optimize if there is an error. $item['task'] = 'after'; $item['error'] = $wp_error; return $item; } if ( empty( $item['data']['hook_suffix'] ) ) { // Next task. $item['task'] = 'optimize'; return $item; } $hook_suffix = $item['data']['hook_suffix']; /** * Fires before optimizing a media. * Any number of files can be optimized, not necessarily all of the media files. * If you want to return a WP_Error, use the existing $wp_error object. * * @since 1.9 * * @param array|WP_Error $data New data to pass along the item. A WP_Error object to stop the process. * @param WP_Error $wp_error Add errors to this object and return it to stop the process. * @param ProcessInterface $process The optimization process. * @param array $item The item being processed. See $this->task(). */ $data = apply_filters( "imagify_before_{$hook_suffix}", [], $wp_error, $this->optimization_process, $item ); // @phpstan-ignore-line if ( is_wp_error( $data ) ) { $wp_error = $data; } elseif ( $data && is_array( $data ) ) { $item['data'] = array_merge( $data, $item['data'] ); } if ( $wp_error->get_error_codes() ) { // Don't optimize if there is an error. $item['task'] = 'after'; $item['error'] = $wp_error; return $item; } // Next task. $item['task'] = 'optimize'; return $item; } /** * Start the optimization job. * * @since 1.9 * * @param array $item See $this->task(). * @return array The item. */ private function task_optimize( $item ) { // Determine which size we're going to optimize. The 'full' size must be optimized before any other. if ( in_array( 'full', $item['sizes'], true ) ) { $current_size = 'full'; $item['sizes'] = array_diff( $item['sizes'], [ 'full' ] ); } else { $current_size = array_shift( $item['sizes'] ); } $item['sizes_done'][] = $current_size; // Optimize the file. $data = $this->optimization_process->optimize_size( $current_size, $item['optimization_level'] ); if ( 'full' === $current_size ) { if ( is_wp_error( $data ) ) { // Don't go further if there is an error. $item['sizes'] = []; $item['error'] = $data; } elseif ( 'already_optimized' === $data['status'] ) { // Status is "already_optimized", try to create next-gen versions only. $item['sizes'] = array_filter( $item['sizes'], [ $this->optimization_process, 'is_size_next_gen' ] ); } elseif ( 'success' !== $data['status'] ) { // Don't go further if the full size has not the "success" status. $item['sizes'] = []; } } if ( ! $item['sizes'] ) { // No more files to optimize. $item['task'] = 'after'; } // Optimize the next file or go to the next task. return $item; } /** * Trigger hooks after the optimization job. * * @since 1.9 * * @param array $item See $this->task(). * @return array The item. */ private function task_after( $item ) { if ( ! empty( $item['data']['delete_backup'] ) ) { $this->optimization_process->delete_backup(); } /** * Fires after optimizing a media. * Any number of files can be optimized, not necessarily all of the media files. * * @since 1.9 * * @param ProcessInterface $process The optimization process. * @param array $item The item being processed. See $this->task(). */ do_action( 'imagify_after_optimize', $this->optimization_process, $item ); if ( empty( $item['data']['hook_suffix'] ) ) { $item['task'] = false; return $item; } $hook_suffix = $item['data']['hook_suffix']; /** * Fires after optimizing a media. * Any number of files can be optimized, not necessarily all of the media files. * * @since 1.9 * * @param ProcessInterface $process The optimization process. * @param array $item The item being processed. See $this->task(). */ do_action( "imagify_after_{$hook_suffix}", $this->optimization_process, $item ); $item['task'] = false; return $item; } /** * Validate an item. * On success, the property $this->optimization_process is set. * * @since 1.9 * * @param array $item See $this->task(). * @return array|bool The item. False if invalid. */ protected function validate_item( $item ) { $this->optimization_process = null; $default = [ 'task' => '', 'id' => 0, 'sizes' => [], 'sizes_done' => [], 'optimization_level' => null, 'process_class' => '', 'data' => [], ]; $item = imagify_merge_intersect( $item, $default ); // Validate some types first. if ( ! is_array( $item['sizes'] ) ) { return false; } if ( isset( $item['error'] ) && ! is_wp_error( $item['error'] ) ) { unset( $item['error'] ); } if ( isset( $item['data']['hook_suffix'] ) && ! is_string( $item['data']['hook_suffix'] ) ) { unset( $item['data']['hook_suffix'] ); } $item['id'] = (int) $item['id']; $item['optimization_level'] = $this->sanitize_optimization_level( $item['optimization_level'] ); if ( ! $item['id'] || ! $item['process_class'] ) { return false; } // Process. $item['process_class'] = '\\' . ltrim( $item['process_class'], '\\' ); if ( ! class_exists( $item['process_class'] ) ) { return false; } $process = $this->get_process( $item ); if ( ! $process ) { return false; } $this->optimization_process = $process; // Validate the current task. if ( empty( $item['task'] ) ) { $item['task'] = 'before'; } if ( ! $item['task'] || ! method_exists( $this, 'task_' . $item['task'] ) ) { return false; } if ( ! $item['sizes'] && 'after' !== $item['task'] ) { // Allow to have no sizes, but only after the optimize task is complete. return false; } if ( ! isset( $item['sizes_done'] ) || ! is_array( $item['sizes_done'] ) ) { $item['sizes_done'] = []; } return $item; } /** * Get the process instance. * * @since 1.9 * * @param array $item See $this->task(). * @return ProcessInterface|bool The instance object on success. False on failure. */ protected function get_process( $item ) { $process_class = $item['process_class']; $process = new $process_class( $item['id'] ); if ( ! $process instanceof ProcessInterface || ! $process->is_valid() ) { return false; } return $process; } /** * Sanitize and validate an optimization level. * If not provided (false, null), fallback to the level set in the plugin's settings. * * @since 1.9 * * @param mixed $optimization_level The optimization level. * @return int */ protected function sanitize_optimization_level( $optimization_level ) { if ( ! is_numeric( $optimization_level ) ) { if ( get_imagify_option( 'lossless' ) ) { return 0; } return get_imagify_option( 'optimization_level' ); } return \Imagify_Options::get_instance()->sanitize_and_validate( 'optimization_level', $optimization_level ); } } PK ̜�\�e�Y! ! Picture/ServiceProvider.phpnu �[��� <?php declare(strict_types=1); namespace Imagify\Picture; use Imagify\Dependencies\League\Container\ServiceProvider\AbstractServiceProvider; /** * Service provider for Picture display */ class ServiceProvider extends AbstractServiceProvider { /** * Services provided by this provider * * @var array */ protected $provides = [ Display::class, ]; /** * Subscribers provided by this provider * * @var array */ public $subscribers = [ Display::class, ]; /** * Check if the service provider provides a specific service. * * @param string $id The id of the service. * * @return bool */ public function provides( string $id ): bool { return in_array( $id, $this->provides, true ); } /** * Registers the provided classes * * @return void */ public function register(): void { $this->getContainer()->addShared( Display::class ) ->addArgument( 'filesystem' ); } /** * Returns the subscribers array * * @return array */ public function get_subscribers() { return $this->subscribers; } } PK ̜�\�ƞ~Q ~Q Picture/Display.phpnu �[��� <?php declare(strict_types=1); namespace Imagify\Picture; use Imagify\EventManagement\SubscriberInterface; use Imagify_Filesystem; /** * Display Next-gen images on the site with <picture> tags. * * @since 1.9 */ class Display implements SubscriberInterface { /** * Option value. * * @var string */ const OPTION_VALUE = 'picture'; /** * Filesystem object. * * @var Imagify_Filesystem */ protected $filesystem; /** * Constructor. * * @param Imagify_Filesystem $filesystem Filesystem instance. */ public function __construct( Imagify_Filesystem $filesystem ) { $this->filesystem = $filesystem; } /** * Array of events this subscriber listens to * * @return array */ public static function get_subscribed_events() { return [ 'template_redirect' => 'start_content_process', 'imagify_process_webp_content' => 'process_content', ]; } /** ----------------------------------------------------------------------------------------- */ /** ADD <PICTURE> TAGS TO THE PAGE ========================================================== */ /** ----------------------------------------------------------------------------------------- */ /** * Start buffering the page content. * * @since 1.9 * * @return void */ public function start_content_process() { if ( ! get_imagify_option( 'display_nextgen' ) ) { return; } if ( self::OPTION_VALUE !== get_imagify_option( 'display_nextgen_method' ) ) { return; } $allow = apply_filters_deprecated( 'imagify_allow_picture_tags_for_webp', [ true ], '2.2', 'imagify_allow_picture_tags_for_nextgen' ); /** * Prevent the replacement of <img> tags into <picture> tags. * * @since 1.9 * * @param bool $allow True to allow the use of <picture> tags (default). False to prevent their use. */ $allow = apply_filters( 'imagify_allow_picture_tags_for_nextgen', true ); if ( ! $allow ) { return; } ob_start( [ $this, 'maybe_process_buffer' ] ); } /** * Maybe process the page content. * * @since 1.9 * * @param string $buffer The buffer content. * * @return string */ public function maybe_process_buffer( $buffer ) { if ( ! $this->is_html( $buffer ) ) { return $buffer; } if ( strlen( $buffer ) <= 255 ) { // Buffer length must be > 255 (IE does not read pages under 255 c). return $buffer; } $buffer = $this->process_content( $buffer ); /** * Filter the page content after Imagify. * * @since 1.9 * * @param string $buffer The page content. */ $buffer = (string) apply_filters( 'imagify_buffer', $buffer ); return $buffer; } /** * Process the content. * * @since 1.9 * * @param string $content The content. * * @return string */ public function process_content( $content ) { $html_no_picture_tags = $this->remove_picture_tags( $content ); $images = $this->get_images( $html_no_picture_tags ); if ( ! $images ) { return $content; } foreach ( $images as $image ) { $tag = $this->build_picture_tag( $image ); $content = str_replace( $image['tag'], $tag, $content ); } return $content; } /** * Remove pre-existing <picture> tags. * * We shouldn't replace images already nested inside picture tags * that are already in the page. * * @since 1.10.0 * * @param string $html Content of the page. * * @return string HTML content without pre-existing <picture> tags. */ private function remove_picture_tags( $html ) { $replace = preg_replace( '#<picture[^>]*>.*?<\/picture\s*>#mis', '', $html ); if ( null === $replace ) { return $html; } return $replace; } /** ----------------------------------------------------------------------------------------- */ /** BUILD HTML TAGS AND ATTRIBUTES ========================================================== */ /** ----------------------------------------------------------------------------------------- */ /** * Build the <picture> tag to insert. * * @since 1.9 * @see $this->process_image() * * @param array $image An array of data. * * @return string A <picture> tag. */ protected function build_picture_tag( $image ) { $to_remove = [ 'alt' => '', 'height' => '', 'width' => '', 'data-lazy-src' => '', 'data-src' => '', 'src' => '', 'data-lazy-srcset' => '', 'data-srcset' => '', 'srcset' => '', 'data-lazy-sizes' => '', 'data-sizes' => '', 'sizes' => '', ]; $attributes = array_diff_key( $image['attributes'], $to_remove ); /** * Filter the attributes to be added to the <picture> tag. * * @since 1.9 * * @param array $attributes A list of attributes to be added to the <picture> tag. * @param array $data Data built from the originale <img> tag. See $this->process_image(). */ $attributes = apply_filters( 'imagify_picture_attributes', $attributes, $image ); /** * Remove Gutenberg specific attributes from picture tag, leave them on img tag. * Optional: $attributes['class'] = 'imagify-webp-cover-wrapper'; for website admin styling ease. */ if ( ! empty( $image['attributes']['class'] ) && strpos( $image['attributes']['class'], 'wp-block-cover__image-background' ) !== false ) { unset( $attributes['style'] ); unset( $attributes['class'] ); unset( $attributes['data-object-fit'] ); unset( $attributes['data-object-position'] ); } $output = '<picture' . $this->build_attributes( $attributes ) . ">\n"; /** * Allow to add more <source> tags to the <picture> tag. * * @since 1.9 * * @param string $more_source_tags Additional <source> tags. * @param array $data Data built from the originale <img> tag. See $this->process_image(). */ $output .= apply_filters( 'imagify_additional_source_tags', '', $image ); $output .= $this->build_source_tag( $image ); $output .= $this->build_img_tag( $image ); $output .= "</picture>\n"; return $output; } /** * Build the <source> tag to insert in the <picture>. * * @since 1.9 * @see $this->process_image() * * @param array $image An array of data. * * @return string A <source> tag. */ protected function build_source_tag( $image ) { $source = ''; foreach ( [ 'avif', 'webp' ] as $image_type ) { $attributes = $this->build_source_attributes( $image, $image_type ); if ( empty( $attributes ) ) { continue; } $source .= '<source' . $this->build_attributes( $attributes ) . "/>\n"; } return $source; } /** * Build the attribute for the source tag. * * @param array $image An array of data. * @param string $image_type Type of image. * * @return array */ protected function build_source_attributes( array $image, string $image_type ): array { $mime_type = ''; $url = ''; switch ( $image_type ) { case 'webp': $mime_type = 'image/webp'; $url = 'webp_url'; break; case 'avif': $mime_type = 'image/avif'; $url = 'avif_url'; break; } $srcset_source = ! empty( $image['srcset_attribute'] ) ? $image['srcset_attribute'] : $image['src_attribute'] . 'set'; $attributes = [ 'type' => $mime_type, $srcset_source => [], ]; if ( ! empty( $image['srcset'] ) ) { foreach ( $image['srcset'] as $srcset ) { if ( empty( $srcset[ $url ] ) ) { continue; } $attributes[ $srcset_source ][] = $srcset[ $url ] . ' ' . $srcset['descriptor']; } } if ( empty( $attributes[ $srcset_source ] ) && empty( $image['src'][ $url ] ) ) { return []; } if ( empty( $attributes[ $srcset_source ] ) ) { $attributes[ $srcset_source ][] = $image['src'][ $url ]; } $attributes[ $srcset_source ] = implode( ', ', $attributes[ $srcset_source ] ); foreach ( [ 'data-lazy-srcset', 'data-srcset', 'srcset' ] as $srcset_attr ) { if ( ! empty( $image['attributes'][ $srcset_attr ] ) && $srcset_attr !== $srcset_source ) { $attributes[ $srcset_attr ] = $image['attributes'][ $srcset_attr ]; } } if ( 'srcset' !== $srcset_source && empty( $attributes['srcset'] ) && ! empty( $image['attributes']['src'] ) ) { // Lazyload: the "src" attr should contain a placeholder (a data image or a blank.gif ). $attributes['srcset'] = $image['attributes']['src']; } foreach ( [ 'data-lazy-sizes', 'data-sizes', 'sizes' ] as $sizes_attr ) { if ( ! empty( $image['attributes'][ $sizes_attr ] ) ) { $attributes[ $sizes_attr ] = $image['attributes'][ $sizes_attr ]; } } /** * Filter the attributes to be added to the <source> tag. * * @since 1.9 * * @param array $attributes A list of attributes to be added to the <source> tag. * @param array $data Data built from the original <img> tag. See $this->process_image(). */ $attributes = apply_filters( 'imagify_picture_source_attributes', $attributes, $image ); return $attributes; } /** * Build the <img> tag to insert in the <picture>. * * @since 1.9 * @see $this->process_image() * * @param array $image An array of data. * * @return string A <img> tag. */ protected function build_img_tag( $image ) { /** * Gutenberg fix. * Check for the 'wp-block-cover__image-background' class on the original image, and leave that class and style attributes if found. */ if ( ! empty( $image['attributes']['class'] ) && strpos( $image['attributes']['class'], 'wp-block-cover__image-background' ) !== false ) { $to_remove = [ 'id' => '', 'title' => '', ]; $attributes = array_diff_key( $image['attributes'], $to_remove ); } else { $to_remove = [ 'class' => '', 'id' => '', 'style' => '', 'title' => '', ]; $attributes = array_diff_key( $image['attributes'], $to_remove ); } /** * Filter the attributes to be added to the <img> tag. * * @since 1.9 * * @param array $attributes A list of attributes to be added to the <img> tag. * @param array $data Data built from the originale <img> tag. See $this->process_image(). */ $attributes = apply_filters( 'imagify_picture_img_attributes', $attributes, $image ); return '<img' . $this->build_attributes( $attributes ) . "/>\n"; } /** * Create HTML attributes from an array. * * @since 1.9 * * @param array $attributes A list of attribute pairs. * * @return string HTML attributes. */ protected function build_attributes( $attributes ) { if ( ! $attributes || ! is_array( $attributes ) ) { return ''; } $out = ''; foreach ( $attributes as $attribute => $value ) { $out .= ' ' . $attribute . '="' . esc_attr( $value ) . '"'; } return $out; } /** ----------------------------------------------------------------------------------------- */ /** VARIOUS TOOLS =========================================================================== */ /** ----------------------------------------------------------------------------------------- */ /** * Get a list of images in a content. * * @since 1.9 * * @param string $content The content. * * @return array */ protected function get_images( $content ) { // Remove comments. $content = preg_replace( '/<!--(.*)-->/Uis', '', $content ); if ( ! preg_match_all( '/<img\s.*>/isU', $content, $matches ) ) { return []; } $images = array_map( [ $this, 'process_image' ], $matches[0] ); $images = array_filter( $images ); /** * Filter the images to display with a <picture> tag. * * @since 1.9 * @see $this->process_image() * * @param array $images A list of arrays. * @param string $content The page content. */ $images = apply_filters( 'imagify_webp_picture_images_to_display', $images, $content ); if ( ! $images || ! is_array( $images ) ) { return []; } foreach ( $images as $i => $image ) { if ( ( empty( $image['src']['webp_exists'] ) || empty( $image['src']['webp_url'] ) ) && ( empty( $image['src']['avif_exists'] ) || empty( $image['src']['avif_url'] ) ) ) { unset( $images[ $i ] ); continue; } if ( empty( $image['src']['webp_exists'] ) || empty( $image['src']['webp_url'] ) ) { unset( $images[ $i ]['src']['webp_url'] ); } if ( empty( $image['src']['avif_exists'] ) || empty( $image['src']['avif_url'] ) ) { unset( $images[ $i ]['src']['avif_url'] ); } unset( $images[ $i ]['src']['webp_path'], $images[ $i ]['src']['webp_exists'] ); unset( $images[ $i ]['src']['avif_path'], $images[ $i ]['src']['avif_exists'] ); if ( empty( $image['srcset'] ) || ! is_array( $image['srcset'] ) ) { unset( $images[ $i ]['srcset'] ); continue; } foreach ( $image['srcset'] as $j => $srcset ) { if ( ! is_array( $srcset ) ) { continue; } if ( ( empty( $srcset['webp_exists'] ) || empty( $srcset['webp_url'] ) ) && ( empty( $srcset['avif_exists'] ) || empty( $srcset['avif_url'] ) ) ) { unset( $images[ $i ]['srcset'][ $j ]['webp_url'] ); unset( $images[ $i ]['srcset'][ $j ]['avif_url'] ); } if ( empty( $srcset['webp_exists'] ) || empty( $srcset['webp_url'] ) ) { unset( $images[ $i ]['srcset'][ $j ]['webp_url'] ); } if ( empty( $srcset['avif_exists'] ) || empty( $srcset['avif_url'] ) ) { unset( $images[ $i ]['srcset'][ $j ]['avif_url'] ); } unset( $images[ $i ]['srcset'][ $j ]['webp_path'], $images[ $i ]['srcset'][ $j ]['webp_exists'] ); unset( $images[ $i ]['srcset'][ $j ]['avif_path'], $images[ $i ]['srcset'][ $j ]['avif_exists'] ); } } return $images; } /** * Process an image tag and get an array containing some data. * * @since 1.9 * * @param string $image An image html tag. * @return array|false { * An array of data if the image has a WebP version. False otherwise. * * @type string $tag The image tag. * @type array $attributes The image attributes (minus src and srcset). * @type array $src { * @type string $url URL to the original image. * @type string $webp_url URL to the WebP version. * } * @type array $srcset { * An array or arrays. Not set if not applicable. * * @type string $url URL to the original image. * @type string $webp_url URL to the WebP version. Not set if not applicable. * @type string $descriptor A src descriptor. * } * } */ protected function process_image( $image ) { static $extensions; $atts_pattern = '/(?<name>[^\s"\']+)\s*=\s*(["\'])\s*(?<value>.*?)\s*\2/s'; if ( ! preg_match_all( $atts_pattern, $image, $tmp_attributes, PREG_SET_ORDER ) ) { // No attributes? return false; } $attributes = []; foreach ( $tmp_attributes as $attribute ) { $attributes[ $attribute['name'] ] = $attribute['value']; } if ( ! empty( $attributes['class'] ) && strpos( $attributes['class'], 'imagify-no-webp' ) !== false ) { // Has the 'imagify-no-webp' class. return false; } // Deal with the src attribute. $src_source = false; foreach ( [ 'data-lazy-src', 'data-src', 'src' ] as $src_attr ) { if ( ! empty( $attributes[ $src_attr ] ) ) { $src_source = $src_attr; break; } } if ( ! $src_source ) { // No src attribute. return false; } if ( ! isset( $extensions ) ) { $extensions = imagify_get_mime_types( 'image' ); $extensions = array_keys( $extensions ); $extensions = implode( '|', $extensions ); } if ( ! preg_match( '@^(?<src>(?:(?:https?:)?//|/).+\.(?<extension>' . $extensions . '))(?<query>\?.*)?$@i', $attributes[ $src_source ], $src ) ) { // Not a supported image format. return false; } $data = [ 'tag' => $image, 'attributes' => $attributes, 'src_attribute' => $src_source, 'src' => [ 'url' => $attributes[ $src_source ], ], 'srcset_attribute' => false, 'srcset' => [], ]; foreach ( $this->get_nextgen_image_data_set( $src ) as $key => $value ) { $data['src'][ $key ] = $value; } // Deal with the srcset attribute. $srcset_source = false; foreach ( [ 'data-lazy-srcset', 'data-srcset', 'srcset' ] as $srcset_attr ) { if ( ! empty( $attributes[ $srcset_attr ] ) ) { $srcset_source = $srcset_attr; break; } } if ( $srcset_source ) { $srcset_data = []; $data['srcset_attribute'] = $srcset_source; $srcset = explode( ',', $attributes[ $srcset_source ] ); foreach ( $srcset as $srcs ) { $srcs = preg_split( '/\s+/', trim( $srcs ) ); if ( count( $srcs ) > 2 ) { // Not a good idea to have space characters in file name. $descriptor = array_pop( $srcs ); $srcs = [ implode( ' ', $srcs ), $descriptor ]; } if ( empty( $srcs[1] ) ) { $srcs[1] = '1x'; } if ( ! preg_match( '@^(?<src>(?:https?:)?//.+\.(?<extension>' . $extensions . '))(?<query>\?.*)?$@i', $srcs[0], $src ) ) { // Not a supported image format. $data['srcset'][] = [ 'url' => $srcs[0], 'descriptor' => $srcs[1], ]; continue; } $srcset_data = [ 'url' => $srcs[0], 'descriptor' => $srcs[1], ]; foreach ( $this->get_nextgen_image_data_set( $src ) as $key => $value ) { $srcset_data[ $key ] = $value; } $data['srcset'][] = $srcset_data; } } /** * Filter a processed image tag. * * @since 1.9 * * @param array $data An array of data for this image. * @param string $image An image html tag. */ $data = apply_filters( 'imagify_webp_picture_process_image', $data, $image ); if ( ! $data || ! is_array( $data ) ) { return false; } if ( ! isset( $data['tag'], $data['attributes'], $data['src_attribute'], $data['src'], $data['srcset_attribute'], $data['srcset'] ) ) { return false; } return $data; } /** * Get the next-gen image(webp & avif) data set. * * @param array $src Array of url/path segments. * * @return array */ protected function get_nextgen_image_data_set( array $src ): array { $webp_url = imagify_path_to_nextgen( $src['src'], 'webp' ); $webp_path = $this->url_to_path( $webp_url ); $avif_url = imagify_path_to_nextgen( $src['src'], 'avif' ); $avif_path = $this->url_to_path( $avif_url ); $query_string = ! empty( $src['query'] ) ? $src['query'] : ''; return [ // WebP data set. 'webp_url' => $webp_url . $query_string, 'webp_path' => $webp_path, 'webp_exists' => $webp_path && $this->filesystem->exists( $webp_path ), // Avif data set. 'avif_url' => $avif_url . $query_string, 'avif_path' => $avif_path, 'avif_exists' => $avif_path && $this->filesystem->exists( $avif_path ), ]; } /** * Tell if a content is HTML. * * @since 1.9 * * @param string $content The content. * * @return bool */ protected function is_html( $content ) { return preg_match( '/<\/html>/i', $content ); } /** * Convert a file URL to an absolute path. * * @since 1.9 * * @param string $url A file URL. * * @return string|bool The file path. False on failure. */ protected function url_to_path( $url ) { static $uploads_url; static $uploads_dir; static $root_url; static $root_dir; static $cdn_url; static $domain_url; /** * $url, $uploads_url, $root_url, and $cdn_url are passed through `set_url_scheme()` only to make sure `stripos()` doesn't fail over a stupid http/https difference. */ if ( ! isset( $uploads_url ) ) { $uploads_url = set_url_scheme( $this->filesystem->get_upload_baseurl() ); $uploads_dir = $this->filesystem->get_upload_basedir( true ); $root_url = set_url_scheme( $this->filesystem->get_site_root_url() ); $root_dir = $this->filesystem->get_site_root(); $cdn_url = apply_filters( 'imagify_cdn_source_url', '' ); $cdn_url = $cdn_url['url'] ? set_url_scheme( $cdn_url['url'] ) : false; $domain_url = wp_parse_url( $root_url ); if ( ! empty( $domain_url['scheme'] ) && ! empty( $domain_url['host'] ) ) { $domain_url = $domain_url['scheme'] . '://' . $domain_url['host'] . '/'; } else { $domain_url = false; } } // Get the right URL format. if ( $domain_url && strpos( $url, '/' ) === 0 ) { // URL like `/path/to/image.jpg.webp`. $url = $domain_url . ltrim( $url, '/' ); } $url = set_url_scheme( $url ); if ( $cdn_url && $domain_url && stripos( $url, $cdn_url ) === 0 ) { // CDN. $url = str_ireplace( $cdn_url, $domain_url, $url ); } // Return the path. if ( stripos( $url, $uploads_url ) === 0 ) { return str_ireplace( $uploads_url, $uploads_dir, $url ); } if ( stripos( $url, $root_url ) === 0 ) { return str_ireplace( $root_url, $root_dir, $url ); } return false; } } PK ̜�\0�P�� � ) Optimization/Process/ProcessInterface.phpnu �[��� <?php declare(strict_types=1); namespace Imagify\Optimization\Process; use Imagify\Media\MediaInterface; use Imagify\Optimization\Data\DataInterface; use Imagify\Optimization\File; use WP_Error; /** * Interface to use to optimize medias. * * @since 1.9 */ interface ProcessInterface { /** * The suffix used in the thumbnail size name. * * @var string * @since 1.9 */ const WEBP_SUFFIX = '@imagify-webp'; /** * The suffix used in the thumbnail size name. * * @var string * @since 2.2 */ const AVIF_SUFFIX = '@imagify-avif'; /** * Tell if the given entry can be accepted in the constructor. * For example it can include `is_numeric( $id )` if the constructor accepts integers. * * @since 1.9 * * @param mixed $id Whatever. * * @return bool */ public static function constructor_accepts( $id ); /** * Get the data instance. * * @since 1.9 * * @return DataInterface|false */ public function get_data(); /** * Get the media instance. * * @since 1.9 * * @return MediaInterface|false */ public function get_media(); /** * Get the File instance of the original file. * * @since 1.9.8 * * @return File|false */ public function get_original_file(); /** * Get the File instance of the full size file. * * @since 1.9.8 * * @return File|false */ public function get_fullsize_file(); /** * Tell if the current media is valid. * * @since 1.9 * * @return bool */ public function is_valid(); /** * Tell if the current user is allowed to operate Imagify in this context. * * @since 1.9 * * @param string $describer Capacity describer. See \Imagify\Context\ContextInterface->get_capacity() for possible values. Can also be a "real" user capacity. * * @return bool */ public function current_user_can( $describer ); /** * Optimize a media files by pushing tasks into the queue. * * @since 1.9 * * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ public function optimize( $optimization_level = null ); /** * Re-optimize a media files with a different level. * * @since 1.9 * * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ public function reoptimize( $optimization_level = null ); /** * Optimize several file sizes by pushing tasks into the queue. * * @since 1.9 * * @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file. * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ public function optimize_sizes( $sizes, $optimization_level = null ); /** * Optimize one file with Imagify directly. * * @since 1.9 * * @param string $size The media size. * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return array|WP_Error The optimization data. A \WP_Error instance on failure. */ public function optimize_size( $size, $optimization_level = null ); /** * Restore the media files from the backup file. * * @since 1.9 * * @return bool|WP_Error True on success. A \WP_Error instance on failure. */ public function restore(); /** * Get the sizes for this media that have not get through optimization. * No sizes are returned if the file is not optimized, has no backup, or is not an image. * The 'full' size os never returned. * * @since 1.9 * * @return array|WP_Error { * A WP_Error object on failure. * An array of data for the thumbnail sizes on success. * Size names are used as array keys. * * @type int $width The image width. * @type int $height The image height. * @type bool $crop True to crop, false to resize. * @type string $name The size name. * @type string $file The name the thumbnail "should" have. * } */ public function get_missing_sizes(); /** * Optimize missing thumbnail sizes. * * @since 1.9 * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ public function optimize_missing_thumbnails(); /** * Delete the backup file. * * @since 1.9 */ public function delete_backup(); /** * Maybe resize an image. * * @since 1.9 * * @param string $size The size name. * @param File $file A File instance. * * @return array|WP_Error A \WP_Error instance on failure, an array on success as follow: { * @type bool $resized True when the image has been resized. * @type bool $backuped True when the image has been backuped. * @type int $file_size The file size in bytes. * } */ public function maybe_resize( $size, $file ); /** * Generate next-gen images if they are missing. * * @since 1.9 * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ public function generate_nextgen_versions(); /** * Delete the next gen format images. * This doesn't delete the related optimization data. * * @since 2.2 * * @param bool $keep_full Set to true to keep the full size. * * @return bool|WP_Error True on success. A \WP_Error object on failure. */ public function delete_nextgen_files( $keep_full = false ); /** * Tell if a thumbnail size is an "Imagify Next-Gen" size. * * @since 2.2 * * @param string $size_name The size name. * * @return string|bool The unsuffixed name of the size if next-gen. False if not next-gen. */ public function is_size_next_gen( $size_name ); /** * Tell if the media has all next-gen versions. * * @return bool */ public function is_full_next_gen(); /** * Tell if the media has a next-gen format. * * @since 2.2 * * @return bool */ public function has_next_gen(); /** * Tell if a process is running for this media. * * @since 1.9 * * @return bool */ public function is_locked(); /** * Set the running status to "running" for a period of time. * * @since 1.9 */ public function lock(); /** * Delete the running status. * * @since 1.9 */ public function unlock(); /** * Tell if a size already has optimization data. * * @since 1.9 * * @param string $size The size name. * * @return bool */ public function size_has_optimization_data( $size ); /** * Update the optimization data for a size. * * @since 1.9 * * @param object $response The API response. * @param string $size The size name. * @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return array { * The optimization data. * * @type string $size The size name. * @type int $level The optimization level. * @type string $status The status: 'success', 'already_optimized', 'error'. * @type bool $success True if successfully optimized. False on error or if already optimized. * @type string $error An error message. * @type int $original_size The weight of the file, before optimization. * @type int $optimized_size The weight of the file, once optimized. * } */ public function update_size_optimization_data( $response, $size, $level ); } PK ̜�\�6�� �� ( Optimization/Process/AbstractProcess.phpnu �[��� <?php declare(strict_types=1); namespace Imagify\Optimization\Process; use Imagify_Filesystem; use Imagify\Deprecated\Traits\Optimization\Process\AbstractProcessDeprecatedTrait; use Imagify\Job\MediaOptimization; use Imagify\Optimization\Data\DataInterface; use Imagify\Optimization\File; use Imagify\Media\MediaInterface; use WP_Error; /** * Abstract class used to optimize medias. * * @since 1.9 */ abstract class AbstractProcess implements ProcessInterface { use AbstractProcessDeprecatedTrait; /** * The suffix used in file name to create a temporary copy of the full size. * * @var string * @since 1.9 */ const TMP_SUFFIX = '@imagify-tmp'; /** * Used for the name of the transient telling if a media is locked. * %1$s is the context, %2$s is the media ID. * * @var string * @since 1.9 */ const LOCK_NAME = 'imagify_%1$s_%2$s_process_locked'; /** * The data optimization object. * * @var DataInterface * @since 1.9 */ protected $data; /** * The optimization data format. * * @var array * @since 1.9 */ protected $data_format = [ 'level' => null, 'status' => null, 'success' => null, 'error' => null, 'original_size' => null, 'optimized_size' => null, ]; /** * A File instance. * * @var File * @since 1.9 */ protected $file; /** * Filesystem object. * * @var Imagify_Filesystem * @since 1.9 */ protected $filesystem; /** * Used to cache the plugin’s options. * * @var array * @since 1.9 */ protected $options = []; /** * Tells the format we are currently processing * * @var string * @since 2.2 */ protected $format; /** * Array of image extensions processed. * * @var array */ protected $extensions = [ 'webp', 'avif', ]; /** * The constructor. * * @since 1.9 * @see self::constructor_accepts() * * @param mixed $id An ID, or whatever type the constructor accepts. */ public function __construct( $id ) { if ( $id instanceof DataInterface ) { $this->data = $id; } elseif ( static::constructor_accepts( $id ) ) { $data_class = str_replace( '\\Optimization\\Process\\', '\\Optimization\\Data\\', get_called_class() ); $data_class = '\\' . ltrim( $data_class, '\\' ); $this->data = new $data_class( $id ); } else { $this->data = false; } $this->filesystem = \Imagify_Filesystem::get_instance(); $this->format = $this->get_current_format(); } /** * Tell if the given entry can be accepted in the constructor. * * @since 1.9 * * @param mixed $id Whatever. * * @return bool */ public static function constructor_accepts( $id ) { if ( $id instanceof DataInterface ) { return true; } $data_class = str_replace( '\\Optimization\\Process\\', '\\Optimization\\Data\\', get_called_class() ); $data_class = '\\' . ltrim( $data_class, '\\' ); return $data_class::constructor_accepts( $id ); } /** * Get the data instance. * * @since 1.9 * * @return DataInterface|false */ public function get_data() { return $this->data; } /** * Get the media instance. * * @since 1.9 * * @return MediaInterface|false */ public function get_media() { if ( ! $this->get_data() ) { return false; } return $this->get_data()->get_media(); } /** * Get the File instance of the original file. * * @since 1.9.8 * * @return File|false */ public function get_original_file() { if ( isset( $this->file ) ) { return $this->file; } $this->file = false; if ( $this->get_media() ) { $this->file = new File( $this->get_media()->get_raw_original_path() ); } return $this->file; } /** * Get the File instance of the full size file. * * @since 1.9.8 * * @return File|false */ public function get_fullsize_file() { if ( isset( $this->file ) ) { return $this->file; } $this->file = false; if ( $this->get_media() ) { $this->file = new File( $this->get_media()->get_raw_fullsize_path() ); } return $this->file; } /** * Tell if the current media is valid. * * @since 1.9 * * @return bool */ public function is_valid() { return $this->get_media() && $this->get_media()->is_valid(); } /** * Tell if the current user is allowed to operate Imagify in this context. * * @since 1.9 * * @param string $describer Capacity describer. See \Imagify\Context\ContextInterface->get_capacity() for possible values. Can also be a "real" user capacity. * @return bool */ public function current_user_can( $describer ) { if ( ! $this->is_valid() ) { return false; } $media = $this->get_media(); return $media->get_context_instance()->current_user_can( $describer, $media->get_id() ); } /** * Optimize a media files. * * @since 1.9 * * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * @param array $args An array of optionnal arguments. * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ public function optimize( $optimization_level = null, $args = [] ) { if ( ! $this->is_valid() ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } $media = $this->get_media(); if ( ! $media->is_supported() ) { return new WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) ); } $data = $this->get_data(); if ( $data->is_optimized() ) { return new WP_Error( 'optimized', __( 'This media has already been optimized by Imagify.', 'imagify' ) ); } if ( $data->is_already_optimized() && $this->has_next_gen() ) { // If already optimized but has next-gen, delete next-gen versions and optimization data. $data->delete_optimization_data(); $deleted = $this->delete_nextgen_files(); if ( is_wp_error( $deleted ) ) { return new WP_Error( 'next_gen_not_deleted', __( 'Previous Next-Gen files could not be deleted.', 'imagify' ) ); } } $sizes = $media->get_media_files(); $args = is_array( $args ) ? $args : []; $args['hook_suffix'] = 'optimize_media'; // Optimize. return $this->optimize_sizes( array_keys( $sizes ), $optimization_level, $args ); } /** * Re-optimize a media files with a different level. * * @since 1.9 * * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * @param array $args An array of optionnal arguments. * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ public function reoptimize( $optimization_level = null, $args = [] ) { if ( ! $this->is_valid() ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } $media = $this->get_media(); if ( ! $media->is_supported() ) { return new WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) ); } $data = $this->get_data(); if ( ! $data->get_optimization_status() ) { return new WP_Error( 'not_processed_yet', __( 'This media has not been processed yet.', 'imagify' ) ); } $optimization_level = $this->sanitize_optimization_level( $optimization_level ); if ( $data->get_optimization_level() === $optimization_level ) { return new WP_Error( 'identical_optimization_level', __( 'This media is already optimized with this level.', 'imagify' ) ); } $this->restore(); $sizes = $media->get_media_files(); $args = is_array( $args ) ? $args : []; $args['hook_suffix'] = 'reoptimize_media'; // Optimize. return $this->optimize_sizes( array_keys( $sizes ), $optimization_level, $args ); } /** * Optimize several file sizes by pushing tasks into the queue. * * @since 1.9 * @see MediaOptimization->task_before() * @see MediaOptimization->task_after() * * @since 2.2 * Addition of the image format * * @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file. * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * @param array $args { * An array of optionnal arguments. * * @type string $hook_suffix Suffix used to trigger hooks before and after optimization. * } * * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure. */ public function optimize_sizes( $sizes, $optimization_level = null, $args = [] ) { if ( ! $this->is_valid() ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } $media = $this->get_media(); if ( ! $media->is_supported() ) { return new WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) ); } if ( ! $sizes ) { return new WP_Error( 'no_sizes', __( 'No sizes given to be optimized.', 'imagify' ) ); } if ( empty( $args['locked'] ) ) { if ( $this->is_locked() ) { return new WP_Error( 'media_locked', __( 'This media is already being processed.', 'imagify' ) ); } $this->lock(); } if ( $media->is_image() ) { // Add Next-Gen conversion. $formats = imagify_nextgen_images_formats(); foreach ( $formats as $format ) { if ( 'avif' === $format ) { $format_suffix = static::AVIF_SUFFIX; } elseif ( 'webp' === $format ) { $format_suffix = static::WEBP_SUFFIX; } $files = $media->get_media_files(); foreach ( $sizes as $size_name ) { if ( empty( $files[ $size_name ] ) ) { continue; } if ( $this->get_mime_type( $format ) === $files[ $size_name ]['mime-type'] ) { continue; } if ( in_array( $size_name . $format_suffix, $sizes, true ) ) { continue; } array_unshift( $sizes, $size_name . $format_suffix ); } } if ( ! $media->get_context_instance()->can_backup() && ! $media->get_backup_path() && ! $this->get_data()->get_size_data( 'full', 'success' ) ) { /** * Backup is NOT activated, and a backup file does NOT exist yet, and the full size is NOT optimized yet. * Next-Gen conversion needs a backup file, even a temporary one: we’ll create one. */ $next_gen = false; foreach ( $sizes as $size_name ) { if ( $this->is_size_next_gen( $size_name ) ) { $next_gen = true; break; } } if ( $next_gen ) { // We have at least one next-gen conversion to do: create a temporary backup. $backuped = $this->get_original_file()->backup( $media->get_raw_backup_path() ); if ( $backuped ) { $args['delete_backup'] = true; } } } } $sizes = array_unique( $sizes ); $optimization_level = $this->sanitize_optimization_level( $optimization_level ); /** * Filter the data sent to the optimization process. * * @since 1.9 * * @param array $new_args Additional data to send to the optimization process. * @param array $args Current data sent to the process. * @param ProcessInterface $process The current optimization process. * @param array $sizes Sizes being processed. * @param int $optimization_level Optimization level. */ $new_args = apply_filters( 'imagify_optimize_sizes_args', [], $args, $this, $sizes, $optimization_level ); if ( $new_args && is_array( $new_args ) ) { $args = array_merge( $new_args, $args ); } /** * Push the item to the queue, save the queue in the DB, empty the queue. * A "batch" is then created in the DB with this unique item, it is then free to loop through its steps (files) without another item interfering (each media optimization has its own dedicated batch/queue). */ MediaOptimization::get_instance()->push_to_queue( [ 'id' => $media->get_id(), 'sizes' => $sizes, 'optimization_level' => $optimization_level, 'process_class' => get_class( $this ), 'data' => $args, ] )->save(); return true; } /** * Optimize one file with Imagify directly. * * @since 1.9 * * @param string $size The media size. * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return array|WP_Error Optimized image data. A WP_Error object on error. */ public function optimize_size( $size, $optimization_level = null ) { if ( ! $this->is_valid() ) { // Bail out. return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } $media = $this->get_media(); $sizes = $media->get_media_files(); $thumb_size = $size; $next_gen = $this->is_size_next_gen( $size ); $path_is_temp = false; if ( $next_gen ) { // We'll make sure the file is an image later. $thumb_size = $next_gen; // Contains the name of the non-next-gen size. $next_gen = true; } if ( empty( $sizes[ $thumb_size ]['path'] ) ) { // Bail out. // This size is not in our list. return new WP_Error( 'unknown_size', sprintf( /* translators: %s is a size name. */ __( 'The size %s is unknown.', 'imagify' ), '<code>' . esc_html( $thumb_size ) . '</code>' ) ); } if ( $this->get_data()->get_size_data( $size, 'success' ) ) { // Bail out. // This size is already optimized with Imagify, and must not be optimized again. if ( $next_gen ) { return new WP_Error( 'size_is_successfully_optimized', sprintf( /* translators: %s is a size name. */ __( 'The Next-Gen format for the size %s already exists.', 'imagify' ), '<code>' . esc_html( $thumb_size ) . '</code>' ) ); } else { return new WP_Error( 'size_is_successfully_optimized', sprintf( /* translators: %s is a size name. */ __( 'The size %s is already optimized by Imagify.', 'imagify' ), '<code>' . esc_html( $thumb_size ) . '</code>' ) ); } } /** * Starting from here, errors will be stored in the optimization data of the size. */ $path = $sizes[ $thumb_size ]['path']; $optimization_level = $this->sanitize_optimization_level( $optimization_level ); if ( $next_gen && $this->get_data()->get_size_data( $thumb_size, 'success' ) ) { // We want a next-gen version but the source file is already optimized by Imagify. $result = $this->create_temporary_copy( $thumb_size, $sizes ); if ( ! $result ) { // Bail out. // Could not create a copy of the non-next-gen version. $response = new WP_Error( 'non_next_gen_copy_failed', sprintf( /* translators: %s is a size name. */ __( 'Could not create an unoptimized copy of the size %s.', 'imagify' ), '<code>' . esc_html( $thumb_size ) . '</code>' ) ); $this->update_size_optimization_data( $response, $size, $optimization_level ); return $response; } /** * $path now targets a temporary file. */ $path = $this->get_temporary_copy_path( $thumb_size, $sizes ); $path_is_temp = true; } $file = new File( $path ); // Original file or temporary copy. if ( ! $file->is_supported( $media->get_allowed_mime_types() ) ) { // Bail out. // This file type is not supported. $extension = $file->get_extension(); if ( ! $extension ) { $response = new WP_Error( 'extension_not_mime', __( 'This file has an extension that does not match a mime type.', 'imagify' ) ); } elseif ( '' === $extension ) { $response = new WP_Error( 'no_extension', __( 'With no extension, this file cannot be optimized.', 'imagify' ) ); } elseif ( ! $extension ) { $response = new WP_Error( 'extension_not_mime', __( 'This file has an extension that does not match a mime type.', 'imagify' ) ); } else { $response = new WP_Error( 'extension_not_supported', sprintf( /* translators: %s is a file extension. */ __( '%s cannot be optimized.', 'imagify' ), '<code>' . esc_html( strtolower( $extension ) ) . '</code>' ) ); } if ( $path_is_temp ) { $this->filesystem->delete( $path ); } $this->update_size_optimization_data( $response, $size, $optimization_level ); return $response; } if ( $next_gen && ! $file->is_image() ) { // Bail out. if ( $path_is_temp ) { $this->filesystem->delete( $path ); } $response = new WP_Error( 'no_next_gen', __( 'This file is not an image and cannot be converted to Next-Gen format.', 'imagify' ) ); $this->update_size_optimization_data( $response, $size, $optimization_level ); return $response; } $is_disabled = ! empty( $sizes[ $thumb_size ]['disabled'] ); /** * Fires before optimizing a file. * Return a WP_Error object to prevent the optimization. * * @since 1.9 * * @param null|WP_Error $response Null by default. Return a WP_Error object to prevent optimization. * @param ProcessInterface $process The optimization process instance. * @param File $file The file instance. If $webp is true, $file references the non-WebP file. * @param string $thumb_size The media size. * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * @param bool $webp The image will be converted to WebP. * @param bool $is_disabled Tell if this size is disabled from optimization. */ $response = apply_filters( 'imagify_before_optimize_size', null, $this, $file, $thumb_size, $optimization_level, $next_gen, $is_disabled ); if ( ! is_wp_error( $response ) ) { if ( $is_disabled ) { // This size must not be optimized. $response = new WP_Error( 'unauthorized_size', sprintf( /* translators: %s is a size name. */ __( 'The size %s is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ), '<code>' . esc_html( $thumb_size ) . '</code>' ) ); } elseif ( ! $this->filesystem->exists( $file->get_path() ) ) { $response = new WP_Error( 'file_not_exists', sprintf( /* translators: %s is a file path. */ __( 'The file %s does not seem to exist.', 'imagify' ), '<code>' . esc_html( $this->filesystem->make_path_relative( $file->get_path() ) ) . '</code>' ) ); } elseif ( $next_gen && ! $this->can_create_next_gen_version( $file->get_path() ) ) { $response = new WP_Error( 'is_animated_gif', __( 'This file is an animated gif: since Imagify does not support animated WebP/AVIF, WebP/AVIF creation for animated gif is disabled.', 'imagify' ) ); } elseif ( ! $this->filesystem->is_writable( $file->get_path() ) ) { $response = new WP_Error( 'file_not_writable', sprintf( /* translators: %s is a file path. */ __( 'The file %s does not seem to be writable.', 'imagify' ), '<code>' . esc_html( $this->filesystem->make_path_relative( $file->get_path() ) ) . '</code>' ) ); } else { // Maybe resize the file. $response = $this->maybe_resize( $thumb_size, $file ); $convert = ''; if ( $next_gen ) { if ( strpos( $size, static::AVIF_SUFFIX ) ) { $convert = 'avif'; } elseif ( strpos( $size, static::WEBP_SUFFIX ) ) { $convert = 'webp'; } } if ( ! is_wp_error( $response ) ) { // Resizing succeeded: optimize the file. $response = $file->optimize( [ 'backup' => ! $response['backuped'] && $this->can_backup( $size ), 'backup_path' => $media->get_raw_backup_path(), 'backup_source' => 'full' === $thumb_size ? $media->get_original_path() : null, 'optimization_level' => $optimization_level, 'convert' => $convert, 'keep_exif' => true, 'context' => $media->get_context(), 'original_size' => $response['file_size'], ] ); $response = $this->compare_next_gen_file_size( [ 'response' => $response, 'file' => $file, 'is_next_gen' => $next_gen, 'next_gen_format' => $convert, 'non_next_gen_thumb_size' => $thumb_size, 'non_next_gen_file_path' => $sizes[ $thumb_size ]['path'], // Don't use $path nor $file->get_path(), it may return the path to a temporary file. 'optimization_level' => $optimization_level, ] ); if ( property_exists( $response, 'message' ) ) { $path_is_temp = false; if ( $path !== $sizes[ $thumb_size ]['path'] ) { $this->filesystem->delete( $path ); } $path = $sizes[ $thumb_size ]['path']; } } } } $data = $this->update_size_optimization_data( $response, $size, $optimization_level ); /** * Fires after optimizing a file. * * @since 1.9 * * @param ProcessInterface $process The optimization process instance. * @param File $file The file instance. * @param string $thumb_size The media size. * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * @param bool $webp The image was supposed to be converted to WebP. * @param bool $is_disabled Tell if this size is disabled from optimization. */ do_action( 'imagify_after_optimize_size', $this, $file, $thumb_size, $optimization_level, $next_gen, $is_disabled ); if ( ! $path_is_temp ) { return $data; } // Delete the temporary copy. $this->filesystem->delete( $path ); if ( is_wp_error( $response ) ) { return $data; } // Rename the optimized file. $destination_path = str_replace( static::TMP_SUFFIX . '.', '.', $file->get_path() ); $this->filesystem->move( $file->get_path(), $destination_path, true ); return $data; } /** * Compare the file size of a file and its Next-Gen version: if the Next-Gen version is heavier than the non-next-gen file, delete it. * * @since 2.2 * * @param array $args { * A list of mandatory arguments. * * @type \sdtClass|WP_Error $response Optimized image data. A WP_Error object on error. * @type File $file The File instance of the file currently being optimized. * @type bool $is_next_gen Tell if we're requesting a next-gen file. * @type string $non_next_gen_thumb_size Name of the corresponding non-next-gen thumbnail size. If we're not creating a Next-Gen file, this corresponds to the current thumbnail size. * @type string $non_next_gen_file_path Path to the corresponding non-next-gen file. If we're not creating a Next-Gen file, this corresponds to the current file path. * @type string $optimization_level The optimization level. * } * * @return \sdtClass|WP_Error Optimized image data. A WP_Error object on error. */ protected function compare_next_gen_file_size( $args ) { static $keep_large_next_gen; if ( ! isset( $keep_large_next_gen ) ) { /** * Allow to not store next-gen images that are larger than their non-next-gen version. * * @since 1.9.4 * * @param bool $keep_large_next-gen Set to false if you prefer your visitors over your Pagespeed score. Default value is true. */ $keep_large_next_gen = apply_filters( 'imagify_keep_large_next_gen', true ); } if ( $keep_large_next_gen || is_wp_error( $args['response'] ) || ! $args['file']->is_image() ) { return $args['response']; } // Optimization succeeded. if ( ! property_exists( $args['response'], 'message' ) && $args['is_next_gen'] ) { /** * We just created a next-gen version: * Check if it is lighter than the (maybe optimized) non-next-gen file. */ $data = $this->get_data()->get_size_data( $args['non_next_gen_thumb_size'] ); if ( ! $data ) { // We haven’t tried to optimize the non-next-gen size yet. return $args['response']; } if ( ! empty( $data['optimized_size'] ) ) { // The non-next-gen size is optimized, we know the file size. $non_next_gen_file_size = $data['optimized_size']; } else { // The non-next-gen size is "already optimized" or "error": grab the file size directly from the file. $non_next_gen_file_size = $this->filesystem->size( $args['non_next_gen_file_path'] ); } if ( ! $non_next_gen_file_size || $non_next_gen_file_size > $args['response']->new_size ) { // The new next-gen file is lighter. return $args['response']; } // The new next-gen file is heavier than the non-next-gen file: delete it and return an error. $this->filesystem->delete( $args['file']->get_path() ); return new WP_Error( 'next_gen_heavy', sprintf( /* translators: %s is a size name. */ __( 'The Next-Gen version of the size %s is heavier than its non-next-gen version.', 'imagify' ), '<code>' . esc_html( $args['non_next_gen_thumb_size'] ) . '</code>' ) ); } /** * We just created a non-next-gen version: * Check if its next-gen version file is lighter than this one. */ $next_gen_size = $args['non_next_gen_thumb_size'] . $args['next_gen_format']; $next_gen_file_size = $this->get_data()->get_size_data( $next_gen_size, 'optimized_size' ); if ( property_exists( $args['response'], 'message' ) || ! $next_gen_file_size || $next_gen_file_size < $args['response']->new_size ) { // The next-gen file is lighter than this one. return $args['response']; } // The new optimized file is lighter than the next-gen file: delete the next-gen file and store an error. $next_gen_path = $args['file']->get_path_to_nextgen( $args['next_gen_format'] ); if ( $next_gen_path && $this->filesystem->is_writable( $next_gen_path ) ) { $this->filesystem->delete( $next_gen_path ); } $next_gen_response = new WP_Error( 'next_gen_heavy', sprintf( /* translators: %s is a size name. */ __( 'The Next-Gen version of the size %s is heavier than its non-next-gen version.', 'imagify' ), '<code>' . esc_html( $args['non_next_gen_thumb_size'] ) . '</code>' ) ); $this->update_size_optimization_data( $next_gen_response, $next_gen_size, $args['optimization_level'] ); return $args['response']; } /** * Restore the media files from the backup file. * * @since 1.9 * * @return bool|WP_Error True on success. A WP_Error instance on failure. */ public function restore() { if ( ! $this->is_valid() ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } $media = $this->get_media(); if ( ! $media->is_supported() ) { return new WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) ); } if ( ! $media->has_backup() ) { return new WP_Error( 'no_backup', __( 'This media has no backup file.', 'imagify' ) ); } if ( $this->is_locked() ) { return new WP_Error( 'media_locked', __( 'This media is already being processed.', 'imagify' ) ); } $this->lock( 'restoring' ); $backup_path = $media->get_backup_path(); $original_path = $media->get_raw_original_path(); if ( $backup_path === $original_path ) { $this->unlock(); return new WP_Error( 'same_path', __( 'Image path and backup path are identical.', 'imagify' ) ); } $dest_dir = $this->filesystem->dir_path( $original_path ); if ( ! $this->filesystem->exists( $dest_dir ) ) { $this->filesystem->make_dir( $dest_dir ); } $dest_file_is_writable = ! $this->filesystem->exists( $original_path ) || $this->filesystem->is_writable( $original_path ); if ( ! $dest_file_is_writable || ! $this->filesystem->is_writable( $dest_dir ) ) { $this->unlock(); return new WP_Error( 'destination_not_writable', __( 'The image to replace is not writable.', 'imagify' ) ); } // Get some data before doing anything. $data = $this->get_data()->get_optimization_data(); $files = $media->get_media_files(); /** * Fires before restoring a media. * Return a WP_Error object to prevent the restoration. * * @since 1.9 * * @param null|WP_Error $response Null by default. Return a WP_Error object to prevent optimization. * @param ProcessInterface $process Instance of this process. */ $response = apply_filters( 'imagify_before_restore_media', null, $this ); if ( ! is_wp_error( $response ) ) { // Create the original image from the backup. $response = $this->filesystem->copy( $backup_path, $original_path, true ); if ( ! $response ) { // Failure. $response = new WP_Error( 'copy_failed', __( 'The backup file could not be copied over the optimized one.', 'imagify' ) ); } else { // Backup successfully copied. $this->filesystem->chmod_file( $original_path ); // Remove old optimization data. $this->get_data()->delete_optimization_data(); if ( $media->is_image() ) { // Restore the original dimensions in the database. $media->update_dimensions(); // Delete the WebP version. $this->delete_nextgen_file( $original_path, true ); // Restore the thumbnails. $response = $this->restore_thumbnails(); } } } /** * Fires after restoring a media. * * @since 1.9 * * @param ProcessInterface $process Instance of this process. * @param bool|WP_Error $response The result of the operation: true on success, a WP_Error object on failure. * @param array $files The list of files, before restoring them. * @param array $data The optimization data, before deleting it. */ do_action( 'imagify_after_restore_media', $this, $response, $files, $data ); $this->unlock(); return $response; } /** * Restore the thumbnails. * * @since 1.9 * * @return bool|WP_Error True on success. A WP_Error instance on failure. */ protected function restore_thumbnails() { $media = $this->get_media(); /** * Delete the next-gen versions. * If the full size file and the original file are not the same, the full size is considered like a thumbnail. * In that case we must also delete the next-gen file associated to the full size. */ $keep_full_next_gen = $media->get_raw_original_path() === $media->get_raw_fullsize_path(); $this->delete_nextgen_files( $keep_full_next_gen, true ); // Generate new thumbnails. return $media->generate_thumbnails(); } /** * Delete the backup file. * * @since 1.9 */ public function delete_backup() { if ( ! $this->is_valid() ) { return; } $backup_path = $this->get_media()->get_backup_path(); if ( $backup_path ) { $this->filesystem->delete( $backup_path ); // Check for the -scaled version in the backup. $scaled_backup_path = preg_replace( '/(\.)([^\.]+)$/', '-scaled.$2', $backup_path ); if ( $this->filesystem->exists( $scaled_backup_path ) ) { // Delete the -scaled version from the backup. $this->filesystem->delete( $scaled_backup_path ); } } } /** * Create a temporary copy of a size file. * * If we need to create a next-gen version, we must create it from an unoptimized image. * The full size is always optimized before the next-gen version creation, and in some cases it’s the same for the thumbnails. * Then we use the backup file to create temporary files. * * @since 1.9 * * @param string $size The image size name. * @param array $sizes A list of thumbnail sizes being optimized. * * @return bool True if the file exists/is created. False on failure. */ protected function create_temporary_copy( $size, $sizes = null ) { $media = $this->get_media(); if ( ! isset( $sizes ) ) { $sizes = $media->get_media_files(); } if ( empty( $sizes[ $size ] ) ) { // What? return false; } $tmp_path = $this->get_temporary_copy_path( $size, $sizes ); if ( $tmp_path && $this->filesystem->exists( $tmp_path ) ) { // The temporary file already exists. return true; } $tmp_file = new File( $tmp_path ); if ( ! $tmp_file->is_image() ) { // The file is not an image. return false; } if ( ! $tmp_file->is_supported( $media->get_allowed_mime_types() ) ) { // The file is not supported. return false; } /** * Use the backup file as source. */ $backup_path = $media->get_backup_path(); if ( ! $backup_path ) { // No backup, no hope for you. return false; } /** * In all cases we must make a copy of the backup file, and not use the backup directly: * sometimes the backup image does not have a valid file extension (yes I’m looking at you NextGEN Gallery). */ $copied = $this->filesystem->copy( $backup_path, $tmp_path, true ); if ( ! $copied ) { return false; } if ( 'full' === $size ) { /** * We create a copy of the backup to be able to create a next-gen version from it. * That means the optimization process will resize the file if needed, so there is nothing more to do here. */ return true; } // We need to create a thumbnail from it. $size_data = $sizes[ $size ]; $context_sizes = $media->get_context_instance()->get_thumbnail_sizes(); if ( ! empty( $context_sizes[ $size ] ) ) { // Not a dynamic size, yay! $size_data = array_merge( $size_data, $context_sizes[ $size ] ); } if ( empty( $size_data['path'] ) ) { // Should not happen. return false; } if ( ! isset( $size_data['crop'] ) ) { /** * In case of a dynamic thumbnail we don’t know if the image must be croped or resized. * * @since 1.9 * * @param bool $crop True to crop the thumbnail, false to resize. Null by default. * @param string $size Name of the thumbnail size. * @param array $size_data Data of the thumbnail being processed. Contains at least 'width', 'height', and 'path'. * @param MediaInterface $media The MediaInterface instance corresponding to the image being processed. */ $crop = apply_filters( 'imagify_crop_thumbnail', null, $size, $size_data, $media ); if ( null !== $crop ) { $size_data['crop'] = (bool) $crop; } } if ( ! isset( $size_data['crop'] ) ) { // We don't have the 'crop' data in that case: let’s try to guess it. if ( ! $size_data['height'] || ! $size_data['width'] ) { // One of the size dimensions is 0, that means crop is probably disabled. $size_data['crop'] = false; } else { if ( ! $this->filesystem->exists( $size_data['path'] ) ) { // Screwed. return false; } $thumb_dimensions = $this->filesystem->get_image_size( $size_data['path'] ); if ( ! $thumb_dimensions || ! $thumb_dimensions['width'] || ! $thumb_dimensions['height'] ) { return false; } // Compare dimensions. $new_height = $thumb_dimensions['width'] * $size_data['height'] / $size_data['width']; // If the difference is > to 1px, let's assume that crop is enabled. $size_data['crop'] = abs( $thumb_dimensions['height'] - $new_height ) > 1; } } $resized = $tmp_file->create_thumbnail( [ 'path' => $tmp_path, 'width' => $size_data['width'], 'height' => $size_data['height'], 'crop' => $size_data['crop'], 'adjust_filename' => false, ] ); if ( is_wp_error( $resized ) ) { return false; } // Make sure the new file has the expected name. $new_tmp_path = $this->filesystem->dir_path( $tmp_path ) . $resized['file']; if ( $new_tmp_path === $tmp_path ) { return true; } return $this->filesystem->move( $new_tmp_path, $tmp_path, true ); } /** * Get the path to a temporary copy of a size file. * * @since 1.9 * * @param string $size The image size name. * @param array $sizes A list of thumbnail sizes being optimized. * * @return string|bool An image path. False on failure. */ protected function get_temporary_copy_path( $size, $sizes = null ) { if ( 'full' === $size ) { $path = $this->get_media()->get_raw_fullsize_path(); } else { if ( ! isset( $sizes ) ) { $sizes = $this->get_media()->get_media_files(); } $path = ! empty( $sizes[ $size ]['path'] ) ? $sizes[ $size ]['path'] : false; } if ( ! $path ) { return false; } $info = $this->filesystem->path_info( $path ); if ( ! $info['file_base'] ) { return false; } return $info['dir_path'] . $info['file_base'] . static::TMP_SUFFIX . '.' . $info['extension']; } /** * Maybe resize an image. * * @since 1.9 * * @param string $size The size name. * @param File $file A File instance. * * @return array|WP_Error A WP_Error instance on failure, an array on success as follow: { * @type bool $resized True when the image has been resized. * @type bool $backuped True when the image has been backuped. * @type int $file_size The file size in bytes. * } */ public function maybe_resize( $size, $file ) { if ( ! $this->can_resize( $size, $file ) ) { // This file should not be resized. return [ 'resized' => false, 'backuped' => false, 'file_size' => 0, ]; } $dimensions = $file->get_dimensions(); if ( ! $dimensions['width'] ) { // Could not get the image dimensions. return new WP_Error( 'no_dimensions', sprintf( /* translators: %s is an error message. */ __( 'Resizing failed: %s', 'imagify' ), __( 'Imagify could not get the image dimensions.', 'imagify' ) ) ); } $media = $this->get_media(); $resize_width = $media->get_context_instance()->get_resizing_threshold(); if ( $resize_width >= $dimensions['width'] ) { // No need to resize. return [ 'resized' => false, 'backuped' => false, 'file_size' => 0, ]; } $resized_path = $file->resize( $dimensions, $resize_width ); if ( is_wp_error( $resized_path ) ) { // The resizement failed. return new WP_Error( 'resize_failure', sprintf( /* translators: %s is an error message. */ __( 'Resizing failed: %s', 'imagify' ), $resized_path->get_error_message() ) ); } if ( $this->can_backup( $size ) ) { $source = 'full' === $size ? $media->get_original_path() : null; $backuped = $file->backup( $media->get_raw_backup_path(), $source ); if ( is_wp_error( $backuped ) ) { // The backup failed. return new WP_Error( 'backup_failure', sprintf( /* translators: %s is an error message. */ __( 'Backup failed: %s', 'imagify' ), $backuped->get_error_message() ) ); } } else { $backuped = false; } $file_size = (int) $this->filesystem->size( $file->get_path() ); $resized = $this->filesystem->move( $resized_path, $file->get_path(), true ); if ( ! $resized ) { // The resizement failed. return new WP_Error( 'resize_move_failure', __( 'The image could not be replaced by the resized one.', 'imagify' ) ); } // Store the new dimensions. $media->update_dimensions(); return [ 'resized' => true, 'backuped' => $backuped, 'file_size' => $file_size, ]; } /** * Tell if a size should be resized. * * @since 1.9 * * @param string $size The size name. * @param File $file A File instance. * * @return bool */ protected function can_resize( $size, $file ) { if ( ! $this->is_valid() ) { return false; } if ( 'full' !== $size && ( 'full' . static::WEBP_SUFFIX !== $size || 'full' . static::AVIF_SUFFIX !== $size ) ) { // We resize only the main file and its next-gen version. return false; } if ( ! $file->is_image() ) { return false; } return $this->get_media()->get_context_instance()->can_resize(); } /** * Tell if a size should be backuped. * * @since 1.9 * * @param string $size The size name. * * @return bool */ protected function can_backup( $size ) { if ( ! $this->is_valid() ) { return false; } if ( 'full' !== $size ) { // We backup only the main file. return false; } return $this->get_media()->get_context_instance()->can_backup(); } /** * Get mime type * * @param string $format nextgen image format. */ private function get_mime_type( $format ) { $mime_types = [ 'avif' => 'image/avif', 'webp' => 'image/webp', ]; return isset( $mime_types[ $format ] ) ? $mime_types[ $format ] : false; } /** * Delete the next gen format images. * This doesn't delete the related optimization data. * * @since 2.2 * * @param bool $keep_full Set to true to keep the full size. * @param bool $all_next_gen True: will delete every next-gen format. False: will delete only the current enabled format. * * @return bool|WP_Error True on success. A WP_Error object on failure. */ public function delete_nextgen_files( $keep_full = false, $all_next_gen = false ) { if ( ! $this->is_valid() ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } $media = $this->get_media(); if ( ! $media->is_image() ) { return new WP_Error( 'media_not_an_image', __( 'This media is not an image.', 'imagify' ) ); } $files = $media->get_media_files(); if ( $keep_full ) { unset( $files['full'] ); } if ( ! $files ) { return true; } $error_count = 0; foreach ( $files as $file ) { if ( 0 === strpos( $file['mime-type'], 'image/' ) ) { $deleted = $this->delete_nextgen_file( $file['path'], $all_next_gen ); if ( is_wp_error( $deleted ) ) { ++$error_count; } } } if ( $error_count ) { return new WP_Error( 'files_not_deleted', sprintf( /* translators: %s is a formatted number, don’t use %d. */ _n( '%s file could not be deleted.', '%s files could not be deleted.', $error_count, 'imagify' ), number_format_i18n( $error_count ) ) ); } return true; } /** * Delete a next gen format image, given its non-next-gen version's path. * This doesn't delete the related optimization data. * * @since 2.2 * * @param string $file_path Path to the non-next-gen file. * @param bool $all_next_gen True: will delete every next-gen format. False: will delete only the current enabled format. * * @return void|WP_Error A \WP_Error object on failure. */ protected function delete_nextgen_file( $file_path, $all_next_gen = false ) { if ( ! $file_path ) { return new WP_Error( 'no_path', __( 'Path to non-next-gen file not provided.', 'imagify' ) ); } $next_gen_file = new File( $file_path ); $formats = $this->extensions; if ( ! $all_next_gen ) { $formats = imagify_nextgen_images_formats(); } // Delete next-gen images. foreach ( $formats as $extension ) { $path = $next_gen_file->get_path_to_nextgen( $extension ); if ( ! $path ) { continue; } $this->delete_file( $path ); } } /** * Delete a next gen format image, given its non-next-gen version's path. * * @param string $next_gen_path Path to the non-next-gen file. * * @return bool|WP_Error True on success. A WP_Error object on failure. */ protected function delete_file( string $next_gen_path ) { if ( empty( $next_gen_path ) ) { return new WP_Error( 'no_$next_gen_path', __( 'Could not get the path to the Next-Gen format file.', 'imagify' ) ); } if ( ! $this->filesystem->exists( $next_gen_path ) ) { return true; } if ( ! $this->filesystem->is_writable( $next_gen_path ) ) { return new WP_Error( 'file_not_writable', sprintf( /* translators: %s is a file path. */ __( 'The file %s does not seem to be writable.', 'imagify' ), '<code>' . esc_html( $this->filesystem->make_path_relative( $next_gen_path ) ) . '</code>' ) ); } if ( ! $this->filesystem->is_file( $next_gen_path ) ) { return new WP_Error( 'not_a_file', sprintf( /* translators: %s is a file path. */ __( 'This does not seem to be a file: %s.', 'imagify' ), '<code>' . esc_html( $this->filesystem->make_path_relative( $next_gen_path ) ) . '</code>' ) ); } $deleted = $this->filesystem->delete( $next_gen_path, false, 'f' ); if ( ! $deleted ) { return new WP_Error( 'file_not_deleted', sprintf( /* translators: %s is a file path. */ __( 'The file %s could not be deleted.', 'imagify' ), '<code>' . esc_html( $this->filesystem->make_path_relative( $next_gen_path ) ) . '</code>' ) ); } return true; } /** * Gives the next-gen image format we are processing. * * @return string Current format we are targeting. */ public function get_current_format() { $format = get_imagify_option( 'optimization_format' ); return ( 'avif' === $format ) ? static::AVIF_SUFFIX : static::WEBP_SUFFIX; } /** * Tell if a thumbnail size is an "Imagify Next-Gen" size. * * @since 1.9 * @since 2.2 addition of the format parameter. * * @param string $size_name The size name. * * @return string|bool The unsuffixed name of the size if next-gen. False if not next-gen. */ public function is_size_next_gen( $size_name ) { $formats = imagify_nextgen_images_formats(); foreach ( $formats as $format ) { $suffix = preg_quote( $this->get_suffix_from_format( $format ), '/' ); if ( preg_match( '/^(?<size>.+)' . $suffix . '$/', (string) $size_name, $matches ) ) { return $matches['size']; } } return false; } /** * Get suffix from format. * * @param string $format Format extension of next-gen image. * @return string */ private function get_suffix_from_format( string $format ): string { $suffixes = [ 'avif' => static::AVIF_SUFFIX, 'webp' => static::WEBP_SUFFIX, ]; return $suffixes[ $format ]; } /** * Tell if the media has a next gen format. * * @since 2.2 * * @return bool */ public function has_next_gen() { if ( ! $this->is_valid() ) { return false; } if ( ! $this->get_media()->is_image() ) { return false; } $data = $this->get_data()->get_optimization_data(); if ( empty( $data['sizes'] ) ) { return false; } $needle = $this->format . '";a:4:{s:7:"success";b:1;'; $data = maybe_serialize( $data['sizes'] ); return is_string( $data ) && strpos( $data, $needle ); } /** * Tell if the media has all Next-Gen versions. * * @return bool */ public function is_full_next_gen() { if ( ! $this->is_valid() ) { return false; } if ( ! $this->get_media()->is_image() ) { return false; } $data = $this->get_data()->get_optimization_data(); $sizes = $data['sizes']; if ( empty( $sizes ) ) { return false; } $keys = array_keys( $sizes ); $non_next_gen_keys = array_values( array_filter( $keys, function ( $key ) { return strpos( (string) $key, $this->format ) === false; } ) ); return array_reduce( $non_next_gen_keys, function ( $is_fully, $key ) use ( $sizes ) { return key_exists( $key . $this->format, $sizes ) && $is_fully; }, true ); } /** * Tell if a Next-Gen version can be created for the given file. * Make sure the file is an image before using this method. * * @since 1.9.5 * * @param string $file_path Path to the file. * * @return bool */ public function can_create_next_gen_version( $file_path ) { if ( ! $file_path ) { return false; } $can = apply_filters_deprecated( 'imagify_pre_can_create_webp_version', [ null, $file_path ], '2.2', 'imagify_pre_can_create_next_gen_version' ); /** * Tell if a next-gen version can be created for the given file. * The file is an image. * * @since 1.9.5 * * @param bool $can True to create a next-gen version, false otherwise. Null by default. * @param string $file_path Path to the file. */ $can = apply_filters( 'imagify_pre_can_create_next_gen_version', $can, $file_path ); if ( isset( $can ) ) { return (bool) $can; } $is_animated_gif = $this->filesystem->is_animated_gif( $file_path ); if ( is_bool( $is_animated_gif ) ) { // Ok if it’s not an animated gif. return ! $is_animated_gif; } // At this point $is_animated_gif is null, which means the file cannot be read (yet). return true; } /** * Generate next-gen images if they are missing. * * @since 1.9 * * @return bool|WP_Error True if successfully launched. A WP_Error instance on failure. */ public function generate_nextgen_versions() { if ( ! $this->is_valid() ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } $media = $this->get_media(); if ( ! $media->is_image() ) { return new WP_Error( 'no_next_gen', __( 'This media is not an image and cannot be converted to next-gen format.', 'imagify' ) ); } if ( ! $media->has_backup() ) { return new WP_Error( 'no_backup', __( 'This media has no backup file.', 'imagify' ) ); } $data = $this->get_data(); if ( ! $data->is_optimized() && ! $data->is_already_optimized() ) { return new WP_Error( 'not_optimized', __( 'This media has not been optimized by Imagify yet.', 'imagify' ) ); } if ( $this->has_next_gen() ) { return new WP_Error( 'has_next_gen', __( 'This media already has next-gen versions.', 'imagify' ) ); } $files = $media->get_media_files(); $sizes = []; $args = [ 'hook_suffix' => 'generate_nextgen_versions', ]; foreach ( $files as $size_name => $file ) { $formats = imagify_nextgen_images_formats(); foreach ( $formats as $format ) { if ( 'avif' === $format ) { $format_suffix = static::AVIF_SUFFIX; } elseif ( 'webp' === $format ) { $format_suffix = static::WEBP_SUFFIX; } if ( $this->get_mime_type( $format ) === $files[ $size_name ]['mime-type'] ) { continue; } array_unshift( $sizes, $size_name . $format_suffix ); } } if ( ! $sizes ) { return new WP_Error( 'no_sizes', __( 'This media does not have files that can be converted to next-gen format.', 'imagify' ) ); } $optimization_level = $data->get_optimization_level(); // Optimize. return $this->optimize_sizes( $sizes, $optimization_level, $args ); } /** * Tell if a process is running for this media. * * @since 1.9 * * @return string|bool The action if locked ('optimizing' or 'restoring'). False if not locked. */ public function is_locked() { $name = $this->get_lock_name(); if ( ! $name ) { return false; } $callback = $this->get_media()->get_context_instance()->is_network_wide() ? 'get_site_transient' : 'get_transient'; $action = call_user_func( $callback, $name ); if ( ! $action ) { return false; } return $this->validate_lock_action( $action ); } /** * Set the running status to "running" for 10 minutes. * * @since 1.9 * * @param string $action The action performed behind this lock: 'optimizing' or 'restoring'. */ public function lock( $action = 'optimizing' ) { $name = $this->get_lock_name(); if ( ! $name ) { return; } $action = $this->validate_lock_action( $action ); $media = $this->get_media(); $callback = $media->get_context_instance()->is_network_wide() ? 'set_site_transient' : 'set_transient'; call_user_func( $callback, $name, $action, 10 * MINUTE_IN_SECONDS ); } /** * Unset the running status. * * @since 1.9 */ public function unlock() { $name = $this->get_lock_name(); if ( ! $name ) { return false; } $callback = $this->get_media()->get_context_instance()->is_network_wide() ? 'delete_site_transient' : 'delete_transient'; call_user_func( $callback, $name ); } /** * Get the name of the transient that stores the lock status. * * @since 1.9 * * @return string|bool The name on success. False on failure. */ protected function get_lock_name() { $media = $this->get_media(); if ( ! $media ) { return false; } /** * Note that the site transient used by WP Background is named '*_process_lock'. * That would give something like 'imagify_optimize_media_process_lock' for the optimization process, while here it would be 'imagify_wp_42_process_locked'. */ return sprintf( static::LOCK_NAME, $media->get_context(), $media->get_id() ); } /** * Validate the lock action. * * @since 1.9 * * @param string $action The action performed behind this lock: 'optimizing' or 'restoring'. * @return string The valid action. */ protected function validate_lock_action( $action ) { switch ( $action ) { case 'restore': case 'restoring': $action = 'restoring'; break; default: $action = 'optimizing'; } return $action; } /** * Tell if a size already has optimization data. * * @since 1.9 * * @param string $size The size name. * @return bool */ public function size_has_optimization_data( $size ) { $data = $this->get_data()->get_optimization_data(); return ! empty( $data['sizes'][ $size ] ); } /** * Update the optimization data for a size. * * @since 1.9 * @since 2.2 - Addition of the format in the call. * * @param object $response The API response. * @param string $size The size name. * @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return array { * The optimization data. * * @type int $level The optimization level. * @type string $status The status: 'success', 'already_optimized', 'error'. * @type bool $success True if successfully optimized. False on error or if already optimized. * @type string $error An error message. * @type int $original_size The weight of the file, before optimization. * @type int $optimized_size The weight of the file, once optimized. * } */ public function update_size_optimization_data( $response, $size, $level ) { $disabled = false; $data = $this->data_format; $data['level'] = is_numeric( $level ) ? (int) $level : $this->get_option( 'optimization_level' ); if ( is_wp_error( $response ) ) { /** * Error. */ $disabled = 'unauthorized_size' === $response->get_error_code(); // Size data. $data['success'] = false; $data['error'] = $response->get_error_message(); // Status. if ( false !== strpos( $data['error'], 'This image is already compressed' ) ) { $data['status'] = 'already_optimized'; } else { $data['status'] = 'error'; } } else { /** * Success. */ $response = (object) array_merge( [ 'original_size' => 0, 'new_size' => 0, 'percent' => 0, ], (array) $response ); // Status. $data['status'] = 'success'; $data['error'] = null; // Size data. $data['success'] = true; if ( property_exists( $response, 'message' ) ) { $data['message'] = imagify_translate_api_message( $response->message ); } $data['original_size'] = $response->original_size; $data['optimized_size'] = $response->new_size; } $_unauthorized = $disabled ? '_unauthorized' : ''; /** * Filter the optimization data. * * @since 1.9 * * @param array $data { * The optimization data. * * @type int $level The optimization level. * @type string $status The status: 'success', 'already_optimized', 'error'. * @type bool $success True if successfully optimized. False on error or if already optimized. * @type string $error An error message. * @type int $original_size The weight of the file, before optimization. * @type int $optimized_size The weight of the file, once optimized. * } * @param object $response The API response. * @param string $size The size name. * @param int $level The optimization level. * @param object $media_data The DataInterface instance of the media. */ $data = (array) apply_filters( "imagify{$_unauthorized}_file_optimization_data", $data, $response, $size, $level, $this->get_data() ); if ( property_exists( $response, 'message' ) ) { $size = str_replace( $this->format, '', $size ); } // Store. $this->get_data()->update_size_optimization_data( $size, $data ); return $data; } /** * Get a plugin’s option. * * @since 1.9 * * @param string $option_name The option name. * * @return mixed */ protected function get_option( $option_name ) { if ( isset( $this->options[ $option_name ] ) ) { return $this->options[ $option_name ]; } $this->options[ $option_name ] = get_imagify_option( $option_name ); return $this->options[ $option_name ]; } /** * Sanitize and validate an optimization level. * If not provided (false, null), fallback to the level set in the plugin's settings. * * @since 1.9 * * @param mixed $optimization_level The optimization level. * * @return int */ protected function sanitize_optimization_level( $optimization_level ) { if ( ! is_numeric( $optimization_level ) ) { if ( $this->get_option( 'lossless' ) ) { return 0; } return $this->get_option( 'optimization_level' ); } return \Imagify_Options::get_instance()->sanitize_and_validate( 'optimization_level', $optimization_level ); } /** * Tell if the media has AVIF versions. * * @since 2.2 * * @return bool */ public function has_avif() { if ( ! $this->is_valid() ) { return false; } if ( ! $this->get_media()->is_image() ) { return false; } $data = $this->get_data()->get_optimization_data(); if ( empty( $data['sizes'] ) ) { return false; } $needle = static::AVIF_SUFFIX . '";a:4:{s:7:"success";b:1;'; $data = maybe_serialize( $data['sizes'] ); return is_string( $data ) && strpos( $data, $needle ); } } PK ̜�\ѭO]z$ z$ Optimization/Process/Noop.phpnu �[��� <?php declare(strict_types=1); namespace Imagify\Optimization\Process; use Imagify\Optimization\Data\DataInterface; use Imagify\Media\MediaInterface; use Imagify\Optimization\File; use WP_Error; /** * Fallback class to optimize medias. */ class Noop implements ProcessInterface { /** * The suffix used in the thumbnail size name. * * @var string * @since 1.9 */ const WEBP_SUFFIX = '@imagify-webp'; /** * The suffix used in the thumbnail size name. * * @var string * @since 2.2 */ const AVIF_SUFFIX = '@imagify-avif'; /** * The suffix used in file name to create a temporary copy of the full size. * * @var string * @since 1.9 */ const TMP_SUFFIX = '@imagify-tmp'; /** * Used for the name of the transient telling if a media is locked. * %1$s is the context, %2$s is the media ID. * * @var string * @since 1.9 */ const LOCK_NAME = 'imagify_%1$s_%2$s_process_locked'; /** * Tell if the given entry can be accepted in the constructor. * For example it can include `is_numeric( $id )` if the constructor accepts integers. * * @since 1.9 * * @param mixed $id Whatever. * * @return bool */ public static function constructor_accepts( $id ) { return false; } /** * Get the data instance. * * @since 1.9 * * @return DataInterface|false */ public function get_data() { return false; } /** * Get the media instance. * * @since 1.9 * * @return MediaInterface|false */ public function get_media() { return false; } /** * Get the File instance of the original file. * * @since 1.9.8 * * @return File|false */ public function get_original_file() { return false; } /** * Get the File instance of the full size file. * * @since 1.9.8 * * @return File|false */ public function get_fullsize_file() { return false; } /** * Get the File instance. * * @since 1.9 * * @return File|false */ public function get_file() { return false; } /** * Tell if the current media is valid. * * @since 1.9 * * @return bool */ public function is_valid() { return false; } /** * Tell if the current user is allowed to operate Imagify in this context. * * @since 1.9 * * @param string $describer Capacity describer. See \Imagify\Context\ContextInterface->get_capacity() for possible values. Can also be a "real" user capacity. * * @return bool */ public function current_user_can( $describer ) { return false; } /** * Optimize a media files by pushing tasks into the queue. * * @since 1.9 * * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return bool|WP_Error True if successfully launched. A WP_Error instance on failure. */ public function optimize( $optimization_level = null ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } /** * Re-optimize a media files with a different level. * * @since 1.9 * * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return bool|WP_Error True if successfully launched. A WP_Error instance on failure. */ public function reoptimize( $optimization_level = null ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } /** * Optimize several file sizes by pushing tasks into the queue. * * @since 1.9 * * @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file. * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return bool|WP_Error True if successfully launched. A WP_Error instance on failure. */ public function optimize_sizes( $sizes, $optimization_level = null ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } /** * Optimize one file with Imagify directly. * * @since 1.9 * * @param string $size The media size. * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return array|WP_Error The optimization data. A WP_Error instance on failure. */ public function optimize_size( $size, $optimization_level = null ) { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } /** * Restore the media files from the backup file. * * @since 1.9 * * @return bool|WP_Error True on success. A WP_Error instance on failure. */ public function restore() { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } /** * Get the sizes for this media that have not get through optimization. * No sizes are returned if the file is not optimized, has no backup, or is not an image. * The 'full' size os never returned. * * @since 1.9 * * @return array|WP_Error { * A WP_Error object on failure. * An array of data for the thumbnail sizes on success. * Size names are used as array keys. * * @type int $width The image width. * @type int $height The image height. * @type bool $crop True to crop, false to resize. * @type string $name The size name. * @type string $file The name the thumbnail "should" have. * } */ public function get_missing_sizes() { return []; } /** * Optimize missing thumbnail sizes. * * @since 1.9 * * @return bool|WP_Error True if successfully launched. A WP_Error instance on failure. */ public function optimize_missing_thumbnails() { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } /** * Delete the backup file. * * @since 1.9 */ public function delete_backup() {} /** * Maybe resize an image. * * @since 1.9 * * @param string $size The size name. * @param File $file A File instance. * * @return array|WP_Error A WP_Error instance on failure, an array on success as follow: { * @type bool $resized True when the image has been resized. * @type bool $backuped True when the image has been backuped. * @type int $file_size The file size in bytes. * } */ public function maybe_resize( $size, $file ) { return [ 'resized' => false, 'backuped' => false, 'file_size' => 0, ]; } /** * Generate Nextgen images if they are missing. * * @since 1.9 * * @return bool|WP_Error True if successfully launched. A WP_Error instance on failure. */ public function generate_nextgen_versions() { return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } /** * Delete the next gen format images. * This doesn't delete the related optimization data. * * @since 2.2 * * @param bool $keep_full Set to true to keep the full size. * @return bool|WP_Error True on success. A WP_Error object on failure. */ public function delete_nextgen_files( $keep_full = false ) { return false; } /** * Tell if a thumbnail size is an "Imagify Next-Gen" size. * * @since 2.2 * * @param string $size_name The size name. * * @return string|bool The unsuffixed name of the size if Next-Gen. False if not a Next-Gen. */ public function is_size_next_gen( $size_name ) { return false; } /** * Tell if the media has all next-gen versions. * * @return bool */ public function is_full_next_gen() { return false; } /** * Tell if the media has a next-gen format. * * @since 2.2 * * @return bool */ public function has_next_gen() { return false; } /** * Tell if a process is running for this media. * * @since 1.9 * * @return bool */ public function is_locked() { return false; } /** * Set the running status to "running" for a period of time. * * @since 1.9 */ public function lock() {} /** * Delete the running status. * * @since 1.9 */ public function unlock() {} /** * Tell if a size already has optimization data. * * @since 1.9 * * @param string $size The size name. * * @return bool */ public function size_has_optimization_data( $size ) { return false; } /** * Update the optimization data for a size. * * @since 1.9 * * @param object $response The API response. * @param string $size The size name. * @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra). * * @return array { * The optimization data. * * @type string $size The size name. * @type int $level The optimization level. * @type string $status The status: 'success', 'already_optimized', 'error'. * @type bool $success True if successfully optimized. False on error or if already optimized. * @type string $error An error message. * @type int $original_size The weight of the file, before optimization. * @type int $optimized_size The weight of the file, once optimized. * } */ public function update_size_optimization_data( $response, $size, $level ) { return [ 'size' => 'noop', 'level' => false, 'status' => '', 'success' => false, 'error' => '', 'original_size' => 0, 'optimized_size' => 0, ]; } } PK ̜�\ӿς� � &