'image/gif', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpg', 'png' => 'image/png', ); /** * Configuration. * * @since 2.2.0 * @access private * * @var Config */ private $config; /** * Image Service API class object. * * @since 2.2.0 * @access private * * @var Extension_ImageService_API */ private $api; /** * Constructor. * * @since 2.2.0 */ public function __construct() { $this->config = Dispatcher::config(); } /** * Get extension information. * * @since 2.2.0 * @static * * @global $wp_version WordPress core version. * * @param array $extensions Extensions. * @param array $config Configuration. * @return array */ public static function w3tc_extensions( $extensions, $config ) { global $wp_version; $description = __( 'Adds the ability to convert images in the Media Library to the modern WebP format for better performance.', 'w3-total-cache' ); if ( version_compare( $wp_version, '5.8', '<' ) ) { $description .= sprintf( // translators: 1: HTML break, 2: WordPress version string, 3: HTML archor open tag, 4: HTML archor close tag. __( '%1$sThis extension works best in WordPress version 5.8 and higher. You are running WordPress version %2$s. Please %3$supdate now%4$s to benefit from this feature.', 'w3-total-cache' ), '
', $wp_version, '', '' ); } $settings_url = esc_url( Util_Ui::admin_url( 'upload.php?page=w3tc_extension_page_imageservice&w3tc_imageservice_action=dismiss_activation_notice' ) ); $library_url = esc_url( Util_Ui::admin_url( 'upload.php?mode=list' ) ); $extensions['imageservice'] = array( 'name' => 'WebP Converter', 'author' => 'BoldGrid', 'description' => esc_html( $description ), 'author_uri' => 'https://www.boldgrid.com/', 'extension_uri' => 'https://www.boldgrid.com/w3-total-cache/', 'extension_id' => 'imageservice', 'settings_exists' => false, 'version' => '1.0', 'enabled' => true, 'disabled_message' => '', 'requirements' => '', 'path' => 'w3-total-cache/Extension_ImageService_Plugin.php', 'notice' => sprintf( // translators: 1: HTML anchor open tag, 2: HTML anchor close tag, 3: HTML anchor open tag, 4: HTML anchor open tag. __( 'Total Cache WebP Converter has been activated. Now, you can %1$sadjust the settings%2$s or go to the %3$sMedia Library%2$s to convert images to WebP. %4$sLearn more%2$s.', 'w3-total-cache' ), '', '', '', '' ), ); // The settings and Media Library links are only valid for single and network sites; not the admin section. if ( ! is_network_admin() ) { $extensions['imageservice']['extra_links'] = array( '' . esc_html__( 'Settings', 'w3-total-cache' ) . '', '' . esc_html__( 'Media Library', 'w3-total-cache' ) . '', ); } return $extensions; } /** * Load the admin extension. * * Runs on the "wp_loaded" action. * * @since 2.2.0 * @static */ public static function w3tc_extension_load_admin() { $o = new Extension_ImageService_Plugin_Admin(); // Enqueue scripts. add_action( 'admin_enqueue_scripts', array( $o, 'admin_enqueue_scripts' ) ); /** * Filters the Media list table columns. * * @since 2.5.0 * * @param string[] $posts_columns An array of columns displayed in the Media list table. * @param bool $detached Whether the list table contains media not attached * to any posts. Default true. */ add_filter( 'manage_media_columns', array( $o, 'add_media_column' ) ); /** * Fires for each custom column in the Media list table. * * Custom columns are registered using the {@see 'manage_media_columns'} filter. * * @since 2.5.0 * * @param string $column_name Name of the custom column. * @param int $post_id Attachment ID. */ add_action( 'manage_media_custom_column', array( $o, 'media_column_row' ), 10, 2 ); // AJAX hooks. add_action( 'wp_ajax_w3tc_imageservice_submit', array( $o, 'ajax_submit' ) ); add_action( 'wp_ajax_w3tc_imageservice_postmeta', array( $o, 'ajax_get_postmeta' ) ); add_action( 'wp_ajax_w3tc_imageservice_revert', array( $o, 'ajax_revert' ) ); add_action( 'wp_ajax_w3tc_imageservice_all', array( $o, 'ajax_convert_all' ) ); add_action( 'wp_ajax_w3tc_imageservice_revertall', array( $o, 'ajax_revert_all' ) ); add_action( 'wp_ajax_w3tc_imageservice_counts', array( $o, 'ajax_get_counts' ) ); add_action( 'wp_ajax_w3tc_imageservice_usage', array( $o, 'ajax_get_usage' ) ); // Admin notices. add_action( 'admin_notices', array( $o, 'display_notices' ) ); /** * Ensure all network sites include WebP support. * * @link https://make.wordpress.org/core/2021/06/07/wordpress-5-8-adds-webp-support/ */ add_filter( 'site_option_upload_filetypes', function ( $filetypes ) { $filetypes = explode( ' ', $filetypes ); if ( ! in_array( 'webp', $filetypes, true ) ) { $filetypes[] = 'webp'; } return implode( ' ', $filetypes ); } ); // Add bulk actions. add_filter( 'bulk_actions-upload', array( $o, 'add_bulk_actions' ) ); /** * Fires when a custom bulk action should be handled. * * The redirect link should be modified with success or failure feedback * from the action to be used to display feedback to the user. * * The dynamic portion of the hook name, `$screen`, refers to the current screen ID. * * @since 4.7.0 * * @link https://core.trac.wordpress.org/browser/tags/5.8/src/wp-admin/upload.php#L206 * * @param string $sendback The redirect URL. * @param string $doaction The action being taken. * @param array $items The items to take the action on. Accepts an array of IDs of posts, * comments, terms, links, plugins, attachments, or users. */ add_filter( 'handle_bulk_actions-upload', array( $o, 'handle_bulk_actions' ), 10, 3 ); /** * Handle auto-optimization on upload. * * @link https://core.trac.wordpress.org/browser/tags/5.8/src/wp-includes/post.php#L4401 * @link https://developer.wordpress.org/reference/hooks/add_attachment/ * * Fires once an attachment has been added. * * @since 2.0.0 * * @param int $post_ID Attachment ID. */ add_action( 'add_attachment', array( $o, 'auto_convert' ) ); /** * Delete optimizations on parent image delation. * * @link https://core.trac.wordpress.org/browser/tags/5.8/src/wp-includes/post.php#L6134 * @link https://developer.wordpress.org/reference/hooks/pre_delete_attachment/ * * Filters whether an attachment deletion should take place. * * @since 5.5.0 * * @param bool|null $delete Whether to go forward with deletion. * @param WP_Post $post Post object. * @param bool $force_delete Whether to bypass the Trash. */ add_filter( 'pre_delete_attachment', array( $o, 'cleanup_optimizations' ), 10, 3 ); // Add admin menu items. add_action( 'admin_menu', array( $o, 'admin_menu' ) ); } /** * Get all images with postmeta key "w3tc_imageservice". * * @since 2.2.0 * @static * * @link https://developer.wordpress.org/reference/classes/wp_query/ * * @return WP_Query */ public static function get_imageservice_attachments() { return new \WP_Query( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => self::$mime_types, 'posts_per_page' => -1, 'ignore_sticky_posts' => true, 'suppress_filters' => true, 'meta_key' => 'w3tc_imageservice', // phpcs:ignore WordPress.DB.SlowDBQuery ) ); } /** * Get all images without postmeta key "w3tc_imageservice". * * @since 2.2.0 * @static * * @link https://developer.wordpress.org/reference/classes/wp_query/ * * @return WP_Query */ public static function get_eligible_attachments() { return new \WP_Query( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => self::$mime_types, 'posts_per_page' => -1, 'ignore_sticky_posts' => true, 'suppress_filters' => true, 'meta_key' => 'w3tc_imageservice', // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_compare' => 'NOT EXISTS', ) ); } /** * Get an attachment filesize. * * @since 2.2.0 * * @global $wp_filesystem * * @param int $post_id Post id. * @return int */ public function get_attachment_filesize( $post_id ) { WP_Filesystem(); global $wp_filesystem; $size = 0; $filepath = get_attached_file( $post_id ); if ( $wp_filesystem->exists( $filepath ) ) { $size = $wp_filesystem->size( $filepath ); } return $size; } /** * Get image counts by status. * * @since 2.2.0 * * @see self::get_imageservice_attachments() * @see self::get_eligible_attachments() * * @return array */ public function get_counts() { $unconverted_posts = self::get_eligible_attachments(); $counts = array( 'sending' => 0, 'processing' => 0, 'converted' => 0, 'notconverted' => 0, 'unconverted' => $unconverted_posts->post_count, 'total' => 0, ); $bytes = array( 'sending' => 0, 'processing' => 0, 'converted' => 0, 'notconverted' => 0, 'unconverted' => 0, 'total' => 0, ); $imageservice_posts = self::get_imageservice_attachments()->posts; foreach ( $imageservice_posts as $post ) { $imageservice_data = get_post_meta( $post->ID, 'w3tc_imageservice', true ); $status = isset( $imageservice_data['status'] ) ? $imageservice_data['status'] : null; $filesize_in = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-in'] ) ? $imageservice_data['download']["\0*\0data"]['x-filesize-in'] : 0; $filesize_out = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-out'] ) ? $imageservice_data['download']["\0*\0data"]['x-filesize-out'] : 0; switch ( $status ) { case 'sending': $size = $this->get_attachment_filesize( $post->ID ); $counts['sending']++; $bytes['sending'] += $size; $bytes['total'] += $size; break; case 'processing': $size = $this->get_attachment_filesize( $post->ID ); $counts['processing']++; $bytes['processing'] += $size; $bytes['total'] += $size; break; case 'converted': $counts['converted']++; $bytes['converted'] += $filesize_in - $filesize_out; $bytes['total'] += $filesize_in - $filesize_out; break; case 'notconverted': $size = $this->get_attachment_filesize( $post->ID ); $counts['notconverted']++; $bytes['notconverted'] += $size; $bytes['total'] += $size; break; case 'unconverted': $size = $this->get_attachment_filesize( $post->ID ); $counts['unconverted']++; $bytes['unconverted'] += $size; $bytes['total'] += $size; break; default: break; } } foreach ( $unconverted_posts->posts as $post ) { $size = $this->get_attachment_filesize( $post->ID ); if ( $size ) { $bytes['unconverted'] += $size; $bytes['total'] += $size; } } $counts['total'] = array_sum( $counts ); $counts['totalbytes'] = $bytes['total']; $counts['sendingbytes'] = $bytes['sending']; $counts['processingbytes'] = $bytes['processing']; $counts['convertedbytes'] = $bytes['converted']; $counts['notconvertedbytes'] = $bytes['notconverted']; $counts['unconvertedbytes'] = $bytes['unconverted']; return $counts; } /** * Load the extension settings page view. * * @since 2.2.0 * * @see Extension_ImageService_Plugin::get_api() * @see Extension_ImageService_Api::get_usage() */ public function settings_page() { $c = $this->config; $counts = $this->get_counts(); $usage = get_transient( 'w3tc_imageservice_usage' ); // Delete transient for displaying activation notice. delete_transient( 'w3tc_activation_imageservice' ); // Save submitted settings. $nonce_val = Util_Request::get_string( '_wpnonce' ); $imageservice_compression_val = Util_Request::get_string( 'imageservice___compression' ); if ( ! empty( $imageservice_compression_val ) && ! empty( $nonce_val ) && wp_verify_nonce( $nonce_val, 'w3tc' ) ) { $settings = $c->get_array( 'imageservice' ); $settings['compression'] = $imageservice_compression_val; $imageservice_auto_val = Util_Request::get_string( 'imageservice___auto' ); if ( ! empty( $imageservice_auto_val ) ) { $settings['auto'] = $imageservice_auto_val; } $imageservice_visibility_val = Util_Request::get_string( 'imageservice___visibility' ); if ( ! empty( $imageservice_visibility_val ) ) { $settings['visibility'] = $imageservice_visibility_val; } $c->set( 'imageservice', $settings ); $c->save(); // Display notice when saving settings. ?>

