fix
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс для работы с резервным копированием изображений
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIOP_Backup extends WIO_Backup {
|
||||
|
||||
const CF_BACKUP_DIR_NAME = 'custom-folders';
|
||||
const NEXTGEN_BACKUP_DIR_NAME = 'nextgen-gallery';
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @access protected
|
||||
* @var object
|
||||
*/
|
||||
protected static $_instance;
|
||||
|
||||
/**
|
||||
* Получает путь к папке с резервными копиями
|
||||
*
|
||||
* @param array $gallery_meta метаданные аттачмента
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNextgenBackupDir( $gallery_meta ) {
|
||||
$backup_dir = $this->getBackupDir();
|
||||
$backup_dir .= self::NEXTGEN_BACKUP_DIR_NAME . '/' . $gallery_meta->gid;
|
||||
|
||||
if ( ! is_dir( $backup_dir ) ) {
|
||||
$backup_dir = $this->mkdir( $backup_dir );
|
||||
|
||||
if ( is_wp_error( $backup_dir ) ) {
|
||||
return $backup_dir;
|
||||
}
|
||||
}
|
||||
|
||||
return trailingslashit( $backup_dir );
|
||||
}
|
||||
|
||||
/**
|
||||
* Делаем резервную копию NextGEN
|
||||
*
|
||||
* @param WRIO_Image_Nextgen $nextgen_image аттачмент
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function backupNextgen( $nextgen_image ) {
|
||||
$backup_origin_images = WRIO_Plugin::app()->getPopulateOption( 'backup_origin_images', false );
|
||||
|
||||
if ( ! $backup_origin_images ) {
|
||||
return false; // если бекап не требуется
|
||||
}
|
||||
|
||||
$original_file = $nextgen_image->get( 'path' );
|
||||
$original_thumbnail_file = $nextgen_image->get( 'thumbnail_path' );
|
||||
$backup_dir = $this->getNextgenBackupDir( $nextgen_image->get( 'gallery_meta' ) );
|
||||
|
||||
if ( is_wp_error( $backup_dir ) ) {
|
||||
return $backup_dir;
|
||||
}
|
||||
|
||||
$backup_file = $backup_dir . $nextgen_image->get( 'file' );
|
||||
|
||||
if ( is_file( $original_file ) ) {
|
||||
if ( ! @copy( $original_file, $backup_file ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to swap original file %s with %s as copy() failed', $backup_file, $original_file ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$backup_thumbnail_file = $backup_dir . $nextgen_image->get( 'thumbnail_file' );
|
||||
|
||||
if ( is_file( $original_thumbnail_file ) ) {
|
||||
if ( @copy( $original_thumbnail_file, $backup_thumbnail_file ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to swap thumbnail file %s with %s as copy() failed', $backup_thumbnail_file, $original_thumbnail_file ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore NextGen images piece by piece.
|
||||
*
|
||||
* @param int $limit Limit on number of NextGen image to restore. Default: 50, maximum 1000.
|
||||
*
|
||||
* @return array {
|
||||
* Result of process: how many images restored and how many remain.
|
||||
* @type int $processed Count of processed images.
|
||||
* @type int $remane Count of remained images to be processed.
|
||||
* }
|
||||
*/
|
||||
public function restoreAllNextGen( $limit = 50 ) {
|
||||
|
||||
if ( ! is_numeric( $limit ) || is_numeric( $limit ) && $limit > 1000 ) {
|
||||
$limit = 50;
|
||||
}
|
||||
|
||||
$queue_table = RIO_Process_Queue::table_name();
|
||||
$nextgen_sql = "SELECT * FROM {$queue_table} WHERE `item_type` = %s AND `result_status` = %s LIMIT %d";
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$nextgen_images = $wpdb->get_results( $wpdb->prepare( $nextgen_sql, 'nextgen', RIO_Process_Queue::STATUS_SUCCESS, $limit ) );
|
||||
|
||||
$result = [
|
||||
'processed' => 0,
|
||||
'remane' => 0,
|
||||
];
|
||||
|
||||
if ( empty( $nextgen_images ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ( $nextgen_images as $nextgen_image ) {
|
||||
$nextgen_model = new WRIO_Image_Nextgen( $nextgen_image->object_id );
|
||||
|
||||
$restored = $nextgen_model->restore();
|
||||
|
||||
if ( ! is_wp_error( $restored ) ) {
|
||||
$result['processed'] = $result['processed']++;
|
||||
} else {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to restore nextgen image ID: %s as %s::%s failed with message: %s', $nextgen_image->object_id, get_class( 'WRIO_Image_Nextgen' ), 'restore()', $restored->get_error_message() ) );
|
||||
}
|
||||
}
|
||||
|
||||
$nextgen_sql_remane = "SELECT COUNT(*) AS remane FROM {$queue_table} WHERE `item_type` = %s AND `result_status` = %s";
|
||||
|
||||
$nextgen_image_remane = $wpdb->get_var( $wpdb->prepare( $nextgen_sql_remane, 'nextgen', RIO_Process_Queue::STATUS_SUCCESS ) );
|
||||
|
||||
if ( $nextgen_image_remane === null ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to get remained number of nextgen image by SQL: %s', $nextgen_sql_remane ) );
|
||||
}
|
||||
|
||||
$result['remane'] = $nextgen_image_remane !== null ? (int) $nextgen_image_remane : 0;
|
||||
|
||||
if ( $result['remane'] === 0 ) {
|
||||
// Should empty original/optimized size once all backups are empty
|
||||
WRIO_Plugin::app()->updateOption( 'nextgen_original_size', 0 );
|
||||
WRIO_Plugin::app()->updateOption( 'nextgen_optimized_size', 0 );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore Custom Folders piece by piece.
|
||||
*
|
||||
* @param int $limit Limit on number of Custom Folders to restore. Default: 50, maximum 1000.
|
||||
*
|
||||
* @return array {
|
||||
* Result of process: how many folders restored and how many remain.
|
||||
* @type int $processed Count of processed folders.
|
||||
* @type int $remane Count of remained folders to be processed.
|
||||
* }
|
||||
*/
|
||||
public function restoreAllCustomFolders( $limit = 50 ) {
|
||||
if ( ! is_numeric( $limit ) || is_numeric( $limit ) && $limit > 1000 ) {
|
||||
$limit = 50;
|
||||
}
|
||||
|
||||
$queue_table = RIO_Process_Queue::table_name();
|
||||
$cf_sql = "SELECT * FROM {$queue_table} WHERE `item_type` = %s AND `result_status` = %s LIMIT %d";
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$cf_images = $wpdb->get_results( $wpdb->prepare( $cf_sql, 'cf_image', RIO_Process_Queue::STATUS_SUCCESS, $limit ) );
|
||||
|
||||
$result = [
|
||||
'processed' => 0,
|
||||
'remane' => 0,
|
||||
];
|
||||
|
||||
if ( empty( $cf_images ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ( $cf_images as $cf_image ) {
|
||||
$cf_model = new WRIO_Folder_Image( $cf_image->object_id, $cf_image );
|
||||
|
||||
$restored = $cf_model->restore();
|
||||
|
||||
if ( ! is_wp_error( $restored ) ) {
|
||||
$result['processed'] = $result['processed']++;
|
||||
} else {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to restore Custom Folder ID: %s as %s::%s failed with message: %s', $cf_image->object_id, get_class( 'WRIO_Folder_Image' ), 'restore()', $restored->get_error_message() ) );
|
||||
}
|
||||
}
|
||||
|
||||
$cf_sql_remane = "SELECT COUNT(*) AS remane FROM {$queue_table} WHERE `item_type` = %s AND `result_status` = %s";
|
||||
|
||||
$cf_image_remane = $wpdb->get_var( $wpdb->prepare( $cf_sql_remane, 'cf_image', RIO_Process_Queue::STATUS_SUCCESS ) );
|
||||
|
||||
if ( $cf_image_remane === null ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to get remained number of Custom Folder by SQL: %s', $cf_sql_remane ) );
|
||||
}
|
||||
|
||||
$result['remane'] = $cf_image_remane !== null ? (int) $cf_image_remane : 0;
|
||||
|
||||
if ( $result['remane'] === 0 ) {
|
||||
// Should empty original/optimized size once all backups are empty
|
||||
WRIO_Plugin::app()->updateOption( 'folders_original_size', 0 );
|
||||
WRIO_Plugin::app()->updateOption( 'folders_optimized_size', 0 );
|
||||
|
||||
$custom_folders = WRIO_Custom_Folders::get_instance();
|
||||
$folders = $custom_folders->getFolders();
|
||||
|
||||
if ( ! empty( $folders ) ) {
|
||||
foreach ( $folders as $folder ) {
|
||||
$folder->reCountOptimizedFiles();
|
||||
}
|
||||
$custom_folders->saveFolders();
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Восстанавливаем из резервной копии NextGEN
|
||||
*
|
||||
* @param WRIO_Image_Nextgen $nextgen_image аттачмент
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function restoreNextgen( $nextgen_image ) {
|
||||
|
||||
$original_file = $nextgen_image->get( 'path' );
|
||||
$original_thumbnail_file = $nextgen_image->get( 'thumbnail_path' );
|
||||
$backup_dir = $this->getNextgenBackupDir( $nextgen_image->get( 'gallery_meta' ) );
|
||||
|
||||
if ( is_wp_error( $backup_dir ) ) {
|
||||
return $backup_dir;
|
||||
}
|
||||
|
||||
$backup_file = $backup_dir . $nextgen_image->get( 'file' );
|
||||
$backup_thumbnail_file = $backup_dir . $nextgen_image->get( 'thumbnail_file' );
|
||||
|
||||
if ( ! is_file( $backup_file ) ) {
|
||||
$error_msg = sprintf( 'Unable to restore from a backup. There is no file (%s).', $backup_file );
|
||||
WRIO_Plugin::app()->logger->error( sprintf( '%s, Nextgen image id: %s', $error_msg, $nextgen_image->get( 'id' ) ) );
|
||||
|
||||
return new WP_Error( 'file_not_exists', $error_msg );
|
||||
}
|
||||
|
||||
// Restore original file
|
||||
if ( ! $this->restore_file( $backup_file, $original_file ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restore thumbnail file
|
||||
if ( ! $this->restore_file( $backup_thumbnail_file, $original_thumbnail_file ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает путь к папке с резервными копиями
|
||||
*
|
||||
* @param string $image_abs_path абсолютный путь к файлу картинки
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCFBackupDir( $image_abs_path ) {
|
||||
$backup_dir = $this->getBackupDir();
|
||||
|
||||
$image_abs_path = wp_normalize_path( $image_abs_path );
|
||||
$wp_abs_path = wp_normalize_path( ABSPATH );
|
||||
|
||||
// Get all subfolders in which the image is stored.
|
||||
// This is necessary to create an alternate subfolders
|
||||
// in directory where they are stored in backups.
|
||||
$subfolders = str_replace( $wp_abs_path, '', dirname( $image_abs_path ) );
|
||||
|
||||
$backup_dir .= self::CF_BACKUP_DIR_NAME . '/' . $subfolders;
|
||||
|
||||
if ( ! is_dir( $backup_dir ) ) {
|
||||
$backup_dir = $this->mkdir( $backup_dir );
|
||||
|
||||
if ( is_wp_error( $backup_dir ) ) {
|
||||
return $backup_dir;
|
||||
}
|
||||
}
|
||||
|
||||
return trailingslashit( $backup_dir );
|
||||
}
|
||||
|
||||
/**
|
||||
* Делаем резервную копию Custom Folder Image
|
||||
*
|
||||
* @param WRIO_Folder_Image $folder_image Custom Folder Image
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function backupCFImage( $folder_image ) {
|
||||
$backup_origin_images = WRIO_Plugin::app()->getPopulateOption( 'backup_origin_images', false );
|
||||
|
||||
if ( ! $backup_origin_images ) {
|
||||
return false; // если бекап не требуется
|
||||
}
|
||||
|
||||
$original_file = $folder_image->get( 'path' );
|
||||
$backup_dir = $this->getCFBackupDir( $original_file );
|
||||
|
||||
if ( is_wp_error( $backup_dir ) ) {
|
||||
return $backup_dir;
|
||||
}
|
||||
|
||||
$backup_file = $backup_dir . wp_basename( $original_file );
|
||||
|
||||
if ( is_file( $original_file ) ) {
|
||||
if ( ! @copy( $original_file, $backup_file ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to swap original file %s with %s as copy() failed', $backup_file, $original_file ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Восстанавливаем из резервной копии Custom Folder Image
|
||||
*
|
||||
* @param WRIO_Folder_Image $folder_image Custom Folder Image
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function restoreCFImage( $folder_image ) {
|
||||
|
||||
$original_file = $folder_image->get( 'path' );
|
||||
$backup_dir = $this->getCFBackupDir( $original_file );
|
||||
|
||||
if ( is_wp_error( $backup_dir ) ) {
|
||||
return $backup_dir;
|
||||
}
|
||||
|
||||
$backup_file = $backup_dir . wp_basename( $original_file );
|
||||
|
||||
if ( ! $this->restore_file( $backup_file, $original_file ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,725 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс для работы с кастомными папками
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIO_Custom_Folders {
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @access protected
|
||||
* @var object
|
||||
*/
|
||||
protected static $_instance;
|
||||
|
||||
/**
|
||||
* @var WRIO_Folder[]
|
||||
*/
|
||||
private $folders = [];
|
||||
|
||||
/**
|
||||
* @var WRIO_Folder_Image[]
|
||||
*/
|
||||
private $folder_images = [];
|
||||
|
||||
/**
|
||||
* WRIO_Custom_Folders constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$folders = WRIO_Plugin::app()->getOption( 'custom_folders', [] );
|
||||
|
||||
if ( ! empty( $folders ) ) {
|
||||
foreach ( (array) $folders as $uid => $folder ) {
|
||||
$this->folders[ $uid ] = new WRIO_Folder( $folder );
|
||||
}
|
||||
}
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object|\WRIO_Custom_Folders object Main instance.
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! isset( static::$_instance ) ) {
|
||||
static::$_instance = new static();
|
||||
}
|
||||
|
||||
return static::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object init.
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
// todo: убрать эти фильтры
|
||||
add_filter( 'wbcr/rio/optimize_template/optimize_ajax_action', [ $this, 'optimizeAjaxAction' ], 10, 2 );
|
||||
add_filter(
|
||||
'wbcr/rio/optimize_template/reoptimize_ajax_action',
|
||||
[
|
||||
$this,
|
||||
'reoptimizeAjaxAction',
|
||||
],
|
||||
10,
|
||||
2
|
||||
);
|
||||
add_filter( 'wbcr/rio/optimize_template/restore_ajax_action', [ $this, 'restoreAjaxAction' ], 10, 2 );
|
||||
|
||||
add_action( 'admin_menu', [ $this, 'add_media_page' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'media_page_assets' ] );
|
||||
add_action( 'wbcr/rio/optimize_template/optimized_percent', [ $this, 'optimizedPercent' ], 10, 2 );
|
||||
add_action( 'wbcr/rio/webp_success', [ $this, 'webpSuccess' ], 20 );
|
||||
}
|
||||
|
||||
public function add_media_page() {
|
||||
add_submenu_page(
|
||||
'upload.php',
|
||||
__( 'Other Media', 'robin-image-optimizer' ),
|
||||
__( 'Other Media', 'robin-image-optimizer' ),
|
||||
'manage_options',
|
||||
'rio-custom-media',
|
||||
[
|
||||
$this,
|
||||
'custom_media_page',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function media_page_assets( $hook ) {
|
||||
if ( 'media_page_rio-custom-media' == $hook ) {
|
||||
wp_enqueue_style( 'wio-install-addons', WRIO_PLUGIN_URL . '/admin/assets/css/media.css', [], WRIO_Plugin::app()->getPluginVersion() );
|
||||
wp_enqueue_style( 'wriop-other-media', WRIOP_PLUGIN_URL . '/admin/assets/css/other-media.css', [], WRIO_Plugin::app()->getPluginVersion() );
|
||||
wp_enqueue_script( 'wio-install-addons', WRIO_PLUGIN_URL . '/admin/assets/js/single-optimization.js', [ 'jquery' ], WRIO_Plugin::app()->getPluginVersion() );
|
||||
}
|
||||
}
|
||||
|
||||
public function custom_media_page() {
|
||||
if ( ! class_exists( 'WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
require_once WRIOP_PLUGIN_DIR . '/includes/classes/class.folders-list-table.php';
|
||||
|
||||
$list_table = new WRIO_Folders_List_Table();
|
||||
$list_table->prepare_items();
|
||||
$list_table->display(); // выводит на экран весь блок с таблицей и фильтрами
|
||||
}
|
||||
|
||||
/**
|
||||
* Get folder by specified id.
|
||||
*
|
||||
* @param string $uid Folder sha256 hash.
|
||||
*
|
||||
* @return bool|WRIO_Folder
|
||||
*/
|
||||
public function getFolder( $uid ) {
|
||||
if ( isset( $this->folders[ $uid ] ) ) {
|
||||
return $this->folders[ $uid ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available folders.
|
||||
*
|
||||
* @return WRIO_Folder[]
|
||||
*/
|
||||
public function getFolders() {
|
||||
return $this->folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new folder by specified path.
|
||||
*
|
||||
* @param string $path Path to folder.
|
||||
*
|
||||
* @return WRIO_Folder|\WP_Error
|
||||
*/
|
||||
public function addFolder( $path ) {
|
||||
|
||||
if ( empty( $path ) ) {
|
||||
return new WP_Error( 'empty_path', 'Path is empty.' );
|
||||
}
|
||||
|
||||
// Use the same base path as the file browser (get_home_path for main site, upload dir for subsites)
|
||||
if ( is_main_site() ) {
|
||||
$base_path = realpath( get_home_path() );
|
||||
} else {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$base_path = realpath( $upload_dir['basedir'] );
|
||||
}
|
||||
|
||||
// Validate that the path exists and is a directory
|
||||
$real_path = realpath( $base_path . DIRECTORY_SEPARATOR . ltrim( $path, '/' ) );
|
||||
if ( false === $real_path ) {
|
||||
return new WP_Error( 'invalid_path', 'The specified path does not exist.' );
|
||||
}
|
||||
|
||||
// Prevent directory traversal attacks - ensure path is within allowed base directory
|
||||
if ( strpos( $real_path, $base_path . DIRECTORY_SEPARATOR ) !== 0 && $real_path !== $base_path ) {
|
||||
return new WP_Error( 'invalid_path', 'The specified path is outside the allowed directory.' );
|
||||
}
|
||||
|
||||
if ( ! is_dir( $real_path ) ) {
|
||||
return new WP_Error( 'not_a_directory', 'The specified path is not a directory.' );
|
||||
}
|
||||
if ( ! is_readable( $real_path ) ) {
|
||||
return new WP_Error( 'not_readable', 'The specified directory is not readable.' );
|
||||
}
|
||||
|
||||
$uid = hash( 'sha256', $path );
|
||||
if ( ! $this->getFolder( $uid ) ) {
|
||||
$folder_data = [
|
||||
'path' => $path,
|
||||
'uid' => $uid,
|
||||
];
|
||||
$new_folder = new WRIO_Folder( $folder_data );
|
||||
$new_folder->reCountFiles();
|
||||
$this->folders[ $uid ] = $new_folder;
|
||||
$this->saveFolders();
|
||||
|
||||
return $new_folder;
|
||||
}
|
||||
|
||||
return new WP_Error( 'folder_already_exists', 'Folder has already been added before.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove folder by sha256 hash.
|
||||
*
|
||||
* @param string $uid SHA256 folder hash id.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function removeFolder( $uid ) {
|
||||
if ( empty( $uid ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$folder = $this->getFolder( $uid );
|
||||
|
||||
if ( ! $folder ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$folder->remove();
|
||||
|
||||
unset( $this->folders[ $uid ] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saved all folders in options.
|
||||
*/
|
||||
public function saveFolders() {
|
||||
$folders = [];
|
||||
foreach ( $this->folders as $uid => $folder ) {
|
||||
$folders[ $uid ] = $folder->toArray();
|
||||
}
|
||||
WRIO_Plugin::app()->updateOption( 'custom_folders', $folders );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает объект image
|
||||
*
|
||||
* @param int $image_id
|
||||
* @param array|false $image_meta
|
||||
*
|
||||
* @return WRIO_Folder_Image
|
||||
*/
|
||||
public function getImage( $image_id, $image_meta = false ) {
|
||||
if ( ! isset( $this->folder_images[ $image_id ] ) ) {
|
||||
$this->folder_images[ $image_id ] = new WRIO_Folder_Image( $image_id, $image_meta );
|
||||
}
|
||||
|
||||
return $this->folder_images[ $image_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Оптимизирует cf_image
|
||||
*
|
||||
* @param int $image_id номер картинки в таблице nextgen
|
||||
* @param string $level качество
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function optimizeImage( $image_id, $level = '' ) {
|
||||
$cf_image = $this->getImage( $image_id );
|
||||
$optimization_data = $cf_image->getOptimizationData();
|
||||
|
||||
if ( 'processing' == $optimization_data->get_result_status() ) {
|
||||
return $this->deferredOptimizeImage( $image_id );
|
||||
}
|
||||
|
||||
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
|
||||
|
||||
if ( $cf_image->isOptimized() ) {
|
||||
$optimized_size = $optimization_data->get_final_size();
|
||||
$original_size = $optimization_data->get_original_size();
|
||||
$webp_optimized_size = $optimization_data->get_extra_data()->get_webp_main_size();
|
||||
$image_statistics->deductFromField( 'webp_optimized_size', $webp_optimized_size );
|
||||
$image_statistics->deductFromField( 'optimized_size', $optimized_size );
|
||||
$image_statistics->deductFromField( 'original_size', $original_size );
|
||||
$cf_image->restore();
|
||||
}
|
||||
|
||||
$image_optimized_data = $cf_image->optimize( $level );
|
||||
|
||||
$original_size = $image_optimized_data['original_size'];
|
||||
$optimized_size = $image_optimized_data['optimized_size'];
|
||||
|
||||
$image_statistics->addToField( 'optimized_size', $optimized_size );
|
||||
$image_statistics->addToField( 'original_size', $original_size );
|
||||
$image_statistics->save();
|
||||
|
||||
$folder = $this->getFolder( $cf_image->get( 'folder_uid' ) );
|
||||
$folder->reCountOptimizedFiles();
|
||||
$this->saveFolders();
|
||||
|
||||
return $image_optimized_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отложенная оптимизация image. Этап 1: отправка на сервер оптимизации
|
||||
*
|
||||
* @param int $image_id
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
protected function deferredOptimizeImage( $image_id ) {
|
||||
$cf_image = $this->getImage( $image_id );
|
||||
$optimization_data = $cf_image->getOptimizationData();
|
||||
$image_processor = WIO_OptimizationTools::getImageProcessor();
|
||||
|
||||
// если текущий сервер оптимизации не поддерживает отложенную оптимизацию, а в очереди есть аттачменты - ставим им ошибку
|
||||
if ( ! $image_processor->isDeferred() ) {
|
||||
$optimization_data->set_result_status( 'error' );
|
||||
/**
|
||||
* @var $extra_data WRIO_CF_Image_Extra_Data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
$extra_data->set_error( 'deferred' );
|
||||
$extra_data->set_error_msg( 'server not support deferred optimization' );
|
||||
$optimization_data->set_extra_data( $extra_data );
|
||||
$optimization_data->save();
|
||||
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Server %s does not support deferred optimization', get_class( $image_processor ) ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
$optimized_data = $cf_image->deferredOptimization();
|
||||
if ( $optimized_data ) {
|
||||
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
|
||||
$image_statistics->addToField( 'folders_optimized_size', $optimized_data['optimized_size'] );
|
||||
$image_statistics->addToField( 'folders_original_size', $optimized_data['original_size'] );
|
||||
$image_statistics->save();
|
||||
}
|
||||
|
||||
return $optimized_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка неоптимизированных изображений
|
||||
*
|
||||
* @param int $max_process_per_request кол-во аттачментов за 1 запуск
|
||||
*
|
||||
* @return array|\WP_Error
|
||||
*/
|
||||
public function processUnoptimizedImages( $max_process_per_request = 5 ) {
|
||||
|
||||
$backup = WRIOP_Backup::get_instance();
|
||||
$backup_origin_images = WRIO_Plugin::app()->getPopulateOption( 'backup_origin_images', false );
|
||||
|
||||
if ( $backup_origin_images && ! $backup->isBackupWritable() ) {
|
||||
return new WP_Error( 'unwritable_backup_dir', __( 'No access for writing backups.', 'robin-image-optimizer' ) );
|
||||
}
|
||||
|
||||
if ( ! $backup->isUploadWritable() ) {
|
||||
return new WP_Error( 'unwritable_upload_dir', __( 'No access for writing backups.', 'robin-image-optimizer' ) );
|
||||
}
|
||||
|
||||
if ( empty( $this->folders ) ) {
|
||||
return new WP_Error( 'folders_not_found', __( 'You need to add a custom folder to start optimization.', 'robin-image-optimizer' ) );
|
||||
}
|
||||
|
||||
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
|
||||
|
||||
$folder_images = $image_statistics->getUnoptimized( $max_process_per_request ); // тут будет выборка
|
||||
$total = $image_statistics->getUnoptimizedCount(); // тут общее кол-во неоптимизированных
|
||||
|
||||
if ( empty( $folder_images ) ) {
|
||||
return new WP_Error( 'no_unoptimized_in_folder', __( 'If some files weren\'t optimized, try removing and re-adding the folder.', 'robin-image-optimizer' ) );
|
||||
}
|
||||
|
||||
$folder_images_count = count( $folder_images );
|
||||
$optimized_count = 0;
|
||||
$optimized_items = [];
|
||||
|
||||
// обработка
|
||||
if ( $folder_images_count ) {
|
||||
foreach ( $folder_images as $folder_image ) {
|
||||
$this->optimizeImage( $folder_image->id );
|
||||
++$optimized_count;
|
||||
$optimized_items[ $folder_image->id ] = $folder_image;
|
||||
}
|
||||
}
|
||||
|
||||
$remain = $total - $folder_images_count;
|
||||
|
||||
// проверяем, есть ли аттачменты в очереди на отложенную оптимизацию
|
||||
$optimized_data = $this->processDeferredOptimization();
|
||||
|
||||
if ( $optimized_data ) {
|
||||
$optimized_count = $optimized_data['optimized_count'];
|
||||
$remain = $total - $optimized_count;
|
||||
}
|
||||
|
||||
if ( $remain <= 0 ) {
|
||||
$remain = 0;
|
||||
}
|
||||
|
||||
$last_optimized = end( $optimized_items );
|
||||
|
||||
$responce = [
|
||||
'remain' => $remain,
|
||||
'end' => false,
|
||||
'optimized_count' => $optimized_count,
|
||||
'last_optimized' => $last_optimized->id ? $image_statistics->get_last_optimized_image( $last_optimized->id ) : null,
|
||||
'statistic' => $image_statistics->load(),
|
||||
];
|
||||
|
||||
return $responce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отложенная оптимизация. Этап 2: получение данных с сервера потимизации
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
protected function processDeferredOptimization() {
|
||||
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
|
||||
$limit = 1;
|
||||
$image_data = $image_statistics->getDeferredUnoptimized( $limit );
|
||||
|
||||
if ( ! $image_data || ! isset( $image_data[0] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$image_data = $image_data[0];
|
||||
$cf_image = $this->getImage( $image_data->id, $image_data );
|
||||
$optimized_data = $cf_image->deferredOptimization();
|
||||
|
||||
if ( $optimized_data ) {
|
||||
$image_statistics->addToField( 'optimized_size', $optimized_data['optimized_size'] );
|
||||
$image_statistics->addToField( 'original_size', $optimized_data['original_size'] );
|
||||
$image_statistics->save();
|
||||
|
||||
$folder = $this->getFolder( $cf_image->get( 'folder_uid' ) );
|
||||
$folder->reCountOptimizedFiles();
|
||||
$this->saveFolders();
|
||||
|
||||
return $optimized_data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Восстановление из резервной копии
|
||||
*
|
||||
* @param int $folder_uid Folder id.
|
||||
* @param int $max_process_per_request кол-во аттачментов за 1 запуск
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function restoreFolderFromBackup( $folder_uid, $max_process_per_request = 5 ) {
|
||||
WRIO_Plugin::app()->updatePopulateOption( 'cron_running', false ); // останавливаем крон
|
||||
|
||||
$processing = new WRIO_Folder_Processing( 'custom-folders' );
|
||||
$processing->cancel_process();
|
||||
WRIO_Plugin::app()->updatePopulateOption( 'process_running', false ); // останавливаем обработку
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$optimized_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$db_table} WHERE item_type = 'cf_image' AND item_hash_alternative = %s AND result_status = 'success' LIMIT 1;", $folder_uid ) );
|
||||
$optimized_images = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$db_table} WHERE item_type = 'cf_image' AND item_hash_alternative = %s AND result_status = 'success' LIMIT %d", $folder_uid, $max_process_per_request ) );
|
||||
|
||||
$images_count = 0;
|
||||
if ( $optimized_images ) {
|
||||
$images_count = count( $optimized_images );
|
||||
}
|
||||
|
||||
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
|
||||
|
||||
// обработка
|
||||
if ( $images_count ) {
|
||||
foreach ( $optimized_images as $row ) {
|
||||
$image_id = intval( $row->id );
|
||||
$cf_image = $this->getImage( $image_id );
|
||||
if ( $cf_image->isOptimized() ) {
|
||||
$restored = $cf_image->restore();
|
||||
|
||||
if ( ! is_wp_error( $restored ) ) {
|
||||
$optimization_data = $cf_image->getOptimizationData();
|
||||
$optimized_size = $optimization_data->get_final_size();
|
||||
$original_size = $optimization_data->get_original_size();
|
||||
$webp_optimized_size = $optimization_data->get_extra_data()->get_webp_main_size();
|
||||
$image_statistics->deductFromField( 'webp_optimized_size', $webp_optimized_size );
|
||||
$image_statistics->deductFromField( 'optimized_size', $optimized_size );
|
||||
$image_statistics->deductFromField( 'original_size', $original_size );
|
||||
|
||||
$folder = $this->getFolder( $cf_image->get( 'folder_uid' ) );
|
||||
$folder->reCountOptimizedFiles();
|
||||
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to restore custom folder. Object info: %s', wp_json_encode( $cf_image ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->saveFolders();
|
||||
$image_statistics->save();
|
||||
}
|
||||
$remain = $optimized_count - $images_count;
|
||||
|
||||
return [
|
||||
'remain' => $remain,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Сбрасывает текущие ошибки оптимизации
|
||||
* Позволяет изображениям, которые оптимизированы с ошибкой, заново пройти оптимизацию.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetCurrentErrors() {
|
||||
// do_action( 'wbcr/rio/multisite_current_blog' );
|
||||
global $wpdb;
|
||||
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
|
||||
$wpdb->update(
|
||||
$db_table,
|
||||
[ 'result_status' => 'unoptimized' ],
|
||||
[
|
||||
'item_type' => 'cf_image',
|
||||
'result_status' => 'error',
|
||||
]
|
||||
);
|
||||
// do_action( 'wbcr/rio/multisite_restore_blog' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук возвращает ajax action для кнопки оптимизации.
|
||||
*
|
||||
* @param string $action
|
||||
* @param string $type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function optimizeAjaxAction( $action, $type ) {
|
||||
if ( $type == 'custom-folders' ) {
|
||||
return 'wriop_process_cf_images';
|
||||
}
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук возвращает ajax action для кнопки переоптимизации.
|
||||
*
|
||||
* @param string $action
|
||||
* @param string $type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function reoptimizeAjaxAction( $action, $type ) {
|
||||
if ( $type == 'custom-folders' ) {
|
||||
return 'wio_cf_reoptimize_image';
|
||||
}
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук возвращает ajax action для кнопки восстановления.
|
||||
*
|
||||
* @param string $action
|
||||
* @param string $type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function restoreAjaxAction( $action, $type ) {
|
||||
if ( $type == 'custom-folders' ) {
|
||||
return 'wio_cf_restore_image';
|
||||
}
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает блок оптимизации.
|
||||
*
|
||||
* @param int $image_id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMediaColumnContent( $image_id ) {
|
||||
$media_library = WRIO_Media_Library::get_instance();
|
||||
$params = $this->calculateParams( $image_id );
|
||||
|
||||
return $media_library->getMediaColumnTemplate( $params, 'custom-folders' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Просчитывает параметры блока оптимизации
|
||||
*
|
||||
* @param int $image_id номер изображения nextgen
|
||||
*
|
||||
* @return array $params
|
||||
*/
|
||||
public function calculateParams( $image_id ) {
|
||||
$cf_image = new WRIO_Folder_Image( $image_id );
|
||||
$isOptimized = $cf_image->isOptimized();
|
||||
$diff_percent = 0;
|
||||
$diff_percent_all = 0;
|
||||
$original_main_size = 0;
|
||||
$attachment_file_size = 0;
|
||||
$optimized_size = 0;
|
||||
$original_size = 0;
|
||||
$optimization_level = '';
|
||||
$error_msg = '';
|
||||
$backuped = '';
|
||||
if ( $isOptimized ) {
|
||||
$optimization_data = $cf_image->getOptimizationData();
|
||||
/**
|
||||
* @var WRIO_CF_Image_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
$optimization_level = $optimization_data->get_processing_level();
|
||||
$original_main_size = $optimization_data->get_original_size();
|
||||
$attachment_file_size = $optimization_data->get_final_size();
|
||||
$optimized_size = $optimization_data->get_final_size();
|
||||
$original_size = $optimization_data->get_original_size();
|
||||
$backuped = $optimization_data->get_is_backed_up();
|
||||
$error_msg = $extra_data->get_error_msg();
|
||||
if ( $attachment_file_size and $original_main_size ) {
|
||||
$diff_percent = round( ( $original_main_size - $attachment_file_size ) * 100 / $original_main_size );
|
||||
}
|
||||
if ( $optimized_size and $original_size ) {
|
||||
$diff_percent_all = round( ( $original_size - $optimized_size ) * 100 / $original_size );
|
||||
}
|
||||
}
|
||||
$params = [
|
||||
'attachment_id' => $image_id,
|
||||
'is_optimized' => $isOptimized,
|
||||
'attach_dimensions' => 0,
|
||||
'attachment_file_size' => $attachment_file_size,
|
||||
'optimized_size' => $optimized_size,
|
||||
'original_size' => $original_size,
|
||||
'original_main_size' => $original_main_size,
|
||||
'thumbnails_optimized' => 0,
|
||||
'optimization_level' => $optimization_level,
|
||||
'error_msg' => $error_msg,
|
||||
'backuped' => $backuped,
|
||||
'diff_percent' => $diff_percent,
|
||||
'diff_percent_all' => $diff_percent_all,
|
||||
'is_skipped' => false,
|
||||
];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает процент оптимизации
|
||||
* Фильтр wbcr/rio/optimize_template/optimized_percent
|
||||
*
|
||||
* @param int $percent процент оптимизации
|
||||
* @param string $type тип страницы
|
||||
*
|
||||
* @return int процент оптимизации
|
||||
*/
|
||||
public function optimizedPercent( $percent, $type ) {
|
||||
if ( 'custom-folders' == $type ) {
|
||||
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
|
||||
|
||||
return $image_statistics->getOptimizedPercent();
|
||||
}
|
||||
|
||||
return $percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохраняет WebP размер для cf_image
|
||||
*
|
||||
* @param RIO_Process_Queue $queue_model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function webpSuccess( $queue_model ) {
|
||||
if ( ! class_exists( 'WRIO\WEBP\Listener' ) ) {
|
||||
return false; // если не установлена премиум версия, то WebP не активен
|
||||
}
|
||||
if ( $queue_model->get_item_type() !== WRIO\WEBP\Listener::DEFAULT_TYPE ) {
|
||||
return false;
|
||||
}
|
||||
if ( $queue_model->get_result_status() !== RIO_Process_Queue::STATUS_SUCCESS ) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @var RIOP_WebP_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $queue_model->get_extra_data();
|
||||
$item_type = $extra_data->get_convert_from();
|
||||
if ( $item_type != 'cf_image' ) {
|
||||
return false;
|
||||
}
|
||||
$optimization_data = RIO_Process_Queue::find_by_hash( $queue_model->get_item_hash_alternative() );
|
||||
if ( ! $optimization_data ) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @var WRIO_CF_Image_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
if ( ! $extra_data ) {
|
||||
return false;
|
||||
}
|
||||
$extra_data->set_webp_main_size( $queue_model->get_final_size() );
|
||||
$optimization_data->set_extra_data( $extra_data );
|
||||
add_filter( 'wbcr/riop/queue_item_save_execute_hook', '__return_false' );
|
||||
$optimization_data->save();
|
||||
remove_filter( 'wbcr/riop/queue_item_save_execute_hook', '__return_false' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID's of unoptimized attachments
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUnoptimizedImages() {
|
||||
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
|
||||
$folder_images = $image_statistics->getUnoptimized( PHP_INT_MAX ); // тут будет выборка
|
||||
|
||||
$return = [];
|
||||
foreach ( $folder_images as $folder_image ) {
|
||||
$return[] = $folder_image->id;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс для работы с custom folder image.
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIO_Folder_Image {
|
||||
|
||||
/**
|
||||
* @var int номер картинки в таблице
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var string путь к картинке относительно папки
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* @var string УРЛ
|
||||
*/
|
||||
private $url;
|
||||
|
||||
/**
|
||||
* @var string уникальный идентификатор директории
|
||||
*/
|
||||
private $folder_uid;
|
||||
|
||||
/**
|
||||
* @var RIO_Process_Queue данные по оптимизации
|
||||
*/
|
||||
private $optimization_data;
|
||||
|
||||
/**
|
||||
* Инициализация картинки из custom folder
|
||||
*
|
||||
* @param int $image_id номер картинки в таблице folders
|
||||
* @param array|false $image_data метаданные картинки
|
||||
*/
|
||||
public function __construct( $image_id, $image_data = false ) {
|
||||
$this->id = $image_id;
|
||||
|
||||
if ( $image_data instanceof RIO_Process_Queue ) {
|
||||
$this->optimization_data = $image_data;
|
||||
} else {
|
||||
$this->optimization_data = $this->createOptimizationData();
|
||||
|
||||
if ( $image_data ) {
|
||||
$this->optimization_data->configure( (array) $image_data );
|
||||
} else {
|
||||
$this->loadOptimizationData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var WRIO_CF_Image_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $this->optimization_data->get_extra_data();
|
||||
// Use get_home_path() to match how real_path_to_relative() calculates relative paths in class.folder.php
|
||||
$base_path = is_main_site() ? get_home_path() : wp_upload_dir()['basedir'] . '/';
|
||||
$this->path = wp_normalize_path( untrailingslashit( $base_path ) . $extra_data->get_file_path() );
|
||||
$this->url = home_url( wp_normalize_path( $extra_data->get_file_path() ) );
|
||||
$this->folder_uid = $this->optimization_data->get_item_hash_alternative();
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает свойство аттачмента
|
||||
*
|
||||
* @param string $property имя свойства
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( $property ) {
|
||||
if ( isset( $this->$property ) ) {
|
||||
return $this->$property;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает данные по оптимизации
|
||||
*
|
||||
* @return RIO_Process_Queue
|
||||
*/
|
||||
public function getOptimizationData() {
|
||||
if ( empty( $this->optimization_data ) ) {
|
||||
$this->optimization_data = $this->createOptimizationData();
|
||||
$this->optimization_data->load();
|
||||
}
|
||||
|
||||
return $this->optimization_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт новый объект RIO_Process_Queue
|
||||
*
|
||||
* @return RIO_Process_Queue
|
||||
*/
|
||||
public function createOptimizationData() {
|
||||
return new RIO_Process_Queue(
|
||||
[
|
||||
'id' => $this->id,
|
||||
'item_type' => 'cf_image',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
protected function loadOptimizationData() {
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $this->optimization_data ) ) {
|
||||
$this->optimization_data = $this->createOptimizationData();
|
||||
}
|
||||
|
||||
$table_name = RIO_Process_Queue::table_name();
|
||||
$sql = $wpdb->prepare(
|
||||
"SELECT * FROM {$table_name} WHERE id = %d AND item_type = %s LIMIT 1;",
|
||||
[
|
||||
$this->id,
|
||||
'cf_image',
|
||||
]
|
||||
);
|
||||
|
||||
$row = $wpdb->get_row( $sql );
|
||||
|
||||
if ( ! empty( $row ) ) {
|
||||
$this->optimization_data->configure( $row );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка на оптимизацию изображения
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOptimized() {
|
||||
$optimization_data = $this->getOptimizationData();
|
||||
if ( empty( $optimization_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( $optimization_data->is_optimized() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether file exists or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFileExists() {
|
||||
if ( file_exists( $this->path ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize folder image.
|
||||
*
|
||||
* @param string $optimization_level Level of optimization.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function optimize( $optimization_level = '' ) {
|
||||
$is_image_backuped = $this->backup();
|
||||
|
||||
if ( is_wp_error( $is_image_backuped ) ) {
|
||||
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to backup with message: %s. Skipping optimization of custom folder', $is_image_backuped->get_error_message() ) );
|
||||
|
||||
return [
|
||||
'errors_count' => 1,
|
||||
'original_size' => 0,
|
||||
'optimized_size' => 0,
|
||||
'optimized_count' => 0,
|
||||
];
|
||||
}
|
||||
// делаем рисайз
|
||||
$image_processor = WIO_OptimizationTools::getImageProcessor();
|
||||
if ( ! $optimization_level ) {
|
||||
$optimization_level = WRIO_Plugin::app()->getPopulateOption( 'image_optimization_level', 'normal' );
|
||||
}
|
||||
if ( $optimization_level == 'custom' ) {
|
||||
$custom_quality = WRIO_Plugin::app()->getPopulateOption( 'image_optimization_level_custom', 100 );
|
||||
$optimization_level = intval( $custom_quality );
|
||||
}
|
||||
$optimization_data = $this->getOptimizationData();
|
||||
$results = [];
|
||||
$results['processing_level'] = $optimization_level;
|
||||
$main_file_path = $this->path;
|
||||
$main_file_url = $this->url;
|
||||
clearstatcache(); // на всякий случай очистим кеш файловой статистики
|
||||
$original_main_size = filesize( $main_file_path ); // оптимизированный размер только главной картинки
|
||||
|
||||
$optimized_img_data = $image_processor->process(
|
||||
[
|
||||
'image_url' => $main_file_url,
|
||||
'image_path' => $main_file_path,
|
||||
'quality' => $image_processor->quality( $optimization_level ),
|
||||
'save_exif' => WRIO_Plugin::app()->getPopulateOption( 'save_exif_data', false ),
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $optimized_img_data ) ) {
|
||||
$results['result_status'] = 'error';
|
||||
/**
|
||||
* @var $extra_data WRIO_CF_Image_Extra_Data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
$extra_data->set_error( 'optimization' );
|
||||
$extra_data->set_error_msg( $optimized_img_data->get_error_message() );
|
||||
$results['extra_data'] = $extra_data;
|
||||
$optimization_data->configure( $results );
|
||||
$optimization_data->save();
|
||||
|
||||
return [
|
||||
'errors_count' => 1,
|
||||
'original_size' => 0,
|
||||
'optimized_size' => 0,
|
||||
'optimized_count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
// отложенная оптимизация
|
||||
if ( isset( $optimized_img_data['status'] ) && $optimized_img_data['status'] == 'processing' ) {
|
||||
$results['result_status'] = 'processing';
|
||||
$results['is_backed_up'] = $is_image_backuped;
|
||||
$results['original_size'] = 0;
|
||||
$results['final_size'] = 0;
|
||||
|
||||
/**
|
||||
* @var $extra_data WRIO_CF_Image_Extra_Data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
$extra_data->set_main_optimized_data( $optimized_img_data );
|
||||
$results['extra_data'] = $extra_data;
|
||||
$optimization_data->configure( $results );
|
||||
$optimization_data->save();
|
||||
|
||||
return [
|
||||
'processing' => 1,
|
||||
'original_size' => 0,
|
||||
'optimized_size' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$this->replaceOriginalFile( $optimized_img_data );
|
||||
|
||||
// некоторые провайдеры не отдают оптимизированный размер, поэтому после замены файла получаем его сами
|
||||
if ( ! $optimized_img_data['optimized_size'] ) {
|
||||
clearstatcache();
|
||||
$optimized_img_data['optimized_size'] = filesize( $main_file_path );
|
||||
}
|
||||
// при отрицательной оптимизации ставим значение оригинала
|
||||
if ( $optimized_img_data['optimized_size'] > $original_main_size ) {
|
||||
$optimized_img_data['optimized_size'] = $original_main_size;
|
||||
}
|
||||
|
||||
$original_size = $original_main_size;
|
||||
$optimized_size = $optimized_img_data['optimized_size'];
|
||||
|
||||
$results['result_status'] = 'success';
|
||||
$results['final_size'] = $optimized_size;
|
||||
$results['original_size'] = $original_size;
|
||||
$results['is_backed_up'] = $is_image_backuped;
|
||||
|
||||
/**
|
||||
* @var $extra_data WRIO_CF_Image_Extra_Data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
$extra_data->set_main_optimized_data( null );
|
||||
$extra_data->set_error( null );
|
||||
$extra_data->set_error_msg( null );
|
||||
$results['extra_data'] = $extra_data;
|
||||
$mime_type = '';
|
||||
if ( function_exists( 'wp_get_image_mime' ) ) {
|
||||
$mime_type = wp_get_image_mime( $main_file_path );
|
||||
}
|
||||
$results['original_mime_type'] = $mime_type;
|
||||
$results['final_mime_type'] = $mime_type;
|
||||
$optimization_data->configure( $results );
|
||||
$optimization_data->save();
|
||||
|
||||
return [
|
||||
'errors_count' => 0,
|
||||
'original_size' => $original_size,
|
||||
'optimized_size' => $optimized_size,
|
||||
'optimized_count' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Отложенная оптимизация аттачмента
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public function deferredOptimization() {
|
||||
$results = [
|
||||
'original_size' => 0,
|
||||
'optimized_size' => 0,
|
||||
'optimized_count' => 0,
|
||||
'processing' => 1,
|
||||
];
|
||||
|
||||
$image_processor = WIO_OptimizationTools::getImageProcessor();
|
||||
$optimization_data = $this->getOptimizationData();
|
||||
|
||||
if ( $optimization_data->get_result_status() != 'processing' ) {
|
||||
return false;
|
||||
}
|
||||
// проверяем главную картинку
|
||||
/**
|
||||
* @var $extra_data WRIO_CF_Image_Extra_Data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
$main_optimized_data = $extra_data->get_main_optimized_data();
|
||||
$main_image_url = '';
|
||||
if ( ! $main_optimized_data['optimized_img_url'] ) {
|
||||
$main_image_url = $image_processor->checkDeferredOptimization( $main_optimized_data );
|
||||
if ( $main_image_url ) {
|
||||
$main_optimized_data['optimized_img_url'] = $main_image_url;
|
||||
$extra_data->set_main_optimized_data( $main_optimized_data );
|
||||
}
|
||||
}
|
||||
|
||||
$thumbnails_processed = true; // для кастомных папок нет превьюшек, поэтому всегда true
|
||||
|
||||
// когда все файлы получены - сохраняем и возвращаем результат
|
||||
if ( $main_image_url && $thumbnails_processed ) {
|
||||
$original_size = 0;
|
||||
$optimized_size = 0;
|
||||
$original_main_size = filesize( $this->get( 'path' ) );
|
||||
$original_size = $original_size + $original_main_size;
|
||||
$this->replaceOriginalFile(
|
||||
[
|
||||
'optimized_img_url' => $main_image_url,
|
||||
]
|
||||
);
|
||||
clearstatcache();
|
||||
$optimized_main_size = filesize( $this->get( 'path' ) );
|
||||
|
||||
// при отрицательной оптимизации ставим значение оригинала
|
||||
if ( $optimized_main_size > $original_main_size ) {
|
||||
$optimized_main_size = $original_main_size;
|
||||
}
|
||||
|
||||
$optimized_size = $optimized_size + $optimized_main_size;
|
||||
clearstatcache();
|
||||
$mime_type = '';
|
||||
|
||||
if ( function_exists( 'wp_get_image_mime' ) ) {
|
||||
$mime_type = wp_get_image_mime( $this->get( 'path' ) );
|
||||
}
|
||||
|
||||
$optimization_data->configure(
|
||||
[
|
||||
'final_size' => $optimized_size,
|
||||
'original_size' => $original_size,
|
||||
'result_status' => 'success',
|
||||
'original_mime_type' => $mime_type,
|
||||
'final_mime_type' => $mime_type,
|
||||
]
|
||||
);
|
||||
$extra_data->set_original_main_size( $original_main_size );
|
||||
|
||||
// удаляем промежуточные данные
|
||||
$extra_data->set_main_optimized_data( null );
|
||||
$extra_data->set_error( null );
|
||||
$extra_data->set_error_msg( null );
|
||||
|
||||
$results['optimized_count'] = 1;
|
||||
$results['original_size'] = $original_size;
|
||||
$results['optimized_size'] = $optimized_size;
|
||||
unset( $results['processing'] );
|
||||
}
|
||||
|
||||
$optimization_data->set_extra_data( $extra_data );
|
||||
$optimization_data->save();
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Заменяет оригинальный файл на оптимизированный
|
||||
*
|
||||
* @param array $optimized_img_data результат оптимизации ввиде массива данных
|
||||
*/
|
||||
public function replaceOriginalFile( $optimized_img_data ) {
|
||||
$optimized_img_url = $optimized_img_data['optimized_img_url'];
|
||||
if ( isset( $optimized_img_data['not_need_download'] ) and $optimized_img_data['not_need_download'] ) {
|
||||
$optimized_file = $optimized_img_url;
|
||||
} else {
|
||||
$optimized_file = $this->remoteDownloadImage( $optimized_img_url );
|
||||
}
|
||||
if ( isset( $optimized_img_data['not_need_replace'] ) and $optimized_img_data['not_need_replace'] ) {
|
||||
// если картинка уже оптимизирована и провайдер её не может уменьшить - он может вернуть положительный ответ, но без самой картинки. В таком случае ничего заменять не надо
|
||||
return true;
|
||||
}
|
||||
if ( ! $optimized_file ) {
|
||||
return false;
|
||||
}
|
||||
$path = $this->path;
|
||||
|
||||
if ( ! is_file( $path ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_put_contents( $path, $optimized_file );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка картинки с удалённого сервера
|
||||
*
|
||||
* todo: RIO-18 можем ли мы создать универсальный метод для всех внешних запросов, чтобы не дублировать код?
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function remoteDownloadImage( $url ) {
|
||||
if ( ! function_exists( 'curl_version' ) ) {
|
||||
return file_get_contents( $url );
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_HEADER, 0 );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
|
||||
curl_setopt( $ch, CURLOPT_URL, $url );
|
||||
|
||||
$image_body = curl_exec( $ch );
|
||||
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
if ( $http_code != '200' ) {
|
||||
$image_body = false;
|
||||
}
|
||||
curl_close( $ch );
|
||||
|
||||
return $image_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Делает резервную копию изображения
|
||||
*/
|
||||
public function backup() {
|
||||
$backup = WRIOP_Backup::get_instance();
|
||||
|
||||
return $backup->backupCFImage( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Восстанавливает из резервной копии
|
||||
*/
|
||||
public function restore() {
|
||||
$backup = WRIOP_Backup::get_instance();
|
||||
$restored = $backup->restoreCFImage( $this );
|
||||
|
||||
if ( is_wp_error( $restored ) ) {
|
||||
return $restored;
|
||||
}
|
||||
|
||||
$optimization_data = $this->getOptimizationData();
|
||||
$optimization_data->set_result_status( 'unoptimized' );
|
||||
$optimization_data->save();
|
||||
|
||||
/**
|
||||
* Хук срабатывает после восстановления cf_image
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param RIO_Process_Queue $optimization_data
|
||||
*/
|
||||
do_action( 'wbcr/rio/cf_image_restored', $this->optimization_data );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс для работы с кастомными папками.
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIO_Folder {
|
||||
|
||||
/**
|
||||
* @var string Folder path.
|
||||
*/
|
||||
protected $path = '';
|
||||
|
||||
/**
|
||||
* @var string SHA256 folder's path hash.
|
||||
*/
|
||||
protected $uid = '';
|
||||
|
||||
/**
|
||||
* @var int Number of files in current folder.
|
||||
*/
|
||||
protected $files_count = 0;
|
||||
|
||||
/**
|
||||
* @var int Number of optimized files in current folder.
|
||||
*/
|
||||
protected $optimized_count = 0;
|
||||
|
||||
/**
|
||||
* @var int Number of errors in current folder.
|
||||
*/
|
||||
protected $errors_count = 0;
|
||||
|
||||
/**
|
||||
* WRIO_Folder constructor.
|
||||
*
|
||||
* @param array $params List of params set on the model.
|
||||
*/
|
||||
public function __construct( $params = [] ) {
|
||||
// нужна проверка пути
|
||||
foreach ( $params as $key => $value ) {
|
||||
$this->set( $key, $value );
|
||||
}
|
||||
if ( ! $this->uid ) {
|
||||
$this->uid = hash( 'sha256', $this->path );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specified object's property.
|
||||
*
|
||||
* @param string $property_name Property name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get( $property_name ) {
|
||||
if ( isset( $this->$property_name ) ) {
|
||||
return $this->$property_name;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set specified object's property.
|
||||
*
|
||||
* @param string $property_name Property name.
|
||||
* @param mixed $value Value to set.
|
||||
*/
|
||||
public function set( $property_name, $value ) {
|
||||
if ( isset( $this->$property_name ) ) {
|
||||
$this->$property_name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert object to associative array form.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray() {
|
||||
return [
|
||||
'path' => $this->path,
|
||||
'files_count' => $this->files_count,
|
||||
'uid' => $this->uid,
|
||||
'optimized_count' => $this->optimized_count,
|
||||
'errors_count' => $this->errors_count,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Обходит каталог и добавляет в индекс файлы
|
||||
*
|
||||
* @param mixed $offset отступ от начала
|
||||
* @param mixed $max_process_elements кол-во элементов за итерацию
|
||||
*
|
||||
* @return int Кол-во элементов, обработанных при индексировании
|
||||
*/
|
||||
public function indexing( $offset = 0, $max_process_elements = 100 ) {
|
||||
global $wpdb;
|
||||
|
||||
$iterator = $this->getRecursiveIterator();
|
||||
$allowed = $this->getAllowedFilesExt();
|
||||
$files = [];
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
|
||||
foreach ( $iterator as $file ) {
|
||||
$ext = substr( $file, strrpos( strtolower( $file ), '.' ) + 1 ); // получаем расширение файла
|
||||
if ( in_array( strtolower( $ext ), $allowed ) ) {
|
||||
// сделать путь относительно корня
|
||||
$files[] = $this->real_path_to_relative( $file->getPathname() );
|
||||
}
|
||||
}
|
||||
|
||||
$files = array_slice( $files, $offset, $max_process_elements );
|
||||
foreach ( $files as $file ) {
|
||||
$file = wp_normalize_path( $file );
|
||||
// $file_path = str_replace( $this->path, '', $file );
|
||||
$file_uid = hash( 'sha256', str_replace( wp_normalize_path( ABSPATH ), '', $file ) );
|
||||
$sql = $wpdb->prepare( "SELECT * FROM {$db_table} WHERE item_hash_alternative = %s AND item_hash = %s;", $this->uid, $file_uid );
|
||||
$row = $wpdb->get_row( $sql );
|
||||
|
||||
if ( empty( $row ) ) {
|
||||
// если файла нет в индексе - добавляем
|
||||
$extra_data = new WRIO_CF_Image_Extra_Data(
|
||||
[
|
||||
'file_path' => $file,
|
||||
'folder_relative_path' => $this->path,
|
||||
]
|
||||
);
|
||||
$optimization_data = new RIO_Process_Queue(
|
||||
[
|
||||
'item_type' => 'cf_image',
|
||||
'item_hash' => $file_uid, // хэш пути к файлу
|
||||
'item_hash_alternative' => $this->uid, // хэш директории будет сделан сеттером
|
||||
'original_size' => 0,
|
||||
'final_size' => 0,
|
||||
'original_mime_type' => '',
|
||||
'final_mime_type' => '',
|
||||
'result_status' => 'unoptimized',
|
||||
'processing_level' => '',
|
||||
'extra_data' => $extra_data,
|
||||
]
|
||||
);
|
||||
$optimization_data->save();
|
||||
} else {
|
||||
// делаем апдейт и выставляем ласт индекс дату. Потом у кого в индексе ласт индекс дата меньше заданной, того уже нет на диске
|
||||
}
|
||||
}
|
||||
|
||||
return count( $files ); // сколько элементов обработано при индексировании.
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет индекс на наличие несуществующих файлов.
|
||||
*
|
||||
* Находит файлы, которые пользователь удалил из папки, но в индексе они ещё есть.
|
||||
* Удаляет из из индекса, вычитает из статистики
|
||||
*
|
||||
* @param mixed $offset отсутп от начала
|
||||
* @param mixed $max_process_elements кол-во элементов за итерацию
|
||||
*
|
||||
* @return int $processed Кол-во обработанных записей. Используется для расчёта отступа
|
||||
*/
|
||||
public function syncIndex( $offset = 0, $max_process_elements = 100 ) {
|
||||
global $wpdb;
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$sql = $wpdb->prepare( "SELECT * FROM {$db_table} WHERE item_hash_alternative = %s LIMIT %d OFFSET %d;", $this->uid, $max_process_elements, $offset );
|
||||
$rows = $wpdb->get_results( $sql );
|
||||
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
|
||||
$processed = 0;
|
||||
$deleted = 0;
|
||||
|
||||
if ( ! empty( $rows ) ) {
|
||||
foreach ( $rows as $row ) {
|
||||
++$processed;
|
||||
$cf_image = new WRIO_Folder_Image( $row->id, $row );
|
||||
if ( ! $cf_image->isFileExists() ) {
|
||||
if ( $cf_image->isOptimized() ) {
|
||||
// если файл оптимизирован - вычитаем из статистики
|
||||
$optimization_data = $cf_image->getOptimizationData();
|
||||
$optimized_size = $optimization_data->get_final_size();
|
||||
$original_size = $optimization_data->get_original_size();
|
||||
$webp_optimized_size = $optimization_data->get_extra_data()->get_webp_main_size();
|
||||
$image_statistics->deductFromField( 'webp_optimized_size', $webp_optimized_size );
|
||||
$image_statistics->deductFromField( 'optimized_size', $optimized_size );
|
||||
$image_statistics->deductFromField( 'original_size', $original_size );
|
||||
}
|
||||
// если файла нет на диске - удаляем из индекса
|
||||
$wpdb->delete(
|
||||
$db_table,
|
||||
[
|
||||
'id' => $cf_image->get( 'id' ),
|
||||
],
|
||||
[ '%d' ]
|
||||
);
|
||||
++$deleted;
|
||||
}
|
||||
}
|
||||
$image_statistics->save();
|
||||
$this->reCountOptimizedFiles();
|
||||
}
|
||||
$processed = $processed - $deleted;
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed list of extensions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedFilesExt() {
|
||||
$allowed_formats = explode( ',', WRIO_Plugin::app()->getOption( 'allowed_formats', 'image/jpeg,image/png,image/gif' ) );
|
||||
$allowed = [];
|
||||
foreach ( $allowed_formats as $format ) {
|
||||
if ( $format == 'image/jpeg' ) {
|
||||
$allowed[] = 'jpg';
|
||||
$allowed[] = 'jpeg';
|
||||
} elseif ( $format == 'image/png' ) {
|
||||
$allowed[] = 'png';
|
||||
}
|
||||
}
|
||||
|
||||
// $allowed = [ 'jpg', 'jpeg', 'png' ];
|
||||
|
||||
return $allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count number of files in a folder.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countFiles() {
|
||||
$iterator = $this->getRecursiveIterator();
|
||||
$allowed = $this->getAllowedFilesExt();
|
||||
$count = 0;
|
||||
foreach ( $iterator as $file ) {
|
||||
$ext = substr( $file, strrpos( strtolower( $file ), '.' ) + 1 ); // получаем расширение файла
|
||||
if ( in_array( strtolower( $ext ), $allowed ) ) {
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count number of indexes files.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function countIndexedFiles() {
|
||||
global $wpdb;
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$sql_files = $wpdb->prepare( "SELECT COUNT(*) FROM {$db_table} WHERE item_type = %s AND item_hash_alternative = %s;", 'cf_image', $this->uid );
|
||||
$files_count = $wpdb->get_var( $sql_files );
|
||||
|
||||
return $files_count;
|
||||
}
|
||||
|
||||
public function reCountFiles() {
|
||||
$this->files_count = $this->countFiles();
|
||||
|
||||
return $this->files_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recount optimized files.
|
||||
*
|
||||
* @return int|null|string
|
||||
*/
|
||||
public function reCountOptimizedFiles() {
|
||||
global $wpdb;
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$sql = "SELECT COUNT(*) FROM {$db_table} WHERE item_type = %s AND result_status = %s AND item_hash_alternative = %s;";
|
||||
$sql_prepared = $wpdb->prepare( $sql, 'cf_image', RIO_Process_Queue::STATUS_SUCCESS, $this->uid );
|
||||
$optimized_count = $wpdb->get_var( $sql_prepared );
|
||||
$this->optimized_count = $optimized_count;
|
||||
|
||||
return $this->optimized_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove folder and deduct its size from optimized and original size.
|
||||
*/
|
||||
public function remove() {
|
||||
// удаляем файлы из индекса и переситываем стату
|
||||
global $wpdb;
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$sql = "SELECT SUM(original_size) AS original_size, SUM(final_size) AS optimized_size FROM {$db_table} WHERE item_type = %s AND result_status = %s AND item_hash_alternative = %s";
|
||||
$prepared_sql = $wpdb->prepare( $sql, 'cf_image', RIO_Process_Queue::STATUS_SUCCESS, $this->uid );
|
||||
$sum = $wpdb->get_row( $prepared_sql );
|
||||
|
||||
// Deduct from statistics
|
||||
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
|
||||
$webp_optimized_size = WRIO_Plugin::app()->updateOption( 'webp_optimized_size', 0 );
|
||||
$image_statistics->deductFromField( 'webp_optimized_size', $webp_optimized_size );
|
||||
$image_statistics->deductFromField( 'optimized_size', $sum->optimized_size );
|
||||
$image_statistics->deductFromField( 'original_size', $sum->original_size );
|
||||
$image_statistics->save();
|
||||
|
||||
// Delete from db
|
||||
$wpdb->delete(
|
||||
$db_table,
|
||||
[
|
||||
'item_hash_alternative' => $this->uid,
|
||||
'item_type' => 'cf_image',
|
||||
],
|
||||
[ '%s', '%s' ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get absolute path from relative.
|
||||
*
|
||||
* Same as 'wp_ajax_wriop_browse_dir'.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function real_path() {
|
||||
if ( is_main_site() ) {
|
||||
$base_path = get_home_path();
|
||||
} else {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$base_path = $upload_dir['basedir'] . '/';
|
||||
}
|
||||
return realpath( $base_path . $this->path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory path relative to the site root
|
||||
* Input: /home/user/test/wp.com/wp-content/uploads/custom-folder/
|
||||
* Output: /wp-content/uploads/custom-folder/
|
||||
*
|
||||
* @param string $path Путь к директории. Может быть абсолютным.
|
||||
*
|
||||
* Same as 'wp_ajax_wriop_browse_dir'.
|
||||
*
|
||||
* @return string $relative_path относительный путь
|
||||
*/
|
||||
public function real_path_to_relative( $path ) {
|
||||
if ( is_main_site() ) {
|
||||
$base_path = untrailingslashit( get_home_path() );
|
||||
} else {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$base_path = untrailingslashit( $upload_dir['basedir'] );
|
||||
}
|
||||
$relative_path = str_replace( $base_path, '', $path );
|
||||
|
||||
return $relative_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get iterator to go scan files in directory.
|
||||
*
|
||||
* @return RecursiveIteratorIterator
|
||||
*/
|
||||
public function getRecursiveIterator() {
|
||||
$iterator = new RecursiveDirectoryIterator( $this->real_path() );
|
||||
|
||||
return new RecursiveIteratorIterator( $iterator );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WRIO_Folders_List_Table
|
||||
*/
|
||||
class WRIO_Folders_List_Table extends WP_List_Table {
|
||||
|
||||
public function __construct() {
|
||||
// Set parent defaults.
|
||||
parent::__construct(
|
||||
[
|
||||
'singular' => 'media', // Singular name of the listed records.
|
||||
'plural' => 'media', // Plural name of the listed records.
|
||||
'ajax' => false, // Does this table support ajax?
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function get_columns() {
|
||||
$columns = [
|
||||
'cb' => '<input type="checkbox" />', // Render a checkbox instead of text.
|
||||
'preview' => _x( 'Preview', 'Column label', 'robin-image-optimizer' ),
|
||||
'file' => _x( 'File path', 'Column label', 'robin-image-optimizer' ),
|
||||
'folder' => _x( 'Folder', 'Column label', 'robin-image-optimizer' ),
|
||||
'status' => _x( 'Status', 'Column label', 'robin-image-optimizer' ),
|
||||
'optimization' => _x( 'Optimization', 'Column label', 'robin-image-optimizer' ),
|
||||
];
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
public function prepare_items() {
|
||||
global $wpdb;
|
||||
|
||||
$per_page = 20;
|
||||
|
||||
$hidden = [];
|
||||
$columns = $this->get_columns();
|
||||
$sortable = $this->get_sortable_columns();
|
||||
$this->_column_headers = [ $columns, $hidden, $sortable ];
|
||||
|
||||
$current_page = $this->get_pagenum();
|
||||
$offset = ( $current_page - 1 ) * $per_page;
|
||||
|
||||
$folder_uid = WRIO_Plugin::app()->request->get( 'folder-filter', null, true );
|
||||
$status = WRIO_Plugin::app()->request->get( 'status-filter', null, true );
|
||||
|
||||
if ( ! in_array( $status, [ 'success', 'unoptimized', 'error' ] ) ) {
|
||||
$status = null;
|
||||
}
|
||||
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
|
||||
$sql_filter = '';
|
||||
|
||||
if ( ! empty( $status ) ) {
|
||||
$sql_filter .= " AND result_status = '" . esc_sql( $status ) . "' ";
|
||||
}
|
||||
|
||||
if ( ! empty( $folder_uid ) ) {
|
||||
$sql_filter .= " AND item_hash_alternative = '" . esc_sql( $folder_uid ) . "' ";
|
||||
}
|
||||
|
||||
$total_items = $wpdb->get_var(
|
||||
"
|
||||
SELECT COUNT(*)
|
||||
FROM {$db_table}
|
||||
WHERE item_type = 'cf_image' {$sql_filter}"
|
||||
);
|
||||
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$db_table}
|
||||
WHERE item_type = 'cf_image' {$sql_filter}
|
||||
ORDER BY id DESC
|
||||
LIMIT %d
|
||||
OFFSET %d",
|
||||
$per_page,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $rows ) ) {
|
||||
$this->items = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( (array) $rows as $key => $row ) {
|
||||
$rows[ $key ] = new RIO_Process_Queue( $row );
|
||||
}
|
||||
|
||||
$this->items = $rows;
|
||||
|
||||
$this->set_pagination_args(
|
||||
[
|
||||
'total_items' => $total_items, // WE have to calculate the total number of items.
|
||||
'per_page' => $per_page, // WE have to determine how many items to show on a page.
|
||||
'total_pages' => ceil( $total_items / $per_page ), // WE have to calculate the total number of pages.
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function display() {
|
||||
$cf = WRIO_Custom_Folders::get_instance();
|
||||
$folders = $cf->getFolders();
|
||||
|
||||
$folder_uid = WRIO_Plugin::app()->request->get( 'folder-filter', null, true );
|
||||
$status = WRIO_Plugin::app()->request->get( 'status-filter', null, true );
|
||||
|
||||
if ( ! in_array( $status, [ 'success', 'unoptimized', 'error' ] ) ) {
|
||||
$status = null;
|
||||
}
|
||||
|
||||
$optimized_count = RIO_Process_Queue::count_by_type_status( 'cf_image', 'success' );
|
||||
$unoptimized_count = RIO_Process_Queue::count_by_type_status( 'cf_image', 'unoptimized' );
|
||||
$error_count = RIO_Process_Queue::count_by_type_status( 'cf_image', 'error' );
|
||||
|
||||
?>
|
||||
<div class="wrap wriop-files-list">
|
||||
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
||||
<form method="get" id="wriop-files-list-form" action="<?php echo admin_url( 'upload.php?page=rio-custom-media' ); ?>">
|
||||
<input type="hidden" name="page" value="rio-custom-media"/>
|
||||
<div class="wp-filter">
|
||||
<div class="filter-items">
|
||||
<label for="folder-filter" class="screen-reader-text"><?php _e( 'Filter by folder', 'robin-image-optimizer' ); ?></label>
|
||||
<select class="folder-filters" name="folder-filter" id="folder-filter">
|
||||
<option value="" selected="selected"><?php _e( 'All Folders', 'robin-image-optimizer' ); ?></option>
|
||||
<?php if ( ! empty( $folders ) ) : ?>
|
||||
<?php foreach ( (array) $folders as $folder ) : ?>
|
||||
<option <?php selected( $folder_uid, $folder->get( 'uid' ) ); ?> value="<?php echo esc_attr( $folder->get( 'uid' ) ); ?>"><?php echo esc_attr( $folder->get( 'path' ) ); ?>
|
||||
(<?php echo esc_attr( $folder->get( 'files_count' ) ); ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
<label for="status-filter" class="screen-reader-text"><?php _e( 'Filter by status', 'robin-image-optimizer' ); ?></label>
|
||||
<select class="folder-filters" name="status-filter" id="status-filter">
|
||||
<option value="" selected="selected"><?php _e( 'All Media Files', 'robin-image-optimizer' ); ?></option>
|
||||
<option <?php selected( $status, 'success' ); ?> value="success"><?php _e( 'Optimized', 'robin-image-optimizer' ); ?>
|
||||
(<?php echo esc_attr( $optimized_count ); ?>)
|
||||
</option>
|
||||
<option <?php selected( $status, 'unoptimized' ); ?> value="unoptimized"><?php _e( 'Unoptimized', 'robin-image-optimizer' ); ?>
|
||||
(<?php echo esc_attr( $unoptimized_count ); ?>)
|
||||
</option>
|
||||
<option <?php selected( $status, 'error' ); ?> value="error"><?php _e( 'Errors', 'robin-image-optimizer' ); ?>
|
||||
(<?php echo esc_attr( $error_count ); ?>)
|
||||
</option>
|
||||
</select>
|
||||
<input type="submit" id="folders-query-submit" class="button" value="Filter">
|
||||
</div>
|
||||
</div>
|
||||
<?php parent::display(); ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
protected function get_sortable_columns() {
|
||||
$sortable_columns = [];
|
||||
|
||||
return $sortable_columns;
|
||||
}
|
||||
|
||||
protected function get_bulk_actions() {
|
||||
$actions = [];
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RIO_Process_Queue $item
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function column_cb( $item ) {
|
||||
return sprintf(
|
||||
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
|
||||
$this->_args['singular'], // Let's simply repurpose the table's singular label ("movie").
|
||||
$item->get_id() // The value of the checkbox should be the record's ID.
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RIO_Process_Queue $item
|
||||
*/
|
||||
protected function column_preview( $item ) {
|
||||
|
||||
/** @var WRIO_CF_Image_Extra_Data $extra_data */
|
||||
$extra_data = $item->get_extra_data();
|
||||
|
||||
if ( ! empty( $extra_data ) ) {
|
||||
$file_relative_path = wp_normalize_path( $extra_data->get_file_path() );
|
||||
$image_url = home_url( $file_relative_path );
|
||||
|
||||
printf(
|
||||
'
|
||||
<span class="media-icon image-icon">
|
||||
<a href="%s"><img src="%s" class="attachment-60x60 size-60x60" alt="" width="60" height="60"></a>
|
||||
</span>',
|
||||
esc_url( $image_url ),
|
||||
esc_url( $image_url )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RIO_Process_Queue $item
|
||||
*/
|
||||
protected function column_file( $item ) {
|
||||
|
||||
/** @var WRIO_CF_Image_Extra_Data $extra_data */
|
||||
$extra_data = $item->get_extra_data();
|
||||
|
||||
if ( ! empty( $extra_data ) ) {
|
||||
$file_relative_path = wp_normalize_path( $extra_data->get_file_path() );
|
||||
$file_name = wp_basename( $file_relative_path );
|
||||
$image_url = home_url( $file_relative_path );
|
||||
|
||||
printf(
|
||||
'
|
||||
<p class="filename">
|
||||
<a href="%s">%s</a>
|
||||
</p>',
|
||||
esc_url( $image_url ),
|
||||
$file_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RIO_Process_Queue $item
|
||||
*/
|
||||
protected function column_folder( $item ) {
|
||||
|
||||
/** @var WRIO_CF_Image_Extra_Data $extra_data */
|
||||
$extra_data = $item->get_extra_data();
|
||||
|
||||
if ( ! empty( $extra_data ) ) {
|
||||
$file_relative_path = wp_normalize_path( $extra_data->get_file_path() );
|
||||
$file_name = wp_basename( $file_relative_path );
|
||||
$folder = str_replace( $file_name, '', $file_relative_path );
|
||||
|
||||
printf( '<code>%s</code > ', $folder );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RIO_Process_Queue $item
|
||||
*/
|
||||
protected function column_status( $item ) {
|
||||
$statuses = [
|
||||
'success' => __( 'Success', 'robin-image-optimizer' ),
|
||||
'error' => __( 'Error', 'robin-image-optimizer' ),
|
||||
'processing' => __( 'Processing', 'robin-image-optimizer' ),
|
||||
'unoptimized' => __( 'Unoptimized', 'robin-image-optimizer' ),
|
||||
'skip' => __( 'Skipped', 'robin-image-optimizer' ),
|
||||
];
|
||||
if ( isset( $statuses[ $item->get_result_status() ] ) ) {
|
||||
echo esc_attr( $statuses[ $item->get_result_status() ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RIO_Process_Queue $item
|
||||
*/
|
||||
protected function column_optimization( $item ) {
|
||||
$cf = WRIO_Custom_Folders::get_instance();
|
||||
echo $cf->getMediaColumnContent( $item->get_id() );
|
||||
}
|
||||
|
||||
protected function process_bulk_action() {
|
||||
// Detect when a bulk action is being triggered.
|
||||
if ( 'delete' === $this->current_action() ) {
|
||||
wp_die( 'Items deleted(or they would be if we had items to delete)! ' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс для работы с галереей nextgen
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIO_Nextgen_Gallery {
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @access protected
|
||||
* @var object
|
||||
*/
|
||||
protected static $_instance;
|
||||
|
||||
/**
|
||||
* @var array $nextgen_images контейнер для хранения nextgen_images
|
||||
*/
|
||||
private $nextgen_images = [];
|
||||
|
||||
/**
|
||||
* Инициализация функционала nextgen галереи. Установка хуков
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! wrio_is_active_nextgen_gallery() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once WRIOP_PLUGIN_DIR . '/includes/classes/models/class.nextgen-extra-data.php';
|
||||
require_once WRIOP_PLUGIN_DIR . '/includes/classes/class.image-nextgen.php';
|
||||
require_once WRIOP_PLUGIN_DIR . '/admin/ajax/optimization.php';
|
||||
|
||||
add_filter( 'ngg_manage_images_number_of_columns', [ $this, 'addColumns' ] );
|
||||
|
||||
add_action( 'wp_ajax_wio_ng_reoptimize_image', 'wbcr_riop_reoptimizeImage' );
|
||||
add_action( 'wp_ajax_wio_ng_restore_image', 'wbcr_riop_restoreImage' );
|
||||
add_action( 'wp_ajax_wio_process_ng_images', 'wbcr_riop_optimizeImages' );
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueueMeadiaScripts' ], 10 );
|
||||
add_action( 'ngg_delete_picture', [ $this, 'deleteImageHook' ], 10, 2 );
|
||||
add_action( 'ngg_recovered_image', [ $this, 'recoverImageHook' ], 10, 1 );
|
||||
|
||||
add_filter(
|
||||
'wbcr/rio/optimize_template/reoptimize_ajax_action',
|
||||
[
|
||||
$this,
|
||||
'reoptimizeAjaxAction',
|
||||
],
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
add_filter( 'wbcr/rio/optimize_template/restore_ajax_action', [ $this, 'restoreAjaxAction' ], 10, 2 );
|
||||
// add_filter( 'wbcr/rio/multisite_blogs', [ $this, 'multisiteBlogs' ], 10, 2 );
|
||||
add_action( 'wbcr/rio/optimize_template/optimized_percent', [ $this, 'optimizedPercent' ], 10, 2 );
|
||||
add_action( 'wbcr/riop/queue_item_saved', [ $this, 'webpSuccess' ], 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object|\WRIO_Nextgen_Gallery object Main instance.
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! isset( static::$_instance ) ) {
|
||||
static::$_instance = new static();
|
||||
}
|
||||
|
||||
return static::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает сайты, на которых установлен NextGen gallery
|
||||
* Используется в мультисайт режиме
|
||||
*
|
||||
* @param array $blogs сайты
|
||||
* @param string $type тип
|
||||
*
|
||||
* @return array сайты, на которых установлен nextgen
|
||||
*/
|
||||
/*
|
||||
public function multisiteBlogs( $blogs, $type ) {
|
||||
if ( 'nextgen' == $type ) {
|
||||
$nextgen_basename = 'nextgen-gallery/nggallery.php';
|
||||
if ( is_plugin_active_for_network( $nextgen_basename ) ) {
|
||||
return $blogs;
|
||||
} else {
|
||||
$nextgen_blogs = [];
|
||||
foreach ( $blogs as $blog ) {
|
||||
switch_to_blog( intval( $blog->blog_id ) );
|
||||
if ( is_plugin_active( $nextgen_basename ) ) {
|
||||
$nextgen_blogs[] = $blog;
|
||||
}
|
||||
restore_current_blog();
|
||||
}
|
||||
|
||||
return $nextgen_blogs;
|
||||
}
|
||||
}
|
||||
|
||||
return $blogs;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Хук возвращает ajax action для кнопки переоптимизации
|
||||
*/
|
||||
public function reoptimizeAjaxAction( $action, $type ) {
|
||||
if ( $type == 'nextgen' ) {
|
||||
return 'wio_ng_reoptimize_image';
|
||||
}
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук возвращает ajax action для кнопки восстановления
|
||||
*/
|
||||
public function restoreAjaxAction( $action, $type ) {
|
||||
if ( $type == 'nextgen' ) {
|
||||
return 'wio_ng_restore_image';
|
||||
}
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляем стили и скрипты в медиабиблиотеку
|
||||
*/
|
||||
public function enqueueMeadiaScripts( $hook ) {
|
||||
if ( strpos( $hook, 'page_nggallery-manage-gallery' ) === false ) {
|
||||
return;
|
||||
}
|
||||
wp_enqueue_style( 'wio-install-addons', WRIO_PLUGIN_URL . '/admin/assets/css/media.css', [], WRIO_Plugin::app()->getPluginVersion() );
|
||||
wp_enqueue_script( 'wio-install-addons', WRIO_PLUGIN_URL . '/admin/assets/js/single-optimization.js', [ 'jquery' ], WRIO_Plugin::app()->getPluginVersion() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет колонку оптимизации в галерею nextgen
|
||||
*/
|
||||
public function addColumns( $count ) {
|
||||
++$count;
|
||||
add_filter( 'ngg_manage_images_column_' . $count . '_header', [ $this, 'columnTitle' ] );
|
||||
add_filter( 'ngg_manage_images_column_' . $count . '_content', [ $this, 'columnContent' ], 10, 2 );
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Название колонки оптимизации в галерее
|
||||
*/
|
||||
public function columnTitle() {
|
||||
return __( 'Image optimizer', 'robin-image-optimizer' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает содержимое блока оптимизации
|
||||
*/
|
||||
public function columnContent( $output, $image ) {
|
||||
$output = $this->getMediaColumnContent( $image->pid );
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает блок оптимизации
|
||||
*/
|
||||
public function getMediaColumnContent( $image_id ) {
|
||||
|
||||
$media_library = WRIO_Media_Library::get_instance();
|
||||
$params = $this->calculateParams( $image_id );
|
||||
|
||||
return $media_library->getMediaColumnTemplate( $params, 'nextgen' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Просчитывает параметры блока оптимизации
|
||||
*
|
||||
* @param int $image_id номер изображения nextgen
|
||||
*
|
||||
* @return array $params
|
||||
*/
|
||||
public function calculateParams( $image_id ) {
|
||||
$nextgen_image = new WRIO_Image_Nextgen( $image_id );
|
||||
$isOptimized = $nextgen_image->isOptimized();
|
||||
$diff_percent = 0;
|
||||
$diff_percent_all = 0;
|
||||
$original_main_size = 0;
|
||||
$attachment_file_size = 0;
|
||||
$optimized_size = 0;
|
||||
$original_size = 0;
|
||||
$optimization_level = '';
|
||||
$error_msg = '';
|
||||
$backuped = '';
|
||||
if ( $isOptimized ) {
|
||||
$optimization_data = $nextgen_image->getOptimizationData();
|
||||
/**
|
||||
* @var WRIO_Nextgen_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
$optimization_level = $optimization_data->get_processing_level();
|
||||
$original_main_size = $extra_data->get_original_main_size();
|
||||
$attachment_file_size = $optimization_data->get_final_size();
|
||||
$optimized_size = $optimization_data->get_final_size();
|
||||
$original_size = $optimization_data->get_original_size();
|
||||
$backuped = $optimization_data->get_is_backed_up();
|
||||
$error_msg = $extra_data->get_error_msg();
|
||||
if ( $attachment_file_size and $original_main_size ) {
|
||||
$diff_percent = round( ( $original_main_size - $attachment_file_size ) * 100 / $original_main_size );
|
||||
}
|
||||
if ( $optimized_size and $original_size ) {
|
||||
$diff_percent_all = round( ( $original_size - $optimized_size ) * 100 / $original_size );
|
||||
}
|
||||
}
|
||||
$params = [
|
||||
'attachment_id' => $image_id,
|
||||
'is_optimized' => $isOptimized,
|
||||
'attach_dimensions' => 0,
|
||||
'attachment_file_size' => $attachment_file_size,
|
||||
'optimized_size' => $optimized_size,
|
||||
'original_size' => $original_size,
|
||||
'original_main_size' => $original_main_size,
|
||||
'thumbnails_optimized' => 1,
|
||||
'optimization_level' => $optimization_level,
|
||||
'error_msg' => $error_msg,
|
||||
'backuped' => $backuped,
|
||||
'diff_percent' => $diff_percent,
|
||||
'diff_percent_all' => $diff_percent_all,
|
||||
'is_skipped' => false,
|
||||
];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает объект nextgen_image
|
||||
*
|
||||
* @param int $image_id
|
||||
* @param array $image_meta
|
||||
*
|
||||
* @return WRIO_Image_Nextgen
|
||||
*/
|
||||
public function getNextgenImage( $image_id, $image_meta = false ) {
|
||||
if ( ! isset( $this->nextgen_images[ $image_id ] ) ) {
|
||||
$this->nextgen_images[ $image_id ] = new WRIO_Image_Nextgen( $image_id, $image_meta );
|
||||
}
|
||||
|
||||
return $this->nextgen_images[ $image_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Оптимизирует nextgen_image
|
||||
*
|
||||
* @param int $image_id номер картинки в таблице nextgen
|
||||
* @param string $level качество
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function optimizeNextgenImage( $image_id, $level = '' ) {
|
||||
$nextgen_image = $this->getNextgenImage( $image_id );
|
||||
$optimization_data = $nextgen_image->getOptimizationData();
|
||||
if ( 'processing' == $optimization_data->get_result_status() ) {
|
||||
return $this->deferredOptimizeNextgenImage( $image_id );
|
||||
}
|
||||
$image_statistics = WRIO_Image_Statistic_Nextgen::get_instance();
|
||||
if ( $nextgen_image->isOptimized() ) {
|
||||
$optimized_size = $optimization_data->get_final_size();
|
||||
$original_size = $optimization_data->get_original_size();
|
||||
$webp_optimized_size = $optimization_data->get_extra_data()->get_webp_main_size();
|
||||
$image_statistics->deductFromField( 'webp_optimized_size', $webp_optimized_size );
|
||||
$image_statistics->deductFromField( 'optimized_size', $optimized_size );
|
||||
$image_statistics->deductFromField( 'original_size', $original_size );
|
||||
$nextgen_image->restore();
|
||||
}
|
||||
$image_optimized_data = $nextgen_image->optimize( $level );
|
||||
$original_size = $image_optimized_data['original_size'];
|
||||
$optimized_size = $image_optimized_data['optimized_size'];
|
||||
$image_statistics->addToField( 'optimized_size', $optimized_size );
|
||||
$image_statistics->addToField( 'original_size', $original_size );
|
||||
$image_statistics->save();
|
||||
|
||||
return $image_optimized_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отложенная оптимизация nextgen_image.
|
||||
*
|
||||
* @param int $image_id
|
||||
*
|
||||
* @return array|bool array on success, false on failure.
|
||||
*/
|
||||
protected function deferredOptimizeNextgenImage( $image_id ) {
|
||||
$nextgen_image = $this->getNextgenImage( $image_id );
|
||||
$optimization_data = $nextgen_image->getOptimizationData();
|
||||
$image_processor = WIO_OptimizationTools::getImageProcessor();
|
||||
|
||||
// если текущий сервер оптимизации не поддерживает отложенную оптимизацию, а в очереди есть аттачменты - ставим им ошибку
|
||||
if ( ! $image_processor->isDeferred() ) {
|
||||
$optimization_data->set_result_status( 'error' );
|
||||
|
||||
/**
|
||||
* @var WRIO_Nextgen_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
$extra_data->set_error( 'deferred' );
|
||||
$extra_data->set_error_msg( 'server not support deferred optimization' );
|
||||
$optimization_data->set_extra_data( $extra_data );
|
||||
$optimization_data->save();
|
||||
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Server %s does not support deferred optimization', get_class( $image_processor ) ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
$optimized_data = $nextgen_image->deferredOptimization();
|
||||
if ( $optimized_data ) {
|
||||
$image_statistics = WRIO_Image_Statistic_Nextgen::get_instance();
|
||||
$image_statistics->addToField( 'optimized_size', $optimized_data['optimized_size'] );
|
||||
$image_statistics->addToField( 'original_size', $optimized_data['original_size'] );
|
||||
$image_statistics->save();
|
||||
}
|
||||
|
||||
return $optimized_data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Обработка не оптимизированных изображений
|
||||
*
|
||||
* @param int $max_process_per_request кол-во аттачментов за 1 запуск
|
||||
*
|
||||
* @return array|\WP_Error
|
||||
*/
|
||||
public function processUnoptimizedImages( $max_process_per_request = 5 ) {
|
||||
$backup = WRIOP_Backup::get_instance();
|
||||
$backup_origin_images = WRIO_Plugin::app()->getPopulateOption( 'backup_origin_images', false );
|
||||
if ( $backup_origin_images and ! $backup->isBackupWritable() ) {
|
||||
return new WP_Error( 'unwritable_backup_dir', __( 'No access for writing backups.', 'robin-image-optimizer' ) );
|
||||
}
|
||||
if ( ! $backup->isUploadWritable() ) {
|
||||
return new WP_Error( 'unwritable_upload_dir', __( 'No access for writing backups.', 'robin-image-optimizer' ) );
|
||||
}
|
||||
$image_statistics = WRIO_Image_Statistic_Nextgen::get_instance();
|
||||
|
||||
// выборка неоптимизированных изображений
|
||||
$gallery_images = $image_statistics->getUnoptimized( $max_process_per_request ); // тут будет выборка
|
||||
$total_unoptimized = $image_statistics->getUnoptimizedCount(); // тут общее кол-во неоптимизированных
|
||||
$gallery_images_count = 0;
|
||||
if ( isset( $gallery_images ) ) {
|
||||
$gallery_images_count = count( $gallery_images );
|
||||
}
|
||||
|
||||
$optimized_count = 0;
|
||||
|
||||
// обработка
|
||||
if ( $gallery_images_count ) {
|
||||
foreach ( $gallery_images as $gallery_image ) {
|
||||
$this->optimizeNextgenImage( $gallery_image->pid );
|
||||
}
|
||||
}
|
||||
|
||||
$remain = $total_unoptimized - $gallery_images_count;
|
||||
// проверяем, есть ли аттачменты в очереди на отложенную оптимизацию
|
||||
$optimized_data = $this->processDeferredOptimization();
|
||||
if ( $optimized_data ) {
|
||||
$optimized_count = $optimized_data['optimized_count'];
|
||||
$remain = $total_unoptimized - $optimized_count;
|
||||
}
|
||||
if ( $remain <= 0 ) {
|
||||
$remain = 0;
|
||||
}
|
||||
$responce = [
|
||||
'remain' => $remain,
|
||||
'end' => false,
|
||||
'last_optimized' => $image_statistics->get_last_optimized_images( $max_process_per_request ),
|
||||
'statistic' => $image_statistics->load(),
|
||||
'optimized_count' => $optimized_count,
|
||||
];
|
||||
|
||||
return $responce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отложенная оптимизация
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
protected function processDeferredOptimization() {
|
||||
global $wpdb;
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$image_id = $wpdb->get_var( "SELECT object_id FROM {$db_table} WHERE item_type = 'nextgen' and result_status = 'processing' LIMIT 1;" );
|
||||
if ( ! $image_id ) {
|
||||
return false;
|
||||
}
|
||||
$nextgen_image = $this->getNextgenImage( $image_id );
|
||||
$optimized_data = $nextgen_image->deferredOptimization();
|
||||
if ( $optimized_data ) {
|
||||
$image_statistics = WRIO_Image_Statistic_Nextgen::get_instance();
|
||||
$image_statistics->addToField( 'optimized_size', $optimized_data['optimized_size'] );
|
||||
$image_statistics->addToField( 'original_size', $optimized_data['original_size'] );
|
||||
$image_statistics->save();
|
||||
|
||||
return $optimized_data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сбрасывает текущие ошибки оптимизации
|
||||
* Позволяет изображениям, которые оптимизированы с ошибкой, заново пройти оптимизацию.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetCurrentErrors() {
|
||||
// do_action( 'wbcr/rio/multisite_current_blog' );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
|
||||
$wpdb->delete(
|
||||
$db_table,
|
||||
[
|
||||
'item_type' => 'nextgen',
|
||||
'result_status' => 'error',
|
||||
],
|
||||
[ '%s', '%s' ]
|
||||
);
|
||||
// do_action( 'wbcr/rio/multisite_restore_blog' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук срабатывает при восстановлении картинок через стандартный интерфейс nextgen
|
||||
*/
|
||||
public function recoverImageHook( $image ) {
|
||||
$image_id = isset( $image->pid ) ? $image->pid : 0;
|
||||
$nextgen_image = new WRIO_Image_Nextgen( $image_id );
|
||||
if ( $nextgen_image->isOptimized() ) {
|
||||
$optimization_data = $nextgen_image->getOptimizationData();
|
||||
$image_statistics = WRIO_Image_Statistic_Nextgen::get_instance();
|
||||
$optimized_size = $optimization_data->get_final_size();
|
||||
$original_size = $optimization_data->get_original_size();
|
||||
$webp_optimized_size = $optimization_data->get_extra_data()->get_webp_main_size();
|
||||
$image_statistics->deductFromField( 'webp_optimized_size', $webp_optimized_size );
|
||||
$image_statistics->deductFromField( 'optimized_size', $optimized_size );
|
||||
$image_statistics->deductFromField( 'original_size', $original_size );
|
||||
$image_statistics->save();
|
||||
|
||||
if ( ! $nextgen_image->restore() ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to restore Nextgen image. Object info: %s', wp_json_encode( $nextgen_image ) ) );
|
||||
}
|
||||
|
||||
$optimization_data->delete();
|
||||
}
|
||||
$this->deleteNextgenOptimizationData( $image_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук срабатывает при удалении картинки
|
||||
*/
|
||||
public function deleteImageHook( $image_id, $image ) {
|
||||
$nextgen_image = new WRIO_Image_Nextgen( $image_id );
|
||||
|
||||
if ( $nextgen_image->isOptimized() ) {
|
||||
$restored = $nextgen_image->restore();
|
||||
|
||||
if ( ! is_wp_error( $restored ) ) {
|
||||
$optimization_data = $nextgen_image->getOptimizationData();
|
||||
$image_statistics = WRIO_Image_Statistic_Nextgen::get_instance();
|
||||
$optimized_size = $optimization_data->get_final_size();
|
||||
$original_size = $optimization_data->get_original_size();
|
||||
$webp_optimized_size = $optimization_data->get_extra_data()->get_webp_main_size();
|
||||
$image_statistics->deductFromField( 'webp_optimized_size', $webp_optimized_size );
|
||||
$image_statistics->deductFromField( 'optimized_size', $optimized_size );
|
||||
$image_statistics->deductFromField( 'original_size', $original_size );
|
||||
$image_statistics->save();
|
||||
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to restore Nextgen image. Object info: %s', wp_json_encode( $nextgen_image ) ) );
|
||||
|
||||
$optimization_data->delete();
|
||||
}
|
||||
}
|
||||
$this->deleteNextgenOptimizationData( $image_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет данные по оптимизации из таблицы в базе данных
|
||||
*
|
||||
* @param int $image_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function deleteNextgenOptimizationData( $image_id = 0 ) {
|
||||
global $wpdb;
|
||||
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
|
||||
$wpdb->delete(
|
||||
$db_table,
|
||||
[
|
||||
'object_id' => $image_id,
|
||||
'item_type' => 'nextgen',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает процент оптимизации
|
||||
* Фильтр wbcr/rio/optimize_template/optimized_percent
|
||||
*
|
||||
* @param int $percent процент оптимизации
|
||||
* @param string $type тип страницы
|
||||
*
|
||||
* @return int процент оптимизации
|
||||
*/
|
||||
public function optimizedPercent( $percent, $type ) {
|
||||
if ( 'nextgen' == $type ) {
|
||||
$image_statistics = WRIO_Image_Statistic_Nextgen::get_instance();
|
||||
|
||||
return $image_statistics->getOptimizedPercent();
|
||||
}
|
||||
|
||||
return $percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохраняет WebP размер
|
||||
*
|
||||
* @param RIO_Process_Queue $queue_model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function webpSuccess( $queue_model ) {
|
||||
if ( ! class_exists( 'WRIO\WEBP\Listener' ) ) {
|
||||
return false; // если не установлена премиум версия, то WebP не активен
|
||||
}
|
||||
if ( $queue_model->get_item_type() !== WRIO\WEBP\Listener::DEFAULT_TYPE ) {
|
||||
return false;
|
||||
}
|
||||
if ( $queue_model->get_result_status() !== RIO_Process_Queue::STATUS_SUCCESS ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var RIOP_WebP_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $queue_model->get_extra_data();
|
||||
$item_type = $extra_data->get_convert_from();
|
||||
|
||||
if ( $item_type != 'nextgen' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $queue_model->object_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$optimization_data = new RIO_Process_Queue(
|
||||
[
|
||||
'object_id' => $queue_model->object_id,
|
||||
'item_type' => 'nextgen',
|
||||
]
|
||||
);
|
||||
|
||||
$optimization_data->load();
|
||||
|
||||
/**
|
||||
* @var WRIO_Nextgen_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
if ( ! $extra_data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$extra_data->set_webp_main_size( $queue_model->get_final_size() );
|
||||
$optimization_data->set_extra_data( $extra_data );
|
||||
add_filter( 'wbcr/riop/queue_item_save_execute_hook', '__return_false' );
|
||||
$optimization_data->save();
|
||||
remove_filter( 'wbcr/riop/queue_item_save_execute_hook', '__return_false' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID's of unoptimized attachments
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUnoptimizedImages() {
|
||||
$image_statistics = WRIO_Image_Statistic_Nextgen::get_instance();
|
||||
$gallery_images = $image_statistics->getUnoptimized( PHP_INT_MAX ); // тут будет выборка
|
||||
|
||||
$return = [];
|
||||
foreach ( $gallery_images as $gallery_image ) {
|
||||
$return[] = $gallery_image->pid;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс для работы с nextgen image
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIO_Image_Nextgen {
|
||||
|
||||
/**
|
||||
* @var int номер картинки в таблице nextgen
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var string путь к картинке
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* @var string путь к миниатюре
|
||||
*/
|
||||
private $thumbnail_path;
|
||||
|
||||
/**
|
||||
* @var string имя файла
|
||||
*/
|
||||
private $file;
|
||||
|
||||
/**
|
||||
* @var string имя файла миниатюры
|
||||
*/
|
||||
private $thumbnail_file;
|
||||
|
||||
/**
|
||||
* @var string УРЛ
|
||||
*/
|
||||
private $url;
|
||||
|
||||
/**
|
||||
* @var string УРЛ миниатюры
|
||||
*/
|
||||
private $thumbnail_url;
|
||||
|
||||
/**
|
||||
* @var array мета данные картинки
|
||||
*/
|
||||
private $meta;
|
||||
|
||||
/**
|
||||
* @var array мета данные галереи
|
||||
*/
|
||||
private $gallery_meta;
|
||||
|
||||
/**
|
||||
* @var RIO_Process_Queue данные по оптимизации
|
||||
*/
|
||||
private $optimization_data = null;
|
||||
|
||||
/**
|
||||
* Инициализация картинки из nextgen gallery
|
||||
*
|
||||
* @param int $image_id номер картинки в таблице nextgen
|
||||
* @param array $image_data метаданные картинки
|
||||
*/
|
||||
public function __construct( $image_id, $image_data = false ) {
|
||||
global $wpdb;
|
||||
|
||||
$this->id = $image_id;
|
||||
|
||||
if ( $image_data ) {
|
||||
$this->meta = $image_data;
|
||||
} else {
|
||||
$this->meta = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}ngg_pictures WHERE pid = " . intval( $image_id ) );
|
||||
}
|
||||
|
||||
$this->file = $this->meta->filename;
|
||||
$this->thumbnail_file = 'thumbs-' . $this->meta->filename;
|
||||
|
||||
global $wpdb;
|
||||
$this->gallery_meta = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}ngg_gallery WHERE gid = " . intval( $this->meta->galleryid ) );
|
||||
|
||||
$this->path = wp_normalize_path( ABSPATH . trailingslashit( $this->gallery_meta->path ) . $this->meta->filename );
|
||||
$this->thumbnail_path = wp_normalize_path( ABSPATH . trailingslashit( $this->gallery_meta->path ) . 'thumbs/' . $this->thumbnail_file );
|
||||
$this->url = site_url( trailingslashit( $this->gallery_meta->path ) . $this->meta->filename );
|
||||
$this->thumbnail_url = site_url( trailingslashit( $this->gallery_meta->path ) . 'thumbs/' . $this->thumbnail_file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает свойство аттачмента
|
||||
*
|
||||
* @param string $property имя свойства
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( $property ) {
|
||||
if ( isset( $this->$property ) ) {
|
||||
return $this->$property;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка на оптимизацию изображения
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOptimized() {
|
||||
$optimization_data = $this->getOptimizationData();
|
||||
if ( empty( $optimization_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( $optimization_data->is_optimized() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает данные по оптимизации
|
||||
*
|
||||
* @return RIO_Process_Queue
|
||||
*/
|
||||
public function getOptimizationData() {
|
||||
if ( empty( $this->optimization_data ) ) {
|
||||
$this->optimization_data = $this->createOptimizationData();
|
||||
$this->optimization_data->load();
|
||||
}
|
||||
|
||||
return $this->optimization_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт новый объект RIO_Process_Queue
|
||||
*
|
||||
* @return RIO_Process_Queue
|
||||
*/
|
||||
public function createOptimizationData() {
|
||||
return new RIO_Process_Queue(
|
||||
[
|
||||
'object_id' => $this->id,
|
||||
'item_type' => 'nextgen',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Оптимизирует изображения
|
||||
*
|
||||
* @param string $optimization_level качество
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function optimize( $optimization_level = '' ) {
|
||||
$is_image_backuped = $this->backup();
|
||||
|
||||
if ( is_wp_error( $is_image_backuped ) ) {
|
||||
$error_msg = $is_image_backuped->get_error_message() . PHP_EOL;
|
||||
|
||||
return [
|
||||
'errors_count' => 1,
|
||||
'original_size' => 0,
|
||||
'optimized_size' => 0,
|
||||
'optimized_count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
// делаем рисайз
|
||||
$image_processor = WIO_OptimizationTools::getImageProcessor();
|
||||
|
||||
if ( ! $optimization_level ) {
|
||||
$optimization_level = WRIO_Plugin::app()->getPopulateOption( 'image_optimization_level', 'normal' );
|
||||
}
|
||||
|
||||
if ( $optimization_level == 'custom' ) {
|
||||
$custom_quality = WRIO_Plugin::app()->getPopulateOption( 'image_optimization_level_custom', 100 );
|
||||
$optimization_level = intval( $custom_quality );
|
||||
}
|
||||
|
||||
$optimization_data = $this->createOptimizationData();
|
||||
$results = [];
|
||||
$results['processing_level'] = $optimization_level;
|
||||
$results['original_mime_type'] = '';
|
||||
$results['final_mime_type'] = '';
|
||||
|
||||
// $gallery_path = trailingslashit( $this->gallery_meta->path );
|
||||
// $main_file_path = wp_normalize_path( ABSPATH . $gallery_path . $this->meta->filename );
|
||||
// $main_file_url = home_url( $gallery_path . $this->meta->filename );
|
||||
|
||||
$main_file_path = $this->path;
|
||||
$main_file_url = $this->url;
|
||||
|
||||
clearstatcache(); // на всякий случай очистим кеш файловой статистики
|
||||
if ( ! file_exists( $main_file_path ) ) {
|
||||
$results['result_status'] = 'error';
|
||||
$results['original_size'] = 0;
|
||||
$results['final_size'] = 0;
|
||||
$extra_data = [
|
||||
'error' => 'file',
|
||||
'error_msg' => __( 'File not found', 'robin-image-optimizer' ),
|
||||
];
|
||||
$results['extra_data'] = new WRIO_Nextgen_Extra_Data( $extra_data );
|
||||
$optimization_data->configure( $results );
|
||||
$optimization_data->save();
|
||||
|
||||
return [
|
||||
'errors_count' => 1,
|
||||
'original_size' => 0,
|
||||
'optimized_size' => 0,
|
||||
'optimized_count' => 0,
|
||||
];
|
||||
}
|
||||
$original_main_size = filesize( $main_file_path ); // оптимизированный размер только главной картинки
|
||||
|
||||
$optimized_img_data = $image_processor->process(
|
||||
[
|
||||
'image_url' => $main_file_url,
|
||||
'image_path' => $main_file_path,
|
||||
'quality' => $image_processor->quality( $optimization_level ),
|
||||
'save_exif' => WRIO_Plugin::app()->getPopulateOption( 'save_exif_data', false ),
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $optimized_img_data ) ) {
|
||||
$results['result_status'] = 'error';
|
||||
$results['original_size'] = 0;
|
||||
$results['final_size'] = 0;
|
||||
$extra_data = [
|
||||
'error' => 'optimization',
|
||||
'error_msg' => $optimized_img_data->get_error_message(),
|
||||
];
|
||||
$results['extra_data'] = new WRIO_Nextgen_Extra_Data( $extra_data );
|
||||
$optimization_data->configure( $results );
|
||||
$optimization_data->save();
|
||||
|
||||
return [
|
||||
'errors_count' => 1,
|
||||
'original_size' => 0,
|
||||
'optimized_size' => 0,
|
||||
'optimized_count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
// оптимизируем thumbnail
|
||||
$optimized_thumbnail_data = $image_processor->process(
|
||||
[
|
||||
'image_url' => $this->thumbnail_url,
|
||||
'image_path' => $this->thumbnail_path,
|
||||
'quality' => $image_processor->quality( $optimization_level ),
|
||||
'save_exif' => WRIO_Plugin::app()->getPopulateOption( 'save_exif_data', false ),
|
||||
]
|
||||
);
|
||||
|
||||
// отложенная оптимизация
|
||||
if ( isset( $optimized_img_data['status'] ) && $optimized_img_data['status'] == 'processing' ) {
|
||||
$results['result_status'] = 'processing';
|
||||
$results['is_backed_up'] = $is_image_backuped;
|
||||
$results['original_size'] = 0;
|
||||
$results['final_size'] = 0;
|
||||
$extra_data = [
|
||||
'main_optimized_data' => $optimized_img_data,
|
||||
'thumbnails_optimized_data' => $optimized_thumbnail_data,
|
||||
'image_relative_path' => str_replace( untrailingslashit( ABSPATH ), '', $this->path ),
|
||||
];
|
||||
$results['extra_data'] = new WRIO_Nextgen_Extra_Data( $extra_data );
|
||||
$optimization_data->configure( $results );
|
||||
$optimization_data->save();
|
||||
|
||||
return [
|
||||
'processing' => 1,
|
||||
'original_size' => 0,
|
||||
'optimized_size' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$this->replaceOriginalFile( $optimized_img_data );
|
||||
|
||||
// некоторые провайдеры не отдают оптимизированный размер, поэтому после замены файла получаем его сами
|
||||
if ( ! $optimized_img_data['optimized_size'] ) {
|
||||
clearstatcache();
|
||||
$optimized_img_data['optimized_size'] = filesize( $main_file_path );
|
||||
}
|
||||
|
||||
// при отрицательной оптимизации ставим значение оригинала
|
||||
if ( $optimized_img_data['optimized_size'] > $original_main_size ) {
|
||||
$optimized_img_data['optimized_size'] = $original_main_size;
|
||||
}
|
||||
|
||||
$original_size = $original_main_size;
|
||||
$optimized_size = $optimized_img_data['optimized_size'];
|
||||
$optimized_main_size = $optimized_img_data['optimized_size'];
|
||||
|
||||
if ( ! is_wp_error( $optimized_thumbnail_data ) ) {
|
||||
$original_size += filesize( $this->thumbnail_path );
|
||||
$this->replaceOriginalFile( $optimized_thumbnail_data, 'thumbnail' );
|
||||
// некоторые провайдеры не отдают оптимизированный размер, поэтому после замены файла получаем его сами
|
||||
if ( ! $optimized_thumbnail_data['optimized_size'] ) {
|
||||
clearstatcache();
|
||||
$optimized_thumbnail_data['optimized_size'] = filesize( $this->thumbnail_path );
|
||||
}
|
||||
$optimized_size += $optimized_thumbnail_data['optimized_size'];
|
||||
}
|
||||
$results['result_status'] = 'success';
|
||||
$results['final_size'] = $optimized_size;
|
||||
$results['original_size'] = $original_size;
|
||||
$results['is_backed_up'] = $is_image_backuped;
|
||||
$extra_data = [
|
||||
'original_main_size' => $original_main_size,
|
||||
'optimized_main_size' => $optimized_main_size,
|
||||
'image_relative_path' => str_replace( wp_normalize_path( untrailingslashit( ABSPATH ) ), '', $this->path ),
|
||||
];
|
||||
$results['extra_data'] = new WRIO_Nextgen_Extra_Data( $extra_data );
|
||||
$mime_type = '';
|
||||
if ( function_exists( 'wp_get_image_mime' ) ) {
|
||||
$mime_type = wp_get_image_mime( $main_file_path );
|
||||
}
|
||||
$results['original_mime_type'] = $mime_type;
|
||||
$results['final_mime_type'] = $mime_type;
|
||||
$optimization_data->configure( $results );
|
||||
$optimization_data->save();
|
||||
|
||||
return [
|
||||
'errors_count' => 0,
|
||||
'original_size' => $original_size,
|
||||
'optimized_size' => $optimized_size,
|
||||
'optimized_count' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Отложенная оптимизация аттачмента
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public function deferredOptimization() {
|
||||
$results = [
|
||||
'original_size' => 0,
|
||||
'optimized_size' => 0,
|
||||
'optimized_count' => 0,
|
||||
'processing' => 1,
|
||||
];
|
||||
$image_processor = WIO_OptimizationTools::getImageProcessor();
|
||||
$optimization_data = $this->getOptimizationData();
|
||||
if ( $optimization_data->get_result_status() != 'processing' ) {
|
||||
return false;
|
||||
}
|
||||
// проверяем главную картинку
|
||||
/**
|
||||
* @var WRIO_Nextgen_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
$main_optimized_data = $extra_data->get_main_optimized_data();
|
||||
$main_image_url = '';
|
||||
if ( ! $main_optimized_data['optimized_img_url'] ) {
|
||||
$main_image_url = $image_processor->checkDeferredOptimization( $main_optimized_data );
|
||||
if ( $main_image_url ) {
|
||||
$main_optimized_data['optimized_img_url'] = $main_image_url;
|
||||
$extra_data->set_main_optimized_data( $main_optimized_data );
|
||||
}
|
||||
}
|
||||
|
||||
$thumbnails_processed = true;
|
||||
$thumbnail_optimized_data = $extra_data->get_thumbnails_optimized_data();
|
||||
if ( ! $thumbnail_optimized_data['optimized_img_url'] ) {
|
||||
$thumbnail_image_url = $image_processor->checkDeferredOptimization( $thumbnail_optimized_data );
|
||||
if ( $thumbnail_image_url ) {
|
||||
$thumbnail_optimized_data['optimized_img_url'] = $thumbnail_image_url;
|
||||
} else {
|
||||
$thumbnails_processed = false;
|
||||
}
|
||||
}
|
||||
$extra_data->set_thumbnails_optimized_data( $thumbnail_optimized_data );
|
||||
|
||||
// когда все файлы получены - сохраняем и возвращаем результат
|
||||
if ( $main_image_url && $thumbnails_processed ) {
|
||||
$original_size = 0;
|
||||
$optimized_size = 0;
|
||||
$thumbnails_count = 0;
|
||||
$original_main_size = filesize( $this->get( 'path' ) );
|
||||
$original_size = $original_size + $original_main_size;
|
||||
$this->replaceOriginalFile(
|
||||
[
|
||||
'optimized_img_url' => $main_image_url,
|
||||
]
|
||||
);
|
||||
clearstatcache();
|
||||
$optimized_main_size = filesize( $this->get( 'path' ) );
|
||||
// при отрицательной оптимизации ставим значение оригинала
|
||||
if ( $optimized_main_size > $original_main_size ) {
|
||||
$optimized_main_size = $original_main_size;
|
||||
}
|
||||
$optimized_size = $optimized_size + $optimized_main_size;
|
||||
$thumbnail_file = $this->get( 'thumbnail_path' );
|
||||
$original_size = $original_size + filesize( $thumbnail_file );
|
||||
$this->replaceOriginalFile(
|
||||
[
|
||||
'optimized_img_url' => $thumbnail_optimized_data['optimized_img_url'],
|
||||
],
|
||||
'thumbnail'
|
||||
);
|
||||
clearstatcache();
|
||||
$optimized_size = $optimized_size + filesize( $thumbnail_file );
|
||||
++$thumbnails_count;
|
||||
$mime_type = '';
|
||||
if ( function_exists( 'wp_get_image_mime' ) ) {
|
||||
$mime_type = wp_get_image_mime( $this->get( 'path' ) );
|
||||
}
|
||||
$optimization_data->configure(
|
||||
[
|
||||
'final_size' => $optimized_size,
|
||||
'original_size' => $original_size,
|
||||
'result_status' => 'success',
|
||||
'original_mime_type' => $mime_type,
|
||||
'final_mime_type' => $mime_type,
|
||||
]
|
||||
);
|
||||
$extra_data->set_original_main_size( $original_main_size );
|
||||
// удаляем промежуточные данные
|
||||
$extra_data->set_main_optimized_data( null );
|
||||
$extra_data->set_thumbnails_optimized_data( null );
|
||||
|
||||
$results['optimized_count'] = 1;
|
||||
$results['original_size'] = $original_size;
|
||||
$results['optimized_size'] = $optimized_size;
|
||||
unset( $results['processing'] );
|
||||
}
|
||||
$optimization_data->set_extra_data( $extra_data );
|
||||
$optimization_data->save();
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Заменяет оригинальный файл на оптимизированный
|
||||
*
|
||||
* @param array $optimized_img_data результат оптимизации ввиде массива данных
|
||||
* @param string $image_size Размер(thumbnail, medium ... )
|
||||
*/
|
||||
public function replaceOriginalFile( $optimized_img_data, $image_size = '' ) {
|
||||
$optimized_img_url = $optimized_img_data['optimized_img_url'];
|
||||
if ( isset( $optimized_img_data['not_need_download'] ) and $optimized_img_data['not_need_download'] ) {
|
||||
$optimized_file = $optimized_img_url;
|
||||
} else {
|
||||
$optimized_file = $this->remoteDownloadImage( $optimized_img_url );
|
||||
}
|
||||
if ( isset( $optimized_img_data['not_need_replace'] ) and $optimized_img_data['not_need_replace'] ) {
|
||||
// если картинка уже оптимизирована и провайдер её не может уменьшить - он может вернуть положительный ответ, но без самой картинки. В таком случае ничего заменять не надо
|
||||
return true;
|
||||
}
|
||||
if ( ! $optimized_file ) {
|
||||
return false;
|
||||
}
|
||||
$path = $this->path;
|
||||
if ( $image_size == 'thumbnail' ) {
|
||||
$path = $this->thumbnail_path;
|
||||
}
|
||||
|
||||
if ( ! is_file( $path ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_put_contents( $path, $optimized_file );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка картинки с удалённого сервера
|
||||
*
|
||||
* todo: RIO-18 можем ли мы создать универсальный метод для всех внешних запросов, чтобы не дублировать код?
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function remoteDownloadImage( $url ) {
|
||||
if ( ! function_exists( 'curl_version' ) ) {
|
||||
return file_get_contents( $url );
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_HEADER, 0 );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
|
||||
curl_setopt( $ch, CURLOPT_URL, $url );
|
||||
|
||||
$image_body = curl_exec( $ch );
|
||||
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
if ( $http_code != '200' ) {
|
||||
$image_body = false;
|
||||
}
|
||||
curl_close( $ch );
|
||||
|
||||
return $image_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Делает резервную копию изображения
|
||||
*/
|
||||
public function backup() {
|
||||
$backup = WRIOP_Backup::get_instance();
|
||||
|
||||
return $backup->backupNextgen( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Восстанавливает из резервной копии.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function restore() {
|
||||
|
||||
$backup = WRIOP_Backup::get_instance();
|
||||
$restored = $backup->restoreNextgen( $this );
|
||||
|
||||
if ( is_wp_error( $restored ) ) {
|
||||
return $restored;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$io_db_table = RIO_Process_Queue::table_name();
|
||||
|
||||
$wpdb->delete(
|
||||
$io_db_table,
|
||||
[
|
||||
'object_id' => $this->id,
|
||||
'item_type' => 'nextgen',
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Хук срабатывает после восстановления nextgen image
|
||||
*
|
||||
* @param RIO_Process_Queue $optimization_data
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
do_action( 'wbcr/rio/nextgen_image_restored', $this->optimization_data );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс для работы со статистическими данными по оптимизации изображений
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIO_Image_Statistic_Folders extends WRIO_Image_Statistic {
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @access protected
|
||||
* @var static
|
||||
*/
|
||||
protected static $_instance;
|
||||
|
||||
/**
|
||||
* Сохранение статистики
|
||||
*/
|
||||
public function save() {
|
||||
WRIO_Plugin::app()->updateOption( 'folders_original_size', $this->statistic['original_size'] );
|
||||
WRIO_Plugin::app()->updateOption( 'folders_optimized_size', $this->statistic['optimized_size'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка статистики и расчёт некоторых параметров
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function load() {
|
||||
$original_size = WRIO_Plugin::app()->getOption( 'folders_original_size', 0 );
|
||||
$optimized_size = WRIO_Plugin::app()->getOption( 'folders_optimized_size', 0 );
|
||||
$total_images = $this->getTotalCount();
|
||||
$error_count = RIO_Process_Queue::count_by_type_status( 'cf_image', 'error' );
|
||||
$skipped_count = RIO_Process_Queue::count_by_type_status( 'cf_image', 'skip' );
|
||||
$optimized_count = $this->getOptimizedCount();
|
||||
|
||||
if ( ! $total_images ) {
|
||||
$total_images = 0;
|
||||
if ( $original_size || $optimized_size ) {
|
||||
// если нет картинок, то и размеров не должно быть
|
||||
$original_size = $this->statistic['original_size'] = 0;
|
||||
$optimized_size = $this->statistic['optimized_size'] = 0;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
if ( ! $error_count ) {
|
||||
$error_count = 0;
|
||||
}
|
||||
if ( ! $skipped_count ) {
|
||||
$skipped_count = 0;
|
||||
}
|
||||
if ( ! $optimized_count ) {
|
||||
$optimized_count = 0;
|
||||
}
|
||||
// unoptimized count: all - optimized - error - skip
|
||||
$unoptimized_count = $total_images - $optimized_count - $error_count - $skipped_count;
|
||||
if ( $optimized_size and $original_size ) {
|
||||
$percent_diff = round( ( $original_size - $optimized_size ) * 100 / $original_size, 1 );
|
||||
$percent_diff_line = round( $optimized_size * 100 / $original_size, 0 );
|
||||
} else {
|
||||
$percent_diff = 0;
|
||||
$percent_diff_line = 100;
|
||||
}
|
||||
if ( $total_images ) {
|
||||
$optimized_images_percent = round( $optimized_count * 100 / $total_images );
|
||||
} else {
|
||||
$optimized_images_percent = 0;
|
||||
}
|
||||
|
||||
return [
|
||||
'original' => $total_images,
|
||||
'optimized' => $optimized_count,
|
||||
'optimized_percent' => $optimized_images_percent,
|
||||
'percent_line' => $percent_diff_line,
|
||||
'unoptimized' => $unoptimized_count,
|
||||
'optimized_size' => $optimized_size,
|
||||
'original_size' => $original_size,
|
||||
'save_size_percent' => $percent_diff,
|
||||
'error' => $error_count,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Общее кол-во изображений
|
||||
*/
|
||||
public function getTotalCount() {
|
||||
$cf = WRIO_Custom_Folders::get_instance();
|
||||
$current_folder = apply_filters( 'wriop_cf_current_folder', false );
|
||||
if ( $current_folder ) {
|
||||
// если нужна конкретная папка
|
||||
$folder = $cf->getFolder( $current_folder );
|
||||
if ( ! $folder ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $folder->get( 'files_count' );
|
||||
}
|
||||
$folders = $cf->getFolders();
|
||||
$total_images = 0;
|
||||
if ( ! $folders ) {
|
||||
return $total_images;
|
||||
}
|
||||
foreach ( $folders as $folder ) {
|
||||
$total_images += $folder->get( 'files_count' );
|
||||
}
|
||||
|
||||
return $total_images;
|
||||
}
|
||||
|
||||
public function getOptimizedCount() {
|
||||
$current_folder = apply_filters( 'wriop_cf_current_folder', false );
|
||||
if ( $current_folder ) {
|
||||
global $wpdb;
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$sql_optimized = $wpdb->prepare( "SELECT COUNT(*) FROM {$db_table} WHERE item_type = 'cf_image' AND item_hash_alternative = %s AND result_status = 'success';", $current_folder );
|
||||
$optimized_count = $wpdb->get_var( $sql_optimized );
|
||||
} else {
|
||||
$optimized_count = RIO_Process_Queue::count_by_type_status( 'cf_image', 'success' );
|
||||
}
|
||||
|
||||
return $optimized_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Кол-во неоптимизированных изображений
|
||||
*/
|
||||
public function getUnoptimizedCount() {
|
||||
global $wpdb;
|
||||
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$current_folder = apply_filters( 'wriop_cf_current_folder', false );
|
||||
|
||||
if ( $current_folder ) {
|
||||
$sql_unoptimized = $wpdb->prepare(
|
||||
"
|
||||
SELECT COUNT(*)
|
||||
FROM {$db_table}
|
||||
WHERE item_type = 'cf_image'
|
||||
AND item_hash_alternative = %s
|
||||
AND result_status IN (%s,%s);",
|
||||
$current_folder,
|
||||
RIO_Process_Queue::STATUS_UNOPTIMIZED,
|
||||
RIO_Process_Queue::STATUS_PROCESSING
|
||||
);
|
||||
} else {
|
||||
$sql_unoptimized = $wpdb->prepare(
|
||||
"
|
||||
SELECT COUNT(*)
|
||||
FROM {$db_table} WHERE
|
||||
item_type = 'cf_image' AND result_status IN (%s,%s);",
|
||||
RIO_Process_Queue::STATUS_UNOPTIMIZED,
|
||||
RIO_Process_Queue::STATUS_PROCESSING
|
||||
);
|
||||
}
|
||||
|
||||
$unoptimized = $wpdb->get_var( $sql_unoptimized );
|
||||
|
||||
return $unoptimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает неоптимизированные изображения
|
||||
*
|
||||
* @param int $limit ограничение выборки
|
||||
*
|
||||
* @return RIO_Process_Queue[]|array
|
||||
*/
|
||||
public function getUnoptimized( $limit = 10 ) {
|
||||
// переделать
|
||||
global $wpdb;
|
||||
|
||||
$unoptimized_items = [];
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$current_folder = apply_filters( 'wriop_cf_current_folder', false );
|
||||
|
||||
if ( $current_folder ) {
|
||||
$sql_unoptimized = $wpdb->prepare( "SELECT * FROM {$db_table} WHERE item_type = 'cf_image' AND item_hash_alternative = %s AND result_status = 'unoptimized' LIMIT %d", $current_folder, $limit );
|
||||
} else {
|
||||
$sql_unoptimized = "SELECT * FROM {$db_table} WHERE item_type = 'cf_image' AND result_status = 'unoptimized' LIMIT " . intval( $limit );
|
||||
}
|
||||
|
||||
$result = $wpdb->get_results( $sql_unoptimized );
|
||||
|
||||
if ( ! empty( $result ) ) {
|
||||
foreach ( $result as $key => $data ) {
|
||||
$unoptimized_items[ $key ] = new RIO_Process_Queue( $data );
|
||||
}
|
||||
}
|
||||
|
||||
return $unoptimized_items;
|
||||
}
|
||||
|
||||
public function getDeferredUnoptimized( $limit = 10 ) {
|
||||
global $wpdb;
|
||||
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$current_folder = apply_filters( 'wriop_cf_current_folder', false );
|
||||
if ( $current_folder ) {
|
||||
$sql_unoptimized = $wpdb->prepare( "SELECT * FROM {$db_table} WHERE item_type = 'cf_image' AND item_hash_alternative = %s AND result_status = 'processing' LIMIT %d", $current_folder, $limit );
|
||||
} else {
|
||||
$sql_unoptimized = "SELECT * FROM {$db_table} WHERE item_type = 'cf_image' and result_status = 'processing' LIMIT " . intval( $limit );
|
||||
}
|
||||
$unoptimized = $wpdb->get_results( $sql_unoptimized );
|
||||
|
||||
return $unoptimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the last optimized images.
|
||||
*
|
||||
* @param int $limit Limit.
|
||||
*
|
||||
* @return array<int, array{
|
||||
* id: int|string,
|
||||
* file_name: string,
|
||||
* url: string,
|
||||
* thumbnail_url: string,
|
||||
* original_size: string,
|
||||
* optimized_size: string,
|
||||
* webp_size?: string,
|
||||
* original_saving: string,
|
||||
* thumbnails_count: int,
|
||||
* type: string,
|
||||
* total_saving: string,
|
||||
* error_msg?: string
|
||||
* }>
|
||||
*/
|
||||
public function get_last_optimized_images( $limit = 100 ) {
|
||||
global $wpdb;
|
||||
|
||||
$items = [];
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
|
||||
$sql = $wpdb->prepare(
|
||||
"SELECT *
|
||||
FROM {$db_table} as t1
|
||||
WHERE t1.item_type = 'cf_image'
|
||||
AND t1.result_status
|
||||
IN (%s, %s)
|
||||
ORDER BY id DESC
|
||||
LIMIT %d;",
|
||||
RIO_Process_Queue::STATUS_SUCCESS,
|
||||
RIO_Process_Queue::STATUS_ERROR,
|
||||
$limit
|
||||
);
|
||||
|
||||
$optimized_images = $wpdb->get_results( $sql, ARRAY_A );
|
||||
|
||||
foreach ( $optimized_images as $row ) {
|
||||
$items[] = $this->format_for_log( new RIO_Process_Queue( $row ) );
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last optimized image record for a specific model.
|
||||
*
|
||||
* @param RIO_Process_Queue $model Queue model instance.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
* @since 1.1
|
||||
*/
|
||||
public function get_last_optimized_image( $model ) {
|
||||
$items = [];
|
||||
$items[] = $this->format_for_log( $model );
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.4
|
||||
*
|
||||
* @param int|RIO_Process_Queue $queue_model
|
||||
*/
|
||||
protected function format_for_log( $queue_model ) {
|
||||
if ( $queue_model instanceof RIO_Process_Queue ) {
|
||||
$cf_image = new WRIO_Folder_Image( $queue_model->id, $queue_model );
|
||||
} else {
|
||||
// todo: Temporarily fix
|
||||
$cf_image = new WRIO_Folder_Image( $queue_model );
|
||||
}
|
||||
|
||||
$optimization_data = $cf_image->getOptimizationData();
|
||||
|
||||
$main_file = $cf_image->get( 'path' );
|
||||
$main_saving = $total_saving = 0;
|
||||
|
||||
if ( $optimization_data->original_size ) {
|
||||
$total_saving = ( $optimization_data->original_size - $optimization_data->final_size ) * 100 / $optimization_data->original_size;
|
||||
$main_saving = $total_saving;
|
||||
}
|
||||
|
||||
$image_url = $cf_image->get( 'url' );
|
||||
$thumbnail_url = $image_url;
|
||||
|
||||
$formated_data = [
|
||||
'id' => $optimization_data->id,
|
||||
'url' => $image_url,
|
||||
'original_url' => $image_url,
|
||||
'thumbnail_url' => $thumbnail_url,
|
||||
'file_name' => wp_basename( $main_file ),
|
||||
'original_size' => size_format( $optimization_data->original_size, 2 ),
|
||||
'optimized_size' => size_format( $optimization_data->final_size, 2 ),
|
||||
'original_saving' => round( $main_saving ) . '%',
|
||||
'thumbnails_count' => 0,
|
||||
'type' => 'success',
|
||||
'total_saving' => round( $total_saving ) . '%',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var WRIO_CF_Image_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
|
||||
if ( ! empty( $extra_data ) ) {
|
||||
$webp_size = $extra_data->get_webp_main_size();
|
||||
if ( $webp_size ) {
|
||||
$webp_size = size_format( $webp_size, 2 );
|
||||
} else {
|
||||
$webp_size = '-';
|
||||
}
|
||||
|
||||
$formated_data['webp_size'] = $webp_size;
|
||||
|
||||
$error = $extra_data->get_error();
|
||||
|
||||
if ( $optimization_data->result_status === RIO_Process_Queue::STATUS_ERROR || ! empty( $error ) ) {
|
||||
$formated_data['type'] = 'error';
|
||||
|
||||
$error_message = $extra_data->get_error_msg();
|
||||
|
||||
$formated_data['error_msg'] = ! empty( $error_message ) ? $error_message : esc_html__( 'Unknown error', 'robin-image-optimizer' );
|
||||
}
|
||||
}
|
||||
|
||||
return $formated_data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс для работы со статистическими данными по оптимизации изображений
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIO_Image_Statistic_Nextgen extends WRIO_Image_Statistic {
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @access protected
|
||||
* @var static
|
||||
*/
|
||||
protected static $_instance;
|
||||
|
||||
/**
|
||||
* Сохранение статистики
|
||||
*/
|
||||
public function save() {
|
||||
WRIO_Plugin::app()->updateOption( 'nextgen_original_size', $this->statistic['original_size'] );
|
||||
WRIO_Plugin::app()->updateOption( 'nextgen_optimized_size', $this->statistic['optimized_size'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка статистики и расчёт некоторых параметров
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function load() {
|
||||
$original_size = WRIO_Plugin::app()->getOption( 'nextgen_original_size', 0 );
|
||||
$optimized_size = WRIO_Plugin::app()->getOption( 'nextgen_optimized_size', 0 );
|
||||
global $wpdb;
|
||||
$io_db_table = RIO_Process_Queue::table_name();
|
||||
|
||||
$sql_unoptimized = "SELECT COUNT(*)
|
||||
FROM {$wpdb->prefix}ngg_pictures as t1 LEFT JOIN {$io_db_table} as t2
|
||||
ON t2.item_type = 'nextgen' and t1.pid = t2.object_id
|
||||
WHERE t2.result_status Is Null or ( t2.result_status != 'success' and t2.result_status != 'error' )";
|
||||
|
||||
$error_count = RIO_Process_Queue::count_by_type_status( 'nextgen', 'error' );
|
||||
$total_images = $this->getTotalCount();
|
||||
if ( ! $total_images ) {
|
||||
$total_images = 0;
|
||||
if ( $original_size || $optimized_size ) {
|
||||
// если нет картинок, то и размеров не должно быть
|
||||
$original_size = $this->statistic['original_size'] = 0;
|
||||
$optimized_size = $this->statistic['optimized_size'] = 0;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
$unoptimized = $wpdb->get_var( $sql_unoptimized );
|
||||
if ( $optimized_size and $original_size ) {
|
||||
$percent_diff = round( ( $original_size - $optimized_size ) * 100 / $original_size, 1 );
|
||||
$percent_diff_line = round( $optimized_size * 100 / $original_size, 0 );
|
||||
} else {
|
||||
$percent_diff = 0;
|
||||
$percent_diff_line = 100;
|
||||
}
|
||||
$optimized_exists_images_count = $total_images - $unoptimized - $error_count; // оптимизированные картинки, которые сейчас есть в медиабиблиотеке
|
||||
if ( $total_images ) {
|
||||
$optimized_images_percent = round( $optimized_exists_images_count * 100 / $total_images );
|
||||
} else {
|
||||
$optimized_images_percent = 0;
|
||||
}
|
||||
|
||||
return [
|
||||
'original' => $total_images,
|
||||
'optimized' => $optimized_exists_images_count,
|
||||
'optimized_percent' => $optimized_images_percent,
|
||||
'percent_line' => $percent_diff_line,
|
||||
'unoptimized' => $unoptimized,
|
||||
'optimized_size' => $optimized_size,
|
||||
'original_size' => $original_size,
|
||||
'save_size_percent' => $percent_diff,
|
||||
'error' => $error_count,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Общее кол-во изображений nextgen
|
||||
*/
|
||||
public function getTotalCount() {
|
||||
global $wpdb;
|
||||
$sql_total = "SELECT COUNT(*) FROM {$wpdb->prefix}ngg_pictures";
|
||||
$total_images = $wpdb->get_var( $sql_total );
|
||||
|
||||
return $total_images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Кол-во неоптимизированных изображений
|
||||
*/
|
||||
public function getUnoptimizedCount() {
|
||||
global $wpdb;
|
||||
$io_db_table = RIO_Process_Queue::table_name();
|
||||
$sql_unoptimized = "SELECT COUNT(*)
|
||||
FROM {$wpdb->prefix}ngg_pictures as t1 LEFT JOIN {$io_db_table} as t2
|
||||
ON t2.item_type = 'nextgen' and t1.pid = t2.object_id
|
||||
WHERE t2.result_status Is Null or ( t2.result_status != 'success' and t2.result_status != 'error' )";
|
||||
$total_images = $wpdb->get_var( $sql_unoptimized );
|
||||
|
||||
return $total_images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает неоптимизированные изображения
|
||||
*
|
||||
* @param string $limit ограничение выборки
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUnoptimized( $limit = 10 ) {
|
||||
global $wpdb;
|
||||
$io_db_table = RIO_Process_Queue::table_name();
|
||||
$sql_unoptimized = "SELECT *
|
||||
FROM {$wpdb->prefix}ngg_pictures as t1 LEFT JOIN {$io_db_table} as t2
|
||||
ON t2.item_type = 'nextgen' and t1.pid = t2.object_id
|
||||
WHERE t2.result_status Is Null or ( t2.result_status != 'success' and t2.result_status != 'error' )
|
||||
LIMIT " . intval( $limit );
|
||||
$unoptimized = $wpdb->get_results( $sql_unoptimized );
|
||||
|
||||
return $unoptimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the last optimized images.
|
||||
*
|
||||
* @param int $limit Limit.
|
||||
*
|
||||
* @return array<int, array{
|
||||
* id: int|string,
|
||||
* file_name: string,
|
||||
* url: string,
|
||||
* thumbnail_url: string,
|
||||
* original_size: string,
|
||||
* optimized_size: string,
|
||||
* webp_size?: string,
|
||||
* original_saving: string,
|
||||
* thumbnails_count: int,
|
||||
* total_saving: string,
|
||||
* type?: string,
|
||||
* error_msg?: string
|
||||
* }>
|
||||
*/
|
||||
public function get_last_optimized_images( $limit = 100 ) {
|
||||
global $wpdb;
|
||||
$logs = [];
|
||||
$db_table = RIO_Process_Queue::table_name();
|
||||
$sql = $wpdb->prepare(
|
||||
"SELECT t1.*,t2.filename as file_name, t3.path as gallery_path
|
||||
FROM {$db_table} as t1
|
||||
LEFT JOIN {$wpdb->prefix}ngg_pictures as t2 ON t1.object_id = t2.pid
|
||||
LEFT JOIN {$wpdb->prefix}ngg_gallery as t3 ON t2.galleryid = t3.gid
|
||||
WHERE t1.item_type = 'nextgen' AND t1.result_status IN (%s, %s)
|
||||
ORDER BY id DESC
|
||||
LIMIT %d ;",
|
||||
RIO_Process_Queue::STATUS_SUCCESS,
|
||||
RIO_Process_Queue::STATUS_ERROR,
|
||||
$limit
|
||||
);
|
||||
$optimized_images = $wpdb->get_results( $sql );
|
||||
|
||||
if ( empty( $optimized_images ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ( $optimized_images as $row ) {
|
||||
$log = [];
|
||||
|
||||
$optimization_data = new RIO_Process_Queue( $row );
|
||||
|
||||
/**
|
||||
* @var WRIO_Nextgen_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $optimization_data->get_extra_data();
|
||||
|
||||
$original_main_size = 0;
|
||||
|
||||
if ( ! empty( $extra_data ) ) {
|
||||
$original_main_size = $extra_data->get_original_main_size();
|
||||
$webp_size = $extra_data->get_webp_main_size();
|
||||
if ( ! empty( $webp_size ) ) {
|
||||
$log['webp_size'] = size_format( $webp_size, 2 );
|
||||
} else {
|
||||
$log['webp_size'] = '-';
|
||||
}
|
||||
|
||||
$error = $extra_data->get_error();
|
||||
|
||||
if ( $row->result_status == RIO_Process_Queue::STATUS_ERROR || ! empty( $error ) ) {
|
||||
$log['type'] = 'error';
|
||||
|
||||
$error_message = $extra_data->get_error_msg();
|
||||
|
||||
$log['error_msg'] = ! empty( $error_message ) ? $error_message : esc_html__( 'Unknown error', 'robin-image-optimizer' );
|
||||
}
|
||||
}
|
||||
|
||||
$main_file = trailingslashit( ABSPATH ) . trailingslashit( $row->gallery_path ) . $row->file_name;
|
||||
$main_file_optimized_size = $main_saving = $total_saving = 0;
|
||||
|
||||
if ( file_exists( $main_file ) ) {
|
||||
$main_file_optimized_size = filesize( $main_file );
|
||||
}
|
||||
|
||||
if ( $original_main_size ) {
|
||||
$main_saving = ( $original_main_size - $main_file_optimized_size ) * 100 / $original_main_size;
|
||||
}
|
||||
|
||||
if ( $row->original_size ) {
|
||||
$total_saving = ( $row->original_size - $row->final_size ) * 100 / $row->original_size;
|
||||
}
|
||||
|
||||
$image_url = site_url( trailingslashit( $row->gallery_path ) . $row->file_name );
|
||||
$thumbnail_url = site_url( trailingslashit( $row->gallery_path ) . 'thumbs/thumbs-' . $row->file_name );
|
||||
|
||||
$logs[] = array_merge(
|
||||
$log,
|
||||
[
|
||||
'id' => $row->id,
|
||||
'url' => $image_url,
|
||||
'thumbnail_url' => $thumbnail_url,
|
||||
'file_name' => preg_replace( '/^.+[\\\\\\/]/', '', $main_file ),
|
||||
'original_size' => size_format( $row->original_size, 2 ),
|
||||
'optimized_size' => size_format( $row->final_size, 2 ),
|
||||
'original_saving' => round( $main_saving ) . '%',
|
||||
'thumbnails_count' => 1,
|
||||
'total_saving' => round( $total_saving ) . '%',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $logs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WP CLI commands for optimize
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIO_CLI_Commands {
|
||||
|
||||
/**
|
||||
* Start optimization
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* <scope>
|
||||
* : What to optimize?
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp robin optimize media-library
|
||||
* wp robin optimize custom-folders
|
||||
* wp robin optimize nextgen
|
||||
*
|
||||
* @when after_wp_load
|
||||
*/
|
||||
public function optimize( $args, $assoc_args ) {
|
||||
list( $scope ) = $args;
|
||||
$process_running = WRIO_Plugin::app()->getPopulateOption( 'process_running', $scope );
|
||||
|
||||
if ( ! $process_running ) {
|
||||
$processing = wrio_get_processing_class( $scope );
|
||||
if ( $scope && is_object( $processing ) ) {
|
||||
WP_CLI::log( "Scope: {$scope}" );
|
||||
$push_items = $processing->push_items();
|
||||
if ( $push_items ) {
|
||||
WP_CLI::log( "Items pushed: {$push_items}" );
|
||||
WRIO_Plugin::app()->updatePopulateOption( 'process_running', $scope );
|
||||
$processing->save()->dispatch();
|
||||
WP_CLI::log( 'Start optimize' );
|
||||
}
|
||||
} else {
|
||||
WP_CLI::error( 'Undefined scope' );
|
||||
}
|
||||
} else {
|
||||
WP_CLI::error( 'Optimize already running!' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop optimization
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* <scope>
|
||||
* : What to stop to optimize?
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp robin stop media-library
|
||||
* wp robin stop custom-folders
|
||||
* wp robin stop nextgen
|
||||
*
|
||||
* @when after_wp_load
|
||||
*/
|
||||
public function stop( $args, $assoc_args ) {
|
||||
list( $scope ) = $args;
|
||||
|
||||
$process_running = WRIO_Plugin::app()->getPopulateOption( 'process_running', $scope );
|
||||
|
||||
if ( $process_running ) {
|
||||
$processing = wrio_get_processing_class( $scope );
|
||||
if ( $scope && $processing ) {
|
||||
WP_CLI::log( "Current scope: {$scope}" );
|
||||
WRIO_Plugin::app()->updatePopulateOption( 'process_running', false );
|
||||
$processing->cancel_process();
|
||||
WP_CLI::log( "Processing scope '{$scope}' is canceled!" );
|
||||
} else {
|
||||
WP_CLI::error( 'Undefined scope' );
|
||||
}
|
||||
} else {
|
||||
WP_CLI::error( 'Optimize not running!' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start optimization
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* <scope>
|
||||
* : What to show status?
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp robin status media-library
|
||||
* wp robin status custom-folders
|
||||
* wp robin status nextgen
|
||||
*
|
||||
* @when after_wp_load
|
||||
*/
|
||||
public function status( $args, $assoc_args ) {
|
||||
list( $scope ) = $args;
|
||||
|
||||
$process_running = WRIO_Plugin::app()->getPopulateOption( 'process_running', false );
|
||||
|
||||
if ( $scope ) {
|
||||
$unoptimized = '';
|
||||
switch ( $scope ) {
|
||||
case 'media-library':
|
||||
$unoptimized = WRIO_Image_Statistic::get_unoptimized_count();
|
||||
break;
|
||||
case 'custom-folders':
|
||||
$unoptimized = WRIO_Image_Statistic_Folders::get_unoptimized_count();
|
||||
break;
|
||||
case 'nextgen':
|
||||
$unoptimized = WRIO_Image_Statistic_Nextgen::get_unoptimized_count();
|
||||
break;
|
||||
}
|
||||
|
||||
WP_CLI::log( "Scope: {$scope}" );
|
||||
WP_CLI::log( 'Status: ' . ( $process_running ? 'running' : 'stopped' ) );
|
||||
WP_CLI::log( "Remains to optimize: {$unoptimized}" );
|
||||
|
||||
} else {
|
||||
WP_CLI::error( 'Undefined scope' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WP_CLI::add_command( 'robin', 'WRIO_CLI_Commands' );
|
||||
@@ -0,0 +1,517 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Abstract class for format conversion API.
|
||||
*
|
||||
* This base class provides a unified interface for converting images to different formats (WebP, AVIF, etc.)
|
||||
* Subclasses must implement format-specific methods.
|
||||
*
|
||||
* @author Alexander Teshabaev <sasha.tesh@gmail.com>
|
||||
*/
|
||||
abstract class WRIO_Format_Converter_Api {
|
||||
|
||||
/**
|
||||
* @var string API url.
|
||||
*/
|
||||
protected $_api_url = 'https://dashboard.robinoptimizer.com/';
|
||||
|
||||
/**
|
||||
* @var RIO_Process_Queue[]|null Queue models to be processed.
|
||||
*/
|
||||
protected $_models = null;
|
||||
|
||||
/**
|
||||
* @var null|int UNIX epoch when last request was processed.
|
||||
*/
|
||||
protected $_last_request_tick = null;
|
||||
|
||||
/**
|
||||
* Get the format name for this converter (e.g., 'webp', 'avif').
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_format_name();
|
||||
|
||||
/**
|
||||
* Get the file extension for this format (e.g., '.webp', '.avif').
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_file_extension();
|
||||
|
||||
/**
|
||||
* Get the MIME type for this format (e.g., 'image/webp', 'image/avif').
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_mime_type();
|
||||
|
||||
/**
|
||||
* Get API query parameters for this format.
|
||||
*
|
||||
* @param bool $quota Whether to include quota-related parameters.
|
||||
*
|
||||
* @return array Query parameters for the API request.
|
||||
*/
|
||||
abstract protected function get_api_query_params( $quota );
|
||||
|
||||
/**
|
||||
* Check if this format is available for free tier users.
|
||||
*
|
||||
* @return bool True if free users can use this format, false if premium required.
|
||||
*/
|
||||
abstract protected function is_free_tier_supported();
|
||||
|
||||
/**
|
||||
* WRIO_Format_Converter_Api constructor.
|
||||
*
|
||||
* @param RIO_Process_Queue[] $models Items to be converted.
|
||||
*/
|
||||
public function __construct( $models ) {
|
||||
$this->_models = $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process image queue.
|
||||
*
|
||||
* When attachment has multiple thumbnails, all of them would be converted one after another.
|
||||
*
|
||||
* @param bool $quota Decrement quota?
|
||||
*
|
||||
* @return bool True on success execution, false on failure or missing item in queue.
|
||||
*/
|
||||
public function process_image_queue( $quota = false ) {
|
||||
$thumb_count = count( $this->_models ) - 1;
|
||||
|
||||
foreach ( $this->_models as $model ) {
|
||||
try {
|
||||
/**
|
||||
* The data.
|
||||
*
|
||||
* @var RIOP_WebP_Extra_Data|null $extra_data The extra data.
|
||||
*/
|
||||
$extra_data = $model->get_extra_data();
|
||||
|
||||
if ( null === $extra_data ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$response = $this->request( $model, $quota );
|
||||
|
||||
if ( $this->can_save( $response ) && $this->save_file( $response, $model ) ) {
|
||||
$extra_data->set_thumbnails_count( $thumb_count );
|
||||
$model->set_extra_data( $extra_data );
|
||||
|
||||
$this->update( $model );
|
||||
}
|
||||
} catch ( Throwable $throwable ) {
|
||||
WRIO_Plugin::app()->logger->error(
|
||||
sprintf(
|
||||
'%1$s conversion failed for queue item #%2$d with unexpected error: %3$s in %4$s:%5$d',
|
||||
ucfirst( $this->get_format_name() ),
|
||||
$model->get_id(),
|
||||
$throwable->getMessage(),
|
||||
$throwable->getFile(),
|
||||
$throwable->getLine()
|
||||
)
|
||||
);
|
||||
|
||||
$model->mark_as_error( $throwable->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request API to convert image.
|
||||
*
|
||||
* @param RIO_Process_Queue $model Queue model.
|
||||
* @param bool $quota Decrement quota?
|
||||
*
|
||||
* @return array|bool|WP_Error
|
||||
*/
|
||||
public function request( $model, $quota = false ) {
|
||||
|
||||
if ( $this->_last_request_tick === null ) {
|
||||
$this->_last_request_tick = time();
|
||||
} else {
|
||||
if ( is_int( $this->_last_request_tick ) && ( time() - $this->_last_request_tick ) < 1 ) {
|
||||
// Need to have some rest before calling REST :D to comply with API request limit
|
||||
sleep( 2 );
|
||||
}
|
||||
|
||||
$this->_last_request_tick = time();
|
||||
}
|
||||
|
||||
$is_premium = wrio_is_license_activate();
|
||||
|
||||
// Check if this format requires premium license
|
||||
if ( ! $is_premium && ! $this->is_free_tier_supported() ) {
|
||||
WRIO_Plugin::app()->logger->warning( sprintf( 'To use %s compression you need a premium license', $this->get_format_name() ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Premium users need a valid license key
|
||||
if ( $is_premium && ! wrio_get_license_key() ) {
|
||||
WRIO_Plugin::app()->logger->error( 'Unable to get license to make proper request to the API' );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$transient_string = md5( WRIO_Plugin::app()->getPrefix() . '_processing_image' . $model->get_item_hash() );
|
||||
|
||||
$transient_value = get_transient( $transient_string );
|
||||
|
||||
if ( is_numeric( $transient_value ) && (int) $transient_value === 1 ) {
|
||||
WRIO_Plugin::app()->logger->info( sprintf( 'Skipping to wp_remote_get() as transient "%s" already exist. Usually it means that no request was returned yet', $transient_string ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
set_transient( $transient_string, 1 );
|
||||
|
||||
try {
|
||||
$url = $this->_api_url . ( $is_premium ? 'v1/image/convert' : 'v1/free/image/convert' );
|
||||
|
||||
/**
|
||||
* @var RIOP_WebP_Extra_Data $extra_data
|
||||
*/
|
||||
$extra_data = $model->get_extra_data();
|
||||
|
||||
$multipart_boundary = '--------------------------' . microtime( true );
|
||||
|
||||
// Get format-specific parameters
|
||||
$format_params = $this->get_api_query_params( $quota );
|
||||
|
||||
// Build multipart body with form fields FIRST
|
||||
$body = '';
|
||||
|
||||
// Add format parameters as form fields
|
||||
foreach ( $format_params as $name => $value ) {
|
||||
$body .= '--' . $multipart_boundary . "\r\n";
|
||||
$body .= 'Content-Disposition: form-data; name="' . $name . '"' . "\r\n\r\n";
|
||||
$body .= $value . "\r\n";
|
||||
}
|
||||
|
||||
// Add image URL if available (use encoded version to preserve special characters)
|
||||
$source_url = $extra_data->get_source_src( false );
|
||||
if ( ! empty( $source_url ) ) {
|
||||
$body .= '--' . $multipart_boundary . "\r\n";
|
||||
$body .= 'Content-Disposition: form-data; name="image_url"' . "\r\n\r\n";
|
||||
$body .= wrio_encode_image_url( $source_url ) . "\r\n";
|
||||
}
|
||||
|
||||
// Then add the file
|
||||
// Check if backup exists and use it for conversion (works for original and thumbnails)
|
||||
$source_file_path = $extra_data->get_source_path();
|
||||
$backup_enabled = \WRIO_Plugin::app()->getPopulateOption( 'backup_origin_images', false );
|
||||
|
||||
if ( $backup_enabled ) {
|
||||
$backup = \WIO_Backup::get_instance();
|
||||
$size_name = $extra_data->get_converted_from_size(); // 'original', 'thumbnail', 'medium', etc.
|
||||
$backup_path = $backup->getAttachmentBackupPath( $model->get_object_id(), $size_name );
|
||||
|
||||
if ( ! empty( $backup_path ) && file_exists( $backup_path ) ) {
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( '%s conversion: Using backup file for %s: %s', strtoupper( $this->get_format_name() ), $size_name, $backup_path ) );
|
||||
$source_file_path = $backup_path;
|
||||
} else {
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( '%s conversion: No backup found for %s, using current file: %s', strtoupper( $this->get_format_name() ), $size_name, $source_file_path ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $source_file_path ) || ! file_exists( $source_file_path ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( '%s conversion: Source file is missing, unable to build request payload. Path: %s', strtoupper( $this->get_format_name() ), empty( $source_file_path ) ? '*empty path*' : $source_file_path ) );
|
||||
|
||||
return new WP_Error( 'http_request_failed', 'Source image file is missing.' );
|
||||
}
|
||||
|
||||
$file_contents = file_get_contents( $source_file_path );
|
||||
|
||||
if ( false === $file_contents ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( '%s conversion: Failed to read source file contents from %s.', strtoupper( $this->get_format_name() ), $source_file_path ) );
|
||||
|
||||
return new WP_Error( 'http_request_failed', 'Failed to read the source image file.' );
|
||||
}
|
||||
|
||||
$body .= '--' . $multipart_boundary . "\r\n";
|
||||
$body .= 'Content-Disposition: form-data; name="file"; filename="' . basename( $source_file_path ) . '"' . "\r\n";
|
||||
$body .= 'Content-Type: ' . $model->get_original_mime_type() . "\r\n\r\n";
|
||||
$body .= $file_contents . "\r\n";
|
||||
|
||||
$body .= '--' . $multipart_boundary . "--\r\n";
|
||||
|
||||
if ( $is_premium ) {
|
||||
$headers = [
|
||||
// should be base64 encoded, otherwise API would fail authentication
|
||||
'Authorization' => 'Bearer ' . base64_encode( wrio_get_license_key() ),
|
||||
'PluginId' => wrio_get_freemius_plugin_id(),
|
||||
'X-License-Source' => wrio_get_license_source(),
|
||||
'X-Site-Url' => home_url(),
|
||||
'Content-Type' => 'multipart/form-data; boundary=' . $multipart_boundary,
|
||||
];
|
||||
} else {
|
||||
$headers = [
|
||||
'Authorization' => 'Bearer ' . base64_encode( home_url() ),
|
||||
'Content-Type' => 'multipart/form-data; boundary=' . $multipart_boundary,
|
||||
'X-Site-Url' => home_url(),
|
||||
];
|
||||
}
|
||||
|
||||
return wp_remote_post(
|
||||
$url,
|
||||
[
|
||||
'timeout' => 60,
|
||||
'headers' => $headers,
|
||||
'body' => $body,
|
||||
]
|
||||
);
|
||||
} finally {
|
||||
delete_transient( $transient_string );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response can be saved.
|
||||
*
|
||||
* @param array|WP_Error|false $response
|
||||
*
|
||||
* @return bool True means response image was successfully saved, false on failure.
|
||||
*/
|
||||
public function can_save( $response ) {
|
||||
WRIO_Plugin::app()->logger->info( sprintf( '%s conversion: Checks to save a %s by response.', ucfirst( $this->get_format_name() ), $this->get_format_name() ) );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Error response from API. Code: %s, error: %s', $response->get_error_code(), $response->get_error_message() ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( false === $response ) {
|
||||
WRIO_Plugin::app()->logger->error( 'Unknown response returned from API or it was not requested, failing to process response' );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for premium API response (binary with content-disposition header)
|
||||
$content_disposition = wp_remote_retrieve_header( $response, 'content-disposition' );
|
||||
|
||||
if ( 0 === strpos( $content_disposition, 'attachment;' ) ) {
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
|
||||
if ( empty( $body ) ) {
|
||||
WRIO_Plugin::app()->logger->error( 'Response returned content-disposition header as "attachment;", but empty body returned, failing to proceed' );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
WRIO_Plugin::app()->logger->info( sprintf( '%s conversion: Image can be saved (premium format).', ucfirst( $this->get_format_name() ) ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for free API response (JSON with download URL)
|
||||
$response_text = wp_remote_retrieve_body( $response );
|
||||
|
||||
if ( ! empty( $response_text ) ) {
|
||||
$response_json = json_decode( $response_text );
|
||||
|
||||
if ( ! empty( $response_json ) ) {
|
||||
// Check for successful free API response
|
||||
if (
|
||||
isset( $response_json->status ) && 'ok' === $response_json->status
|
||||
&& isset( $response_json->response->dest ) && ! empty( $response_json->response->dest )
|
||||
) {
|
||||
WRIO_Plugin::app()->logger->info( sprintf( '%s conversion: Image can be saved (free format with URL).', ucfirst( $this->get_format_name() ) ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
if ( isset( $response_json->error ) && ! empty( $response_json->error ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Unable to convert attachment as API returned error: "%s"', wp_json_encode( $response_json ) ) );
|
||||
}
|
||||
|
||||
if ( isset( $response_json->status ) && 401 === (int) $response_json->status ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Error response from API. Code: %s, error: %s', $response_json->message, $response_json->code ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save file from response.
|
||||
*
|
||||
* It is assumed that it was checked by can_save() method.
|
||||
*
|
||||
* @param array|WP_Error|false $response
|
||||
* @param RIO_Process_Queue $queue_model
|
||||
*
|
||||
* @return bool
|
||||
* @see can_save() for further information.
|
||||
*/
|
||||
public function save_file( $response, $queue_model ) {
|
||||
try {
|
||||
$save_path = $this->get_save_path( $queue_model );
|
||||
} catch ( Throwable $exception ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Unable to process response failed to get save path: "%s"', $exception->getMessage() ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
WRIO_Plugin::app()->logger->info( sprintf( '%s conversion: Try to save %s image in %s.', ucfirst( $this->get_format_name() ), $this->get_format_name(), $save_path ) );
|
||||
|
||||
// Check if this is a free API response (JSON with download URL)
|
||||
$response_text = wp_remote_retrieve_body( $response );
|
||||
$response_json = json_decode( $response_text );
|
||||
|
||||
if (
|
||||
! empty( $response_json ) && isset( $response_json->status ) && 'ok' === $response_json->status
|
||||
&& isset( $response_json->response->dest ) && ! empty( $response_json->response->dest )
|
||||
) {
|
||||
// Free API: Download image from the provided URL
|
||||
$download_url = $response_json->response->dest;
|
||||
WRIO_Plugin::app()->logger->info( sprintf( '%s conversion: Downloading from free API URL: %s', ucfirst( $this->get_format_name() ), $download_url ) );
|
||||
|
||||
$download_response = wp_remote_get( $download_url, [ 'timeout' => 60 ] );
|
||||
|
||||
if ( is_wp_error( $download_response ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to download converted image from %s: %s', $download_url, $download_response->get_error_message() ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $download_response );
|
||||
} else {
|
||||
// Premium API: Image data is directly in the response body
|
||||
$body = $response_text;
|
||||
}
|
||||
|
||||
$file_saved = @file_put_contents( $save_path, $body );
|
||||
|
||||
if ( ! $file_saved ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to save file "%s" with file_put_contents()', $save_path ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
WRIO_Plugin::app()->logger->info( sprintf( '%s conversion: Image saved successfully!', ucfirst( $this->get_format_name() ) ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update processing item data to finish its cycle.
|
||||
*
|
||||
* @param RIO_Process_Queue $queue_model Queue model to be update.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update( $queue_model ) {
|
||||
|
||||
try {
|
||||
$save_path = $this->get_save_path( $queue_model );
|
||||
} catch ( \Exception $exception ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Unable to update queue model #%s as of exception: %s', $queue_model->get_id(), $exception->getMessage() ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$queue_model->result_status = RIO_Process_Queue::STATUS_SUCCESS;
|
||||
$queue_model->final_size = wrio_get_file_size( $save_path );
|
||||
|
||||
$image_statistics = WRIO_Image_Statistic::get_instance();
|
||||
wp_suspend_cache_addition( true ); // Stop caching
|
||||
$stat_field = $this->get_format_name() . '_optimized_size';
|
||||
$image_statistics->addToField( $stat_field, $queue_model->final_size );
|
||||
$image_statistics->save();
|
||||
wp_suspend_cache_addition(); // Resume caching
|
||||
|
||||
/**
|
||||
* @var RIOP_WebP_Extra_Data $updated_extra_data
|
||||
*/
|
||||
$updated_extra_data = $queue_model->get_extra_data();
|
||||
$updated_extra_data->set_converted_src( $this->get_save_url( $queue_model ) );
|
||||
$updated_extra_data->set_converted_path( $save_path );
|
||||
|
||||
$queue_model->extra_data = $updated_extra_data;
|
||||
|
||||
/**
|
||||
* Hook fires after successful format conversion
|
||||
*
|
||||
* @param RIO_Process_Queue $queue_model
|
||||
* @param string $format Format name ('webp', 'avif', etc.)
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
do_action( 'wbcr/rio/format_conversion_success', $queue_model, $this->get_format_name() );
|
||||
|
||||
// Backward compatibility hook for WebP
|
||||
if ( $this->get_format_name() === 'webp' ) {
|
||||
do_action( 'wbcr/rio/webp_success', $queue_model );
|
||||
}
|
||||
|
||||
return $queue_model->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete save url.
|
||||
*
|
||||
* @param RIO_Process_Queue $queue_model Instance of queue item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_save_url( $queue_model ) {
|
||||
/**
|
||||
* @var $extra_data RIOP_WebP_Extra_Data
|
||||
*/
|
||||
$extra_data = $queue_model->get_extra_data();
|
||||
|
||||
if ( empty( $extra_data ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Unable to get extra data for queue item #%s', $queue_model->get_id() ) );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$origin_file_name = wp_basename( $extra_data->get_source_src() );
|
||||
$converted_file_name = trim( wp_basename( $extra_data->get_source_path() ) ) . $this->get_file_extension();
|
||||
|
||||
return str_replace( $origin_file_name, $converted_file_name, $extra_data->get_source_src() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get absolute save path.
|
||||
*
|
||||
* @param \RIO_Process_Queue $queue_model
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception on failure to create missing directory
|
||||
*/
|
||||
public function get_save_path( $queue_model ) {
|
||||
/**
|
||||
* @var $extra_data RIOP_WebP_Extra_Data
|
||||
*/
|
||||
$extra_data = $queue_model->get_extra_data();
|
||||
|
||||
if ( empty( $extra_data ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Unable to get extra data for queue item #%s', $queue_model->get_id() ) );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = dirname( $extra_data->get_source_path() );
|
||||
|
||||
// Create DIR when does not exist
|
||||
if ( ! file_exists( $path ) ) {
|
||||
$message = sprintf( 'Failed to create directory %s with mode %s recursively', $path, 0755 );
|
||||
WRIO_Plugin::app()->logger->error( $message );
|
||||
throw new \Exception( $message );
|
||||
}
|
||||
|
||||
return trailingslashit( $path ) . trim( wp_basename( $extra_data->get_source_path() ) ) . $this->get_file_extension();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AVIF format converter implementation.
|
||||
*
|
||||
* Converts images to AVIF format using the Robin Image Optimizer API.
|
||||
* AVIF provides superior compression compared to WebP but requires premium license.
|
||||
*
|
||||
* @author Alexander Teshabaev <sasha.tesh@gmail.com>
|
||||
*/
|
||||
class WRIO_Format_Converter_AVIF extends WRIO_Format_Converter_Api {
|
||||
|
||||
/**
|
||||
* Get the format name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_format_name() {
|
||||
return 'avif';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file extension for AVIF format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_file_extension() {
|
||||
return '.avif';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MIME type for AVIF format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_mime_type() {
|
||||
return 'image/avif';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API query parameters for AVIF conversion.
|
||||
*
|
||||
* @param bool $quota Whether to include quota-related parameters.
|
||||
*
|
||||
* @return array Query parameters for the API request.
|
||||
*/
|
||||
protected function get_api_query_params( $quota ) {
|
||||
// AVIF does not use 'type' parameter per requirements
|
||||
return [ 'format' => 'avif' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if AVIF format is available for free tier users.
|
||||
*
|
||||
* @return bool False - AVIF requires premium license.
|
||||
*/
|
||||
protected function is_free_tier_supported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Factory class for creating format converter instances.
|
||||
*
|
||||
* Provides a centralized way to create format converters and detect enabled formats.
|
||||
*
|
||||
* @author Alexander Teshabaev <sasha.tesh@gmail.com>
|
||||
*/
|
||||
class WRIO_Format_Converter_Factory {
|
||||
|
||||
/**
|
||||
* Create a format converter instance based on the specified format.
|
||||
*
|
||||
* @param RIO_Process_Queue[] $models Queue models to process.
|
||||
* @param string $format Format name ('webp' or 'avif').
|
||||
*
|
||||
* @return WRIO_Format_Converter_Api Format converter instance.
|
||||
*/
|
||||
public static function create( $models, $format ) {
|
||||
switch ( $format ) {
|
||||
case 'avif':
|
||||
return new WRIO_Format_Converter_AVIF( $models );
|
||||
case 'webp':
|
||||
default:
|
||||
return new WRIO_Format_Converter_WebP( $models );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of enabled conversion formats.
|
||||
*
|
||||
* @return string[] Array of enabled format names ('webp', 'avif').
|
||||
*/
|
||||
public static function get_enabled_formats() {
|
||||
$formats = [];
|
||||
|
||||
if ( self::is_avif_enabled() ) {
|
||||
$formats[] = 'avif';
|
||||
}
|
||||
|
||||
if ( self::is_webp_enabled() ) {
|
||||
$formats[] = 'webp';
|
||||
}
|
||||
|
||||
return $formats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WebP conversion is enabled.
|
||||
*
|
||||
* @return bool True if WebP conversion is enabled.
|
||||
*/
|
||||
public static function is_webp_enabled() {
|
||||
$option = WRIO_Plugin::app()->getPopulateOption( 'convert_webp_format', false );
|
||||
|
||||
return $option === true || $option === 1 || $option === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if AVIF conversion is enabled.
|
||||
*
|
||||
* @return bool True if AVIF conversion is enabled and user has premium license.
|
||||
*/
|
||||
public static function is_avif_enabled() {
|
||||
if ( ! wrio_is_license_activate() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$option = WRIO_Plugin::app()->getPopulateOption( 'convert_avif_format', false );
|
||||
|
||||
return $option === true || $option === 1 || $option === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any format conversion is enabled.
|
||||
*
|
||||
* @return bool True if WebP or AVIF conversion is enabled.
|
||||
*/
|
||||
public static function is_format_conversion_enabled() {
|
||||
return self::is_webp_enabled() || self::is_avif_enabled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* WebP format converter implementation.
|
||||
*
|
||||
* Converts images to WebP format using the Robin Image Optimizer API.
|
||||
*
|
||||
* @author Alexander Teshabaev <sasha.tesh@gmail.com>
|
||||
*/
|
||||
class WRIO_Format_Converter_WebP extends WRIO_Format_Converter_Api {
|
||||
|
||||
/**
|
||||
* Get the format name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_format_name() {
|
||||
return 'webp';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file extension for WebP format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_file_extension() {
|
||||
return '.webp';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MIME type for WebP format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_mime_type() {
|
||||
return 'image/webp';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API query parameters for WebP conversion.
|
||||
*
|
||||
* @param bool $quota Whether to include quota-related parameters.
|
||||
*
|
||||
* @return array Query parameters for the API request.
|
||||
*/
|
||||
protected function get_api_query_params( $quota ) {
|
||||
$params = [ 'format' => 'webp' ];
|
||||
|
||||
if ( $quota ) {
|
||||
$params['type'] = 'webp'; // Only add 'type' for quota check
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WebP format is available for free tier users.
|
||||
*
|
||||
* @return bool True - WebP is available for free users.
|
||||
*/
|
||||
protected function is_free_tier_supported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static wrapper for get_save_path for backward compatibility.
|
||||
*
|
||||
* @param \RIO_Process_Queue $queue_model
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function get_save_path_static( $queue_model ) {
|
||||
$extra_data = $queue_model->get_extra_data();
|
||||
|
||||
if ( empty( $extra_data ) ) {
|
||||
WRIO_Plugin::app()->logger->error( sprintf( 'Unable to get extra data for queue item #%s', $queue_model->get_id() ) );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = dirname( $extra_data->get_source_path() );
|
||||
|
||||
if ( ! file_exists( $path ) ) {
|
||||
$message = sprintf( 'Failed to create directory %s with mode %s recursively', $path, 0755 );
|
||||
WRIO_Plugin::app()->logger->error( $message );
|
||||
throw new \Exception( $message );
|
||||
}
|
||||
|
||||
return trailingslashit( $path ) . trim( wp_basename( $extra_data->get_source_path() ) ) . '.webp';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class WRIO_Webp_Hash_Src holds hash data about type, source src and normalized src which is mainly used to compare
|
||||
* or replace data.
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class WRIO_Url {
|
||||
/**
|
||||
* Check whether URI is valid or not.
|
||||
*
|
||||
* @param string $src Image path.
|
||||
* @param bool $decode Whether to decode src.
|
||||
*
|
||||
* @return null|string NULL on failure to get valid uri.
|
||||
*/
|
||||
public static function normalize( $src, $decode = true ) {
|
||||
$url_parts = wp_parse_url( $src );
|
||||
|
||||
// Unsupported scheme.
|
||||
if ( isset( $url_parts['scheme'] ) && 'http' !== $url_parts['scheme'] && 'https' !== $url_parts['scheme'] ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is a relative path, try to get the URL.
|
||||
if ( ! isset( $url_parts['host'] ) && ! isset( $url_parts['scheme'] ) ) {
|
||||
$src = site_url( $src );
|
||||
}
|
||||
|
||||
$content_url = content_url();
|
||||
$content_url_http = str_replace( 'https://', 'http://', $content_url );
|
||||
if ( false === strpos( $src, $content_url ) && false === strpos( $src, $content_url_http ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( $decode ) {
|
||||
return urldecode( $src );
|
||||
}
|
||||
|
||||
return $src;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class WRIO_Nextgen_Extra_Data is a DTO model for `nextgen` post type used for `extra_data`
|
||||
* property in RIO_Process_Queue.
|
||||
*
|
||||
* @see RIO_Process_Queue::$extra_data for further information
|
||||
*/
|
||||
class WRIO_CF_Image_Extra_Data extends RIO_Base_Extra_Data {
|
||||
|
||||
/**
|
||||
* @var string путь к файлу относительно папки
|
||||
*/
|
||||
protected $file_path = null;
|
||||
|
||||
/**
|
||||
* @var string тип ошибки
|
||||
*/
|
||||
protected $error = null;
|
||||
|
||||
/**
|
||||
* @var string текст сообщения об ошибке
|
||||
*/
|
||||
protected $error_msg = null;
|
||||
|
||||
/**
|
||||
* @var int оригинальный размер основного файла
|
||||
*/
|
||||
protected $original_main_size = null;
|
||||
|
||||
/**
|
||||
* @var int оптимизированный размер основного файла
|
||||
*/
|
||||
protected $optimized_main_size = null;
|
||||
|
||||
/**
|
||||
* @var array ответ от сервера оптимизации по основному файлу
|
||||
*/
|
||||
protected $main_optimized_data = null;
|
||||
|
||||
/**
|
||||
* @var string путь к папке относительно корня WP
|
||||
*/
|
||||
protected $folder_relative_path = null;
|
||||
|
||||
/**
|
||||
* @var int размер основного изображения
|
||||
*/
|
||||
protected $webp_main_size = null;
|
||||
|
||||
/**
|
||||
* get_file_path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_file_path() {
|
||||
return $this->file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_file_path
|
||||
*
|
||||
* @param string $file_path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_file_path( $file_path ) {
|
||||
$this->file_path = $file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_error
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_error() {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_error
|
||||
*
|
||||
* @param string $error
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_error( $error ) {
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_error_msg
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_error_msg() {
|
||||
return $this->error_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_error_msg
|
||||
*
|
||||
* @param string $error_msg
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_error_msg( $error_msg ) {
|
||||
$this->error_msg = $error_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_original_main_size
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_original_main_size() {
|
||||
return $this->original_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_original_main_size
|
||||
*
|
||||
* @param int $original_main_size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_original_main_size( $original_main_size ) {
|
||||
$this->original_main_size = $original_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_optimized_main_size
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_optimized_main_size() {
|
||||
return $this->optimized_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_optimized_main_size
|
||||
*
|
||||
* @param int $optimized_main_size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_optimized_main_size( $optimized_main_size ) {
|
||||
$this->optimized_main_size = $optimized_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_main_optimized_data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_main_optimized_data() {
|
||||
return (array) $this->main_optimized_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_main_optimized_data
|
||||
*
|
||||
* @param array $main_optimized_data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_main_optimized_data( $main_optimized_data ) {
|
||||
$this->main_optimized_data = $main_optimized_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_folder_relative_path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_folder_relative_path() {
|
||||
return $this->folder_relative_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_folder_relative_path
|
||||
*
|
||||
* @param string $folder_relative_path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_folder_relative_path( $folder_relative_path ) {
|
||||
$this->folder_relative_path = $folder_relative_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает абсолютный путь к папке
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_folder_absolute_path() {
|
||||
$relative_path = $this->get_folder_relative_path();
|
||||
// Use get_home_path() to match how real_path_to_relative() calculates relative paths
|
||||
$base_path = is_main_site() ? get_home_path() : wp_upload_dir()['basedir'] . '/';
|
||||
|
||||
return wp_normalize_path( untrailingslashit( $base_path ) . $relative_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает путь к изображению относительно корня WP
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_relative_path() {
|
||||
return wp_normalize_path( $this->get_file_path() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает абсолютный путь к изображению
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_absolute_path() {
|
||||
$relative_path = $this->get_image_relative_path();
|
||||
// Use get_home_path() to match how real_path_to_relative() calculates relative paths
|
||||
$base_path = is_main_site() ? get_home_path() : wp_upload_dir()['basedir'] . '/';
|
||||
|
||||
return wp_normalize_path( untrailingslashit( $base_path ) . $relative_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает URL изображения
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_url() {
|
||||
$relative_path = $this->get_image_relative_path();
|
||||
|
||||
return home_url( $relative_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает WebP размер основного изображения
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_webp_main_size() {
|
||||
return $this->webp_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает WebP размер основного изображения
|
||||
*
|
||||
* @param int $webp_main_size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_webp_main_size( $webp_main_size ) {
|
||||
$this->webp_main_size = $webp_main_size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class WRIO_Nextgen_Extra_Data is a DTO model for `nextgen` post type used for `extra_data`
|
||||
* property in RIO_Process_Queue.
|
||||
*
|
||||
* @see RIO_Process_Queue::$extra_data for further information
|
||||
*/
|
||||
class WRIO_Nextgen_Extra_Data extends RIO_Base_Extra_Data {
|
||||
|
||||
/**
|
||||
* @var string тип ошибки
|
||||
*/
|
||||
protected $error = null;
|
||||
|
||||
/**
|
||||
* @var string текст сообщения об ошибке
|
||||
*/
|
||||
protected $error_msg = null;
|
||||
|
||||
/**
|
||||
* @var int оригинальный размер основного файла
|
||||
*/
|
||||
protected $original_main_size = null;
|
||||
|
||||
/**
|
||||
* @var int оптимизированный размер основного файла
|
||||
*/
|
||||
protected $optimized_main_size = null;
|
||||
|
||||
/**
|
||||
* @var array ответ от сервера оптимизации по основному файлу
|
||||
*/
|
||||
protected $main_optimized_data = null;
|
||||
|
||||
/**
|
||||
* @var array ответ от сервера оптимизации по превьюшке
|
||||
*/
|
||||
protected $thumbnails_optimized_data = null;
|
||||
|
||||
/**
|
||||
* @var string относительный путь к изображению
|
||||
*/
|
||||
protected $image_relative_path = null;
|
||||
|
||||
/**
|
||||
* @var int размер основного изображения
|
||||
*/
|
||||
protected $webp_main_size = null;
|
||||
|
||||
/**
|
||||
* get_error
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_error() {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_error
|
||||
*
|
||||
* @param string $error
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_error( $error ) {
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_error_msg
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_error_msg() {
|
||||
return $this->error_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_error_msg
|
||||
*
|
||||
* @param string $error_msg
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_error_msg( $error_msg ) {
|
||||
$this->error_msg = $error_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_original_main_size
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_original_main_size() {
|
||||
return $this->original_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_original_main_size
|
||||
*
|
||||
* @param int $original_main_size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_original_main_size( $original_main_size ) {
|
||||
$this->original_main_size = $original_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_optimized_main_size
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_optimized_main_size() {
|
||||
return $this->optimized_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_optimized_main_size
|
||||
*
|
||||
* @param int $optimized_main_size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_optimized_main_size( $optimized_main_size ) {
|
||||
$this->optimized_main_size = $optimized_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_main_optimized_data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_main_optimized_data() {
|
||||
return (array) $this->main_optimized_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_main_optimized_data
|
||||
*
|
||||
* @param array $main_optimized_data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_main_optimized_data( $main_optimized_data ) {
|
||||
$this->main_optimized_data = $main_optimized_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_thumbnails_optimized_data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_thumbnails_optimized_data() {
|
||||
return (array) $this->thumbnails_optimized_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_thumbnails_optimized_data
|
||||
*
|
||||
* @param array $thumbnails_optimized_data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_thumbnails_optimized_data( $thumbnails_optimized_data ) {
|
||||
$this->thumbnails_optimized_data = $thumbnails_optimized_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_image_relative_path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_relative_path() {
|
||||
return $this->image_relative_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_image_relative_path
|
||||
*
|
||||
* @param string $image_relative_path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_image_relative_path( $image_relative_path ) {
|
||||
$this->image_relative_path = $image_relative_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает путь к превьюшке относительно корня WP
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_thumbnail_relative_path() {
|
||||
$basename = wp_basename( $this->image_relative_path );
|
||||
$dir = dirname( $this->image_relative_path );
|
||||
|
||||
return $dir . '/thumbs/thumbs-' . $basename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает абсолютный путь к изображению
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_absolute_path() {
|
||||
$relative_path = $this->get_image_relative_path();
|
||||
|
||||
return wp_normalize_path( untrailingslashit( ABSPATH ) . $relative_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает абсолютный путь к превьюшке
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_thumbnail_absolute_path() {
|
||||
$relative_path = $this->get_image_thumbnail_relative_path();
|
||||
|
||||
return wp_normalize_path( untrailingslashit( ABSPATH ) . $relative_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает URL изображения
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_url() {
|
||||
$relative_path = $this->get_image_relative_path();
|
||||
|
||||
return home_url( $relative_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает URL изображения-превьюшки
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_thumbnail_url() {
|
||||
$relative_path = $this->get_image_thumbnail_relative_path();
|
||||
|
||||
return home_url( $relative_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает WebP размер основного изображения
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_webp_main_size() {
|
||||
return $this->webp_main_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает WebP размер основного изображения
|
||||
*
|
||||
* @param int $webp_main_size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_webp_main_size( $webp_main_size ) {
|
||||
$this->webp_main_size = $webp_main_size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WRIO_WebP_Api - Backward compatibility wrapper for format conversion.
|
||||
*
|
||||
* This class now delegates to the new unified format converter system.
|
||||
*
|
||||
* @author Alexander Teshabaev <sasha.tesh@gmail.com>
|
||||
* @deprecated Use WRIO_Format_Converter_Factory instead.
|
||||
*/
|
||||
class WRIO_WebP_Api {
|
||||
|
||||
/**
|
||||
* @var WRIO_Format_Converter_Api Format converter instance.
|
||||
*/
|
||||
private $_format_converter;
|
||||
|
||||
/**
|
||||
* WRIO_WebP_Api constructor.
|
||||
*
|
||||
* @param RIO_Process_Queue[] $model Item to be converted to WebP.
|
||||
*/
|
||||
public function __construct( $model ) {
|
||||
// Delegate to new unified format converter (always use WebP for backward compatibility)
|
||||
$this->_format_converter = WRIO_Format_Converter_Factory::create( $model, 'webp' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process image queue based on provided attachment ID.
|
||||
*
|
||||
* @param bool $quota decr quota?.
|
||||
*
|
||||
* @return bool true on success execution, false on failure or missing item in queue.
|
||||
*/
|
||||
public function process_image_queue( $quota = false ) {
|
||||
return $this->_format_converter->process_image_queue( $quota );
|
||||
}
|
||||
|
||||
/**
|
||||
* Request API
|
||||
*
|
||||
* @param RIO_Process_Queue $model Queue model.
|
||||
* @param bool $quota decr quota?.
|
||||
*
|
||||
* @return array|bool|WP_Error
|
||||
*/
|
||||
public function request( $model, $quota = false ) {
|
||||
return $this->_format_converter->request( $model, $quota );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process response from API.
|
||||
*
|
||||
* @param array|WP_Error|false $response
|
||||
*
|
||||
* @return bool True means response image was successfully saved, false on failure.
|
||||
*/
|
||||
public function can_save( $response ) {
|
||||
return $this->_format_converter->can_save( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save file from response.
|
||||
*
|
||||
* @param array|WP_Error|false $response
|
||||
* @param RIO_Process_Queue $queue_model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save_file( $response, $queue_model ) {
|
||||
return $this->_format_converter->save_file( $response, $queue_model );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update processing item data to finish its cycle.
|
||||
*
|
||||
* @param RIO_Process_Queue $queue_model Queue model to be update.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update( $queue_model ) {
|
||||
return $this->_format_converter->update( $queue_model );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete save url.
|
||||
*
|
||||
* @param RIO_Process_Queue $queue_model Instance of queue item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_save_url( $queue_model ) {
|
||||
return $this->_format_converter->get_save_url( $queue_model );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get absolute save path.
|
||||
*
|
||||
* @param \RIO_Process_Queue $queue_model
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception on failure to create missing directory
|
||||
*/
|
||||
public static function get_save_path( $queue_model ) {
|
||||
return WRIO_Format_Converter_WebP::get_save_path_static( $queue_model );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
|
||||
namespace WRIO\WEBP\HTML;
|
||||
|
||||
// Exit if accessed directly
|
||||
use WRIO_Logger;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class WRIO\WEBP\HTML\Delivery converts and replace JPEG & PNG images within HTML doc.
|
||||
*
|
||||
* Images converted via third-party service, saved locally and then replaced based on parsed DOM <img>, or other elements.
|
||||
*
|
||||
* @link https://css-tricks.com/using-webp-images/
|
||||
* @link https://dev.opera.com/articles/responsive-images/#different-image-types-use-case
|
||||
* @link https://ru.wordpress.org/plugins/webp-express/
|
||||
* @link https://github.com/rosell-dk/
|
||||
* @version 1.0
|
||||
*/
|
||||
class Delivery {
|
||||
|
||||
/**
|
||||
* Legacy constant for no delivery mode.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NONE_DELIVERY_MODE = 'none';
|
||||
const DEFAULT_DELIVERY_MODE = 'picture';
|
||||
const PICTURE_DELIVERY_MODE = 'picture';
|
||||
const URL_DELIVERY_MODE = 'url';
|
||||
const REDIRECT_DELIVERY_MODE = 'redirect';
|
||||
|
||||
/**
|
||||
* WRIO_Webp constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the class.
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
if ( ! static::should_use_converted_images() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \WRIO_Plugin::app()->is_keep_error_log_on_frontend() ) {
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'WebP/AVIF option enabled and browser "%s" is supported, ready to process buffer', isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '*undefined*' ) );
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'WebP/AVIF delivery mode: %s', static::get_delivery_mode() ) );
|
||||
}
|
||||
|
||||
/*
|
||||
TODO:
|
||||
Which hook should we use, and should we make it optional?
|
||||
- Cache enabler uses 'template_redirect'
|
||||
- ShortPixes uses 'init'
|
||||
|
||||
We go with template_redirect now, because it is the "innermost".
|
||||
This lowers the risk of problems with plugins used rewriting URLs to point to CDN.
|
||||
(We need to process the output *before* the other plugin has rewritten the URLs,
|
||||
if the "Only for webps that exists" feature is enabled)
|
||||
*/
|
||||
|
||||
if ( static::is_delivery_mode_enabled() ) {
|
||||
// Use template_redirect for frontend output buffering (fires during page render)
|
||||
// This is more reliable than 'init' as it only fires on frontend requests
|
||||
add_action( 'template_redirect', [ $this, 'process_buffer' ], 1 );
|
||||
}
|
||||
|
||||
if ( static::is_picture_delivery_mode() ) {
|
||||
add_action( 'wp_head', [ $this, 'add_picture_fill_js' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether any format conversion is enabled (WebP or AVIF).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function should_use_converted_images() {
|
||||
return \WRIO_Format_Converter_Factory::is_format_conversion_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @since 1.0.4
|
||||
*/
|
||||
public static function is_redirect_delivery_mode() {
|
||||
return self::REDIRECT_DELIVERY_MODE === static::get_delivery_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @since 1.0.4
|
||||
*/
|
||||
public static function is_picture_delivery_mode() {
|
||||
return self::PICTURE_DELIVERY_MODE === static::get_delivery_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @since 1.0.4
|
||||
*/
|
||||
public static function is_url_delivery_mode() {
|
||||
return self::URL_DELIVERY_MODE === static::get_delivery_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether any delivery mode is enabled that modifies HTML output.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_delivery_mode_enabled() {
|
||||
$delivery_mode = static::get_delivery_mode();
|
||||
|
||||
return in_array(
|
||||
$delivery_mode,
|
||||
[
|
||||
self::PICTURE_DELIVERY_MODE,
|
||||
self::URL_DELIVERY_MODE,
|
||||
self::NONE_DELIVERY_MODE,
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 1.0.4
|
||||
*/
|
||||
public static function get_delivery_mode() {
|
||||
$delivery_mode = \WRIO_Plugin::app()->getPopulateOption( 'webp_delivery_mode' );
|
||||
|
||||
if ( ! empty( $delivery_mode ) ) {
|
||||
return $delivery_mode;
|
||||
}
|
||||
|
||||
return self::DEFAULT_DELIVERY_MODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.4
|
||||
*/
|
||||
public function add_picture_fill_js() {
|
||||
// Don't do anything with the RSS feed.
|
||||
// - and no need for PictureJs in the admin
|
||||
if ( is_feed() || is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<script>' . 'document.createElement( "picture" );' . 'if(!window.HTMLPictureElement && document.addEventListener) {' . 'window.addEventListener("DOMContentLoaded", function() {' . 'var s = document.createElement("script");' . 's.src = "' . WRIOP_PLUGIN_URL . '/assets/js/picturefill.min.js' . '";' . 'document.body.appendChild(s);' . '});' . '}' . '</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process HTML template buffer.
|
||||
*/
|
||||
public function process_buffer() {
|
||||
// template_redirect only fires on frontend, so no need to check is_admin() or AJAX
|
||||
ob_start( [ $this, 'process_alter_html' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process tags to replace those elements which match converted to WebP within buffer.
|
||||
*
|
||||
* @param string $content HTML buffer.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function process_alter_html( $content ) {
|
||||
$raw_content = $content;
|
||||
|
||||
// Don't do anything with the RSS feed.
|
||||
if ( is_feed() || empty( $content ) || ! is_null( json_decode( $content ) ) ) {
|
||||
// WRIO_Plugin::app()->logger->info( "Buffer content is empty, skipping processing" );
|
||||
return $content;
|
||||
}
|
||||
if ( static::is_picture_delivery_mode() ) {
|
||||
if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) {
|
||||
// for AMP pages the <picture> tag is not allowed
|
||||
return $content;
|
||||
}
|
||||
|
||||
require_once WRIOP_PLUGIN_DIR . '/includes/classes/webp/class-webp-html-picture-tags.php';
|
||||
$content = Picture_Tags::replace( $content );
|
||||
} elseif ( static::is_url_delivery_mode() ) {
|
||||
if ( ! is_admin() ) {
|
||||
require_once WRIOP_PLUGIN_DIR . '/includes/classes/webp/class-webp-html-image-urls-replacer.php';
|
||||
$content = Urls_Replacer::replace( $content );
|
||||
}
|
||||
}
|
||||
|
||||
// If the search and replacement are completed with an error, then return the raw content.
|
||||
// If this is not prevented, in case of an error the user will receive a white screen.
|
||||
if ( empty( $content ) ) {
|
||||
if ( \WRIO_Plugin::app()->is_keep_error_log_on_frontend() ) {
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( 'Failed search and replace urls. Empty result received (%s).', base64_encode( $content ) ) );
|
||||
}
|
||||
|
||||
return $raw_content;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get url for webp
|
||||
* returns second argument if no webp
|
||||
*
|
||||
* @param string $source_url (ie http://example.com/wp-content/image.jpg)
|
||||
* @param string $return_value_on_fail
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
/**
|
||||
* Get URL for converted format (WebP or AVIF).
|
||||
*
|
||||
* Checks for available converted formats in order of preference (AVIF first, then WebP).
|
||||
*
|
||||
* @param string $source_url Original image URL.
|
||||
* @param string $return_value_on_fail Value to return if conversion not found.
|
||||
*
|
||||
* @return string Converted image URL or fallback value.
|
||||
* @since 1.9.0
|
||||
*/
|
||||
public static function get_converted_url( $source_url, $return_value_on_fail ) {
|
||||
$enabled_formats = \WRIO_Format_Converter_Factory::get_enabled_formats();
|
||||
|
||||
if ( empty( $enabled_formats ) || ! static::is_support_format( $source_url ) ) {
|
||||
if ( \WRIO_Plugin::app()->is_keep_error_log_on_frontend() ) {
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( "Skipped converted image lookup. Original image format is not supported for conversion, or converted image delivery is disabled.\r\nSource url: %s", $source_url ) );
|
||||
}
|
||||
|
||||
return $return_value_on_fail;
|
||||
}
|
||||
|
||||
if ( ! preg_match( '#^https?://#', $source_url ) ) {
|
||||
$source_url = wrio_rel_to_abs_url( $source_url );
|
||||
}
|
||||
|
||||
$is_wpmedia_url = static::is_wpmedia_url( $source_url );
|
||||
|
||||
// If the image is stored on a remote server, need to skip it
|
||||
if ( static::is_external_url( $source_url ) && ! $is_wpmedia_url ) {
|
||||
if ( \WRIO_Plugin::app()->is_keep_error_log_on_frontend() ) {
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( "Skipped converted image lookup. Image is hosted on a remote server.\r\nSource url: %s", $source_url ) );
|
||||
}
|
||||
|
||||
return $return_value_on_fail;
|
||||
}
|
||||
|
||||
if ( $is_wpmedia_url ) {
|
||||
$upload_dir = wp_get_upload_dir();
|
||||
|
||||
$source_parts = wp_parse_url( $source_url );
|
||||
$base_parts = wp_parse_url( $upload_dir['baseurl'] );
|
||||
|
||||
// If URL parsing fails, bail
|
||||
if ( empty( $source_parts['path'] ) || empty( $base_parts['path'] ) ) {
|
||||
return $return_value_on_fail;
|
||||
}
|
||||
|
||||
// Must be same host to treat it as local upload (ignore scheme)
|
||||
$source_host = $source_parts['host'] ?? '';
|
||||
$base_host = $base_parts['host'] ?? '';
|
||||
|
||||
if ( $source_host && $base_host && strtolower( $source_host ) !== strtolower( $base_host ) ) {
|
||||
return $return_value_on_fail;
|
||||
}
|
||||
|
||||
// Path must start with uploads base path
|
||||
$base_path = rtrim( $base_parts['path'], '/' ); // eg /app/uploads
|
||||
$source_path = $source_parts['path']; // eg /app/uploads/2025/12/demo.png
|
||||
|
||||
if ( strpos( $source_path, $base_path . '/' ) !== 0 ) {
|
||||
return $return_value_on_fail;
|
||||
}
|
||||
|
||||
// Convert URL path to filesystem path
|
||||
$relative = ltrim( substr( $source_path, strlen( $base_path ) ), '/' );
|
||||
$file_path = trailingslashit( $upload_dir['basedir'] ) . $relative;
|
||||
} else {
|
||||
$file_path = wrio_url_to_abs_path( $source_url );
|
||||
}
|
||||
|
||||
// If you could not find original image, skip it. Perhaps an error
|
||||
// in absolute path formation to the directory where the
|
||||
// image is stored.
|
||||
if ( empty( $file_path ) || ! file_exists( $file_path ) ) {
|
||||
if ( \WRIO_Plugin::app()->is_keep_error_log_on_frontend() ) {
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( "Skipped converted image lookup. Unable to find the original image on disk.\r\nRelative path: (%s)\r\nSource url: (%s)", $file_path, $source_url ) );
|
||||
}
|
||||
|
||||
return $return_value_on_fail;
|
||||
}
|
||||
|
||||
// Check AVIF first if enabled, then WebP.
|
||||
foreach ( $enabled_formats as $format ) {
|
||||
$extension = '.' . strtolower( $format );
|
||||
$converted_file_path = $file_path . $extension;
|
||||
|
||||
if ( file_exists( $converted_file_path ) ) {
|
||||
return $source_url . $extension;
|
||||
}
|
||||
}
|
||||
|
||||
if ( \WRIO_Plugin::app()->is_keep_error_log_on_frontend() ) {
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( "Skipped converted image delivery. No converted file was found for the original image.\r\nSource url: %s\r\nChecked formats: %s", $source_url, implode( ', ', $enabled_formats ) ) );
|
||||
}
|
||||
|
||||
return $return_value_on_fail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WebP URL (backward compatibility wrapper).
|
||||
*
|
||||
* @param string $source_url Original image URL.
|
||||
* @param string $return_value_on_fail Value to return if WebP not found.
|
||||
*
|
||||
* @return string WebP image URL or fallback value.
|
||||
* @deprecated Use get_converted_url() instead.
|
||||
*/
|
||||
public static function get_webp_url( $source_url, $return_value_on_fail ) {
|
||||
return static::get_converted_url( $source_url, $return_value_on_fail );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $source_url
|
||||
*
|
||||
* @return bool
|
||||
* @since 1.4.0
|
||||
*/
|
||||
protected static function is_wpmedia_url( $source_url ) {
|
||||
$upload_dir = wp_get_upload_dir();
|
||||
|
||||
if ( isset( $upload_dir['error'] ) && $upload_dir['error'] !== false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Normalize both URLs to https for comparison.
|
||||
$source_url_normalized = set_url_scheme( $source_url, 'https' );
|
||||
$baseurl_normalized = set_url_scheme( $upload_dir['baseurl'], 'https' );
|
||||
|
||||
return false !== strpos( $source_url_normalized, $baseurl_normalized );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $source_url
|
||||
*
|
||||
* @return bool
|
||||
* @since 1.4.0
|
||||
*/
|
||||
protected static function is_support_format( $source_url ) {
|
||||
// Match .jpg, .jpeg, or .png at end of URL (before optional query string)
|
||||
if ( ! preg_match( '#\.(jpe?g|png)($|\?)#i', $source_url ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $source_url
|
||||
*
|
||||
* @return bool
|
||||
* @since 1.4.0
|
||||
*/
|
||||
protected static function is_external_url( $source_url ) {
|
||||
if ( strpos( $source_url, get_site_url() ) === false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether browser supports WebP or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_supported_browser() {
|
||||
if ( isset( $_SERVER['HTTP_ACCEPT'] ) && strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) !== false || isset( $_SERVER['HTTP_USER_AGENT'] ) && strpos( $_SERVER['HTTP_USER_AGENT'], ' Chrome/' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace WRIO\WEBP\HTML;
|
||||
|
||||
use DOMUtilForWebP\ImageUrlReplacer;
|
||||
|
||||
class Urls_Replacer extends ImageUrlReplacer {
|
||||
|
||||
/**
|
||||
* Replace URL with converted format (WebP or AVIF).
|
||||
*
|
||||
* @param string $url Original image URL.
|
||||
*
|
||||
* @return string|null Converted URL or null if not applicable.
|
||||
*/
|
||||
public function replaceUrl( $url ) {
|
||||
// Check if URL ends with supported source formats
|
||||
if ( ! preg_match( '#\.(png|jpe?g)($|\?)#i', $url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Delivery::get_converted_url( $url, null );
|
||||
}
|
||||
|
||||
public function attributeFilter( $attrName ) {
|
||||
// Allow "src", "srcset" and data-attributes that smells like they are used for images
|
||||
// The following rule matches all attributes used for lazy loading images that we know of
|
||||
return preg_match( '#^(src|srcset|(data-[^=]*(lazy|small|slide|img|large|src|thumb|source|set|bg-url)[^=]*))$#i', $attrName );
|
||||
|
||||
// If you want to limit it further, only allowing attributes known to be used for lazy load,
|
||||
// use the following regex instead:
|
||||
// return preg_match('#^(src|srcset|data-(src|srcset|cvpsrc|cvpset|thumb|bg-url|large_image|lazyload|source-url|srcsmall|srclarge|srcfull|slide-img|lazy-original))$#i', $attrName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace WRIO\WEBP\HTML;
|
||||
|
||||
/**
|
||||
* Class AlterHtmlPicture - convert an <img> tag to a <picture> tag and add the webp versions of the images
|
||||
* Based this code on code from the ShortPixel plugin, which used code from Responsify WP plugin
|
||||
*/
|
||||
|
||||
use DOMUtilForWebP\PictureTags;
|
||||
|
||||
class Picture_Tags extends PictureTags {
|
||||
|
||||
public function replaceUrl( $url ) {
|
||||
return Delivery::get_converted_url( $url, null );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
<?php
|
||||
|
||||
namespace WRIO\WEBP;
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use WP_Post;
|
||||
use WRIO\WEBP\HTML\Delivery;
|
||||
|
||||
/**
|
||||
* Class Listener listens to new events via hooks.
|
||||
*
|
||||
* For example, once attachment optimized and if WebP option enabled it will kicked and converted.
|
||||
*
|
||||
* Same applies for custom folder and NextGen plugin.
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
class Listener {
|
||||
|
||||
/**
|
||||
* Default type.
|
||||
*/
|
||||
const DEFAULT_TYPE = 'webp';
|
||||
|
||||
/**
|
||||
* @var null|\RIO_Process_Queue[] Saved queue items.
|
||||
*/
|
||||
private $_saved_models = null;
|
||||
/**
|
||||
* @var string|null Format to convert to (webp, avif). Set during convert_webp().
|
||||
*/
|
||||
private $_current_format = null;
|
||||
|
||||
/**
|
||||
* WRIO_Webp constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the object.
|
||||
*/
|
||||
public function init() {
|
||||
// Always register the hook - the convert_webp method will check format.
|
||||
// This allows single-image conversion (e.g., clicking "Convert to WebP" button)
|
||||
// to work even when global conversion is disabled.
|
||||
add_action( 'wbcr/riop/queue_item_saved', [ $this, 'convert_webp' ], 10, 3 );
|
||||
|
||||
add_action( 'wbcr/rio/attachment_restored', [ $this, 'process_attachment_restore' ] );
|
||||
add_action( 'wbcr/rio/cf_image_restored', [ $this, 'process_attachment_restore' ] );
|
||||
add_action( 'wbcr/rio/nextgen_image_restored', [ $this, 'process_attachment_restore' ] );
|
||||
}
|
||||
|
||||
public function convert_webp( $model, $quota = false, $format = null ) {
|
||||
/**
|
||||
* @var \RIO_Process_Queue $model
|
||||
*/
|
||||
// Skip if already a format conversion item (prevent recursion)
|
||||
if ( in_array( $model->get_item_type(), [ 'webp', 'avif' ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use provided format, or fall back to all enabled formats
|
||||
if ( $format === null ) {
|
||||
$formats = \WRIO_Format_Converter_Factory::get_enabled_formats();
|
||||
|
||||
// If no formats are enabled, exit early
|
||||
if ( empty( $formats ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process each enabled format
|
||||
foreach ( $formats as $format_type ) {
|
||||
$this->_current_format = $format_type;
|
||||
if ( $format_type !== 'original' ) {
|
||||
$this->process_queue_item( $model );
|
||||
|
||||
if ( ! empty( $this->_saved_models ) ) {
|
||||
$converter = \WRIO_Format_Converter_Factory::create( $this->_saved_models, $format_type );
|
||||
$converter->process_image_queue( $quota );
|
||||
$this->_saved_models = []; // Clear for next format
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Store format for use in save() method
|
||||
$this->_current_format = $format;
|
||||
|
||||
if ( $format === 'original' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->process_queue_item( $model );
|
||||
|
||||
if ( ! empty( $this->_saved_models ) ) {
|
||||
$converter = \WRIO_Format_Converter_Factory::create( $this->_saved_models, $format );
|
||||
$converter->process_image_queue( $quota );
|
||||
}
|
||||
|
||||
$this->_saved_models = null;
|
||||
$this->_current_format = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process attachment restore.
|
||||
*
|
||||
* @param \RIO_Process_Queue|null $model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function process_attachment_restore( $model ) {
|
||||
if ( ! $model instanceof \RIO_Process_Queue ) {
|
||||
\WRIO_Plugin::app()->logger->warning( 'process_attachment_restore called with invalid model (null or wrong type)' );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for both webp and avif items
|
||||
foreach ( [ 'webp', 'avif' ] as $item_type ) {
|
||||
$item_params = [
|
||||
'object_id' => $model->get_object_id(),
|
||||
'item_type' => $item_type,
|
||||
];
|
||||
|
||||
if ( 'cf_image' == $model->get_item_type() ) {
|
||||
unset( $item_params['object_id'] ); // для custom folders не нужен номер объекта
|
||||
/**
|
||||
* @var $extra_data \WRIO_CF_Image_Extra_Data
|
||||
*/
|
||||
$extra_data = $model->get_extra_data();
|
||||
$item_params['item_hash'] = hash( 'sha256', $extra_data->get_image_url() );
|
||||
}
|
||||
|
||||
$delete_items = \RIO_Process_Queue::find_all( $item_params );
|
||||
|
||||
if ( empty( $delete_items ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $delete_items as $item ) {
|
||||
/**
|
||||
* @var $extra \RIOP_WebP_Extra_Data
|
||||
*/
|
||||
$extra = $item->get_extra_data();
|
||||
|
||||
if ( empty( $extra ) ) {
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( 'Failed to clean-up queue item #%s as it is missing extra data', $item->get_id() ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
$converted_path = $extra->get_converted_path();
|
||||
if ( ! empty( $converted_path ) ) {
|
||||
if ( @unlink( $converted_path ) ) {
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'Unlinked %s from disk, ready to delete item #%s from DB', $converted_path, $item->get_id() ) );
|
||||
} else {
|
||||
\WRIO_Plugin::app()->logger->error( sprintf( 'Failed to unlink %s from disk', $converted_path ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $item->delete() ) {
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'Deleted #%s as attachment #%s was recovered', $item->get_id(), $item->get_object_id() ) );
|
||||
} else {
|
||||
\WRIO_Plugin::app()->logger->error( sprintf( 'Failed to delete queue item #%s as delete() method failed', $item->get_id() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process new queue item.
|
||||
*
|
||||
* @param \RIO_Process_Queue|null $model Model to process.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function process_queue_item( $model ) {
|
||||
if ( ! ( $model instanceof \RIO_Process_Queue ) ) {
|
||||
\WRIO_Plugin::app()->logger->info( 'Model must be instance of RIO_Process_Queue to be process by %s' . __FUNCTION__ );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
if ( ! $model->is_optimized() ) {
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'Skipping to process attachment #%s as it is not optimized', $model->get_id() ) );
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
switch ( $model->get_item_type() ) {
|
||||
case 'attachment':
|
||||
$this->process_attachment( $model );
|
||||
break;
|
||||
case 'cf_image':
|
||||
$this->process_custom_folder( $model );
|
||||
break;
|
||||
case 'nextgen':
|
||||
$this->process_nextgen( $model );
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process attachment.
|
||||
*
|
||||
* Finds attachment by id, gets its src and srcset and saves them on the database.
|
||||
*
|
||||
* After this, images are processed by Cron or manually via admin GUI.
|
||||
*
|
||||
* @param \RIO_Process_Queue $model Attachment model to process.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function process_attachment( $model ) {
|
||||
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'Start WebP conversion process for attachment #%s', $model->get_id() ) );
|
||||
|
||||
$attachment = get_post( $model->get_object_id() );
|
||||
|
||||
if ( empty( $attachment ) ) {
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( 'WebP conversion: No attachment found by #%s', $model->get_object_id() ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowed_mimes = wrio_get_allowed_formats();
|
||||
|
||||
if ( ! in_array( $attachment->post_mime_type, $allowed_mimes ) ) {
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( 'WebP conversion: Attachment #%s with MIME type %s cannot be processed as only these are allowed: %s', $attachment->ID, $attachment->post_mime_type, implode( ', ', $allowed_mimes ) ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$attachment_meta = static::get_attachment_data( $attachment );
|
||||
|
||||
if ( empty( $attachment_meta ) ) {
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( 'WebP conversion: Unable to get attachment #%s meta such as height, abs. path, URL, etc. Skipping WebP processing...', $attachment->ID ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var $data array
|
||||
*/
|
||||
foreach ( $attachment_meta as $hash => $data ) {
|
||||
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'WebP conversion: Ready to save hash "%s" (extra data: %s) as it does not exist yet', $hash, wp_json_encode( $data ) ) );
|
||||
|
||||
$source_path = isset( $data['absolute_path'] ) ? $data['absolute_path'] : null;
|
||||
|
||||
if ( empty( $source_path ) || ! file_exists( $source_path ) ) {
|
||||
\WRIO_Plugin::app()->logger->error( sprintf( "WebP conversion: Image is not found.\r\nSource path: %s", $source_path ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
$source_src = $data['url'];
|
||||
|
||||
// Include format in hash so WebP and AVIF conversions are tracked separately
|
||||
$format = $this->_current_format ?? 'webp';
|
||||
$hash_seed = $source_src . '|' . $format;
|
||||
$item_hash = hash( 'sha256', $hash_seed );
|
||||
$webp_queue = \RIO_Process_Queue::find_by_hash( $item_hash );
|
||||
|
||||
if ( $webp_queue instanceof \RIO_Process_Queue ) {
|
||||
// Reset existing record for re-conversion
|
||||
\WRIO_Plugin::app()->logger->warning( sprintf( "WebP conversion: Skipped because the webp image already exists.\r\nSource scr: %s", $source_src ) );
|
||||
$webp_queue->result_status = \RIO_Process_Queue::STATUS_PROCESSING;
|
||||
$webp_queue->final_size = 0;
|
||||
$webp_queue->save();
|
||||
$this->_saved_models[] = $webp_queue;
|
||||
continue;
|
||||
}
|
||||
|
||||
$extra_data = new \RIOP_WebP_Extra_Data(
|
||||
[
|
||||
'convert_from' => 'attachment',
|
||||
'converted_from_size' => $data['size'],
|
||||
'source_src' => $source_src,
|
||||
'source_path' => $source_path,
|
||||
]
|
||||
);
|
||||
|
||||
$saved = $this->save(
|
||||
[
|
||||
'item_hash' => $hash_seed, // Include format in hash seed
|
||||
'object_id' => $attachment->ID,
|
||||
'original_mime_type' => $attachment->post_mime_type,
|
||||
],
|
||||
$extra_data
|
||||
);
|
||||
|
||||
if ( $saved instanceof \RIO_Process_Queue ) {
|
||||
$this->_saved_models[] = $saved;
|
||||
}
|
||||
}
|
||||
|
||||
$count_models = is_array( $this->_saved_models ) ? count( $this->_saved_models ) : 0;
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'End WebP conversion process for attachment #%s. Saved models: %d', $model->get_id(), $count_models ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process custom folder.
|
||||
*
|
||||
* @param \RIO_Process_Queue $model Model to process.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function process_custom_folder( $model ) {
|
||||
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'Start WebP conversion process for Custom folder item #%s', $model->get_id() ) );
|
||||
|
||||
/**
|
||||
* @var $model_extra_data \WRIO_CF_Image_Extra_Data
|
||||
*/
|
||||
$model_extra_data = $model->get_extra_data();
|
||||
|
||||
// Include format in hash so WebP and AVIF conversions are tracked separately
|
||||
$format = $this->_current_format ?? 'webp';
|
||||
$hash_seed = $model_extra_data->get_image_url() . '|' . $format;
|
||||
|
||||
$webp_exists = \RIO_Process_Queue::find_by_hash( hash( 'sha256', $hash_seed ) );
|
||||
|
||||
if ( $webp_exists ) {
|
||||
// Reset existing record for re-conversion
|
||||
$webp_exists->result_status = \RIO_Process_Queue::STATUS_PROCESSING;
|
||||
$webp_exists->final_size = 0;
|
||||
$webp_exists->save();
|
||||
$this->_saved_models[] = $webp_exists;
|
||||
} else {
|
||||
$extra_data = new \RIOP_WebP_Extra_Data(
|
||||
[
|
||||
'convert_from' => 'cf_image',
|
||||
'source_src' => $model_extra_data->get_image_url(),
|
||||
'source_path' => $model_extra_data->get_image_absolute_path(),
|
||||
]
|
||||
);
|
||||
|
||||
$saved = $this->save(
|
||||
[
|
||||
'item_hash' => $hash_seed,
|
||||
'item_hash_alternative' => $model_extra_data->get_image_relative_path(),
|
||||
'original_mime_type' => $model->get_original_mime_type(),
|
||||
],
|
||||
$extra_data
|
||||
);
|
||||
|
||||
if ( $saved instanceof \RIO_Process_Queue ) {
|
||||
$this->_saved_models[] = $saved;
|
||||
}
|
||||
}
|
||||
|
||||
$count_models = is_array( $this->_saved_models ) ? count( $this->_saved_models ) : 0;
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'End WebP conversion process for Custom folder #%s. Saved models: %d', $model->get_id(), $count_models ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process NextGen plugin images.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/nextgen-gallery/ Plugin link.
|
||||
*
|
||||
* @param \RIO_Process_Queue $model Model to process.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function process_nextgen( $model ) {
|
||||
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'Start WebP conversion process for NextGen item #%s', $model->get_id() ) );
|
||||
|
||||
/**
|
||||
* @var $model_extra_data \WRIO_Nextgen_Extra_Data
|
||||
*/
|
||||
$model_extra_data = $model->get_extra_data();
|
||||
|
||||
if ( ! ( $model_extra_data instanceof \WRIO_Nextgen_Extra_Data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Include format in hash so WebP and AVIF conversions are tracked separately
|
||||
$format = $this->_current_format ?? 'webp';
|
||||
$hash_seed = $model_extra_data->get_image_url() . '|' . $format;
|
||||
|
||||
$webp_exists = \RIO_Process_Queue::find_by_hash( hash( 'sha256', $hash_seed ) );
|
||||
|
||||
if ( $webp_exists ) {
|
||||
// Reset existing record for re-conversion
|
||||
$webp_exists->result_status = \RIO_Process_Queue::STATUS_PROCESSING;
|
||||
$webp_exists->final_size = 0;
|
||||
$webp_exists->save();
|
||||
$this->_saved_models[] = $webp_exists;
|
||||
} else {
|
||||
// Original
|
||||
$extra_data = new \RIOP_WebP_Extra_Data(
|
||||
[
|
||||
'convert_from' => 'nextgen',
|
||||
'converted_from_size' => null,
|
||||
'source_src' => $model_extra_data->get_image_url(),
|
||||
'source_path' => $model_extra_data->get_image_absolute_path(),
|
||||
]
|
||||
);
|
||||
|
||||
$original_saved = $this->save(
|
||||
[
|
||||
'item_hash' => $hash_seed,
|
||||
'original_mime_type' => $model->get_original_mime_type(),
|
||||
'object_id' => $model->get_object_id(),
|
||||
],
|
||||
$extra_data
|
||||
);
|
||||
|
||||
if ( $original_saved instanceof \RIO_Process_Queue ) {
|
||||
$this->_saved_models[] = $original_saved;
|
||||
}
|
||||
}
|
||||
|
||||
// Thumbnail
|
||||
$thumbnail_hash_seed = $model_extra_data->get_image_thumbnail_url() . '|' . $format;
|
||||
$thumbnail_webp_exists = \RIO_Process_Queue::find_by_hash( hash( 'sha256', $thumbnail_hash_seed ) );
|
||||
|
||||
if ( $thumbnail_webp_exists ) {
|
||||
// Reset existing record for re-conversion
|
||||
$thumbnail_webp_exists->result_status = \RIO_Process_Queue::STATUS_PROCESSING;
|
||||
$thumbnail_webp_exists->final_size = 0;
|
||||
$thumbnail_webp_exists->save();
|
||||
$this->_saved_models[] = $thumbnail_webp_exists;
|
||||
} else {
|
||||
$extra_data_thumbnail = new \RIOP_WebP_Extra_Data(
|
||||
[
|
||||
'convert_from' => 'nextgen',
|
||||
'converted_from_size' => null,
|
||||
'source_src' => $model_extra_data->get_image_thumbnail_url(),
|
||||
'source_path' => $model_extra_data->get_image_thumbnail_absolute_path(),
|
||||
]
|
||||
);
|
||||
|
||||
$thumbmail_saved = $this->save(
|
||||
[
|
||||
'item_hash' => $thumbnail_hash_seed,
|
||||
'original_mime_type' => $model->get_original_mime_type(),
|
||||
],
|
||||
$extra_data_thumbnail
|
||||
);
|
||||
|
||||
if ( $thumbmail_saved instanceof \RIO_Process_Queue ) {
|
||||
$this->_saved_models[] = $thumbmail_saved;
|
||||
}
|
||||
}
|
||||
|
||||
$count_models = is_array( $this->_saved_models ) ? count( $this->_saved_models ) : 0;
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'End WebP conversion process for NextGen #%s. Saved models: %d', $model->get_id(), $count_models ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment data such as height, width, absolute path and URL.
|
||||
*
|
||||
* @param WP_Post|int $attachment Attachment to get data for.
|
||||
*
|
||||
* @return array {
|
||||
* Associative array of attachment data and its thumbnails, where key is sha256 hash or attachment URL.
|
||||
*
|
||||
* @type string $size Size of an image, e.g. medium.
|
||||
* @type int $height Height in pixels.
|
||||
* @type int $width Width in pixels.
|
||||
* @type string $mime MIME type, e.g. image/jpeg.
|
||||
* @type string $absolute_path Absolute path to thumbnail.
|
||||
* @type string $url URL path to thumbnail.
|
||||
* }
|
||||
*/
|
||||
public static function get_attachment_data( $attachment ) {
|
||||
$attachment = get_post( $attachment );
|
||||
$hashmap = [];
|
||||
|
||||
if ( empty( $attachment ) ) {
|
||||
return $hashmap;
|
||||
}
|
||||
|
||||
$dirs = wp_upload_dir();
|
||||
|
||||
if ( isset( $dirs['error'] ) && $dirs['error'] !== false ) {
|
||||
return $hashmap;
|
||||
}
|
||||
|
||||
$attachment_meta = wp_get_attachment_metadata( $attachment->ID );
|
||||
|
||||
// Fallback to get attachment meta it can be empty when WordPress failed to create it or invocation
|
||||
// of method was produced too soon
|
||||
if ( empty( $attachment_meta ) ) {
|
||||
$exploded_url = explode( 'wp-content/uploads/', $attachment->guid, 2 );
|
||||
|
||||
if ( isset( $exploded_url[1] ) ) {
|
||||
$exploded_relative_path = trim( $exploded_url[1] );
|
||||
$path_from_url = trailingslashit( $dirs['basedir'] ) . $exploded_relative_path;
|
||||
|
||||
// Need to remove this filter, as it would start recursion
|
||||
remove_filter( 'wp_generate_attachment_metadata', 'WRIO_Media_Library::optimize_after_upload' );
|
||||
|
||||
$attachment_meta = wp_generate_attachment_metadata( $attachment->ID, $path_from_url );
|
||||
|
||||
add_filter( 'wp_generate_attachment_metadata', 'WRIO_Media_Library::optimize_after_upload', 10, 2 );
|
||||
}
|
||||
}
|
||||
if ( empty( $attachment_meta ) ) {
|
||||
\WRIO_Plugin::app()->logger->error( sprintf( 'Attachment #%d metadata is empty. Webp image can not be converted.', $attachment->ID ) );
|
||||
|
||||
return $hashmap;
|
||||
}
|
||||
|
||||
if ( isset( $dirs['basedir'] ) && isset( $attachment_meta['file'] ) ) {
|
||||
|
||||
$original = [
|
||||
'size' => 'original',
|
||||
'height' => $attachment_meta['height'],
|
||||
'width' => $attachment_meta['width'],
|
||||
'absolute_path' => wp_normalize_path( trailingslashit( $dirs['basedir'] ) . $attachment_meta['file'] ),
|
||||
'url' => \WRIO_Url::normalize( trailingslashit( $dirs['baseurl'] ) . $attachment_meta['file'] ),
|
||||
];
|
||||
|
||||
$hashmap[ hash( 'sha256', $original['url'] ) ] = $original;
|
||||
|
||||
if ( ! empty( $attachment_meta['sizes'] ) && is_array( $attachment_meta['sizes'] ) ) {
|
||||
foreach ( $attachment_meta['sizes'] as $size => $size_data ) {
|
||||
// [2019, 01, somename.jpg]
|
||||
$exploded = explode( '/', $attachment_meta['file'] );
|
||||
|
||||
// [2019, 01]
|
||||
array_pop( $exploded );
|
||||
|
||||
// [2019, 01, someothername.jpg]
|
||||
$exploded[] = $size_data['file'];
|
||||
|
||||
$new_file = implode( '/', $exploded );
|
||||
|
||||
$url = \WRIO_Url::normalize( trailingslashit( $dirs['baseurl'] ) . $new_file );
|
||||
$absolute_path = wp_normalize_path( trailingslashit( $dirs['basedir'] ) . $new_file );
|
||||
$hashed_url = hash( 'sha256', $url );
|
||||
|
||||
$hashmap[ $hashed_url ] = [
|
||||
'size' => $size,
|
||||
'height' => $size_data['height'],
|
||||
'width' => $size_data['width'],
|
||||
'mime' => $size_data['mime-type'],
|
||||
'absolute_path' => $absolute_path,
|
||||
'url' => $url,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $hashmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new image to be converted to WebP.
|
||||
*
|
||||
* @param array $props List of properties to be set on the model.
|
||||
* @param \RIOP_WebP_Extra_Data $extra_data List of extra data params
|
||||
*
|
||||
* @return false|\RIO_Process_Queue
|
||||
*/
|
||||
public function save( $props, $extra_data ) {
|
||||
|
||||
$model = new \RIO_Process_Queue();
|
||||
|
||||
if ( isset( $props['item_hash'] ) ) {
|
||||
$model->set_item_hash( $props['item_hash'] );
|
||||
}
|
||||
|
||||
if ( isset( $props['item_hash_alternative'] ) ) {
|
||||
$model->set_item_hash_alternative( $props['item_hash_alternative'] );
|
||||
}
|
||||
|
||||
if ( isset( $props['object_id'] ) ) {
|
||||
$model->object_id = $props['object_id'];
|
||||
}
|
||||
|
||||
if ( isset( $props['original_mime_type'] ) ) {
|
||||
$model->original_mime_type = $props['original_mime_type'];
|
||||
}
|
||||
|
||||
// Use the format stored during convert_webp(), or fall back to global setting
|
||||
$format = $this->_current_format ?? 'webp';
|
||||
|
||||
$model->item_type = $format; // 'webp' or 'avif'
|
||||
$model->result_status = \RIO_Process_Queue::STATUS_PROCESSING;
|
||||
$model->processing_level = \WRIO_Plugin::app()->getPopulateOption( 'image_optimization_level', \RIO_Process_Queue::LEVEL_NORMAL );
|
||||
$model->is_backed_up = false;
|
||||
$model->original_size = @filesize( $extra_data->get_source_path() );
|
||||
$model->final_size = 0; // to be known
|
||||
$model->final_mime_type = ( $format === 'avif' ) ? 'image/avif' : 'image/webp';
|
||||
$model->extra_data = $extra_data;
|
||||
|
||||
$is_saved = $model->save();
|
||||
|
||||
if ( $is_saved ) {
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'Saved item with src "%s" and hash "%s" successfully', $extra_data->get_source_src(), $model->get_item_hash() ) );
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
\WRIO_Plugin::app()->logger->info( sprintf( 'Failed to save item with src "%s" and hash "%s" as save() method failed, check SQL for errors', $extra_data->get_source_src(), $model->get_item_hash() ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
|
||||
namespace WRIO\WEBP;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
*/
|
||||
class Server {
|
||||
|
||||
/**
|
||||
* return the server home path
|
||||
*
|
||||
* @since 1.0.4
|
||||
*/
|
||||
public static function get_home_path() {
|
||||
$home = set_url_scheme( get_option( 'home' ), 'http' );
|
||||
$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
|
||||
|
||||
if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
|
||||
$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
|
||||
$pos = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
|
||||
|
||||
if ( $pos !== false ) {
|
||||
$home_path = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
|
||||
$home_path = trim( $home_path, '/\\' ) . DIRECTORY_SEPARATOR;
|
||||
|
||||
} else {
|
||||
$wp_path_rel_to_home = DIRECTORY_SEPARATOR . trim( $wp_path_rel_to_home, '/\\' ) . DIRECTORY_SEPARATOR;
|
||||
|
||||
$real_apth = realpath( ABSPATH ) . DIRECTORY_SEPARATOR;
|
||||
|
||||
$pos = strpos( $real_apth, $wp_path_rel_to_home );
|
||||
$home_path = substr( $real_apth, 0, $pos );
|
||||
$home_path = trim( $home_path, '/\\' ) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
} else {
|
||||
$home_path = ABSPATH;
|
||||
}
|
||||
|
||||
$home_path = trim( $home_path, '\\/ ' );
|
||||
|
||||
// not for windows
|
||||
if ( DIRECTORY_SEPARATOR != '\\' ) {
|
||||
$home_path = DIRECTORY_SEPARATOR . $home_path;
|
||||
}
|
||||
|
||||
return $home_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the server run Apache
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_apache() {
|
||||
$is_apache = ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Apache' ) !== false || strpos( $_SERVER['SERVER_SOFTWARE'], 'LiteSpeed' ) !== false );
|
||||
|
||||
return $is_apache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the server run on nginx
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_nginx() {
|
||||
$is_nginx = ( strpos( $_SERVER['SERVER_SOFTWARE'], 'nginx' ) !== false );
|
||||
|
||||
return $is_nginx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the server run on IIS
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_iss() {
|
||||
$is_IIS = ! static::is_apache() && ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) !== false || strpos( $_SERVER['SERVER_SOFTWARE'], 'ExpressionDevServer' ) !== false );
|
||||
|
||||
return $is_IIS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the server run on IIS version 7 and up
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_iis7() {
|
||||
$is_iis7 = static::is_iss() && intval( substr( $_SERVER['SERVER_SOFTWARE'], strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/' ) + 14 ) ) >= 7;
|
||||
|
||||
return $is_iis7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is permalink enabled?
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @return bool
|
||||
* @global \WP_Rewrite $wp_rewrite
|
||||
*/
|
||||
public static function is_permalink() {
|
||||
global $wp_rewrite;
|
||||
|
||||
if ( ! isset( $wp_rewrite ) || ! is_object( $wp_rewrite ) || ! $wp_rewrite->using_permalinks() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whatever the htaccess config file is writable
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_writable_htaccess( $htaccess_file ) {
|
||||
if ( ( ! file_exists( $htaccess_file ) && static::is_permalink() ) || is_writable( $htaccess_file ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whatever the web.config config file is writable
|
||||
*
|
||||
* @since 1.0.4
|
||||
*/
|
||||
public static function is_writable_webconfig_file() {
|
||||
$home_path = static::get_home_path();
|
||||
$web_config_file = $home_path . 'web.config';
|
||||
|
||||
if ( ( ! file_exists( $web_config_file ) && self::is_permalink() ) || win_is_writable( $web_config_file ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.4
|
||||
* @return bool
|
||||
*/
|
||||
public static function got_mod_rewrite() {
|
||||
if ( self::apache_mod_loaded( 'mod_rewrite', true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the specified module exist in the Apache config?
|
||||
*
|
||||
* @since 1.0.4
|
||||
*
|
||||
* @param string $mod The module, e.g. mod_rewrite.
|
||||
* @param bool $default Optional. The default return value if the module is not found. Default false.
|
||||
*
|
||||
* @return bool Whether the specified module is loaded.
|
||||
* @global bool $is_apache
|
||||
*/
|
||||
public static function apache_mod_loaded( $mod, $default = false ) {
|
||||
if ( ! static::is_apache() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( function_exists( 'apache_get_modules' ) ) {
|
||||
$mods = apache_get_modules();
|
||||
if ( in_array( $mod, $mods ) ) {
|
||||
return true;
|
||||
}
|
||||
} elseif ( getenv( 'HTTP_MOD_REWRITE' ) !== false ) {
|
||||
$mod_found = getenv( 'HTTP_MOD_REWRITE' ) == 'On' ? true : false;
|
||||
|
||||
return $mod_found;
|
||||
} elseif ( function_exists( 'phpinfo' ) && false === strpos( ini_get( 'disable_functions' ), 'phpinfo' ) ) {
|
||||
ob_start();
|
||||
phpinfo( 8 );
|
||||
$phpinfo = ob_get_clean();
|
||||
if ( false !== strpos( $phpinfo, $mod ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whatever server using the .htaccess config file
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @return bool
|
||||
*/
|
||||
public static function server_use_htaccess() {
|
||||
$home_path = static::get_home_path();
|
||||
$htaccess_file = $home_path . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
|
||||
if ( ( ! file_exists( $htaccess_file ) && is_writable( $home_path ) && static::is_permalink() ) || is_writable( $htaccess_file ) ) {
|
||||
if ( static::got_mod_rewrite() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans webp rules from htaccess file. Use when deactivating a plugin
|
||||
* or turn off webp support option.
|
||||
*
|
||||
* @since 1.0.4
|
||||
*/
|
||||
public static function htaccess_clear_webp_rules() {
|
||||
$wp_upload_dir = wp_upload_dir();
|
||||
$root_htaccess_file_path = static::get_home_path() . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
$wp_content_htaccess_file_path = trailingslashit( WP_CONTENT_DIR ) . '.htaccess';
|
||||
|
||||
static::insert_with_markers( $root_htaccess_file_path, '' );
|
||||
|
||||
if ( isset( $wp_upload_dir['error'] ) && $wp_upload_dir['error'] !== false ) {
|
||||
\WRIO_Plugin::app()->logger->error( 'It is not possible to update webp rules for htaccess, because upload dir is not writable.' );
|
||||
} else {
|
||||
|
||||
$upload_base = $wp_upload_dir['basedir'];
|
||||
$uploads_htaccess_file_path = trailingslashit( $upload_base ) . '.htaccess';
|
||||
|
||||
static::insert_with_markers( $uploads_htaccess_file_path, '' );
|
||||
}
|
||||
|
||||
static::insert_with_markers( $wp_content_htaccess_file_path, '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add webp rules in htaccess file. Use when activating a plugin
|
||||
* or turn on webp support option.
|
||||
*
|
||||
* @since @since 1.0.4
|
||||
*
|
||||
* @param bool $clear
|
||||
*/
|
||||
public static function htaccess_update_webp_rules() {
|
||||
// [BS] Backward compat. 11/03/2019 - remove possible settings from root .htaccess
|
||||
$wp_upload_dir = wp_upload_dir();
|
||||
$root_htaccess_file_path = static::get_home_path() . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
$wp_content_htaccess_file_path = trailingslashit( WP_CONTENT_DIR ) . '.htaccess';
|
||||
|
||||
$rules = '
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
|
||||
##### TRY FIRST the file appended with .webp (ex. test.jpg.webp) #####
|
||||
# Does browser explicitly support webp?
|
||||
RewriteCond %{HTTP_USER_AGENT} Chrome [OR]
|
||||
# OR Is request from Page Speed
|
||||
RewriteCond %{HTTP_USER_AGENT} "Google Page Speed Insights" [OR]
|
||||
# OR does this browser explicitly support webp
|
||||
RewriteCond %{HTTP_ACCEPT} image/webp
|
||||
# AND is the request a jpg or png?
|
||||
RewriteCond %{REQUEST_URI} ^(.+)\.(?:jpe?g|png)$
|
||||
# AND does a .ext.webp image exist?
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.webp -f
|
||||
# THEN send the webp image and set the env var webp
|
||||
RewriteRule ^(.+)$ $1.webp [NC,T=image/webp,E=webp,L]
|
||||
|
||||
##### IF NOT, try the file with replaced extension (test.webp) #####
|
||||
RewriteCond %{HTTP_USER_AGENT} Chrome [OR]
|
||||
RewriteCond %{HTTP_USER_AGENT} "Google Page Speed Insights" [OR]
|
||||
RewriteCond %{HTTP_ACCEPT} image/webp
|
||||
# AND is the request a jpg or png? (also grab the basepath %1 to match in the next rule)
|
||||
RewriteCond %{REQUEST_URI} ^(.+)\.(?:jpe?g|png)$
|
||||
# AND does a .ext.webp image exist?
|
||||
RewriteCond %{DOCUMENT_ROOT}/%1.webp -f
|
||||
# THEN send the webp image and set the env var webp
|
||||
RewriteRule (.+)\.(?:jpe?g|png)$ $1.webp [NC,T=image/webp,E=webp,L]
|
||||
|
||||
</IfModule>
|
||||
<IfModule mod_headers.c>
|
||||
# If REDIRECT_webp env var exists, append Accept to the Vary header
|
||||
Header append Vary Accept env=REDIRECT_webp
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
AddType image/webp .webp
|
||||
</IfModule>
|
||||
';
|
||||
|
||||
static::insert_with_markers( $root_htaccess_file_path, $rules );
|
||||
|
||||
if ( isset( $wp_upload_dir['error'] ) && $wp_upload_dir['error'] !== false ) {
|
||||
\WRIO_Plugin::app()->logger->error( 'It is not possible to update webp rules for htaccess, because upload dir is not writable.' );
|
||||
} else {
|
||||
|
||||
$upload_base = $wp_upload_dir['basedir'];
|
||||
$uploads_htaccess_file_path = trailingslashit( $upload_base ) . '.htaccess';
|
||||
|
||||
static::insert_with_markers( $uploads_htaccess_file_path, $rules );
|
||||
}
|
||||
|
||||
static::insert_with_markers( $wp_content_htaccess_file_path, $rules );
|
||||
}
|
||||
|
||||
public static function insert_with_markers( $file_path, $content ) {
|
||||
if ( ! static::is_writable_htaccess( $file_path ) ) {
|
||||
\WRIO_Plugin::app()->logger->error( sprintf( 'It is not possible to update webp rules for htaccess, because file (%s) is not writable.', $file_path ) );
|
||||
} else {
|
||||
if ( ! static::got_mod_rewrite() ) {
|
||||
\WRIO_Plugin::app()->logger->error( "It isn't possible to update webp rules for htaccess, because mode rewrite is unsupported." );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! insert_with_markers( $file_path, 'Robin Image Optimizer Webp', $content ) ) {
|
||||
\WRIO_Plugin::app()->logger->error( 'Failed write webp rules to htaccess file (%s)' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"rosell-dk/dom-util-for-webp": "^0.3.0"
|
||||
}
|
||||
}
|
||||
73
wp-content/plugins/robin-image-optimizer/libs/addons/includes/classes/webp/composer.lock
generated
Normal file
73
wp-content/plugins/robin-image-optimizer/libs/addons/includes/classes/webp/composer.lock
generated
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8d03ee8cf464879ef4a5fd1a6fba0c95",
|
||||
"packages": [
|
||||
{
|
||||
"name": "rosell-dk/dom-util-for-webp",
|
||||
"version": "0.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rosell-dk/dom-util-for-webp.git",
|
||||
"reference": "bae8f4a9b666726359d28bfb227d886c12f136a9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/rosell-dk/dom-util-for-webp/zipball/bae8f4a9b666726359d28bfb227d886c12f136a9",
|
||||
"reference": "bae8f4a9b666726359d28bfb227d886c12f136a9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.11",
|
||||
"phpunit/phpunit": "5.7.27",
|
||||
"squizlabs/php_codesniffer": "3.*"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"scripts-descriptions": {
|
||||
"ci": "Run tests before CI",
|
||||
"phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
|
||||
"phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
|
||||
"cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
|
||||
"cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
|
||||
"test": "Launches the preconfigured PHPUnit"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DOMUtilForWebP\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bjørn Rosell",
|
||||
"homepage": "https://www.bitwise-it.dk/contact",
|
||||
"role": "Project Author"
|
||||
}
|
||||
],
|
||||
"description": "Replace image URLs found in HTML",
|
||||
"keywords": [
|
||||
"Webp",
|
||||
"html",
|
||||
"images",
|
||||
"replace"
|
||||
],
|
||||
"time": "2019-07-31T14:17:18+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit84320a6a225dd65e40820c51b32fdca4::getLoader();
|
||||
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class InstalledVersions
|
||||
{
|
||||
private static $installed = array (
|
||||
'root' =>
|
||||
array (
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'aliases' =>
|
||||
array (
|
||||
0 => '9999999-dev',
|
||||
),
|
||||
'reference' => '3d4062ddde198994093315acc55c6232067ce027',
|
||||
'name' => '__root__',
|
||||
),
|
||||
'versions' =>
|
||||
array (
|
||||
'__root__' =>
|
||||
array (
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'aliases' =>
|
||||
array (
|
||||
0 => '9999999-dev',
|
||||
),
|
||||
'reference' => '3d4062ddde198994093315acc55c6232067ce027',
|
||||
),
|
||||
'rosell-dk/dom-util-for-webp' =>
|
||||
array (
|
||||
'pretty_version' => '0.3.1',
|
||||
'version' => '0.3.1.0',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => 'bae8f4a9b666726359d28bfb227d886c12f136a9',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
return array_keys(self::$installed['versions']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function isInstalled($packageName)
|
||||
{
|
||||
return isset(self::$installed['versions'][$packageName]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints($constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
if (!isset(self::$installed['versions'][$packageName])) {
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset(self::$installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = self::$installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', self::$installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', self::$installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', self::$installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
if (!isset(self::$installed['versions'][$packageName])) {
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
if (!isset(self::$installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::$installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
if (!isset(self::$installed['versions'][$packageName])) {
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::$installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
if (!isset(self::$installed['versions'][$packageName])) {
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
if (!isset(self::$installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::$installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getRootPackage()
|
||||
{
|
||||
return self::$installed['root'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getRawData()
|
||||
{
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'DOMUtilForWebP\\' => array($vendorDir . '/rosell-dk/dom-util-for-webp/src'),
|
||||
);
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit84320a6a225dd65e40820c51b32fdca4
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit84320a6a225dd65e40820c51b32fdca4', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit84320a6a225dd65e40820c51b32fdca4', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit84320a6a225dd65e40820c51b32fdca4::getInitializer($loader));
|
||||
} else {
|
||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->set($namespace, $path);
|
||||
}
|
||||
|
||||
$map = require __DIR__ . '/autoload_psr4.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->setPsr4($namespace, $path);
|
||||
}
|
||||
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit84320a6a225dd65e40820c51b32fdca4
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'D' =>
|
||||
array (
|
||||
'DOMUtilForWebP\\' => 15,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'DOMUtilForWebP\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit84320a6a225dd65e40820c51b32fdca4::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit84320a6a225dd65e40820c51b32fdca4::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit84320a6a225dd65e40820c51b32fdca4::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "rosell-dk/dom-util-for-webp",
|
||||
"version": "0.3.1",
|
||||
"version_normalized": "0.3.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rosell-dk/dom-util-for-webp.git",
|
||||
"reference": "bae8f4a9b666726359d28bfb227d886c12f136a9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/rosell-dk/dom-util-for-webp/zipball/bae8f4a9b666726359d28bfb227d886c12f136a9",
|
||||
"reference": "bae8f4a9b666726359d28bfb227d886c12f136a9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.11",
|
||||
"phpunit/phpunit": "5.7.27",
|
||||
"squizlabs/php_codesniffer": "3.*"
|
||||
},
|
||||
"time": "2019-07-31T14:17:18+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"scripts-descriptions": {
|
||||
"ci": "Run tests before CI",
|
||||
"phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
|
||||
"phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
|
||||
"cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
|
||||
"cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
|
||||
"test": "Launches the preconfigured PHPUnit"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DOMUtilForWebP\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bjørn Rosell",
|
||||
"homepage": "https://www.bitwise-it.dk/contact",
|
||||
"role": "Project Author"
|
||||
}
|
||||
],
|
||||
"description": "Replace image URLs found in HTML",
|
||||
"keywords": [
|
||||
"Webp",
|
||||
"html",
|
||||
"images",
|
||||
"replace"
|
||||
],
|
||||
"install-path": "../rosell-dk/dom-util-for-webp"
|
||||
}
|
||||
],
|
||||
"dev": true
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php return array (
|
||||
'root' =>
|
||||
array (
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'aliases' =>
|
||||
array (
|
||||
0 => '9999999-dev',
|
||||
),
|
||||
'reference' => '3d4062ddde198994093315acc55c6232067ce027',
|
||||
'name' => '__root__',
|
||||
),
|
||||
'versions' =>
|
||||
array (
|
||||
'__root__' =>
|
||||
array (
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'aliases' =>
|
||||
array (
|
||||
0 => '9999999-dev',
|
||||
),
|
||||
'reference' => '3d4062ddde198994093315acc55c6232067ce027',
|
||||
),
|
||||
'rosell-dk/dom-util-for-webp' =>
|
||||
array (
|
||||
'pretty_version' => '0.3.1',
|
||||
'version' => '0.3.1.0',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => 'bae8f4a9b666726359d28bfb227d886c12f136a9',
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->exclude('tests')
|
||||
->in(__DIR__)
|
||||
;
|
||||
|
||||
$config = PhpCsFixer\Config::create();
|
||||
$config
|
||||
->setRules([
|
||||
'@PSR2' => true,
|
||||
'array_syntax' => [
|
||||
'syntax' => 'short',
|
||||
],
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
||||
|
||||
return $config;
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "rosell-dk/dom-util-for-webp",
|
||||
"description": "Replace image URLs found in HTML",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"minimum-stability": "stable",
|
||||
"keywords": ["webp", "replace", "images", "html"],
|
||||
"scripts": {
|
||||
"ci": [
|
||||
"@build",
|
||||
"@test",
|
||||
"@phpcs-all",
|
||||
"@composer validate --no-check-all --strict",
|
||||
"@phpstan-global"
|
||||
],
|
||||
"cs-fix-all": [
|
||||
"php-cs-fixer fix src"
|
||||
],
|
||||
"cs-fix": "php-cs-fixer fix",
|
||||
"cs-dry": "php-cs-fixer fix --dry-run --diff",
|
||||
"test": "phpunit --coverage-text --coverage-clover=coverage.clover",
|
||||
"phpcs": "phpcs --standard=PSR2",
|
||||
"phpcbf": "phpcbf --standard=PSR2",
|
||||
"phpstan": "vendor/bin/phpstan analyse src --level=4",
|
||||
"phpstan-global": "~/.composer/vendor/bin/phpstan analyse src --level=4"
|
||||
},
|
||||
"extra": {
|
||||
"scripts-descriptions": {
|
||||
"ci": "Run tests before CI",
|
||||
"phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
|
||||
"phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
|
||||
"cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
|
||||
"cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
|
||||
"test": "Launches the preconfigured PHPUnit"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "DOMUtilForWebP\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "DOMUtilForWebPTests\\": "tests/" }
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bjørn Rosell",
|
||||
"homepage": "https://www.bitwise-it.dk/contact",
|
||||
"role": "Project Author"
|
||||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.11",
|
||||
"phpunit/phpunit": "5.7.27",
|
||||
"squizlabs/php_codesniffer": "3.*"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="false"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Dom util for WebP Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">src/</directory>
|
||||
<exclude>
|
||||
<directory>./vendor</directory>
|
||||
<directory>./tests</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<logging>
|
||||
<log type="junit" target="build/report.junit.xml"/>
|
||||
<log type="coverage-clover" target="build/logs/clover.xml"/>
|
||||
<log type="coverage-text" target="build/coverage.txt"/>
|
||||
<!--<log type="coverage-html" target="build/coverage"/>-->
|
||||
</logging>
|
||||
|
||||
</phpunit>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace DOMUtilForWebP;
|
||||
|
||||
//use Sunra\PhpSimple\HtmlDomParser;
|
||||
|
||||
/**
|
||||
* Highly configurable class for replacing image URLs in HTML (both src and srcset syntax)
|
||||
*
|
||||
* Based on http://simplehtmldom.sourceforge.net/ - a library for easily manipulating HTML by means of a DOM.
|
||||
* The great thing about this library is that it supports working on invalid HTML and it only applies the changes you
|
||||
* make - very gently.
|
||||
*
|
||||
* TODO: Check out how ewww does it
|
||||
*
|
||||
* Behaviour can be customized by overriding the public methods (replaceUrl, $searchInTags, etc)
|
||||
*
|
||||
* Default behaviour:
|
||||
* - The modified URL is the same as the original, with ".webp" appended (replaceUrl)
|
||||
* - Limits to these tags: <img>, <source>, <input> and <iframe> ($searchInTags)
|
||||
* - Limits to these attributes: "src", "src-set" and any attribute starting with "data-" (attributeFilter)
|
||||
* - Only replaces URLs that ends with "png", "jpg" or "jpeg" (no query strings either) (replaceUrl)
|
||||
*
|
||||
*
|
||||
*/
|
||||
class ImageUrlReplacer
|
||||
{
|
||||
|
||||
// define tags to be searched.
|
||||
// The div and li are on the list because these are often used with lazy loading
|
||||
// should we add <meta> ?
|
||||
// Probably not for open graph images or twitter
|
||||
// so not these:
|
||||
// - <meta property="og:image" content="[url]">
|
||||
// - <meta property="og:image:secure_url" content="[url]">
|
||||
// - <meta name="twitter:image" content="[url]">
|
||||
// Meta can also be used in schema.org micro-formatting, ie:
|
||||
// - <meta itemprop="image" content="[url]">
|
||||
//
|
||||
// How about preloaded images? - yes, suppose we should replace those
|
||||
// - <link rel="prefetch" href="[url]">
|
||||
// - <link rel="preload" as="image" href="[url]">
|
||||
public static $searchInTags = ['img', 'source', 'input', 'iframe', 'div', 'li', 'link', 'a', 'section'];
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string|null webp url or, if URL should not be changed, return nothing
|
||||
**/
|
||||
public function replaceUrl($url)
|
||||
{
|
||||
// Match .jpg, .jpeg, or .png at end of URL (before optional query string)
|
||||
if (!preg_match('#\.(png|jpe?g)($|\?)#i', $url)) {
|
||||
return null;
|
||||
}
|
||||
return $url . '.webp';
|
||||
}
|
||||
|
||||
public function replaceUrlOr($url, $returnValueIfDenied)
|
||||
{
|
||||
$url = $this->replaceUrl($url);
|
||||
return (isset($url) ? $url : $returnValueIfDenied);
|
||||
}
|
||||
|
||||
/*
|
||||
public function isValidUrl($url)
|
||||
{
|
||||
return preg_match('#(png|jpe?g)$#', $url);
|
||||
}*/
|
||||
|
||||
public function handleSrc($attrValue)
|
||||
{
|
||||
return $this->replaceUrlOr($attrValue, $attrValue);
|
||||
}
|
||||
|
||||
public function handleSrcSet($attrValue)
|
||||
{
|
||||
// $attrValue is ie: <img data-x="1.jpg 1000w, 2.jpg">
|
||||
$srcsetArr = explode(',', $attrValue);
|
||||
foreach ($srcsetArr as $i => $srcSetEntry) {
|
||||
// $srcSetEntry is ie "image.jpg 520w", but can also lack width, ie just "image.jpg"
|
||||
// it can also be ie "image.jpg 2x"
|
||||
$srcSetEntry = trim($srcSetEntry);
|
||||
$entryParts = preg_split('/\s+/', $srcSetEntry, 2);
|
||||
if (count($entryParts) == 2) {
|
||||
list($src, $descriptors) = $entryParts;
|
||||
} else {
|
||||
$src = $srcSetEntry;
|
||||
$descriptors = null;
|
||||
}
|
||||
|
||||
$webpUrl = $this->replaceUrlOr($src, false);
|
||||
if ($webpUrl !== false) {
|
||||
$srcsetArr[$i] = $webpUrl . (isset($descriptors) ? ' ' . $descriptors : '');
|
||||
}
|
||||
}
|
||||
return implode(', ', $srcsetArr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if attribute value looks like it has srcset syntax.
|
||||
* "image.jpg 100w" does for example. And "image.jpg 1x". Also "image1.jpg, image2.jpg 1x"
|
||||
* Mixing x and w is invalid (according to
|
||||
* https://stackoverflow.com/questions/26928828/html5-srcset-mixing-x-and-w-syntax)
|
||||
* But we accept it anyway
|
||||
* It is not the job of this function to see if the first part is an image URL
|
||||
* That will be done in handleSrcSet.
|
||||
*
|
||||
*/
|
||||
public function looksLikeSrcSet($value)
|
||||
{
|
||||
if (preg_match('#\s\d*(w|x)#', $value)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleAttribute($value)
|
||||
{
|
||||
if (self::looksLikeSrcSet($value)) {
|
||||
return self::handleSrcSet($value);
|
||||
}
|
||||
return self::handleSrc($value);
|
||||
}
|
||||
|
||||
public function attributeFilter($attrName)
|
||||
{
|
||||
$attrName = strtolower($attrName);
|
||||
if (($attrName == 'src') || ($attrName == 'srcset') || (strpos($attrName, 'data-') === 0)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function processCSSRegExCallback($matches)
|
||||
{
|
||||
list($all, $pre, $quote, $url, $post) = $matches;
|
||||
return $pre . $this->replaceUrlOr($url, $url) . $post;
|
||||
}
|
||||
|
||||
public function processCSS($css)
|
||||
{
|
||||
$declarations = explode(';', $css);
|
||||
foreach ($declarations as $i => &$declaration) {
|
||||
if (preg_match('#(background(-image)?)\\s*:#', $declaration)) {
|
||||
// https://regexr.com/46qdg
|
||||
//$regex = '#(url\s*\(([\"\']?))([^\'\";\)]*)(\2\s*\))#';
|
||||
$parts = explode(',', $declaration);
|
||||
//print_r($parts);
|
||||
foreach ($parts as &$part) {
|
||||
//echo 'part:' . $part . "\n";
|
||||
$regex = '#(url\\s*\\(([\\"\\\']?))([^\\\'\\";\\)]*)(\\2\\s*\\))#';
|
||||
$part = preg_replace_callback($regex, 'self::processCSSRegExCallback', $part);
|
||||
//echo 'result:' . $part . "\n";
|
||||
}
|
||||
$declarations[$i] = implode(',', $parts);
|
||||
}
|
||||
}
|
||||
return implode(';', $declarations);
|
||||
}
|
||||
|
||||
public function replaceHtml($html)
|
||||
{
|
||||
if ($html == '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/4812691/preserve-line-breaks-simple-html-dom-parser
|
||||
|
||||
// function str_get_html($str, $lowercase=true, $forceTagsClosed=true, $target_charset = DEFAULT_TARGET_CHARSET,
|
||||
// $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT)
|
||||
|
||||
//$dom = HtmlDomParser::str_get_html($html, false, false, 'UTF-8', false);
|
||||
$dom = str_get_html($html, false, false, 'UTF-8', false);
|
||||
|
||||
// MAX_FILE_SIZE is defined in simple_html_dom.
|
||||
// For safety sake, we make sure it is defined before using
|
||||
defined('MAX_FILE_SIZE') || define('MAX_FILE_SIZE', 600000);
|
||||
|
||||
if ($dom === false) {
|
||||
if (strlen($html) > MAX_FILE_SIZE) {
|
||||
return '<!-- Alter HTML was skipped because the HTML is too big to process! ' .
|
||||
'(limit is set to ' . MAX_FILE_SIZE . ' bytes) -->' . "\n" . $html;
|
||||
}
|
||||
return '<!-- Alter HTML was skipped because the helper library refused to process the html -->' .
|
||||
"\n" . $html;
|
||||
}
|
||||
|
||||
// Replace attributes (src, srcset, data-src, etc)
|
||||
foreach (self::$searchInTags as $tagName) {
|
||||
$elems = $dom->find($tagName);
|
||||
foreach ($elems as $index => $elem) {
|
||||
foreach ($elem->getAllAttributes() as $attrName => $attrValue) {
|
||||
if ($this->attributeFilter($attrName)) {
|
||||
// Use direct property assignment instead of setAttribute()
|
||||
// simple_html_dom's setAttribute() may not persist changes properly
|
||||
$elem->$attrName = $this->handleAttribute($attrValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace <style> elements
|
||||
$elems = $dom->find('style');
|
||||
foreach ($elems as $index => $elem) {
|
||||
$css = $this->processCSS($elem->innertext);
|
||||
if ($css != $elem->innertext) {
|
||||
$elem->innertext = $css;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace "style attributes
|
||||
$elems = $dom->find('*[style]');
|
||||
foreach ($elems as $index => $elem) {
|
||||
$css = $this->processCSS($elem->style);
|
||||
if ($css != $elem->style) {
|
||||
$elem->style = $css;
|
||||
}
|
||||
}
|
||||
|
||||
return $dom->save();
|
||||
}
|
||||
|
||||
/* Main replacer function */
|
||||
public static function replace($html)
|
||||
{
|
||||
if (!function_exists('str_get_html')) {
|
||||
require_once __DIR__ . '/../src-vendor/simple_html_dom/simple_html_dom.inc';
|
||||
}
|
||||
$iur = new static();
|
||||
return $iur->replaceHtml($html);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace DOMUtilForWebP;
|
||||
|
||||
//use Sunra\PhpSimple\HtmlDomParser;
|
||||
/**
|
||||
* Class PictureTags - convert an <img> tag to a <picture> tag and add the webp versions of the images
|
||||
* Based this code on code from the ShortPixel plugin, which used code from Responsify WP plugin
|
||||
*/
|
||||
|
||||
use \WebPExpress\AlterHtmlHelper;
|
||||
|
||||
class PictureTags
|
||||
{
|
||||
|
||||
public function replaceUrl($url)
|
||||
{
|
||||
// Match .jpg, .jpeg, or .png at end of URL (before optional query string)
|
||||
if (!preg_match('#\.(png|jpe?g)($|\?)#i', $url)) {
|
||||
return;
|
||||
}
|
||||
return $url . '.webp';
|
||||
}
|
||||
|
||||
public function replaceUrlOr($url, $returnValueIfDenied)
|
||||
{
|
||||
$url = $this->replaceUrl($url);
|
||||
return (isset($url) ? $url : $returnValueIfDenied);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for attributes such as "data-lazy-src" and "data-src" and prefer them over "src"
|
||||
*
|
||||
* @param array $attributes an array of attributes for the element
|
||||
* @param string $attrName ie "src", "srcset" or "sizes"
|
||||
*
|
||||
* @return array an array with "value" key and "attrName" key. ("value" is the value of the attribute and
|
||||
* "attrName" is the name of the attribute used)
|
||||
*
|
||||
*/
|
||||
private static function lazyGet($attributes, $attrName)
|
||||
{
|
||||
return array(
|
||||
'value' =>
|
||||
(isset($attributes['data-lazy-' . $attrName]) && strlen($attributes['data-lazy-' . $attrName])) ?
|
||||
trim($attributes['data-lazy-' . $attrName])
|
||||
: (isset($attributes['data-' . $attrName]) && strlen($attributes['data-' . $attrName]) ?
|
||||
trim($attributes['data-' . $attrName])
|
||||
: (isset($attributes[$attrName]) && strlen($attributes[$attrName]) ?
|
||||
trim($attributes[$attrName]) : false)),
|
||||
'attrName' =>
|
||||
(isset($attributes['data-lazy-' . $attrName]) && strlen($attributes['data-lazy-' . $attrName])) ?
|
||||
'data-lazy-' . $attrName
|
||||
: (isset($attributes['data-' . $attrName]) && strlen($attributes['data-' . $attrName]) ?
|
||||
'data-' . $attrName
|
||||
: (isset($attributes[$attrName]) && strlen($attributes[$attrName]) ? $attrName : false))
|
||||
);
|
||||
}
|
||||
|
||||
private static function getAttributes($html)
|
||||
{
|
||||
if (function_exists("mb_encode_numericentity")) {
|
||||
$html = mb_encode_numericentity($html, [0x80, 0x10FFFF, 0, ~0], 'UTF-8');
|
||||
}
|
||||
if (class_exists('\\DOMDocument')) {
|
||||
$dom = new \DOMDocument();
|
||||
@$dom->loadHTML($html);
|
||||
$image = $dom->getElementsByTagName('img')->item(0);
|
||||
$attributes = [];
|
||||
foreach ($image->attributes as $attr) {
|
||||
$attributes[$attr->nodeName] = $attr->nodeValue;
|
||||
}
|
||||
return $attributes;
|
||||
} else {
|
||||
//$dom = HtmlDomParser::str_get_html($html, false, false, 'UTF-8', false);
|
||||
$dom = str_get_html($html, false, false, 'UTF-8', false);
|
||||
if ($dom !== false) {
|
||||
$elems = $dom->find('img,IMG');
|
||||
foreach ($elems as $index => $elem) {
|
||||
$attributes = [];
|
||||
foreach ($elem->getAllAttributes() as $attrName => $attrValue) {
|
||||
$attributes[strtolower($attrName)] = $attrValue;
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a string with all attributes.
|
||||
*
|
||||
* @param array $attribute_array
|
||||
* @return string
|
||||
*/
|
||||
private static function createAttributes($attribute_array)
|
||||
{
|
||||
$attributes = '';
|
||||
foreach ($attribute_array as $attribute => $value) {
|
||||
if ( 'src' !== $attribute ) {
|
||||
$attributes .= $attribute . '="' . esc_attr( $value ) . '" ';
|
||||
} else {
|
||||
$attributes .= $attribute . '="' . esc_url( $value ) . '" ';
|
||||
}
|
||||
}
|
||||
if ($attributes == '') {
|
||||
return '';
|
||||
}
|
||||
// Removes the extra space after the last attribute. Add space before
|
||||
return ' ' . substr($attributes, 0, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace <image> tag with <picture> tag.
|
||||
*/
|
||||
private function replaceCallback($match)
|
||||
{
|
||||
$imgTag = $match[0];
|
||||
|
||||
// Do nothing with images that have the 'webpexpress-processed' class.
|
||||
if (strpos($imgTag, 'webpexpress-processed') !== false) {
|
||||
return $imgTag;
|
||||
}
|
||||
$imgAttributes = self::getAttributes($imgTag);
|
||||
|
||||
$srcInfo = self::lazyGet($imgAttributes, 'src');
|
||||
$srcsetInfo = self::lazyGet($imgAttributes, 'srcset');
|
||||
$sizesInfo = self::lazyGet($imgAttributes, 'sizes');
|
||||
|
||||
// add the exclude class so if this content is processed again in other filter,
|
||||
// the img is not converted again in picture
|
||||
$imgAttributes['class'] = (isset($imgAttributes['class']) ? $imgAttributes['class'] . " " : "") .
|
||||
"webpexpress-processed";
|
||||
|
||||
$srcsetWebP = '';
|
||||
if ($srcsetInfo['value']) {
|
||||
$srcsetArr = explode(', ', $srcsetInfo["value"]);
|
||||
$srcsetArrWebP = [];
|
||||
foreach ($srcsetArr as $i => $srcSetEntry) {
|
||||
// $srcSetEntry is ie "http://example.com/image.jpg 520w"
|
||||
$result = preg_split('/\s+/', trim($srcSetEntry));
|
||||
$src = trim($srcSetEntry);
|
||||
$width = null;
|
||||
if ($result && count($result) >= 2) {
|
||||
list($src, $width) = $result;
|
||||
}
|
||||
|
||||
$webpUrl = $this->replaceUrlOr($src, false);
|
||||
if ($webpUrl !== false) {
|
||||
$srcsetArrWebP[] = $webpUrl . (isset($width) ? ' ' . $width : '');
|
||||
}
|
||||
}
|
||||
$srcsetWebP = implode(', ', $srcsetArrWebP);
|
||||
if (strlen($srcsetWebP) == 0) {
|
||||
// We have no webps for you, so no reason to create <picture> tag
|
||||
return $imgTag;
|
||||
}
|
||||
$sizesAttr = ($sizesInfo['value'] ? (' ' . $sizesInfo['attrName'] . '="' . $sizesInfo['value'] . '"') : '');
|
||||
$sourceSrcAttrName = $srcsetInfo['attrName'];
|
||||
if ($sourceSrcAttrName == 'src') {
|
||||
// "src" isn't allowed in <source> tag with <picture> tag as parent.
|
||||
$sourceSrcAttrName = 'srcset';
|
||||
}
|
||||
return '<picture>'
|
||||
. '<source ' . $sourceSrcAttrName . '="' . $srcsetWebP . '"' . $sizesAttr . ' type="image/webp">'
|
||||
. '<img' . self::createAttributes($imgAttributes) . '>'
|
||||
. '</picture>';
|
||||
} else {
|
||||
$srcWebP = $this->replaceUrlOr($srcInfo['value'], false);
|
||||
if ($srcWebP === false) {
|
||||
// No reason to create <picture> tag
|
||||
return $imgTag;
|
||||
}
|
||||
|
||||
$sourceSrcAttrName = $srcInfo['attrName'];
|
||||
if ($sourceSrcAttrName == 'src') {
|
||||
// "src" isn't allowed in <source> tag with <picture> tag as parent.
|
||||
$sourceSrcAttrName = 'srcset';
|
||||
}
|
||||
|
||||
return '<picture>'
|
||||
. '<source ' . $sourceSrcAttrName . '="' . $srcWebP . '" type="image/webp">'
|
||||
. '<img' . self::createAttributes($imgAttributes) . '>'
|
||||
. '</picture>';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
public function replaceHtml($content)
|
||||
{
|
||||
// TODO: We should not replace <img> tags that are inside <picture> tags already, now should we?
|
||||
return preg_replace_callback('/<img[^>]*>/i', array($this, 'replaceCallback'), $content);
|
||||
}
|
||||
|
||||
/* Main replacer function */
|
||||
public static function replace($html)
|
||||
{
|
||||
if (!function_exists('str_get_html')) {
|
||||
require_once __DIR__ . '/../src-vendor/simple_html_dom/simple_html_dom.inc';
|
||||
}
|
||||
$pt = new static();
|
||||
return $pt->replaceHtml($html);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* Supporting functions for premium plugin
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the path to NextGen galleries on monosites.
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @return string|bool An absolute path. False if it can't be retrieved.
|
||||
*/
|
||||
function wrio_get_ngg_galleries_path() {
|
||||
$galleries_path = get_site_option( 'ngg_options' );
|
||||
|
||||
if ( empty( $galleries_path['gallerypath'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$galleries_path = wp_normalize_path( $galleries_path['gallerypath'] );
|
||||
$galleries_path = trim( $galleries_path, '/' ); // Something like `wp-content/gallery`.
|
||||
|
||||
$ngg_root = defined( 'NGG_GALLERY_ROOT_TYPE' ) ? NGG_GALLERY_ROOT_TYPE : 'site';
|
||||
|
||||
if ( $galleries_path && 'content' === $ngg_root ) {
|
||||
$ngg_root = wp_normalize_path( WP_CONTENT_DIR );
|
||||
$ngg_root = trim( $ngg_root, '/' ); // Something like `abs-path/to/wp-content`.
|
||||
|
||||
$exploded_root = explode( '/', $ngg_root );
|
||||
$exploded_galleries = explode( '/', $galleries_path );
|
||||
$first_gallery_dirname = reset( $exploded_galleries );
|
||||
$last_root_dirname = end( $exploded_root );
|
||||
|
||||
if ( $last_root_dirname === $first_gallery_dirname ) {
|
||||
array_shift( $exploded_galleries );
|
||||
$galleries_path = implode( '/', $exploded_galleries );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'content' === $ngg_root ) {
|
||||
$ngg_root = wp_normalize_path( WP_CONTENT_DIR );
|
||||
} else {
|
||||
$ngg_root = wp_normalize_path( ABSPATH );
|
||||
}
|
||||
|
||||
if ( strpos( $galleries_path, $ngg_root ) !== 0 ) {
|
||||
$galleries_path = $ngg_root . $galleries_path;
|
||||
}
|
||||
|
||||
return $galleries_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to WooCommerce logs on monosites.
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @access public
|
||||
* @return string An absolute path.
|
||||
*/
|
||||
function wrio_get_wc_logs_path() {
|
||||
if ( defined( 'WC_LOG_DIR' ) ) {
|
||||
return WC_LOG_DIR;
|
||||
}
|
||||
|
||||
$wp_upload_dir = wp_upload_dir();
|
||||
|
||||
if ( isset( $wp_upload_dir['error'] ) && $wp_upload_dir['error'] !== false ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$wp_upload_dir_path = wp_normalize_path( trailingslashit( $wp_upload_dir['basedir'] ) );
|
||||
|
||||
return $wp_upload_dir_path . 'wc-logs';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to EWWW optimization tools.
|
||||
* It is the same for all sites on multisite.
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @return string An absolute path.
|
||||
*/
|
||||
function wrio_get_ewww_tools_path() {
|
||||
if ( defined( 'EWWW_IMAGE_OPTIMIZER_TOOL_PATH' ) ) {
|
||||
return wp_normalize_path( EWWW_IMAGE_OPTIMIZER_TOOL_PATH );
|
||||
}
|
||||
|
||||
return trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) ) . 'ewww';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to ShortPixel backup folder.
|
||||
* It is the same for all sites on multisite (and yes, you'll get a surprise if your upload base dir -aka uploads/sites/12/- is not 2 folders deeper than theuploads folder).
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @access public
|
||||
* @return string An absolute path.
|
||||
*/
|
||||
function wrio_get_shortpixel_path() {
|
||||
if ( defined( 'SHORTPIXEL_BACKUP_FOLDER' ) ) {
|
||||
return trailingslashit( SHORTPIXEL_BACKUP_FOLDER );
|
||||
}
|
||||
|
||||
$wp_upload_dir = wp_upload_dir();
|
||||
|
||||
if ( isset( $wp_upload_dir['error'] ) && $wp_upload_dir['error'] !== false ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$wp_upload_dir_path = wp_normalize_path( trailingslashit( $wp_upload_dir['basedir'] ) );
|
||||
|
||||
return $wp_upload_dir_path . 'ShortpixelBackups';
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Reference in New Issue
Block a user