: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Directory Smush: Dir class
* @package Smush\Core\Modules
* @author Umesh Kumar <umesh@incsub.com>
* @copyright (c) 2016, Incsub (http://incsub.com)
namespace Smush\Core\Modules;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Smush\Core\Installer;
if ( ! defined( 'WPINC' ) ) {
class Dir extends Abstract_Module {
* Contains a list of optimised images.
public $optimised_images;
* Flag to check if dir smush table exist.
public static $table_exist;
* Total Stats for the image optimisation.
* Handle Ajax request 'smush_get_directory_list'.
* This needs to be before self::should_continue so that the request from network admin is processed.
if ( ! $this->scanner ) {
$this->scanner = new Helpers\DScanner();
add_action( 'wp_ajax_smush_get_directory_list', array( $this, 'directory_list' ) );
// Scan the given directory path for the list of images.
add_action( 'wp_ajax_image_list', array( $this, 'image_list' ) );
add_action( 'wp_ajax_directory_smush_start', array( $this, 'directory_smush_start' ) );
add_action( 'wp_ajax_directory_smush_check_step', array( $this, 'directory_smush_check_step' ) );
add_action( 'wp_ajax_directory_smush_finish', array( $this, 'directory_smush_finish' ) );
add_action( 'wp_ajax_directory_smush_cancel', array( $this, 'directory_smush_cancel' ) );
add_action( 'current_screen', array( $this, 'initialize' ), 10 );
* To get access to get_current_screen(), we need to move this under the current_screen action.
public function initialize() {
if ( function_exists( 'get_current_screen' ) ) {
$current_screen = get_current_screen();
$current_page = ! empty( $current_screen ) ? $current_screen->base : '';
if ( false === strpos( $current_page, 'page_smush-directory' ) ) {
if ( ! self::should_continue() ) {
// Remove directory smush from tabs if not required.
add_filter( 'smush_setting_tabs', array( $this, 'remove_directory_tab' ) );
if ( ! $this->scanner ) {
$this->scanner = new Helpers\DScanner();
if ( ! $this->scanner->is_scanning() ) {
$this->scanner->reset_scan();
// Check and show missing directory smush table error.
add_action( 'wp_smush_header_notices', array( $this, 'show_table_error' ) );
// Check directory smush table after screen is set.
add_action( 'current_screen', array( $this, 'check_table' ) );
// Check to see if the scanner should be running.
add_action( 'admin_footer', array( $this, 'check_scan' ) );
* Do not display Directory smush for subsites.
* @return bool True/False, whether to display the Directory smush or not
public static function should_continue() {
if ( Settings::is_ajax_network_admin() ) {
// Do not show directory smush, if not main site in a network.
if ( is_multisite() && ( ! is_main_site() || ! is_network_admin() ) ) {
* Run the scanner on page refresh (if it's running).
public function check_scan() {
if ( $this->scanner->is_scanning() ) {
jQuery( document ).ready( function() {
jQuery('#wp-smush-progress-dialog').show();
window.WP_Smush.directory.scanner.scan();
* Directory Smush: Start smush.
public function directory_smush_start() {
check_ajax_referer( 'wp-smush-ajax' );
$capability = is_multisite() ? 'manage_network' : 'manage_options';
if ( ! Helper::is_user_allowed( $capability ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
$this->scanner->init_scan();
do_action('wp_smush_directory_smush_start');
* Directory Smush: Smush step.
public function directory_smush_check_step() {
check_ajax_referer( 'wp-smush-ajax' );
$capability = is_multisite() ? 'manage_network' : 'manage_options';
if ( ! Helper::is_user_allowed( $capability ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
$urls = $this->get_scanned_images();
$current_step = isset( $_POST['step'] ) ? absint( $_POST['step'] ) : 0;
$this->scanner->update_current_step( $current_step );
if ( isset( $urls[ $current_step ] ) ) {
$this->optimise_image( (int) $urls[ $current_step ]['id'] );
* Directory Smush: Finish smush.
public function directory_smush_finish() {
check_ajax_referer( 'wp-smush-ajax' );
$capability = is_multisite() ? 'manage_network' : 'manage_options';
if ( ! Helper::is_user_allowed( $capability ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
$items = isset( $_POST['items'] ) ? absint( $_POST['items'] ) : 0;
$failed = isset( $_POST['failed'] ) ? absint( $_POST['failed'] ) : 0;
$skipped = isset( $_POST['skipped'] ) ? absint( $_POST['skipped'] ) : 0;
// If any images failed to smush, store count.
set_transient( 'wp-smush-dir-scan-failed-items', $failed, 60 * 5 ); // 5 minutes max.
set_transient( 'wp-smush-dir-scan-skipped-items', $skipped, 60 * 5 ); // 5 minutes max.
// Store optimized items count.
set_transient( 'wp-smush-show-dir-scan-notice', $items, 60 * 5 ); // 5 minutes max.
$this->scanner->reset_scan();
* Directory Smush: Cancel smush.
public function directory_smush_cancel() {
check_ajax_referer( 'wp-smush-ajax' );
$capability = is_multisite() ? 'manage_network' : 'manage_options';
if ( ! Helper::is_user_allowed( $capability ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
$this->scanner->reset_scan();
* Handles the ajax request for image optimisation in a folder
* @param int $id Image ID.
private function optimise_image( $id ) {
$error_msg = esc_html__( 'Incorrect image id', 'wp-smushit' );
wp_send_json_error( $error_msg );
// Check smush limit for free users.
if ( ! WP_Smush::is_pro() ) {
// Free version bulk smush, check the transient counter value.
$should_continue = Core::check_bulk_limit( false, 'dir_sent_count' );
// Send a error for the limit.
if ( ! $should_continue ) {
'error' => 'dir_smush_limit_exceeded',
$scanned_images = $this->get_unsmushed_images();
$image = $this->get_image( $id, '', $scanned_images );
wp_send_json_success( array( 'skipped' => true ) );
if ( false !== stripos( $path, 'phar://' ) ) {
'error' => esc_html__( 'Potential Phar PHP Object Injection detected', 'wp-smushit' ),
// We have the image path, optimise.
$results = WP_Smush::get_instance()->core()->mod->smush->do_smushit( $path );
if ( is_wp_error( $results ) ) {
$error_msg = $results->get_error_message();
} elseif ( empty( $results['data'] ) ) {
// If there are no stats.
$error_msg = esc_html__( "Image couldn't be optimized", 'wp-smushit' );
if ( ! empty( $error_msg ) ) {
// Store the error in DB. All good, Update the stats.
"UPDATE {$wpdb->base_prefix}smush_dir_images SET error=%s WHERE id=%d LIMIT 1",
); // Db call ok; no-cache ok.
if ( ! $this->settings ) {
$this->settings = Settings::get_instance();
// All good, Update the stats.
"UPDATE {$wpdb->base_prefix}smush_dir_images SET error=NULL, image_size=%d, file_time=%d, lossy=%d, meta=%d WHERE id=%d LIMIT 1",
$results['data']->after_size,
filectime( $path ), // Get file time.
$this->settings->get( 'lossy' ),
$this->settings->get( 'strip_exif' ),
); // Db call ok; no-cache ok.
// Update bulk limit transient.
Core::update_smush_count( 'dir_sent_count' );
* Create the Smush image table to store the paths of scanned images, and stats
public function create_table() {
// If table is already created, returns.
if ( self::table_exist() ) {
$charset_collate = $wpdb->get_charset_collate();
* Table: wp_smush_dir_images
* id -> Auto Increment ID
* path -> Absolute path to the image file
* resize -> Whether the image was resized or not
* lossy -> Whether the image was super-smushed/lossy or not
* image_size -> Current image size post optimisation
* orig_size -> Original image size before optimisation
* file_time -> Unix time for the file creation, to match it against the current creation time,
* in order to confirm if it is optimised or not
* last_scan -> Timestamp, Get images form last scan by latest timestamp
* are from latest scan only and not the whole list from db
* meta -> For any future use
$sql = "CREATE TABLE IF NOT EXISTS {$wpdb->base_prefix}smush_dir_images (
id mediumint(9) UNSIGNED NOT NULL AUTO_INCREMENT,
error varchar(55) DEFAULT NULL,
image_size int(10) unsigned,
orig_size int(10) unsigned,
file_time int(10) unsigned,
last_scan timestamp DEFAULT '0000-00-00 00:00:00',
UNIQUE KEY path_hash (path_hash),
KEY image_size (image_size)
// Include the upgrade library to initialize a table.
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
* PHP 8.1 trigger an error when calling query "DESCRIBE {$table};" if the table doesn't exists.
* Since we only create table when it doesn't exists, so don't need to use dbDelta for this case.
self::$table_exist = self::table_exist( true );
* Get the image ids and path for last scanned images
* @return array Array of last scanned images containing image id and path
public function get_scanned_images() {
$results = $wpdb->get_results( "SELECT id, path, orig_size FROM {$wpdb->base_prefix}smush_dir_images WHERE last_scan = (SELECT MAX(last_scan) FROM {$wpdb->base_prefix}smush_dir_images ) GROUP BY id ORDER BY id", ARRAY_A ); // Db call ok; no-cache ok.
if ( is_wp_error( $results ) ) {
Helper::logger()->dir()->error( sprintf( 'Query error: %s', $results->get_error_message() ) );
* Get only images that need compressing.
* @return array Array of images that require compression.
public function get_unsmushed_images() {
$condition = 'image_size IS NULL';
if ( $this->settings->get( 'lossy' ) ) {
$condition .= ' OR lossy <> 1';
if ( $this->settings->get( 'strip_exif' ) ) {
$condition .= ' OR meta <> 1';
$results = $wpdb->get_results( "SELECT id, path, orig_size FROM {$wpdb->base_prefix}smush_dir_images WHERE {$condition} && last_scan = (SELECT MAX(last_scan) FROM {$wpdb->base_prefix}smush_dir_images ) GROUP BY id ORDER BY id", ARRAY_A ); // Db call ok; no-cache ok.
if ( is_wp_error( $results ) ) {
Helper::logger()->dir()->error( sprintf( 'Query error: %s', $results->get_error_message() ) );
* Get the paths and errors from last scan.
* @return array Array of last scanned images
public function get_image_errors() {
return $wpdb->get_results(
FROM {$wpdb->base_prefix}smush_dir_images
AND last_scan = ( SELECT MAX(last_scan) FROM {$wpdb->base_prefix}smush_dir_images )
); // Db call ok; no-cache ok.