get_usage(); } // Ensure that the monthly limit is represented correctly. $usage['limit_monthly'] = $usage['limit_monthly'] ? $usage['limit_monthly'] : __( 'Unlimited', 'w3-total-cache' ); require W3TC_DIR . '/Extension_ImageService_Page_View.php'; } /** * Add admin menu items. * * @since 2.2.0 */ public function admin_menu() { // Add settings submenu to Media top-level menu. add_submenu_page( 'upload.php', esc_html__( 'Total Cache WebP Converter', 'w3-total-cache' ), esc_html__( 'Total Cache WebP Converter', 'w3-total-cache' ), 'edit_posts', 'w3tc_extension_page_imageservice', array( $this, 'settings_page' ) ); } /** * Enqueue scripts and styles for admin pages. * * Runs on the "admin_enqueue_scripts" action. * * @since 2.2.0 * * @see Util_Ui::admin_url() * @see Licensing_Core::get_tos_choice() */ public function admin_enqueue_scripts() { // Enqueue JavaScript for the Media Library (upload) and extension settings admin pages. $page_val = Util_Request::get_string( 'page' ); $is_settings_page = ! empty( $page_val ) && 'w3tc_extension_page_imageservice' === $page_val; $is_media_page = 'upload' === get_current_screen()->id; if ( $is_settings_page ) { wp_enqueue_style( 'w3tc-options' ); wp_enqueue_style( 'w3tc-bootstrap-css' ); wp_enqueue_script( 'w3tc-options' ); } if ( $is_settings_page || $is_media_page ) { wp_localize_script( 'w3tc-lightbox', 'w3tc_nonce', array( wp_create_nonce( 'w3tc' ) ) ); wp_enqueue_script( 'w3tc-lightbox' ); wp_enqueue_style( 'w3tc-lightbox' ); wp_register_script( 'w3tc-imageservice', esc_url( plugin_dir_url( __FILE__ ) . 'Extension_ImageService_Plugin_Admin.js' ), array( 'jquery' ), W3TC_VERSION, true ); wp_localize_script( 'w3tc-imageservice', 'w3tcData', array( 'nonces' => array( 'submit' => wp_create_nonce( 'w3tc_imageservice_submit' ), 'postmeta' => wp_create_nonce( 'w3tc_imageservice_postmeta' ), 'revert' => wp_create_nonce( 'w3tc_imageservice_revert' ), ), 'lang' => array( 'convert' => __( 'Convert', 'w3-total-cache' ), 'sending' => __( 'Sending...', 'w3-total-cache' ), 'submitted' => __( 'Submitted', 'w3-total-cache' ), 'processing' => __( 'Processing...', 'w3-total-cache' ), 'converted' => __( 'Converted', 'w3-total-cache' ), 'notConverted' => __( 'Not converted', 'w3-total-cache' ), 'reverting' => __( 'Reverting...', 'w3-total-cache' ), 'reverted' => __( 'Reverted', 'w3-total-cache' ), 'revert' => __( 'Revert', 'w3-total-cache' ), 'error' => __( 'Error', 'w3-total-cache' ), 'ajaxFail' => __( 'Failed to retrieve a response. Please reload the page to try again.', 'w3-total-cache' ), 'apiError' => __( 'API error. Please reload the page to try again,', 'w3-total-cache' ), 'refresh' => __( 'Refresh', 'w3-total-cache' ), 'refreshing' => __( 'Refreshing...', 'w3-total-cache' ), 'settings' => __( 'Settings', 'w3-total-cache' ), 'submittedAllDesc' => sprintf( // translators: 1: HTML anchor open tag, 2: HTML anchor close tag. __( 'Images queued for conversion. Progress can be seen in the %1$sMedia Library%2$s.', 'w3-total-cache' ), '', '' ), 'notConvertedDesc' => sprintf( // translators: 1: HTML anchor open tag, 2: HTML anchor close tag. __( 'The converted image would be larger than the original; conversion canceled. %1$sLearn more%2$s.', 'w3-total-cache' ), '', '' ), ), 'tos_choice' => Licensing_Core::get_tos_choice(), 'track_usage' => $this->config->get_boolean( 'common.track_usage' ), 'ga_profile' => ( defined( 'W3TC_DEVELOPER' ) && W3TC_DEVELOPER ) ? 'G-Q3CHQJWERM' : 'G-5TFS8M5TTY', 'settings' => $this->config->get_array( 'imageservice' ), 'settingsUrl' => esc_url( Util_Ui::admin_url( 'upload.php?page=w3tc_extension_page_imageservice' ) ), ) ); wp_enqueue_script( 'w3tc-imageservice' ); wp_enqueue_style( 'w3tc-imageservice', esc_url( plugin_dir_url( __FILE__ ) . 'Extension_ImageService_Plugin_Admin.css' ), array(), W3TC_VERSION, 'all' ); } } /** * Add image service controls to the Media Library table in list view. * * Runs on the "manage_media_columns" filter. * * @since 2.2.0 * * @param string[] $posts_columns An array of columns displayed in the Media list table. * @param bool $detached Whether the list table contains media not attached * to any posts. Default true. */ public function add_media_column( $posts_columns, $detached = true ) { // Delete transient for displaying activation notice. delete_transient( 'w3tc_activation_imageservice' ); $posts_columns['imageservice'] = ' ' . esc_html__( 'WebP Converter', 'w3-total-cache' ); return $posts_columns; } /** * Fires for each custom column in the Media list table. * * Custom columns are registered using the {@see 'manage_media_columns'} filter. * Runs on the "manage_media_custom_column" action. * * @since 2.5.0 * * @see self::remove_optimizations() * * @link https://developer.wordpress.org/reference/functions/size_format/ * * @param string $column_name Name of the custom column. * @param int $post_id Attachment ID. */ public function media_column_row( $column_name, $post_id ) { static $settings; if ( 'imageservice' === $column_name ) { $post = get_post( $post_id ); $imageservice_data = get_post_meta( $post_id, 'w3tc_imageservice', true ); $settings = isset( $settings ) ? $settings : $this->config->get_array( 'imageservice' ); // Display controls and info for eligible images. if ( in_array( $post->post_mime_type, self::$mime_types, true ) ) { $filepath = get_attached_file( $post_id ); $status = isset( $imageservice_data['status'] ) ? $imageservice_data['status'] : null; // Check if image still has the converted file. It could have been deleted. if ( 'converted' === $status && isset( $imageservice_data['post_child'] ) ) { $child_data = get_post_meta( $imageservice_data['post_child'], 'w3tc_imageservice', true ); if ( empty( $child_data['is_converted_file'] ) ) { $status = null; $this->remove_optimizations( $post_id ); } } // If processed, then show information. if ( 'converted' === $status ) { $converted_percent = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-out-percent'] ) ? $imageservice_data['download']["\0*\0data"]['x-filesize-out-percent'] : null; $reduced_percent = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-reduced'] ) ? $imageservice_data['download']["\0*\0data"]['x-filesize-reduced'] : null; $filesize_in = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-in'] ) ? $imageservice_data['download']["\0*\0data"]['x-filesize-in'] : null; $filesize_out = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-out'] ) ? $imageservice_data['download']["\0*\0data"]['x-filesize-out'] : null; if ( $converted_percent ) { $converted_class = rtrim( $converted_percent, '%' ) > 100 ? 'w3tc-converted-increased' : 'w3tc-converted-reduced'; ?>
', '' ); ?>
| post_parent ); } } } /** * Add bulk actions. * * @since 2.2.0 * * @param array $actions Bulk actions. * @return array */ public function add_bulk_actions( array $actions ) { $actions['w3tc_imageservice_convert'] = 'W3 Total Convert'; $actions['w3tc_imageservice_revert'] = 'W3 Total Convert Revert'; return $actions; } /** * Handle bulk actions. * * @since 2.2.0 * * @see self::submit_images() * @see self::revert_optimizations() * * @link https://developer.wordpress.org/reference/hooks/handle_bulk_actions-screen/ * @link https://make.wordpress.org/core/2016/10/04/custom-bulk-actions/ * @link https://core.trac.wordpress.org/browser/tags/5.8/src/wp-admin/upload.php#L206 * * @since WordPress 4.7.0 * * @param string $location The redirect URL. * @param string $doaction The action being taken. * @param array $post_ids The items to take the action on. Accepts an array of IDs of attachments. * @return string */ public function handle_bulk_actions( $location, $doaction, array $post_ids ) { // Remove custom query args. $location = remove_query_arg( array( 'w3tc_imageservice_submitted', 'w3tc_imageservice_reverted' ), $location ); switch ( $doaction ) { case 'w3tc_imageservice_convert': $stats = $this->submit_images( $post_ids ); $location = add_query_arg( array( 'w3tc_imageservice_submitted' => $stats['submitted'], 'w3tc_imageservice_successful' => $stats['successful'], 'w3tc_imageservice_skipped' => $stats['skipped'], 'w3tc_imageservice_errored' => $stats['errored'], 'w3tc_imageservice_invalid' => $stats['invalid'], ), $location ); break; case 'w3tc_imageservice_revert': $this->revert_optimizations( $post_ids ); $location = add_query_arg( 'w3tc_imageservice_reverted', 1, $location ); break; default: break; } return $location; } /** * Display bulk action results admin notice. * * @since 2.2.0 * * @uses $_GET['w3tc_imageservice_submitted'] Number of submittions. * @uses $_GET['w3tc_imageservice_successful'] Number of successful submissions. * @uses $_GET['w3tc_imageservice_skipped'] Number of skipped submissions. * @uses $_GET['w3tc_imageservice_errored'] Number of errored submissions. * @uses $_GET['w3tc_imageservice_invalid'] Number of invalid submissions. */ public function display_notices() { $submitted = Util_Request::get_integer( 'w3tc_imageservice_submitted' ); if ( ! empty( $submitted ) ) { $successful_val = Util_Request::get_integer( 'w3tc_imageservice_successful' ); $successful = ! empty( $successful_val ) ? $successful_val : 0; $skipped_val = Util_Request::get_integer( 'w3tc_imageservice_skipped' ); $skipped = ! empty( $skipped_val ) ? $skipped_val : 0; $errored_val = Util_Request::get_integer( 'w3tc_imageservice_errored' ); $errored = ! empty( $errored_val ) ? $errored_val : 0; $invalid_val = Util_Request::get_integer( 'w3tc_imageservice_invalid' ); $invalid = ! empty( $invalid_val ) ? $invalid_val : 0; ?>

Total Cache WebP Converter

', esc_attr( $submitted ) ); // Print extra stats if debug is on. if ( defined( 'W3TC_DEBUG' ) && W3TC_DEBUG ) { ?>

Total Cache WebP Converter

id ) { // Media Library: Get the display mode. $mode = get_user_option( 'media_library_mode', get_current_user_id() ) ? get_user_option( 'media_library_mode', get_current_user_id() ) : 'grid'; // If not in list mode, then print a notice to switch to it. if ( 'list' !== $mode ) { ?>

Total Cache WebP Converter - ', '' ); ?>

', '' ) ); global $wp_filesystem; $stats = array( 'skipped' => 0, 'submitted' => 0, 'successful' => 0, 'errored' => 0, 'invalid' => 0, ); foreach ( $post_ids as $post_id ) { // Skip silently (do not count) if not an allowed MIME type. if ( ! in_array( get_post_mime_type( $post_id ), self::$mime_types, true ) ) { continue; } $filepath = get_attached_file( $post_id ); // Skip if attachment file does not exist. if ( ! $wp_filesystem->exists( $filepath ) ) { $stats['skipped']++; continue; } // Submit current image. $response = Extension_ImageService_Plugin::get_api()->convert( $filepath ); $stats['submitted']++; if ( isset( $response['error'] ) ) { $stats['errored']++; continue; } if ( empty( $response['job_id'] ) || empty( $response['signature'] ) ) { $stats['invalid']++; continue; } // Remove old optimizations. $this->remove_optimizations( $post_id ); // Save the job info. self::update_postmeta( $post_id, array( 'status' => 'processing', 'processing' => $response, ) ); $stats['successful']++; } return $stats; } /** * Revert optimizations of images. * * @since 2.2.0 * * @param array $post_ids Attachment post ids. */ public function revert_optimizations( array $post_ids ) { foreach ( $post_ids as $post_id ) { // Skip if not an allowed MIME type. if ( ! in_array( get_post_mime_type( $post_id ), self::$mime_types, true ) ) { continue; } $this->remove_optimizations( $post_id ); } } /** * Update postmeta. * * @since 2.2.0 * @static * * @link https://developer.wordpress.org/reference/functions/update_post_meta/ * * @param int $post_id Post id. * @param array $data Postmeta data. * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure or if the value * passed to the function is the same as the one that is already in the database. */ public static function update_postmeta( $post_id, array $data ) { $postmeta = (array) get_post_meta( $post_id, 'w3tc_imageservice', true ); $postmeta = array_merge( $postmeta, $data ); return update_post_meta( $post_id, 'w3tc_imageservice', $postmeta ); } /** * Copy postmeta from one post to another. * * @since 2.2.0 * @static * * @link https://developer.wordpress.org/reference/functions/update_post_meta/ * * @param int $post_id_1 Post id 1. * @param int $post_id_2 Post id 2. * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure or if the value * passed to the function is the same as the one that is already in the database. */ public static function copy_postmeta( $post_id_1, $post_id_2 ) { $postmeta = (array) get_post_meta( $post_id_1, 'w3tc_imageservice', true ); // Do not copy "post_child". unset( $postmeta['post_child'] ); return update_post_meta( $post_id_2, 'w3tc_imageservice', $postmeta ); } /** * Remove optimizations. * * @since 2.2.0 * * @link https://developer.wordpress.org/reference/functions/wp_delete_attachment/ * * @param int $post_id Parent post id. * @return WP_Post|false|null Post data on success, false or null on failure. */ public function remove_optimizations( $post_id ) { $result = null; // Get child post id. $postmeta = (array) get_post_meta( $post_id, 'w3tc_imageservice', true ); $child_id = isset( $postmeta['post_child'] ) ? $postmeta['post_child'] : null; if ( $child_id ) { // Delete optimization. $result = wp_delete_attachment( $child_id, true ); } // Delete postmeta. delete_post_meta( $post_id, 'w3tc_imageservice' ); return $result; } /** * Handle auto-optimization on image upload. * * @since 2.2.0 * * @param int $post_id Post id. */ public function auto_convert( $post_id ) { $settings = $this->config->get_array( 'imageservice' ); $enabled = isset( $settings['auto'] ) && 'enabled' === $settings['auto']; if ( $enabled && in_array( get_post_mime_type( $post_id ), self::$mime_types, true ) ) { $this->submit_images( array( $post_id ) ); } } /** * Delete optimizations on parent image delation. * * Does not filter the WordPress operation. We use this as an action trigger. * * @since 2.2.0 * * @param bool|null $delete Whether to go forward with deletion. * @param WP_Post $post Post object. * @param bool $force_delete Whether to bypass the Trash. * @return null */ public function cleanup_optimizations( $delete, $post, $force_delete ) { if ( $force_delete ) { $this->remove_optimizations( $post->ID ); } return null; } /** * AJAX: Submit an image for processing. * * @since 2.2.0 * * @global $wp_filesystem * * @see Extension_ImageService_Plugin::get_api() * * @uses $_POST['post_id'] Post id. */ public function ajax_submit() { check_ajax_referer( 'w3tc_imageservice_submit' ); // Check WP_Filesystem credentials. Util_WpFile::ajax_check_credentials( sprintf( // translators: 1: HTML achor open tag, 2: HTML anchor close tag. __( '%1$sLearn more%2$s.', 'w3-total-cache' ), '', '' ) ); // Check for post id. $post_id_val = Util_Request::get_integer( 'post_id' ); $post_id = ! empty( $post_id_val ) ? $post_id_val : null; if ( ! $post_id ) { wp_send_json_error( array( 'error' => __( 'Missing input post id.', 'w3-total-cache' ), ), 400 ); } global $wp_filesystem; // Verify the image file exists. $filepath = get_attached_file( $post_id ); if ( ! $wp_filesystem->exists( $filepath ) ) { wp_send_json_error( array( 'error' => sprintf( // translators: 1: Image filepath. __( 'File "%1$s" does not exist.', 'w3-total-cache' ), $filepath ), ), 412 ); } // Submit the job request. $response = Extension_ImageService_Plugin::get_api()->convert( $filepath ); // Check for non-200 status code. if ( isset( $response['code'] ) && 200 !== $response['code'] ) { wp_send_json_error( $response, $response['code'] ); } // Check for error. if ( isset( $response['error'] ) ) { wp_send_json_error( $response, 417 ); } // Check for valid response data. if ( empty( $response['job_id'] ) || empty( $response['signature'] ) ) { wp_send_json_error( array( 'error' => __( 'Invalid API response.', 'w3-total-cache' ), ), 417 ); } // Remove old optimizations. $this->remove_optimizations( $post_id ); // Save the job info. self::update_postmeta( $post_id, array( 'status' => 'processing', 'processing' => $response, ) ); wp_send_json_success( $response ); } /** * AJAX: Get the status of an image, from postmeta. * * @since 2.2.0 * * @uses $_POST['post_id'] Post id. */ public function ajax_get_postmeta() { check_ajax_referer( 'w3tc_imageservice_postmeta' ); $post_id_val = Util_Request::get_integer( 'post_id' ); $post_id = ! empty( $post_id_val ) ? $post_id_val : null; if ( $post_id ) { wp_send_json_success( (array) get_post_meta( $post_id, 'w3tc_imageservice', true ) ); } else { wp_send_json_error( array( 'error' => __( 'Missing input post id.', 'w3-total-cache' ), ), 400 ); } } /** * AJAX: Revert an optimization. * * @since 2.2.0 * * @uses $_POST['post_id'] Parent post id. */ public function ajax_revert() { check_ajax_referer( 'w3tc_imageservice_revert' ); $post_id_val = Util_Request::get_integer( 'post_id' ); $post_id = ! empty( $post_id_val ) ? $post_id_val : null; if ( $post_id ) { $result = $this->remove_optimizations( $post_id ); if ( $result ) { wp_send_json_success( $result ); } else { wp_send_json_error( array( 'error' => __( 'Missing converted attachment id.', 'w3-total-cache' ), ), 410 ); } } else { wp_send_json_error( array( 'error' => __( 'Missing input post id.', 'w3-total-cache' ), ), 400 ); } } /** * AJAX: Convert all images. * * @since 2.2.0 * * @see self::get_eligible_attachments() * @see self::submit_images() */ public function ajax_convert_all() { check_ajax_referer( 'w3tc_imageservice_submit' ); $results = $this->get_eligible_attachments(); $post_ids = array(); // Allow plenty of time to complete. ignore_user_abort( true ); set_time_limit( 0 ); foreach ( $results->posts as $post ) { $post_ids[] = $post->ID; } $stats = $this->submit_images( $post_ids ); wp_send_json_success( $stats ); } /** * AJAX: Revert all converted images. * * @since 2.2.0 * * @see self::get_imageservice_attachments() * @see self::remove_optimizations() */ public function ajax_revert_all() { check_ajax_referer( 'w3tc_imageservice_submit' ); $results = $this->get_imageservice_attachments(); $revert_count = 0; // Allow plenty of time to complete. ignore_user_abort( true ); set_time_limit( 0 ); foreach ( $results->posts as $post ) { if ( $this->remove_optimizations( $post->ID ) ) { $revert_count++; } } wp_send_json_success( array( 'revert_count' => $revert_count ) ); } /** * AJAX: Get image counts by status. * * @since 2.2.0 * * @see get_counts() */ public function ajax_get_counts() { check_ajax_referer( 'w3tc_imageservice_submit' ); wp_send_json_success( $this->get_counts() ); } /** * AJAX: Get image API usage. * * @since 2.2.0 * * @see Extension_ImageService_Plugin::get_api() * @see Extension_ImageService_Api::get_usage() */ public function ajax_get_usage() { check_ajax_referer( 'w3tc_imageservice_submit' ); wp_send_json_success( Extension_ImageService_Plugin::get_api()->get_usage() ); } }