Headline
CVE-2022-3568: imagemagick-engine/imagemagick-engine.php at v.1.7.2 · orangelabweb/imagemagick-engine
The ImageMagick Engine plugin for WordPress is vulnerable to deserialization of untrusted input via the ‘cli_path’ parameter in versions up to, and including 1.7.5. This makes it possible for unauthenticated users to call files using a PHAR wrapper, granted they can trick a site administrator into performing an action such as clicking on a link, that will deserialize and call arbitrary PHP Objects that can be used to perform a variety of malicious actions granted a POP chain is also present. It also requires that the attacker is successful in uploading a file with the serialized payload.
<?php /* Plugin Name: ImageMagick Engine Plugin URI: https://wordpress.org/plugins/imagemagick-engine/ Description: Improve the quality of re-sized images by replacing standard GD library with ImageMagick Author: Orangelab Author URI: https://orangelab.com/ Version: 1.7.2 Text Domain: imagemagick-engine Copyright @ 2021 Orangelab AB Licenced under the GNU GPL: This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ if ( ! defined( ‘ABSPATH’ ) ) { die( ‘Must not be called directly’ ); } /* * Constants */ define( 'IME_OPTION_VERSION’, 1 ); define( 'IME_VERSION’, ‘1.7.2’ ); /* * Global variables */ // Plugin options default values – change on plugin admin page $ime_options_default = [ ‘enabled’ => false, ‘mode’ => null, ‘cli_path’ => null, ‘handle_sizes’ => [ ‘thumbnail’ => 'size’, ‘medium’ => 'quality’, ‘medium_large’ => 'quality’, ‘large’ => 'quality’, ], ‘quality’ => [ ‘quality’ => -1, ‘size’ => 70, ], ‘interlace’ => false, ‘quality’ => '’, ‘version’ => constant( ‘IME_OPTION_VERSION’ ), ]; // Available modes $ime_available_modes = [ ‘php’ => __( 'Imagick PHP module’, ‘imagemagick-engine’ ), ‘cli’ => __( 'ImageMagick command-line’, ‘imagemagick-engine’ ), ]; // Available quality modes $ime_available_quality_modes = [ 'quality’, 'size’, ‘skip’ ]; // Current options $ime_options = null; // Keep track of attachment file & sizes between different filters $ime_image_sizes = null; $ime_image_file = null; /* * Functions */ add_action( 'plugins_loaded’, ‘ime_init_early’ ); add_action( 'init’, ‘ime_init’ ); /* Plugin setup (early) */ function ime_init_early() { load_plugin_textdomain( 'imagemagick-engine’, false, dirname( plugin_basename( __FILE__ ) ) . ‘/languages’ ); if ( ime_active() ) { add_filter( 'intermediate_image_sizes_advanced’, 'ime_filter_image_sizes’, 99, 1 ); add_filter( 'wp_read_image_metadata’, 'ime_filter_read_image_metadata’, 10, 3 ); add_filter( 'wp_generate_attachment_metadata’, 'ime_filter_attachment_metadata’, 10, 2 ); } } /* Plugin setup */ function ime_init() { if ( is_admin() ) { add_action( 'admin_menu’, ‘ime_admin_menu’ ); add_filter( 'plugin_action_links’, 'ime_filter_plugin_actions’, 10, 2 ); add_filter( 'media_meta’, 'ime_filter_media_meta’, 10, 2 ); add_action( 'wp_ajax_ime_test_im_path’, ‘ime_ajax_test_im_path’ ); add_action( 'wp_ajax_ime_process_image’, ‘ime_ajax_process_image’ ); add_action( 'wp_ajax_ime_regeneration_get_images’, ‘ime_ajax_regeneration_get_images’ ); wp_register_script( 'ime-admin’, plugins_url( '/js/ime-admin.js’, __FILE__ ), [ 'jquery’, ‘jquery-ui-progressbar’ ], constant(‘IME_VERSION’) ); } } /* Are we enabled with valid mode? */ function ime_active() { return ime_get_option( ‘enabled’ ) && ime_mode_valid(); } /* Check if mode is valid */ function ime_mode_valid( $mode = null ) { if ( empty( $mode ) ) { $mode = ime_get_option( ‘mode’ ); } $fn = ‘ime_im_’ . $mode . '_valid’; return ( ! empty( $mode ) && function_exists( $fn ) && call_user_func( $fn ) ); } // Check version of a registered WordPress script function ime_script_version_compare( $handle, $version, $compare = ‘>=’ ) { global $wp_scripts; if ( ! is_a( $wp_scripts, ‘WP_Scripts’ ) ) { $wp_scripts = new WP_Scripts(); } $query = $wp_scripts->query( $handle, ‘registered’ ); if ( ! $query ) { return false; } return version_compare( $query->ver, $version, $compare ); } // Get array of available image sizes function ime_available_image_sizes() { global $_wp_additional_image_sizes; $sizes = [ ‘thumbnail’ => __( 'Thumbnail’, ‘imagemagick-engine’ ), ‘medium’ => __( 'Medium’, ‘imagemagick-engine’ ), ‘medium_large’ => __( 'Medium Large’, ‘imagemagick-engine’ ), ‘large’ => __( 'Large’, ‘imagemagick-engine’ ), ]; // Standard sizes if ( isset( $_wp_additional_image_sizes ) && count( $_wp_additional_image_sizes ) ) { foreach ( $_wp_additional_image_sizes as $name => $spec ) { $sizes[ $name ] = $name; } } return $sizes; } /* * Plugin option handling */ // Setup plugin options function ime_setup_options() { global $ime_options; // Already setup? if ( is_array( $ime_options ) ) { return; } $ime_options = get_option( ‘ime_options’ ); // No stored options yet? if ( ! is_array( $ime_options ) ) { global $ime_options_default; $ime_options = $ime_options_default; } // Do we need to upgrade options? if ( ! array_key_exists( 'version’, $ime_options ) || $ime_options[‘version’] < constant( ‘IME_OPTION_VERSION’ ) ) { /* * Future compatability code goes here! */ $ime_options[‘version’] = constant( ‘IME_OPTION_VERSION’ ); ime_store_options(); } } // Store plugin options function ime_store_options() { global $ime_options; ime_setup_options(); $stored_options = get_option( ‘ime_options’ ); if ( $stored_options === false ) { add_option( 'ime_options’, $ime_options, null, false ); } else { update_option( 'ime_options’, $ime_options ); } } // Get plugin option function ime_get_option( $option_name, $default = null ) { ime_setup_options(); global $ime_options, $ime_options_default; if ( is_array( $ime_options ) && array_key_exists( $option_name, $ime_options ) ) { return $ime_options[ $option_name ]; } if ( ! is_null( $default ) ) { return $default; } if ( is_array( $ime_options_default ) && array_key_exists( $option_name, $ime_options_default ) ) { return $ime_options_default[ $option_name ]; } return null; } // Set plugin option function ime_set_option( $option_name, $option_value, $store = false ) { ime_setup_options(); global $ime_options; $ime_options[ $option_name ] = $option_value; if ( $store ) { ime_store_options(); } } // Should images be converted with interlace or not function ime_interlace() { return ime_get_option( ‘interlace’ ); } // Get image quality setting for type function ime_get_quality( $resize_mode = ‘quality’ ) { $quality = ime_get_option( ‘quality’, '-1’ ); if ( ! $quality ) { return -1; } if ( ! is_array( $quality ) ) { return $quality; } if ( isset( $quality[ $resize_mode ] ) ) { return $quality[ $resize_mode ]; } return -1; } // Get resize mode for size function ime_get_resize_mode( $size ) { $handle_sizes = ime_get_option( ‘handle_sizes’ ); if ( isset( $handle_sizes[ $size ] ) && is_string( $handle_sizes[ $size ] ) ) { return $handle_sizes[ $size ]; } else { return 'quality’; // default to quality } } /* * WP integration & image handling functions */ /* * Filter image sizes (in wp_generate_attachment_metadata()). * * We store the sizes we are interested in, and remove those sizes from the * list so that WP doesn’t handle them – we will take care of them later. * * The reason we do things this way is so we do not resize image twize (once * by WordPress using GD, and then again by us). */ function ime_filter_image_sizes( $sizes ) { global $ime_image_sizes; $handle_sizes = ime_get_option( ‘handle_sizes’ ); foreach ( $handle_sizes as $s => $handle ) { if ( ! $handle || $handle == ‘skip’ || ! array_key_exists( $s, $sizes ) ) { continue; } $ime_image_sizes[ $s ] = $sizes[ $s ]; unset( $sizes[ $s ] ); } return $sizes; } /* * Filter to get target file name. * * Function wp_generate_attachment_metadata calls wp_read_image_metadata which * gives us a hook to get the target filename. */ function ime_filter_read_image_metadata( $metadata, $file, $ignore ) { global $ime_image_file; $ime_image_file = $file; return $metadata; } /* * Filter new attachment metadata * * Resize image for the sizes we are interested in. * * Parts of function copied from wp-includes/media.php:image_resize() */ function ime_filter_attachment_metadata( $metadata, $attachment_id ) { global $ime_image_sizes, $ime_image_file; // Any sizes we are interested in? if ( empty( $ime_image_sizes ) ) { return $metadata; } $attachment = get_post( $attachment_id ); // We can only process attachments. if ( ‘attachment’ !== get_post_type( $attachment ) ) { return $metadata; } $editor = wp_get_image_editor( $ime_image_file ); if ( is_wp_error( $editor ) ) { // Display a more helpful error message. if ( ‘image_no_editor’ === $editor->get_error_code() ) { $editor = new WP_Error( 'image_no_editor’, __( 'The current image editor cannot process this file type.’, ‘regenerate-thumbnails’ ) ); } $editor->add_data( array( ‘attachment’ => $attachment, ‘status’ => 415, ) ); return $editor; } // Get size & image type of original image $old_stats = @getimagesize( $ime_image_file ); if ( ! $old_stats ) { return $metadata; } list($orig_w, $orig_h, $orig_type) = $old_stats; /* * Sort out the filename, extension (and image type) of resized images */ $info = pathinfo( $ime_image_file ); $dir = $info[‘dirname’]; $ext = $info[‘extension’]; /* * Do the actual resize */ foreach ( $ime_image_sizes as $size => $size_data ) { $width = $size_data[‘width’]; $height = $size_data[‘height’]; // ignore sizes equal to or larger than original size if ( $orig_w <= $width && $orig_h <= $height ) { continue; } $crop = $size_data[‘crop’]; $dims = image_resize_dimensions( $orig_w, $orig_h, $width, $height, $crop ); if ( ! $dims ) { continue; } list($dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) = $dims; $suffix = “{$dst_w}x{$dst_h}"; $new_filename = $editor->generate_filename( $suffix, null, $ext ); $resized = ime_im_resize( $ime_image_file, $new_filename, $dst_w, $dst_h, $crop, ime_get_resize_mode( $size ) ); if ( ! $resized ) { continue; } $metadata[‘sizes’][ $size ] = [ ‘file’ => wp_basename( $new_filename ), ‘width’ => $dst_w, ‘height’ => $dst_h, ]; if ( ! isset( $metadata[‘image-converter’] ) || ! is_array( $metadata[‘image-converter’] ) ) { $metadata[‘image-converter’] = []; } $metadata[‘image-converter’][ $size ] = 'IME’; // Set correct file permissions $stat = stat( dirname( $new_filename ) ); $perms = $stat[‘mode’] & 0000666; //same permissions as parent folder, strip off the executable bits @ chmod( $new_filename, $perms ); } $ime_image_sizes = null; return $metadata; } // Resize file by calling mode specific resize function function ime_im_resize( $old_file, $new_file, $width, $height, $crop, $resize_mode = ‘quality’ ) { $mode = ime_get_option( ‘mode’ ); $fn = ‘ime_im_’ . $mode . '_valid’; if ( empty( $mode ) || ! function_exists( $fn ) || ! call_user_func( $fn ) ) { return false; } $fn = ‘ime_im_’ . $mode . '_resize’; $success = ( function_exists( $fn ) && call_user_func( $fn, $old_file, $new_file, $width, $height, $crop, $resize_mode ) ); do_action( 'ime_after_resize’, $success, $old_file, $new_file, $width, $height, $crop, $resize_mode ); return $success; } // Is this the filename of a jpeg? function ime_im_filename_is_jpg( $filename ) { $info = pathinfo( $filename ); $ext = $info[‘extension’]; return ( strcasecmp( $ext, ‘jpg’ ) == 0 ) || ( strcasecmp( $ext, ‘jpeg’ ) == 0 ); } // Get file extenstion function ime_im_get_filetype( $filename ) { $info = pathinfo( $filename ); return strtolower( $info[‘extension’] ); } /* * PHP ImageMagick (“Imagick”) class handling */ // Does class exist? function ime_im_php_valid() { return class_exists( ‘Imagick’ ); } // Resize file using PHP Imagick class function ime_im_php_resize( $old_file, $new_file, $width, $height, $crop, $resize_mode = ‘quality’ ) { $im = new Imagick( $old_file ); if ( ! $im->valid() ) { return false; } try { $im->setImageFormat( ime_im_get_filetype( $old_file ) ); $quality = ime_get_quality( $resize_mode ); if ( is_numeric( $quality ) && $quality >= 0 && $quality <= 100 && ime_im_filename_is_jpg( $new_file ) ) { $im->setImageCompression( Imagick::COMPRESSION_JPEG ); $im->setImageCompressionQuality( $quality ); } if ( ime_interlace() ) { $im->setInterlaceScheme( Imagick::INTERLACE_PLANE ); } if ( $resize_mode == ‘size’ ) { $im->stripImage(); } if ( $crop ) { /* * Unfortunately we cannot use the PHP module * cropThumbnailImage() function as it strips profile data. * * Crop an area proportional to target $width and $height and * fall through to scaleImage() below. */ $geo = $im->getImageGeometry(); $orig_width = $geo[‘width’]; $orig_height = $geo[‘height’]; if ( ( $orig_width / $width ) < ( $orig_height / $height ) ) { $crop_width = $orig_width; $crop_height = ceil( ( $height * $orig_width ) / $width ); $off_x = 0; $off_y = ceil( ( $orig_height - $crop_height ) / 2 ); } else { $crop_width = ceil( ( $width * $orig_height ) / $height ); $crop_height = $orig_height; $off_x = ceil( ( $orig_width - $crop_width ) / 2 ); $off_y = 0; } $im->cropImage( $crop_width, $crop_height, $off_x, $off_y ); } $im->scaleImage( $width, $height, true ); $im->setImagePage( $width, $height, 0, 0 ); // to make sure canvas is correct $im->writeImage( $new_file ); return file_exists( $new_file ); } catch ( ImagickException $ie ) { return false; } } /* * ImageMagick executable handling */ // Do we have a valid ImageMagick executable set? function ime_im_cli_valid() { $cmd = ime_im_cli_command(); return !empty($cmd) && @is_executable($cmd); } // Test if we are allowed to exec executable! function ime_im_cli_check_executable($fullpath) { if (!@is_executable($fullpath)) { return false; } @exec( ‘"’ . $fullpath . '” --version’, $output ); ime_set_option( 'imagemagick_version’, $output, true ); return (is_array($output)) ? (count( $output ) > 0) : false; } /* * Try to get realpath of path * * This won’t work if there is open_basename restrictions. */ function ime_try_realpath( $path ) { $realpath = @realpath( $path ); if ( $realpath ) { return $realpath; } else { return $path; } } // Check if path leads to ImageMagick executable function ime_im_cli_check_command( $path, $executable = ‘convert’ ) { $path = ime_try_realpath( $path ); $cmd = $path . ‘/’ . $executable; if ( ime_im_cli_check_executable( $cmd ) ) { return $cmd; } $cmd = $cmd . '.exe’; if ( ime_im_cli_check_executable( $cmd ) ) { return $cmd; } return null; } // Try to find a valid ImageMagick executable function ime_im_cli_find_command( $executable = ‘convert’ ) { $possible_paths = [ '/usr/bin’, ‘/usr/local/bin’ ]; foreach ( $possible_paths as $path ) { if ( ime_im_cli_check_command( $path, $executable ) ) { return $path; } } return null; } // Get ImageMagick executable function ime_im_cli_command( $executable = ‘convert’ ) { $path = ime_get_option( ‘cli_path’ ); if ( ! empty( $path ) ) { return ime_im_cli_check_command( $path, $executable ); } $path = ime_im_cli_find_command( $executable ); if ( empty( $path ) ) { return null; } ime_set_option( ‘cli_path’, $path, true ); return ime_im_cli_check_command( $path, $executable ); } // Check if we are running under Windows (which differs for character escape) function ime_is_windows() { return ( constant( ‘PHP_SHLIB_SUFFIX’ ) == ‘dll’ ); } // Resize using ImageMagick executable function ime_im_cli_resize( $old_file, $new_file, $width, $height, $crop, $resize_mode = ‘quality’ ) { $cmd = ime_im_cli_command(); if ( empty( $cmd ) ) { return false; } $old_file = addslashes( $old_file ); $new_file = addslashes( $new_file ); $geometry = $width . ‘x’ . $height; // limits are 150mb and 128mb $cmd = “\"$cmd\” \"$old_file\" -limit memory 157286400 -limit map 134217728 -resize $geometry"; if ( $crop ) { // ‘^’ is an escape character on Windows $cmd .= ( ime_is_windows() ? ‘^^’ : ‘^’ ) . " -gravity center -extent $geometry"; } else { $cmd .= ‘!’; // force these dimensions } $quality = ime_get_quality( $resize_mode ); if ( is_numeric( $quality ) && $quality >= 0 && $quality <= 100 && ime_im_filename_is_jpg( $new_file ) ) { $cmd .= ' -quality ' . intval( $quality ); } if ( ime_interlace() ) { $cmd .= ' -interlace Plane’; } if ( $resize_mode == ‘size’ ) { $cmd .= ' -strip’; } $cmd .= ' "’ . $new_file . ‘"’; exec( $cmd ); return file_exists( $new_file ); } /* * AJAX functions */ // Test if a path is correct for IM binary function ime_ajax_test_im_path() { if ( ! current_user_can( ‘manage_options’ ) ) { die(); } $r = ime_im_cli_check_command( $_REQUEST[‘cli_path’] ); echo empty( $r ) ? ‘0’ : ‘1’; die(); } // Get list of attachments to regenerate function ime_ajax_regeneration_get_images() { global $wpdb; if ( ! current_user_can( ‘manage_options’ ) ) { wp_die( ‘Sorry, but you do not have permissions to perform this action.’ ); } // Query for the IDs only to reduce memory usage $images = $wpdb->get_results( “SELECT ID FROM $wpdb->posts WHERE post_type = ‘attachment’ AND post_mime_type LIKE ‘image/%’ AND post_mime_type != 'image/svg+xml’” ); // Generate the list of IDs $ids = []; foreach ( $images as $image ) { $ids[] = $image->ID; } $ids = implode( ‘,’, $ids ); die( $ids ); } // Process single attachment ID function ime_ajax_process_image() { global $ime_image_sizes, $ime_image_file, $_wp_additional_image_sizes; error_reporting( E_ERROR | E_WARNING ); if ( ! current_user_can( ‘manage_options’ ) || ! ime_mode_valid() ) { die( '-1’ ); } if ( ! isset( $_REQUEST[‘id’] ) ) { die( '-1’ ); } $id = intval( $_REQUEST[‘id’] ); if ( $id <= 0 ) { die( '-1’ ); } $temp_sizes = $_REQUEST[‘sizes’]; if ( empty( $temp_sizes ) ) { die( '-1’ ); } $temp_sizes = explode( ‘|’, $temp_sizes ); if ( count( $temp_sizes ) < 1 ) { die( '-1’ ); } $temp_sizes = apply_filters( 'intermediate_image_sizes’, $temp_sizes ); foreach ( $temp_sizes as $s ) { $sizes[ $s ] = [ ‘width’ => '’, ‘height’ => '’, ‘crop’ => false, ]; if ( isset( $_wp_additional_image_sizes[ $s ][‘width’] ) ) { $sizes[ $s ][‘width’] = intval( $_wp_additional_image_sizes[ $s ][‘width’] ); // For theme-added sizes } else { $sizes[ $s ][‘width’] = get_option( “{$s}_size_w” ); // For default sizes set in options } if ( isset( $_wp_additional_image_sizes[ $s ][‘height’] ) ) { $sizes[ $s ][‘height’] = intval( $_wp_additional_image_sizes[ $s ][‘height’] ); // For theme-added sizes } else { $sizes[ $s ][‘height’] = get_option( “{$s}_size_h” ); // For default sizes set in options } if ( isset( $_wp_additional_image_sizes[ $s ][‘crop’] ) ) { $sizes[ $s ][‘crop’] = intval( $_wp_additional_image_sizes[ $s ][‘crop’] ); // For theme-added sizes } else { $sizes[ $s ][‘crop’] = get_option( “{$s}_crop” ); // For default sizes set in options } } remove_filter( 'intermediate_image_sizes_advanced’, 'ime_filter_image_sizes’, 99, 1 ); $sizes = apply_filters( ‘intermediate_image_sizes_advanced’, $sizes ); $force = isset( $_REQUEST[‘force’] ) && ! ! $_REQUEST[‘force’]; $ime_image_file = function_exists(‘wp_get_original_image_path’) ? wp_get_original_image_path( $id ) : get_attached_file( $id ); if ( false === $ime_image_file || ! file_exists( $ime_image_file ) ) { die( '-1’ ); } $metadata = wp_get_attachment_metadata( $id ); // Do not re-encode IME images unless forced if ( ! $force && isset( $metadata[‘image-converter’] ) && is_array( $metadata[‘image-converter’] ) ) { $converter = $metadata[‘image-converter’]; foreach ( $sizes as $s => $ignore ) { if ( isset( $converter[ $s ] ) && $converter[ $s ] == ‘IME’ ) { unset( $sizes[ $s ] ); } } if ( count( $sizes ) < 1 ) { die( 1 ); } } $ime_image_sizes = $sizes; set_time_limit( 60 ); $new_meta = ime_filter_attachment_metadata( $metadata, $id ); wp_update_attachment_metadata( $id, $new_meta ); /* * Normally the old file gets overwritten by the new one when * regenerating resized images. * * However, if the specifications of image sizes were changed this * will result in different resized file names. * * Make sure they get deleted. */ // No old sizes, nothing to check if ( ! isset( $metadata[‘sizes’] ) || empty( $metadata[‘sizes’] ) ) { die( ‘1’ ); } $dir = trailingslashit( dirname( $ime_image_file ) ); foreach ( $metadata[‘sizes’] as $size => $sizeinfo ) { $old_file = $sizeinfo[‘file’]; // Does file exist in new meta? $exists = false; foreach ( $new_meta[‘sizes’] as $ignore => $new_sizeinfo ) { if ( $old_file != $new_sizeinfo[‘file’] ) { continue; } $exists = true; break; } if ( $exists ) { continue; } // Old file did not exist in new meta. Delete it! @ unlink( $dir . $old_file ); } die( ‘1’ ); } /* * Admin page functions */ /* Add admin page */ function ime_admin_menu() { $ime_page = add_options_page( 'ImageMagick Engine’, 'ImageMagick Engine’, 'manage_options’, 'imagemagick-engine’, ‘ime_option_page’ ); $script_pages = [ $ime_page, 'media.php’, 'media-new.php’, 'media-upload.php’, 'media-upload-popup’, 'post.php’, ‘upload.php’ ]; foreach ( $script_pages as $page ) { add_action( 'admin_print_scripts-' . $page, ‘ime_admin_print_scripts’ ); add_action( 'admin_print_styles-' . $page, ‘ime_admin_print_styles’ ); } } /* Enqueue admin page scripts */ function ime_admin_print_scripts() { wp_enqueue_script( ‘ime-admin’ ); $data = [ ‘noimg’ => __( 'You dont have any images to regenerate’, ‘imagemagick-engine’ ), ‘done’ => __( 'All done!’, ‘imagemagick-engine’ ), ‘processed_fmt’ => __( 'Processed %d images’, ‘imagemagick-engine’ ), ‘failed’ => ‘<strong>’ . __( 'Failed to resize image!’, ‘imagemagick-engine’ ) . '</strong>’, ‘resized’ => __( 'Resized using ImageMagick Engine’, ‘imagemagick-engine’ ), ]; wp_localize_script( 'ime-admin’, 'ime_admin’, $data ); } /* Enqueue admin page style */ function ime_admin_print_styles() { wp_enqueue_style( 'ime-admin-style’, plugins_url( '/css/ime-admin.css’, __FILE__ ), [] ); } /* Add settings to plugin action links */ function ime_filter_plugin_actions( $links, $file ) { if ( $file == plugin_basename( __FILE__ ) ) { $settings_link = ‘<a href="options-general.php?page=imagemagick-engine">’ . __( 'Settings’, ‘imagemagick-engine’ ) . '</a>’; array_unshift( $links, $settings_link ); // before other links } return $links; } /* * Add admin information if attachment is converted using plugin */ function ime_filter_media_meta( $content, $post ) { global $wp_version; if ( ! ime_mode_valid() ) { return $content; } if ( version_compare( $wp_version, '3.5.0’, ‘>=’ ) ) { // WordPress 3.5 and newer if ( ! wp_image_editor_supports( [ ‘mime_type’ => $post->post_mime_type ] ) ) { return $content; } } else { // WordPress older than 3.5 if ( ! gd_edit_image_support( $post->post_mime_type ) ) { return $content; } } $metadata = wp_get_attachment_metadata( $post->ID ); $ime = false; if ( is_array( $metadata ) && array_key_exists( 'image-converter’, $metadata ) ) { foreach ( $metadata[‘image-converter’] as $size => $converter ) { if ( $converter != ‘IME’ ) { continue; } $ime = true; break; } } $content .= '</p><p>’; if ( $ime ) { $message = ' <div class="ime-media-message" id="ime-message-' . $post->ID . ‘">’ . __( 'Resized using ImageMagick Engine’, ‘imagemagick-engine’ ) . '</div>’; $resize = __( 'Resize image’, ‘imagemagick-engine’ ); $force = '1’; } else { $message = '<div class="ime-media-message" id="ime-message-' . $post->ID . '" style="display: none;"></div>’; $resize = __( 'Resize using ImageMagick Engine’, ‘imagemagick-engine’ ); $force = '0’; } $handle_sizes = ime_get_option( ‘handle_sizes’ ); $sizes = []; foreach ( $handle_sizes as $s => $h ) { if ( ! $h ) { continue; } $sizes[] = $s; } $sizes = implode( '|’, $sizes ); $resize_call = 'imeRegenMediaImage(' . $post->ID . ‘, \’’ . $sizes . '\’, ' . $force . '); return false;’; $content .= '<a href="#" id="ime-regen-link-' . $post->ID . ‘" class="button ime-regen-button" onclick="’ . $resize_call . ‘">’ . $resize . '</a> ' . $message . ' <div id="ime-spinner-' . $post->ID . ‘" class="ime-spinner"><img src="’ . admin_url( ‘images/wpspin_light.gif’ ) . '" /></div>’; return $content; } // Url to admin images function ime_option_admin_images_url() { return get_bloginfo( ‘wpurl’ ) . '/wp-admin/images/’; } // Url to status icon function ime_option_status_icon( $yes = true ) { return ime_option_admin_images_url() . ( $yes ? ‘yes’ : ‘no’ ) . '.png’; } // Display, or not function ime_option_display( $display = true, $echo = true ) { if ( $display ) { return '’; } $s = ' style="display: none" '; if ( $echo ) { echo $s; } return $s; } /* Plugin admin / status page */ function ime_option_page() { global $ime_available_modes, $ime_available_quality_modes; if ( ! current_user_can( ‘manage_options’ ) ) { wp_die( ‘Sorry, but you do not have permissions to change settings.’ ); } /* Make sure post was from this page */ if ( count( $_POST ) > 0 ) { check_admin_referer( ‘ime-options’ ); } $sizes = ime_available_image_sizes(); if ( isset( $_POST[‘regenerate-images’] ) ) { ime_show_regenerate_images( array_keys( $sizes ) ); return; } /* Should we update settings? */ if ( isset( $_POST[‘update_settings’] ) ) { $new_enabled = isset( $_POST[‘enabled’] ) && ! ! $_POST[‘enabled’]; ime_set_option( 'enabled’, $new_enabled ); if ( isset( $_POST[‘mode’] ) && array_key_exists( $_POST[‘mode’], $ime_available_modes ) ) { ime_set_option( 'mode’, $_POST[‘mode’] ); } if ( isset( $_POST[‘cli_path’] ) ) { ime_set_option( 'cli_path’, ime_try_realpath( trim( $_POST[‘cli_path’] ) ) ); } $new_quality = [ ‘quality’ => -1, ‘size’ => 70, ]; if ( isset( $_POST[‘quality-quality’] ) ) { if ( is_numeric( $_POST[‘quality-quality’] ) ) { $new_quality[‘quality’] = min( 100, max( 0, intval( $_POST[‘quality-quality’] ) ) ); } elseif ( empty( $_POST[‘quality-quality’] ) ) { $new_quality[‘quality’] = -1; } } if ( isset( $_POST[‘quality-size’] ) ) { if ( is_numeric( $_POST[‘quality-size’] ) ) { $new_quality[‘size’] = min( 100, max( 0, intval( $_POST[‘quality-size’] ) ) ); } elseif ( empty( $_POST[‘quality-size’] ) ) { $new_quality[‘size’] = -1; } } ime_set_option( 'quality’, $new_quality ); $new_interlace = isset( $_POST[‘interlace’] ) && ! ! $_POST[‘interlace’]; ime_set_option( 'interlace’, $new_interlace ); $new_handle_sizes = []; foreach ( $sizes as $s => $name ) { $new_mode = isset( $_POST[ 'handle-mode-' . $s ] ) ? $_POST[ 'handle-mode-' . $s ] : 'skip’; if ( in_array( $new_mode, $ime_available_quality_modes ) ) { $mode = $new_mode; } else { $mode = 'quality’; } $new_handle_sizes[ $s ] = $mode; } ime_set_option( 'handle_sizes’, $new_handle_sizes ); ime_store_options(); echo ‘<div id="message" class="updated fade"><p>’ . __( 'Settings updated’, ‘imagemagick-engine’ ) . '</p></div>’; } $modes_valid = $ime_available_modes; $any_valid = false; foreach ( $modes_valid as $m => $ignore ) { $modes_valid[ $m ] = ime_mode_valid( $m ); if ( $modes_valid[ $m ] ) { $any_valid = true; } } $current_mode = ime_get_option( ‘mode’ ); if ( ! isset( $modes_valid[ $current_mode ] ) || ! $modes_valid[ $current_mode ] ) { $current_mode = null; } if ( is_null( $current_mode ) && $any_valid ) { foreach ( $modes_valid as $m => $valid ) { if ( $valid ) { $current_mode = $m; break; } } } $enabled = ime_get_option( ‘enabled’ ) && $current_mode; $cli_path = ime_get_option( ‘cli_path’ ); if ( is_null( $cli_path ) ) { $cli_path = ime_im_cli_command(); } $cli_path_ok = ime_im_cli_check_command( $cli_path ); $quality = ime_get_option( ‘quality’ ); if ( ! is_array( $quality ) ) { $n = [ ‘quality’ => -1, ‘size’ => 70, ]; if ( is_numeric( $quality ) && $quality > 0 ) { $n[‘quality’] = $quality; } $quality = $n; } $interlace = ime_get_option( ‘interlace’ ); $handle_sizes = ime_get_option( ‘handle_sizes’ ); if ( ! $any_valid ) { echo ‘<div id="warning" class="error"><p>’ . sprintf( __( 'No valid ImageMagick mode found! Please check %1$sFAQ%2$s for installation instructions.’, ‘imagemagick-engine’ ), '<a href="http://wp.orangelab.se/imagemagick-engine/documentation#installation">’, ‘</a>’ ) . '</p></div>’; } elseif ( ! $enabled ) { echo ‘<div id="warning" class="error"><p>’ . __( 'ImageMagick Engine is not enabled.’, ‘imagemagick-engine’ ) . '</p></div>’; } ?> <div class="wrap"> <div id="regen-message" class="hidden updated fade"></div> <h2><?php _e( 'ImageMagick Engine Settings’, ‘imagemagick-engine’ ); ?></h2> <div id="ime-regeneration" title="<?php _e( 'Regenerating images’, ‘imagemagick-engine’ ); ?>…"> <noscript><p><em><?php _e( 'You must enable Javascript in order to proceed!’, ‘imagemagick-engine’ ); ?></em></p></noscript> <p><strong><?php _e( 'Regenerating images’, ‘imagemagick-engine’ ); ?>…</strong></p> <div id="ime-regenbar"> <div id="ime-regenbar-percent"></div> </div> </div> <form action="options-general.php?page=imagemagick-engine" method="post" name="update_options"> <?php wp_nonce_field( ‘ime-options’ ); ?> <div id="poststuff" class="metabox-holder has-right-sidebar"> <div class="inner-sidebar"> <div class="meta-box-sortables ui-sortable"> <div id="regenerate-images-metabox" class="postbox"> <h3 class="hndle"><span><?php _e( 'Regenerate Images’, ‘imagemagick-engine’ ); ?></span></h3> <div class="inside"> <div class="submitbox"> <table border=0> <tr> <td scope="row" valign="top" style="padding-right: 20px;"><?php _e( 'Sizes’, ‘imagemagick-engine’ ); ?>:</td> <td> <?php foreach ( $sizes as $s => $name ) { echo '<input type="checkbox" name="regen-size-' . $s . '" value="1" ' . ( ( isset( $handle_sizes[ $s ] ) && $handle_sizes[ $s ] != ‘skip’ && $handle_sizes[ $s ] != false ) ? ' checked="checked" ' : ‘’ ) . ' /> ' . $name . '<br />’; } ?> </td> </tr> </table> <p><?php _e( 'ImageMagick images too’, ‘imagemagick-engine’ ); ?>: <input type="checkbox" name="force" id="force" value="1" /></p> <?php if ( ! ime_active() ) { echo ‘<p class="howto">’ . __( 'Resize will use standard WordPress functions.’, ‘imagemagick-engine’ ) . '</p>’; } ?> <p><input class="button-primary" type="button" id="regenerate-images" value="<?php _e( 'Regenerate’, ‘imagemagick-engine’ ); ?>" /></p> <p class="description"><?php _e( '(this can take a long time)', ‘imagemagick-engine’ ); ?></p> </div> </div> </div> </div> </div> <div id="post-body"> <div id="post-body-content"> <div id="ime-settings" class="postbox"> <h3 class="hndle"><span><?php _e( 'Settings’, ‘imagemagick-engine’ ); ?></span></h3> <div class="inside"> <table class="form-table"> <tr> <th scope="row" valign="top"><?php _e( 'Enable enhanced image engine’, ‘imagemagick-engine’ ); ?>:</th> <td> <input type="checkbox" id="enabled" name="enabled" value="1" <?php echo $enabled ? ' CHECKED ' : '’; echo $any_valid ? ‘’ : ' disabled=disabled '; ?> /> </td> </tr> <tr> <th scope="row" valign="top"><?php _e( 'Image engine’, ‘imagemagick-engine’ ); ?>:</th> <td> <select id="ime-select-mode" name="mode"> <?php foreach ( $modes_valid as $m => $valid ) { echo ‘<option value="’ . $m . '"’; if ( $m == $current_mode ) { echo ' selected=selected '; } echo ‘>’ . $ime_available_modes[ $m ] . '</option>’; } ?> </select> </td> </tr> <tr id="ime-row-php"> <th scope="row" valign="top"><?php _e( 'Imagick PHP module’, ‘imagemagick-engine’ ); ?>:</th> <td> <img src="<?php echo ime_option_status_icon( $modes_valid[‘php’] ); ?>" /> <?php echo $modes_valid[‘php’] ? __( 'Imagick PHP module found’, ‘imagemagick-engine’ ) : __( 'Imagick PHP module not found’, ‘imagemagick-engine’ ); ?> </td> </tr> <tr id="ime-row-cli"> <th scope="row" valign="top"><?php _e( 'ImageMagick path’, ‘imagemagick-engine’ ); ?>:</th> <td> <img id="cli_path_yes" class="cli_path_icon" src="<?php echo ime_option_status_icon( true ); ?>" alt="" <?php ime_option_display( $cli_path_ok ); ?> /> <img id="cli_path_no" class="cli_path_icon" src="<?php echo ime_option_status_icon( false ); ?>" alt="<?php _e( 'Command not found’, ‘qp-qie’ ); ?>" <?php ime_option_display( ! $cli_path_ok ); ?> /> <img id="cli_path_progress" src="<?php echo ime_option_admin_images_url(); ?>wpspin_light.gif" alt="<?php _e( 'Testing command…’, ‘qp-qie’ ); ?>" <?php ime_option_display( false ); ?> /> <input id="cli_path" type="text" name="cli_path" size="<?php echo max( 30, strlen( $cli_path ) + 5 ); ?>" value="<?php echo $cli_path; ?>" /> <input type="button" name="ime_cli_path_test" id="ime_cli_path_test" value="<?php _e( 'Test path’, ‘imagemagick-engine’ ); ?>" class="button-secondary" /> <span <?php ime_option_display( $cli_path_ok ); ?>><br><br><?php echo ime_get_option( ‘imagemagick_version’ )[0]; ?></span> </td> </tr> <tr> <th scope="row" valign="top"><?php _e( 'ImageMagick quality’, ‘imagemagick-engine’ ); ?>:</th> <td> <p><input id="quality-quality" type="text" name="quality-quality" size="3" value="<?php echo ( ( isset( $quality[‘quality’] ) && $quality[‘quality’] > 0 ) ? $quality[‘quality’] : ‘’ ); ?>" /> <?php _e( 'Optimize for quality’, ‘imagemagick-engine’ ); ?></p> <p><input id="quality-size" type="text" name="quality-size" size="3" value="<?php echo ( ( isset( $quality[‘size’] ) && $quality[‘size’] > 0 ) ? $quality[‘size’] : ‘’ ); ?>" /> <?php _e( 'Optimize for size’, ‘imagemagick-engine’ ); ?></p> <p class="ime-description"><?php _e( 'Set to 0-100. Higher value gives better image quality but larger file size. Leave empty for default value, computed dynamically.’, ‘imagemagick-engine’ ); ?></p> </td> </tr> <tr> <th scope="row" valign="top"><?php _e( 'Image interlace?’, ‘imagemagick-engine’ ); ?>:</th> <td> <input type="checkbox" id="interlace" name="interlace" value="1" <?php echo $interlace ? ' CHECKED ' : '’; ?> /> <p class="ime-description"><?php _e( ‘Adds interlace option to ImageMagick when images are processed.’, ‘imagemagick-engine’ ); ?></p> </td> </tr> <tr> <td colspan="2" class="ime-handle-table-wrapper"> <table border=’0’ class="ime-handle-table" id="ime-handle-table"> <tr> <th scope="row" class="ime-headline" valign="top"><?php _e( 'Optimize for’, ‘imagemagick-engine’ ); ?></th> <td class="ime-headline ime-fixed-width"><?php _e( 'Quality’, ‘imagemagick-engine’ ); ?></td> <td class="ime-headline ime-fixed-width"><?php _e( 'Size’, ‘imagemagick-engine’ ); ?></td> <td class="ime-headline"><?php _e( 'None (use WP instead)', ‘imagemagick-engine’ ); ?></td> </tr> <?php foreach ( $sizes as $s => $name ) { // fixup for old (pre 1.5.0) options if ( ! isset( $handle_sizes[ $s ] ) || ! $handle_sizes[ $s ] ) { $handle_sizes[ $s ] = 'skip’; } elseif ( $handle_sizes[ $s ] === true ) { $handle_sizes[ $s ] = 'quality’; } ?> <tr> <th scope="row" valign="top"><?php echo $name; ?></th> <td class="ime-fixed-width"> <input type="radio" name="handle-mode-<?php echo $s; ?>" value="quality" <?php checked( 'quality’, $handle_sizes[ $s ] ); ?> /> </td> <td class="ime-fixed-width"> <input type="radio" name="handle-mode-<?php echo $s; ?>" value="size" <?php checked( 'size’, $handle_sizes[ $s ] ); ?> /> </td> <td> <input type="radio" name="handle-mode-<?php echo $s; ?>" value="skip" <?php checked( 'skip’, $handle_sizes[ $s ] ); ?> /> </td> </tr> <?php } ?> </table> </td> </tr> <tr> <td> <input class="button-primary" type="submit" name="update_settings" value="<?php _e( 'Save Changes’, ‘imagemagick-engine’ ); ?>" /> </td> </tr> </table> </div> </div> </div> </div> </form> </div> <?php } ?>