From 92af02f901fd26da9c7d97871e2fff2834ee711d Mon Sep 17 00:00:00 2001 From: Git I Hate You Date: Wed, 11 Apr 2018 13:20:25 +0200 Subject: [PATCH 1/6] Escape % characters for prepared SQL queries When a query is "prepared" (aka `vsprintf()`), escape the % characters that are parts of LIKE clauses. Changed 2 methods signature to prevent having 4 parameters. --- inc/classes/class-imagify-admin-ajax-post.php | 4 +- inc/classes/class-imagify-db.php | 65 ++++++++++++++----- inc/functions/attachments.php | 5 +- 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/inc/classes/class-imagify-admin-ajax-post.php b/inc/classes/class-imagify-admin-ajax-post.php index f8e881ae4..eb1f75a9f 100755 --- a/inc/classes/class-imagify-admin-ajax-post.php +++ b/inc/classes/class-imagify-admin-ajax-post.php @@ -688,7 +688,9 @@ public function imagify_get_unoptimized_attachment_ids_callback() { $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(); + $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause( array( + 'prepared' => true, + ) ); $ids = $wpdb->get_col( $wpdb->prepare( // WPCS: unprepared SQL ok. " SELECT p.ID diff --git a/inc/classes/class-imagify-db.php b/inc/classes/class-imagify-db.php index ad1680a5c..8ad3f9762 100644 --- a/inc/classes/class-imagify-db.php +++ b/inc/classes/class-imagify-db.php @@ -194,37 +194,55 @@ public static function get_required_wp_metadata_join_clause( $id_field = 'p.ID', * It returns an empty string if the database has no attachments without the required metadada. * * @since 1.7 + * @since 1.7.1.2 Use a single $arg parameter instead of 3. New $prepared parameter. * @access public * @author Grégory Viguier * - * @param string $aliases The aliases to use for the meta values. - * @param bool $matching Set to false to get a query to fetch invalid metas. - * @param bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway. - * @return string A query. + * @param array $args { + * Optional. An array of arguments. + * + * string $aliases The aliases to use for the meta values. + * bool $matching Set to false to get a query to fetch invalid metas. + * bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway. + * bool $prepared Set to true if the query will be prepared with using $wpdb->prepare(). + * }. + * @return string A query. */ - public static function get_required_wp_metadata_where_clause( $aliases = array(), $matching = true, $test = true ) { + public static function get_required_wp_metadata_where_clause( $args = array() ) { static $query = array(); + $args = imagify_merge_intersect( $args, array( + 'aliases' => array(), + 'matching' => true, + 'test' => true, + 'prepared' => false, + ) ); + + list( $aliases, $matching, $test, $prepared ) = array_values( $args ); + if ( $test && ! imagify_has_attachments_without_required_metadata() ) { return ''; } - if ( is_string( $aliases ) ) { + if ( $aliases && is_string( $aliases ) ) { $aliases = array( '_wp_attached_file' => $aliases, ); + } elseif ( ! is_array( $aliases ) ) { + $aliases = array(); } $aliases = imagify_merge_intersect( $aliases, self::get_required_wp_metadata_aliases() ); $key = implode( '|', $aliases ) . '|' . (int) $matching; if ( isset( $query[ $key ] ) ) { - return $query[ $key ]; + return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ]; } + unset( $args['prepared'] ); $alias_1 = $aliases['_wp_attached_file']; $alias_2 = $aliases['_wp_attachment_metadata']; - $extensions = self::get_extensions_where_clause( $alias_1, $matching, $test ); + $extensions = self::get_extensions_where_clause( $args ); if ( $matching ) { $query[ $key ] = "AND $alias_1.meta_value NOT LIKE '%://%' AND $alias_1.meta_value NOT LIKE '_:\\\\\%' $extensions"; @@ -232,7 +250,7 @@ public static function get_required_wp_metadata_where_clause( $aliases = array() $query[ $key ] = "AND ( $alias_2.meta_value IS NULL OR $alias_1.meta_value IS NULL OR $alias_1.meta_value LIKE '%://%' OR $alias_1.meta_value LIKE '_:\\\\\%' $extensions )"; } - return $query[ $key ]; + return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ]; } /** @@ -240,18 +258,33 @@ public static function get_required_wp_metadata_where_clause( $aliases = array() * It returns an empty string if the database has no attachments without the required metadada. * * @since 1.7 + * @since 1.7.1.2 Use a single $arg parameter instead of 3. New $prepared parameter. * @access public * @author Grégory Viguier * - * @param string $alias The alias to use for the meta value. - * @param bool $matching Set to false to get a query to fetch metas NOT matching the file extensions. - * @param bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway. - * @return string A query. + * @param array $args { + * Optional. An array of arguments. + * + * string $alias The alias to use for the meta value. + * bool $matching Set to false to get a query to fetch metas NOT matching the file extensions. + * bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway. + * bool $prepared Set to true if the query will be prepared with using $wpdb->prepare(). + * }. + * @return string A query. */ - public static function get_extensions_where_clause( $alias = false, $matching = true, $test = true ) { + public static function get_extensions_where_clause( $args = false ) { static $extensions; static $query = array(); + $args = imagify_merge_intersect( $args, array( + 'alias' => array(), + 'matching' => true, + 'test' => true, + 'prepared' => false, + ) ); + + list( $alias, $matching, $test, $prepared ) = array_values( $args ); + if ( $test && ! imagify_has_attachments_without_required_metadata() ) { return ''; } @@ -270,7 +303,7 @@ public static function get_extensions_where_clause( $alias = false, $matching = $key = $alias . '|' . (int) $matching; if ( isset( $query[ $key ] ) ) { - return $query[ $key ]; + return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ]; } if ( $matching ) { @@ -279,7 +312,7 @@ public static function get_extensions_where_clause( $alias = false, $matching = $query[ $key ] = "OR ( LOWER( $alias.meta_value ) NOT LIKE '%." . implode( "' AND LOWER( $alias.meta_value ) NOT LIKE '%.", $extensions ) . "' )"; } - return $query[ $key ]; + return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ]; } /** diff --git a/inc/functions/attachments.php b/inc/functions/attachments.php index 7a253b476..d98f3888d 100755 --- a/inc/functions/attachments.php +++ b/inc/functions/attachments.php @@ -124,7 +124,10 @@ function imagify_has_attachments_without_required_metadata() { $mime_types = Imagify_DB::get_mime_types(); $statuses = Imagify_DB::get_post_statuses(); $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause( 'p.ID', false, false ); - $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause( array(), false, false ); + $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause( array( + 'matching' => false, + 'test' => false, + ) ); $has = (bool) $wpdb->get_var( // WPCS: unprepared SQL ok. " SELECT p.ID From 867cb4718568e1269c41da856db8ba42be1dabf4 Mon Sep 17 00:00:00 2001 From: Git I Hate You Date: Wed, 11 Apr 2018 14:16:52 +0200 Subject: [PATCH 2/6] Fixes fatal error on Imagify update In `imagify_sync_theme_plugin_files_on_update()`, the class `Imagify` from the new plugin was loaded, calling new code while old code is already loaded. The solution is to patch `Imagify` for the updates from 1.7, and delay the job in `admin_init` instead of `upgrader_post_install`, using a transient. --- inc/admin/custom-folders.php | 89 ++++++++++++++++++++++------------- inc/classes/class-imagify.php | 17 ++++++- 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/inc/admin/custom-folders.php b/inc/admin/custom-folders.php index 0ad55e570..d422793ed 100644 --- a/inc/admin/custom-folders.php +++ b/inc/admin/custom-folders.php @@ -3,14 +3,15 @@ add_filter( 'upgrader_post_install', 'imagify_sync_theme_plugin_files_on_update', IMAGIFY_INT_MAX, 3 ); /** - * Sync files after a theme or plugin has been updated. + * Sync files right after a theme or plugin has been updated. * * @since 1.7 * @author Grégory Viguier * - * @param bool $response Installation response. - * @param array $hook_extra Extra arguments passed to hooked filters. - * @param array $result Installation result data. + * @param bool $response Installation response. + * @param array $hook_extra Extra arguments passed to hooked filters. + * @param array $result Installation result data. + * @return bool */ function imagify_sync_theme_plugin_files_on_update( $response, $hook_extra, $result ) { global $wpdb; @@ -19,46 +20,66 @@ function imagify_sync_theme_plugin_files_on_update( $response, $hook_extra, $res return $response; } - $folders_db = Imagify_Folders_DB::get_instance(); - $files_db = Imagify_Files_DB::get_instance(); + $folders_to_sync = get_site_transient( 'imagify_themes_plugins_to_sync' ); + $folders_to_sync = is_array( $folders_to_sync ) ? $folders_to_sync : array(); - if ( ! $folders_db->can_operate() || ! $files_db->can_operate() ) { - return; - } + $folders_to_sync[] = $result['destination']; - if ( ! Imagify_Requirements::is_api_key_valid() ) { - return; - } + set_site_transient( 'imagify_themes_plugins_to_sync', $folders_to_sync, DAY_IN_SECONDS ); + + return $response; +} + +add_action( 'admin_init', 'imagify_sync_theme_plugin_files_after_update' ); +/** + * Sync files after some themes or plugins have been updated. + * + * @since 1.7.1.2 + * @author Grégory Viguier + */ +function imagify_sync_theme_plugin_files_after_update() { + global $wpdb; - if ( Imagify_Requirements::is_over_quota() ) { + $folders_to_sync = get_site_transient( 'imagify_themes_plugins_to_sync' ); + + if ( ! $folders_to_sync || ! is_array( $folders_to_sync ) ) { return; } - $folder_path = trailingslashit( $result['destination'] ); + delete_site_transient( 'imagify_themes_plugins_to_sync' ); - if ( Imagify_Files_Scan::is_path_forbidden( $folder_path ) ) { - // This theme or plugin must not be optimized. - return $response; + $folders_db = Imagify_Folders_DB::get_instance(); + $files_db = Imagify_Files_DB::get_instance(); + + if ( ! $folders_db->can_operate() || ! $files_db->can_operate() ) { + return; } - // Get the related folder. - $placeholder = Imagify_Files_Scan::add_placeholder( $folder_path ); - $folder = Imagify_Folders_DB::get_instance()->get_in( 'path', $placeholder ); + foreach ( $folders_to_sync as $folder_path ) { + $folder_path = trailingslashit( $folder_path ); - if ( ! $folder ) { - // This theme or plugin is not in the database. - return $response; - } + if ( Imagify_Files_Scan::is_path_forbidden( $folder_path ) ) { + // This theme or plugin must not be optimized. + continue; + } - // Sync the folder files. - Imagify_Custom_Folders::synchronize_files_from_folders( array( - $folder['folder_id'] => array( - 'folder_id' => $folder['folder_id'], - 'path' => $placeholder, - 'active' => $folder['active'], - 'folder_path' => $folder_path, - ), - ) ); + // Get the related folder. + $placeholder = Imagify_Files_Scan::add_placeholder( $folder_path ); + $folder = Imagify_Folders_DB::get_instance()->get_in( 'path', $placeholder ); - return $response; + if ( ! $folder ) { + // This theme or plugin is not in the database. + continue; + } + + // Sync the folder files. + Imagify_Custom_Folders::synchronize_files_from_folders( array( + $folder['folder_id'] => array( + 'folder_id' => $folder['folder_id'], + 'path' => $placeholder, + 'active' => $folder['active'], + 'folder_path' => $folder_path, + ), + ) ); + } } diff --git a/inc/classes/class-imagify.php b/inc/classes/class-imagify.php index f912ac224..140f95b8c 100644 --- a/inc/classes/class-imagify.php +++ b/inc/classes/class-imagify.php @@ -11,7 +11,7 @@ class Imagify extends Imagify_Deprecated { * * @var string */ - const VERSION = '1.1.1'; + const VERSION = '1.1.2'; /** * The Imagify API endpoint. * @@ -63,6 +63,11 @@ class Imagify extends Imagify_Deprecated { * The constructor. */ protected function __construct() { + if ( ! class_exists( 'Imagify_Filesystem' ) ) { + // Dirty patch used when updating from 1.7. + include_once IMAGIFY_CLASSES_PATH . 'class-imagify-filesystem.php'; + } + $this->api_key = get_imagify_option( 'api_key' ); $this->filesystem = Imagify_Filesystem::get_instance(); @@ -98,13 +103,21 @@ public static function get_instance() { */ public function get_user() { static $user; + global $wp_current_filter; + + if ( isset( $user ) ) { + return $user; + } - if ( ! isset( $user ) ) { + if ( ! in_array( 'upgrader_post_install', (array) $wp_current_filter, true ) ) { $this->headers = $this->all_headers; $user = $this->http_call( 'users/me/', array( 'timeout' => 10, ) ); + } else { + // Dirty patch used when updating from 1.7. + $user = new WP_Error(); } return $user; From 224622c148fe074ab3914e217635d416668393f5 Mon Sep 17 00:00:00 2001 From: Git I Hate You Date: Wed, 11 Apr 2018 16:20:33 +0200 Subject: [PATCH 3/6] Reset opcache after Imagify is updated --- inc/admin/upgrader.php | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/inc/admin/upgrader.php b/inc/admin/upgrader.php index d316bdef7..dd093d9ba 100755 --- a/inc/admin/upgrader.php +++ b/inc/admin/upgrader.php @@ -270,3 +270,52 @@ function _imagify_new_upgrade( $network_version, $site_version ) { delete_option( $wpdb->prefix . 'ngg_imagify_data_db_version' ); } } + +add_filter( 'upgrader_post_install', 'imagify_maybe_reset_opcache', 20, 3 ); +/** + * Maybe reset opcache after Imagify update. + * + * @since 1.7.1.2 + * @author Grégory Viguier + * + * @param bool $response Installation response. + * @param array $hook_extra Extra arguments passed to hooked filters. + * @param array $result Installation result data. + * @return bool + */ +function imagify_maybe_reset_opcache( $response, $hook_extra, $result ) { + static $imagify_path; + static $can_reset; + + if ( empty( $hook_extra['plugin'] ) || empty( $result['destination'] ) ) { + return $response; + } + + if ( ! isset( $imagify_path ) ) { + $imagify_path = trailingslashit( wp_normalize_path( IMAGIFY_PATH ) ); + } + + $destination = trailingslashit( wp_normalize_path( $result['destination'] ) ); + + if ( $destination !== $imagify_path ) { + return $response; + } + + if ( ! isset( $can_reset ) ) { + $can_reset = true; + + if ( ! function_exists( 'opcache_reset' ) ) { + $can_reset = false; + } + + if ( ! empty( ini_get( 'opcache.restrict_api' ) ) && strpos( __FILE__, ini_get( 'opcache.restrict_api' ) ) !== 0 ) { + $can_reset = false; + } + } + + if ( $can_reset ) { + opcache_reset(); + } + + return $response; +} From 2aa5dffa9064c0e0cebfbc1140b6b87d91622f67 Mon Sep 17 00:00:00 2001 From: Git I Hate You Date: Wed, 11 Apr 2018 16:33:53 +0200 Subject: [PATCH 4/6] Hotfix: bump to 1.7.1.2 + changelog. --- imagify.php | 4 ++-- package.json | 2 +- readme.txt | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/imagify.php b/imagify.php index 34551e2f5..37dc0bf96 100644 --- a/imagify.php +++ b/imagify.php @@ -3,7 +3,7 @@ * Plugin Name: Imagify * Plugin URI: https://wordpress.org/plugins/imagify/ * Description: Dramaticaly reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth using Imagify, the new most advanced image optimization tool. - * Version: 1.7.1 + * Version: 1.7.1.2 * Author: WP Media * Author URI: https://wp-media.me/ * Licence: GPLv2 @@ -17,7 +17,7 @@ defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' ); // Imagify defines. -define( 'IMAGIFY_VERSION' , '1.7.1' ); +define( 'IMAGIFY_VERSION' , '1.7.1.2' ); define( 'IMAGIFY_SLUG' , 'imagify' ); define( 'IMAGIFY_FILE' , __FILE__ ); define( 'IMAGIFY_PATH' , realpath( plugin_dir_path( IMAGIFY_FILE ) ) . '/' ); diff --git a/package.json b/package.json index 216934c70..c6cc5882a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "imagify", "description": "Imagify Image Optimizer. Dramatically reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth.", - "version": "1.7.1", + "version": "1.7.1.2", "homepage": "https://wordpress.org/plugins/imagify/", "license": "GPL-2.0", "private": true, diff --git a/readme.txt b/readme.txt index 374879806..27e5a20c1 100755 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: wp_media, GregLone Tags: compress image, images, performance, optimization, photos, upload, resize, gif, png, jpg, reduce image size, retina Requires at least: 3.7.0 Tested up to: 4.9.5 -Stable tag: 1.7.1 +Stable tag: 1.7.1.2 Dramatically reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth. @@ -138,6 +138,11 @@ When the plugin is disabled, your existing images remain optimized. Backups of t 4. Other Media Page == Changelog == += 1.7.1.2 - 2018/04/12 = +* Improvement: reset OPcache after Imagify being updated. +* Bug Fix: a fatal error upon Imagify update. +* Bug Fix: a case where the bulk optimizer wrongly says that all images are already optimized. + = 1.7.1 - 2018/04/10 = * New: compatibility with Regenerate Thumbnails (v3) plugin. * Improvement: better performance of the bulk optimization on sites with huge media library. This is done by not updating the statistics display periodically, but only when the job is done. From 8b1e25b095d734f9548ca07b394737e9b61d02f6 Mon Sep 17 00:00:00 2001 From: Git I Hate You Date: Thu, 12 Apr 2018 00:13:15 +0200 Subject: [PATCH 5/6] Hotfix: delete the new transient on uninstall. --- uninstall.php | 1 + 1 file changed, 1 insertion(+) diff --git a/uninstall.php b/uninstall.php index 7589b90c8..83c8af625 100755 --- a/uninstall.php +++ b/uninstall.php @@ -18,6 +18,7 @@ // Delete all transients. delete_site_transient( 'imagify_check_licence_1' ); delete_site_transient( 'imagify_user' ); +delete_site_transient( 'imagify_themes_plugins_to_sync' ); delete_transient( 'imagify_bulk_optimization_level' ); delete_transient( 'imagify_bulk_optimization_infos' ); delete_transient( 'imagify_large_library' ); From 581381dda285a3b35d1f5edbc1f6515744f44e10 Mon Sep 17 00:00:00 2001 From: Git I Hate You Date: Thu, 12 Apr 2018 01:30:49 +0200 Subject: [PATCH 6/6] Change the hook used to reset opcache This one is safer because it is triggered after the maintenance mode is disabled. Also, this is the same hook that WordPress will use when this functionnality is released in core. --- inc/admin/upgrader.php | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/inc/admin/upgrader.php b/inc/admin/upgrader.php index dd093d9ba..0e0742470 100755 --- a/inc/admin/upgrader.php +++ b/inc/admin/upgrader.php @@ -271,34 +271,43 @@ function _imagify_new_upgrade( $network_version, $site_version ) { } } -add_filter( 'upgrader_post_install', 'imagify_maybe_reset_opcache', 20, 3 ); +add_action( 'upgrader_process_complete', 'imagify_maybe_reset_opcache', 20, 2 ); /** * Maybe reset opcache after Imagify update. * * @since 1.7.1.2 * @author Grégory Viguier * - * @param bool $response Installation response. - * @param array $hook_extra Extra arguments passed to hooked filters. - * @param array $result Installation result data. - * @return bool + * @param object $wp_upgrader Plugin_Upgrader instance. + * @param array $hook_extra { + * Array of bulk item update data. + * + * @type string $action Type of action. Default 'update'. + * @type string $type Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'. + * @type bool $bulk Whether the update process is a bulk update. Default true. + * @type array $plugins Array of the basename paths of the plugins' main files. + * } */ -function imagify_maybe_reset_opcache( $response, $hook_extra, $result ) { +function imagify_maybe_reset_opcache( $wp_upgrader, $hook_extra ) { static $imagify_path; static $can_reset; - if ( empty( $hook_extra['plugin'] ) || empty( $result['destination'] ) ) { - return $response; + if ( ! isset( $hook_extra['action'], $hook_extra['type'], $hook_extra['plugins'] ) ) { + return; } - if ( ! isset( $imagify_path ) ) { - $imagify_path = trailingslashit( wp_normalize_path( IMAGIFY_PATH ) ); + if ( 'update' !== $hook_extra['action'] || 'plugin' !== $hook_extra['type'] || ! is_array( $hook_extra['plugins'] ) ) { + return; } - $destination = trailingslashit( wp_normalize_path( $result['destination'] ) ); + $plugins = array_flip( $hook_extra['plugins'] ); - if ( $destination !== $imagify_path ) { - return $response; + if ( ! isset( $imagify_path ) ) { + $imagify_path = plugin_basename( IMAGIFY_FILE ); + } + + if ( ! isset( $plugins[ $imagify_path ] ) ) { + return; } if ( ! isset( $can_reset ) ) { @@ -316,6 +325,4 @@ function imagify_maybe_reset_opcache( $response, $hook_extra, $result ) { if ( $can_reset ) { opcache_reset(); } - - return $response; }