1126 lines
35 KiB
PHP
1126 lines
35 KiB
PHP
<?php
|
||
|
||
// Exit if accessed directly
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* Класс для работы с WordPress attachment.
|
||
*
|
||
* @version 1.0
|
||
*/
|
||
class WIO_Attachment {
|
||
|
||
/**
|
||
* @var int
|
||
*/
|
||
private $id;
|
||
|
||
|
||
/**
|
||
* @var array meta-данные
|
||
*/
|
||
private $attachment_meta;
|
||
|
||
/**
|
||
* @var array массив с данными о папке uploads
|
||
*/
|
||
private $wp_upload_dir;
|
||
|
||
/**
|
||
* @var string
|
||
*/
|
||
private $url;
|
||
|
||
/**
|
||
* @var string
|
||
*/
|
||
private $path;
|
||
|
||
/**
|
||
* @var RIO_Process_Queue
|
||
*/
|
||
private $optimization_data;
|
||
|
||
/**
|
||
* Инициализация аттачмента
|
||
*
|
||
* @param int $attachment_id Номер аттачмента из медиабиблиотеки
|
||
* @param array|false $attachment_meta метаданные аттачмента. Ключи массива аналогичны функции wp_get_attachment_metadata
|
||
*/
|
||
public function __construct( $attachment_id, $attachment_meta = false ) {
|
||
$this->id = $attachment_id;
|
||
$this->wp_upload_dir = wp_upload_dir();
|
||
$this->attachment_meta = $attachment_meta;
|
||
|
||
if ( ! $attachment_meta ) {
|
||
// some meta can be missing due to: https://wordpress.stackexchange.com/q/330174/149161
|
||
$this->attachment_meta = wp_get_attachment_metadata( $this->id );
|
||
}
|
||
|
||
$this->set_paths();
|
||
}
|
||
|
||
/**
|
||
* @return bool
|
||
* @since 1.3.9
|
||
*/
|
||
public function isset_attachment_meta() {
|
||
return $this->attachment_meta && isset( $this->attachment_meta['file'] );
|
||
}
|
||
|
||
/**
|
||
* @since 1.3.9
|
||
*/
|
||
public function set_paths() {
|
||
if ( ! $this->isset_attachment_meta() ) {
|
||
return;
|
||
}
|
||
|
||
$this->url = trailingslashit( $this->wp_upload_dir['baseurl'] ) . $this->attachment_meta['file'];
|
||
$this->path = wp_normalize_path( trailingslashit( $this->wp_upload_dir['basedir'] ) . $this->attachment_meta['file'] );
|
||
}
|
||
|
||
/**
|
||
* Актуализирует мета данные аттачмента и загружает актуальные мета данные и данные по оптимизации из базы.
|
||
*/
|
||
public function reload( $attachment_meta = [] ) {
|
||
if ( empty( $attachment_meta ) ) {
|
||
$attachment_meta = wp_get_attachment_metadata( $this->id );
|
||
}
|
||
$this->attachment_meta = $attachment_meta;
|
||
$this->optimization_data = new RIO_Process_Queue(
|
||
[
|
||
'object_id' => $this->id,
|
||
'object_name' => '',
|
||
'item_type' => 'attachment',
|
||
]
|
||
);
|
||
$this->optimization_data->load();
|
||
$this->set_paths();
|
||
}
|
||
|
||
/**
|
||
* Fallback to get attachment meta it can be empty when WordPress failed to create it or invocation
|
||
* of method was produced too soon.
|
||
*
|
||
* @return bool
|
||
* @since 1.3.9
|
||
*/
|
||
public function regenerate_metadata() {
|
||
if ( $this->isset_attachment_meta() ) {
|
||
return true;
|
||
}
|
||
|
||
WRIO_Plugin::app()->logger->info( sprintf( 'Try regenerate metadata for attachment #%d', $this->id ) );
|
||
|
||
// Need to remove this filter, as it would start recursion
|
||
remove_filter( 'wp_generate_attachment_metadata', 'WRIO_Media_Library::optimize_after_upload' );
|
||
|
||
$file_path = get_attached_file( $this->id );
|
||
|
||
if ( empty( $file_path ) ) {
|
||
$attachment = get_post( $this->id );
|
||
|
||
if ( empty( $attachment ) || 'attachment' !== $attachment->post_type ) {
|
||
return false;
|
||
}
|
||
|
||
$file_path = wrio_url_to_abs_path( $attachment->guid );
|
||
}
|
||
|
||
if ( empty( $file_path ) || ! file_exists( $file_path ) ) {
|
||
WRIO_Plugin::app()->logger->info( sprintf( 'Failed regenerate attachment meta data. Attachment file (%s) doesn\'t exists!', $file_path ) );
|
||
|
||
return false;
|
||
}
|
||
|
||
if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
|
||
require ABSPATH . 'wp-admin/includes/image.php';
|
||
}
|
||
$attachment_meta = wp_generate_attachment_metadata( $this->id, $file_path );
|
||
|
||
if ( empty( $attachment_meta ) ) {
|
||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed regenerate meta data for attachment file (%s).', $file_path ) );
|
||
|
||
return false;
|
||
}
|
||
|
||
WRIO_Plugin::app()->logger->debug( sprintf( 'Generated metadata: %s', var_export( $attachment_meta, true ) ) );
|
||
|
||
// Updating metadata in database
|
||
wp_update_attachment_metadata( $this->id, $attachment_meta );
|
||
|
||
$this->reload( $attachment_meta );
|
||
|
||
add_filter( 'wp_generate_attachment_metadata', 'WRIO_Media_Library::optimize_after_upload', 10, 2 );
|
||
|
||
WRIO_Plugin::app()->logger->info( sprintf( 'Finish regenerate metadata for attachment #%d!', $this->id ) );
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Добавляем сообщение в лог файл.
|
||
*
|
||
* @param string $message Текст сообщения об ошибке.
|
||
*/
|
||
public function writeLog( $message ) {
|
||
|
||
$char = "\t-> ";
|
||
$nl = PHP_EOL;
|
||
|
||
$error = sprintf( 'Error to optimize attachment (ID: #%s). Message: "%s"', $this->id, trim( $message ) ) . $nl;
|
||
$error .= $char . sprintf( 'Attachment optimized? %s', ( $this->isOptimized() ? 'Yes' : 'No' ) ) . $nl;
|
||
$error .= $char . sprintf( 'Should be resized? %s', ( $this->isNeedResize() ? 'Yes' : 'No' ) ) . $nl;
|
||
$error .= $char . sprintf( 'Original size: %sx%s', $this->attachment_meta['width'], $this->attachment_meta['height'] ) . $nl;
|
||
$error .= $char . sprintf( 'Relative path: %s', $this->attachment_meta['file'] ) . $nl;
|
||
$error .= $char . sprintf( 'Server used: %s', wrio_is_license_activate() ? 'premium' : 'free' ) . $nl;
|
||
|
||
if ( ! empty( $this->attachment_meta['sizes'] ) ) {
|
||
$error .= $char . ' Additional sizes:' . $nl;
|
||
foreach ( $this->attachment_meta['sizes'] as $size_type => $size_info ) {
|
||
$error .= "\t" . $char . sprintf( 'Type: %s, size: %sx%s, MIME type: %s', $size_type, $size_info['width'], $size_info['height'], $size_info['mime-type'] ) . $nl;
|
||
}
|
||
}
|
||
|
||
WRIO_Plugin::app()->logger->error( $error );
|
||
}
|
||
|
||
/**
|
||
* Возвращает объект с информацией об оптимизации
|
||
*
|
||
* @return RIO_Process_Queue
|
||
*/
|
||
public function getOptimizationData() {
|
||
$this->optimization_data = new RIO_Process_Queue(
|
||
[
|
||
'object_id' => $this->id,
|
||
'object_name' => '',
|
||
'item_type' => 'attachment',
|
||
]
|
||
);
|
||
$this->optimization_data->load();
|
||
|
||
return $this->optimization_data;
|
||
}
|
||
|
||
/**
|
||
* Возвращает объект с информацией о конвертации в указанный формат
|
||
*
|
||
* @param string $format Format to get conversion data for ('webp' or 'avif')
|
||
*
|
||
* @return RIO_Process_Queue
|
||
*/
|
||
public function getConversionData( $format = 'webp' ) {
|
||
$optimization_data = new RIO_Process_Queue(
|
||
[
|
||
'object_id' => $this->id,
|
||
'object_name' => '',
|
||
'item_type' => $format,
|
||
]
|
||
);
|
||
$optimization_data->load();
|
||
|
||
return $optimization_data;
|
||
}
|
||
|
||
/**
|
||
* Read file size reliably within the current request.
|
||
* PHP caches stat() results; we clear cache for this path.
|
||
*
|
||
* @param mixed $file_path The file path.
|
||
*
|
||
* @return int
|
||
*/
|
||
protected function get_file_size( $file_path ) {
|
||
return wrio_get_file_size( $file_path );
|
||
}
|
||
|
||
/**
|
||
* Оптимизация аттачмента.
|
||
*
|
||
* @param string $optimization_level Уровень оптимизации изображения.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function optimize( $optimization_level = '' ) {
|
||
$optimize_results = [
|
||
'original_size' => 0,
|
||
'optimized_size' => 0,
|
||
];
|
||
|
||
if ( empty( $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 = [
|
||
'original_size' => 0,
|
||
'final_size' => 0,
|
||
'original_mime_type' => '',
|
||
'final_mime_type' => '',
|
||
];
|
||
$results['processing_level'] = $optimization_level;
|
||
|
||
try {
|
||
// The path may be empty because no metadata has been created for the image.
|
||
// We should try to create image metadata again.
|
||
if ( ! $this->isset_attachment_meta() ) {
|
||
WRIO_Plugin::app()->logger->warning( sprintf( 'Attachment #%d doesn\'t have metadata.', $this->id ) );
|
||
|
||
$this->regenerate_metadata();
|
||
}
|
||
|
||
if ( empty( $this->path ) || ! file_exists( $this->path ) ) {
|
||
$results['result_status'] = 'error';
|
||
|
||
$error_message = __( 'Attachment cannot be optimized.', 'robin-image-optimizer' );
|
||
|
||
if ( empty( $this->path ) ) {
|
||
// translators: %d is the attachment ID
|
||
$error_message .= ' ' . sprintf( __( 'Attachment #%d doesn\'t have metadata, the image may be damaged.', 'robin-image-optimizer' ), $this->id );
|
||
} else {
|
||
// translators: %s is the file path
|
||
$error_message .= ' ' . sprintf( __( 'File "(%s)" doesn\'t exist', 'robin-image-optimizer' ), $this->path );
|
||
}
|
||
|
||
$extra_data = [
|
||
'error' => 'path',
|
||
'error_msg' => $error_message,
|
||
];
|
||
|
||
$results['extra_data'] = new RIO_Attachment_Extra_Data( $extra_data );
|
||
$optimization_data->configure( $results );
|
||
$optimization_data->save();
|
||
|
||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to find original attachment #%s located in %s. Skipping optimization. This may be caused due to bug in %s function, which returns false for attachment meta', $this->id, empty( $this->path ) ? '*empty path*' : $this->path, 'wp_get_attachment_metadata()' ) );
|
||
|
||
return $optimize_results;
|
||
}
|
||
|
||
// сначала бекапим
|
||
$is_image_backuped = $this->backup();
|
||
|
||
if ( is_wp_error( $is_image_backuped ) ) {
|
||
$error_msg = $is_image_backuped->get_error_message();
|
||
$this->writeLog( $error_msg );
|
||
|
||
$results['result_status'] = 'error';
|
||
$extra_data = [
|
||
'error' => 'backup',
|
||
'error_msg' => 'Failed to backup',
|
||
];
|
||
$results['extra_data'] = new RIO_Attachment_Extra_Data( $extra_data );
|
||
$optimization_data->configure( $results );
|
||
$optimization_data->save();
|
||
|
||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to make backup of original attachment #%s. Skipping optimization.', $this->id ) );
|
||
|
||
return $optimize_results;
|
||
}
|
||
|
||
$results['is_backed_up'] = $is_image_backuped;
|
||
|
||
$original_main_size = $this->get_file_size( $this->path );
|
||
|
||
// если файл большой - изменяем размер
|
||
if ( $this->isNeedResize() ) {
|
||
$this->resize();
|
||
}
|
||
|
||
$image_processor = WIO_OptimizationTools::getImageProcessor();
|
||
|
||
clearstatcache(); // на всякий случай очистим кеш файловой статистики
|
||
|
||
$optimized_img_data = $image_processor->process(
|
||
[
|
||
'image_url' => $this->get( 'url' ),
|
||
'image_path' => $this->get( 'path' ),
|
||
'quality' => $image_processor->quality( $optimization_level ),
|
||
'save_exif' => WRIO_Plugin::app()->getPopulateOption( 'save_exif_data', false ),
|
||
'is_thumb' => false,
|
||
]
|
||
);
|
||
|
||
// проверяем на ошибку
|
||
if ( is_wp_error( $optimized_img_data ) ) {
|
||
$error_msg = $optimized_img_data->get_error_message();
|
||
$this->writeLog( $error_msg );
|
||
|
||
$results['result_status'] = 'error';
|
||
|
||
$extra_data = [
|
||
'error' => 'optimization',
|
||
'error_msg' => $error_msg,
|
||
];
|
||
|
||
$results['extra_data'] = new RIO_Attachment_Extra_Data( $extra_data );
|
||
|
||
$optimization_data->configure( $results );
|
||
$optimization_data->save();
|
||
|
||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to process (url: %s, path: %s, quality: %s) as error was returned: %s', $this->get( 'url' ), $this->get( 'path' ), $image_processor->quality( $optimization_level ), $error_msg ) );
|
||
|
||
return $optimize_results;
|
||
}
|
||
|
||
$results['original_mime_type'] = '';
|
||
$results['final_mime_type'] = '';
|
||
|
||
// отложенная оптимизация
|
||
if ( isset( $optimized_img_data['status'] ) && 'processing' === $optimized_img_data['status'] ) {
|
||
$results['result_status'] = 'processing';
|
||
$results['original_size'] = 0;
|
||
$results['final_size'] = 0;
|
||
|
||
$extra_data = [
|
||
'original_main_size' => $original_main_size,
|
||
'main_optimized_data' => $optimized_img_data,
|
||
'thumbnails_optimized_data' => $this->optimizeImageSizes(),
|
||
];
|
||
|
||
$results['extra_data'] = new RIO_Attachment_Extra_Data( $extra_data );
|
||
|
||
$optimization_data->configure( $results );
|
||
$optimization_data->save();
|
||
$optimize_results['processing'] = 1;
|
||
|
||
return $optimize_results;
|
||
}
|
||
|
||
// скачиваем и заменяем главную картинку
|
||
$image_downloaded = $this->replaceOriginalFile( $optimized_img_data );
|
||
|
||
// некоторые провайдеры не отдают оптимизированный размер, поэтому после замены файла получаем его сами
|
||
if ( ! $optimized_img_data['optimized_size'] ) {
|
||
clearstatcache();
|
||
$optimized_img_data['optimized_size'] = $this->get_file_size( $this->get( 'path' ) );
|
||
}
|
||
|
||
// при отрицательной оптимизации ставим значение оригинала
|
||
if ( $optimized_img_data['optimized_size'] > $original_main_size ) {
|
||
$optimized_img_data['optimized_size'] = $original_main_size;
|
||
}
|
||
|
||
if ( $image_downloaded ) {
|
||
// просчитываем статистику
|
||
$optimize_results['original_size'] += $original_main_size;
|
||
$optimize_results['optimized_size'] += $optimized_img_data['optimized_size'];
|
||
$thumbnails_count = 0;
|
||
|
||
// оптимизируем дополнительные размеры
|
||
$optimized_img_sizes_data = $this->optimizeImageSizes();
|
||
|
||
// добавляем к статистике данные по оптимизации доп размеров
|
||
if ( ! empty( $optimized_img_sizes_data ) ) {
|
||
$optimize_results['original_size'] += $optimized_img_sizes_data['original_size'];
|
||
$optimize_results['optimized_size'] += $optimized_img_sizes_data['optimized_size'];
|
||
$thumbnails_count = $optimized_img_sizes_data['thumbnails_count'];
|
||
}
|
||
|
||
$results['result_status'] = 'success';
|
||
$results['final_size'] = $optimize_results['optimized_size'];
|
||
$results['original_size'] = $optimize_results['original_size'];
|
||
|
||
$extra_data = [
|
||
'thumbnails_count' => $thumbnails_count,
|
||
'original_main_size' => $original_main_size,
|
||
];
|
||
|
||
$results['extra_data'] = new RIO_Attachment_Extra_Data( $extra_data );
|
||
$mime_type = '';
|
||
|
||
if ( function_exists( 'wp_get_image_mime' ) ) {
|
||
$mime_type = wp_get_image_mime( $this->get( 'path' ) );
|
||
} else {
|
||
WRIO_Plugin::app()->logger->error( 'App is missing wp_get_image_mime() function, unable to get MIME type' );
|
||
}
|
||
|
||
$results['original_mime_type'] = $mime_type;
|
||
$results['final_mime_type'] = $mime_type;
|
||
$optimization_data->configure( $results );
|
||
} else {
|
||
$error_msg = 'Failed to get optimized image from remote server';
|
||
$this->writeLog( $error_msg );
|
||
|
||
$results['result_status'] = 'error';
|
||
|
||
$extra_data = [
|
||
'error' => 'download',
|
||
'error_msg' => $error_msg,
|
||
];
|
||
|
||
$results['extra_data'] = new RIO_Attachment_Extra_Data( $extra_data );
|
||
$optimization_data->configure( $results );
|
||
}
|
||
|
||
$optimization_data->save();
|
||
|
||
return $optimize_results;
|
||
} catch ( Throwable $throwable ) {
|
||
$this->mark_and_log_failure( $throwable, 'optimization', $optimization_level );
|
||
}
|
||
|
||
return $optimize_results;
|
||
}
|
||
|
||
/**
|
||
* Log and persist unexpected optimization failures.
|
||
*
|
||
* @param Throwable $throwable Exception or error that was thrown.
|
||
* @param string $context Processing context.
|
||
* @param string|int $processing_level Current processing level.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function mark_and_log_failure( $throwable, $context = 'optimization', $processing_level = '' ) {
|
||
$error_message = sprintf(
|
||
'Unexpected %1$s failure for attachment #%2$d: %3$s in %4$s:%5$d',
|
||
$context,
|
||
$this->id,
|
||
$throwable->getMessage(),
|
||
$throwable->getFile(),
|
||
$throwable->getLine()
|
||
);
|
||
|
||
WRIO_Plugin::app()->logger->error( $error_message );
|
||
|
||
$optimization_data = $this->getOptimizationData();
|
||
$optimization_data->mark_as_error(
|
||
$error_message,
|
||
[
|
||
'processing_level' => $processing_level,
|
||
'original_size' => 0,
|
||
'final_size' => 0,
|
||
'original_mime_type' => '',
|
||
'final_mime_type' => '',
|
||
]
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Log and persist unexpected format conversion failures without rewriting attachment optimization state.
|
||
*
|
||
* @param Throwable $throwable Exception or error that was thrown.
|
||
* @param string $format Target format.
|
||
* @param string $context Processing context.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function mark_conversion_failure( $throwable, $format, $context = 'conversion' ) {
|
||
$error_message = sprintf(
|
||
'Unexpected %1$s failure for attachment #%2$d (%3$s): %4$s in %5$s:%6$d',
|
||
$context,
|
||
$this->id,
|
||
$format,
|
||
$throwable->getMessage(),
|
||
$throwable->getFile(),
|
||
$throwable->getLine()
|
||
);
|
||
|
||
WRIO_Plugin::app()->logger->error( $error_message );
|
||
|
||
$conversion_data = $this->getConversionData( $format );
|
||
if ( ! $conversion_data->get_id() ) {
|
||
return;
|
||
}
|
||
|
||
$conversion_data->mark_as_error( $error_message );
|
||
}
|
||
|
||
/**
|
||
* Отложенная оптимизация аттачмента
|
||
*
|
||
* @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 RIO_Attachment_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;
|
||
$thumbnails = (array) $extra_data->get_thumbnails_optimized_data();
|
||
$thumbnails = json_decode( json_encode( $thumbnails ), true ); // рекурсивная конвертация объекта в массив
|
||
|
||
if ( is_array( $thumbnails['thumbnails'] ) ) {
|
||
foreach ( $thumbnails['thumbnails'] as &$thumbnail_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( $thumbnails );
|
||
}
|
||
|
||
// когда все файлы получены - сохраняем и возвращаем результат
|
||
if ( $main_image_url && $thumbnails_processed ) {
|
||
$original_size = 0;
|
||
$optimized_size = 0;
|
||
$thumbnails_count = 0;
|
||
$original_main_size = (int) $extra_data->get_original_main_size();
|
||
if ( $original_main_size <= 0 ) {
|
||
$original_main_size = $this->get_file_size( $this->get( 'path' ) );
|
||
}
|
||
$original_size = $original_size + $original_main_size;
|
||
|
||
$this->replaceOriginalFile(
|
||
[
|
||
'optimized_img_url' => $main_image_url,
|
||
]
|
||
);
|
||
|
||
clearstatcache();
|
||
|
||
$optimized_main_size = $this->get_file_size( $this->get( 'path' ) );
|
||
|
||
// при отрицательной оптимизации ставим значение оригинала
|
||
if ( $optimized_main_size > $original_main_size ) {
|
||
$optimized_main_size = $original_main_size;
|
||
}
|
||
|
||
$optimized_size = $optimized_size + $optimized_main_size;
|
||
|
||
if ( is_array( $thumbnails['thumbnails'] ) ) {
|
||
foreach ( $thumbnails['thumbnails'] as $thumbnail_size => $thumbnail ) {
|
||
$thumbnail_file = $this->getImageSizePath( $thumbnail_size );
|
||
$original_thumbnail_size = $this->get_file_size( $thumbnail_file );
|
||
$original_size = $original_size + $original_thumbnail_size;
|
||
|
||
$this->replaceOriginalFile(
|
||
[
|
||
'optimized_img_url' => $thumbnail['optimized_img_url'],
|
||
],
|
||
$thumbnail_size
|
||
);
|
||
|
||
clearstatcache();
|
||
|
||
$optimized_thumbnail_size = $this->get_file_size( $thumbnail_file );
|
||
|
||
// при отрицательной оптимизации ставим значение оригинала
|
||
if ( $optimized_thumbnail_size > $original_thumbnail_size ) {
|
||
$optimized_thumbnail_size = $original_thumbnail_size;
|
||
}
|
||
|
||
$optimized_size = $optimized_size + $optimized_thumbnail_size;
|
||
|
||
++$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_thumbnails_count( $thumbnails_count );
|
||
|
||
// удаляем промежуточные данные
|
||
$extra_data->set_main_optimized_data( null );
|
||
$extra_data->set_thumbnails_optimized_data( null );
|
||
$extra_data->set_main_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;
|
||
}
|
||
|
||
/**
|
||
* Метод проверяет, оптимизирован ли аттачмент
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function isOptimized() {
|
||
$optimization_data = $this->getOptimizationData();
|
||
if ( $optimization_data->is_optimized() ) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Возвращает все размеры аттачмента, которые нужно оптимизировать
|
||
*
|
||
* @return array|false
|
||
*/
|
||
public function getAllowedSizes() {
|
||
$allowed_sizes = WRIO_Plugin::app()->getPopulateOption( 'allowed_sizes_thumbnail', 'thumbnail,medium' );
|
||
|
||
if ( ! $allowed_sizes ) {
|
||
return false;
|
||
}
|
||
|
||
$allowed_sizes = explode( ',', $allowed_sizes );
|
||
|
||
return $allowed_sizes;
|
||
}
|
||
|
||
/**
|
||
* Оптимизация других размеров аттачмента.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function optimizeImageSizes() {
|
||
$allowed_sizes = $this->getAllowedSizes();
|
||
|
||
if ( $allowed_sizes === false ) {
|
||
return [];
|
||
}
|
||
|
||
$image_processor = WIO_OptimizationTools::getImageProcessor();
|
||
$quality = WRIO_Plugin::app()->getPopulateOption( 'image_optimization_level', 'normal' );
|
||
|
||
if ( $quality === 'custom' ) {
|
||
$custom_quality = WRIO_Plugin::app()->getPopulateOption( 'image_optimization_level_custom', 100 );
|
||
$quality = intval( $custom_quality );
|
||
}
|
||
|
||
$exif = WRIO_Plugin::app()->getPopulateOption( 'save_exif_data', false );
|
||
|
||
$original_size = 0;
|
||
$optimized_size = 0;
|
||
$errors_count = 0;
|
||
$optimized_count = 0;
|
||
$thumbnails = [];
|
||
|
||
foreach ( $allowed_sizes as $image_size ) {
|
||
$url = $this->getImageSizeUrl( $image_size );
|
||
$path = $this->getImageSizePath( $image_size );
|
||
|
||
if ( ! $url || ! $path ) {
|
||
continue;
|
||
}
|
||
|
||
$original_file_size = 0;
|
||
|
||
if ( is_file( $path ) ) {
|
||
$original_file_size = $this->get_file_size( $path );
|
||
}
|
||
|
||
$optimized_img_data = $image_processor->process(
|
||
[
|
||
'image_url' => $url,
|
||
'image_path' => $path,
|
||
'quality' => $image_processor->quality( $quality ),
|
||
'save_exif' => $exif,
|
||
'is_thumb' => true,
|
||
]
|
||
);
|
||
// проверяем на ошибку
|
||
if ( is_wp_error( $optimized_img_data ) ) {
|
||
++$errors_count;
|
||
} else {
|
||
// скачиваем и заменяем картинку
|
||
$this->replaceOriginalFile( $optimized_img_data, $image_size );
|
||
// некоторые провайдеры не отдают оптимизированный размер, поэтому после замены файла получаем его сами
|
||
if ( ! $optimized_img_data['optimized_size'] ) {
|
||
clearstatcache();
|
||
$optimized_img_data['optimized_size'] = $this->get_file_size( $path );
|
||
}
|
||
if ( ! $optimized_img_data['src_size'] ) {
|
||
$optimized_img_data['src_size'] = $original_file_size;
|
||
}
|
||
|
||
// при отрицательной оптимизации ставим значение оригинала
|
||
if ( $optimized_img_data['optimized_size'] > $original_file_size ) {
|
||
$optimized_img_data['optimized_size'] = $original_file_size;
|
||
}
|
||
|
||
$thumbnails[ $image_size ] = $optimized_img_data;
|
||
|
||
// просчитываем статистику
|
||
$original_size += $optimized_img_data['src_size'];
|
||
$optimized_size += $optimized_img_data['optimized_size'];
|
||
++$optimized_count;
|
||
}
|
||
}
|
||
|
||
return [
|
||
'errors_count' => $errors_count,
|
||
'original_size' => $original_size,
|
||
'optimized_size' => $optimized_size,
|
||
'thumbnails_count' => $optimized_count,
|
||
'thumbnails' => $thumbnails,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Возвращает путь.
|
||
*
|
||
* @param string $image_size Размер(thumbnail, medium ... )
|
||
*
|
||
* @return string
|
||
*/
|
||
public function getPath( $image_size = '' ) {
|
||
|
||
if ( empty( $image_size ) ) {
|
||
$path = $this->path;
|
||
} else {
|
||
$path = $this->getImageSizePath( $image_size );
|
||
}
|
||
|
||
return $path;
|
||
}
|
||
|
||
/**
|
||
* Заменяет оригинальный файл на оптимизированный.
|
||
*
|
||
* @param array $optimized_img_data Hезультат оптимизации ввиде массива данных.
|
||
* @param string $image_size Размер (thumbnail, medium ... )
|
||
*
|
||
* @return bool
|
||
*/
|
||
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'] ) && (bool) $optimized_img_data['not_need_download'] ) {
|
||
$optimized_file = $optimized_img_url;
|
||
} else {
|
||
$optimized_file = $this->remoteDownloadImage( $optimized_img_url );
|
||
}
|
||
|
||
if ( empty( $optimized_file ) ) {
|
||
WRIO_Plugin::app()->logger->error( sprintf( 'Unable to replace original image with new as failed to download %s', $optimized_img_url ) );
|
||
|
||
return false;
|
||
}
|
||
|
||
if ( isset( $optimized_img_data['not_need_replace'] ) && $optimized_img_data['not_need_replace'] ) {
|
||
// если картинка уже оптимизирована и провайдер её не может уменьшить - он может вернуть положительный ответ, но без самой картинки. В таком случае ничего заменять не надо
|
||
return true;
|
||
}
|
||
|
||
$attachment_size_path = $this->getPath( $image_size );
|
||
|
||
if ( ! is_file( $attachment_size_path ) ) {
|
||
return false;
|
||
}
|
||
|
||
$bytes = @file_put_contents( $attachment_size_path, $optimized_file );
|
||
|
||
if ( $bytes === false ) {
|
||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to put new image\'s %s content to %s as file_put_contents() failed', $optimized_img_url, $attachment_size_path ) );
|
||
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Скачивание изображения с удалённого сервера
|
||
*
|
||
* @param string $url
|
||
*
|
||
* @return string|null Image content on success, NULL on failure.
|
||
*/
|
||
protected function remoteDownloadImage( $url ) {
|
||
$user_agent = wrio_get_user_agent();
|
||
|
||
if ( ! function_exists( 'curl_version' ) ) {
|
||
$context_options = [
|
||
'ssl' => [
|
||
'verify_peer' => false,
|
||
'verify_peer_name' => false,
|
||
],
|
||
'http' => [
|
||
'header' => 'User-Agent: ' . $user_agent,
|
||
],
|
||
];
|
||
|
||
$content = @file_get_contents( $url, false, stream_context_create( $context_options ) );
|
||
|
||
if ( $content === false ) {
|
||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to get content of "%s" using file_get_contents()', $url ) );
|
||
|
||
return null;
|
||
}
|
||
|
||
return $content;
|
||
}
|
||
|
||
$ch = curl_init();
|
||
curl_setopt( $ch, CURLOPT_HEADER, 0 );
|
||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
|
||
curl_setopt( $ch, CURLOPT_URL, $url );
|
||
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
|
||
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
|
||
curl_setopt( $ch, CURLOPT_USERAGENT, $user_agent );
|
||
|
||
$image_body = curl_exec( $ch );
|
||
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||
if ( $http_code !== 200 ) {
|
||
$image_body = false;
|
||
}
|
||
curl_close( $ch );
|
||
|
||
if ( $image_body === false ) {
|
||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to get content of "%s" using curl_exec(). HTTP code: ' . $http_code, $url ) );
|
||
|
||
return null;
|
||
}
|
||
|
||
return $image_body;
|
||
}
|
||
|
||
/**
|
||
* Возвращает свойство аттачмента
|
||
*
|
||
* @param string $property имя свойства
|
||
*
|
||
* @return mixed
|
||
*/
|
||
public function get( $property ) {
|
||
if ( isset( $this->$property ) ) {
|
||
return $this->$property;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Возвращает URL изображения по указанному размеру
|
||
*
|
||
* @param string $size - размер изображения(thumbnail,medium,large...)
|
||
*
|
||
* @return string|null
|
||
*/
|
||
public function getImageSizeUrl( $size = 'thumbnail' ) {
|
||
if ( ! isset( $this->attachment_meta['sizes'][ $size ] ) ) {
|
||
return null;
|
||
}
|
||
|
||
$file = $this->attachment_meta['sizes'][ $size ]['file'];
|
||
$url = str_replace( wp_basename( $this->url ), $file, $this->url );
|
||
|
||
return $url;
|
||
}
|
||
|
||
/**
|
||
* Возвращает путь к изображению по указанному размеру.
|
||
*
|
||
* @param string $size Размер изображения (thumbnail, medium, large ...)
|
||
*
|
||
* @return string Путь до изображения.
|
||
*/
|
||
public function getImageSizePath( $size = 'thumbnail' ) {
|
||
if ( ! isset( $this->attachment_meta['sizes'][ $size ] ) ) {
|
||
return null;
|
||
}
|
||
|
||
$file = $this->attachment_meta['sizes'][ $size ]['file'];
|
||
$path = str_replace( wp_basename( $this->path ), $file, $this->path );
|
||
|
||
return $path;
|
||
}
|
||
|
||
/**
|
||
* Проверка необходимости делать изменение размера.
|
||
*
|
||
* @return bool
|
||
*/
|
||
protected function isNeedResize() {
|
||
$resize_large_images = WRIO_Plugin::app()->getPopulateOption( 'resize_larger', true );
|
||
|
||
if ( ! $resize_large_images ) {
|
||
return false;
|
||
}
|
||
|
||
$resize_larger_w = (int) WRIO_Plugin::app()->getPopulateOption( 'resize_larger_w', 1600 );
|
||
$resize_larger_h = (int) WRIO_Plugin::app()->getPopulateOption( 'resize_larger_h', 1600 );
|
||
|
||
if ( ! $resize_larger_w && ! $resize_larger_h ) {
|
||
return false;
|
||
}
|
||
|
||
// если ширина и высота установлены и > 0
|
||
if ( $this->attachment_meta['width'] >= $this->attachment_meta['height'] ) {
|
||
$larger_side = $this->attachment_meta['width'];
|
||
$resize_larger_side = $resize_larger_w;
|
||
} else {
|
||
$larger_side = $this->attachment_meta['height'];
|
||
$resize_larger_side = $resize_larger_h;
|
||
}
|
||
// если ширина 0, то рисайзим по высоте
|
||
if ( ! $resize_larger_w ) {
|
||
$resize_larger_side = $resize_larger_h;
|
||
$larger_side = $this->attachment_meta['height'];
|
||
}
|
||
// если высота 0, то рисайзим по ширине
|
||
if ( ! $resize_larger_h ) {
|
||
$resize_larger_side = $resize_larger_w;
|
||
$larger_side = $this->attachment_meta['width'];
|
||
}
|
||
// если большая сторона картинки меньше, чем задано в настройках, то не рисайзим.
|
||
if ( $larger_side <= $resize_larger_side ) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Возвращает метаданные аттачмента
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getMetaData() {
|
||
return $this->attachment_meta;
|
||
}
|
||
|
||
/**
|
||
* Изменяет размер изображения до заданного в настройках размера.
|
||
*
|
||
* @return bool
|
||
*/
|
||
protected function resize() {
|
||
$resize_larger_h = (int) WRIO_Plugin::app()->getPopulateOption( 'resize_larger_h', 1600 );
|
||
$resize_larger_w = (int) WRIO_Plugin::app()->getPopulateOption( 'resize_larger_w', 1600 );
|
||
|
||
$image = wp_get_image_editor( $this->path );
|
||
|
||
if ( is_wp_error( $image ) ) {
|
||
WRIO_Plugin::app()->logger->error( sprintf( 'Failed to get image edit via wp_get_image_editor(), error: "%s"', $image->get_error_message() ) );
|
||
|
||
return false;
|
||
}
|
||
|
||
$current_size = $image->get_size();
|
||
$new_width = 0;
|
||
$new_height = 0;
|
||
|
||
// если обе стороны заданы
|
||
if ( $resize_larger_h && $resize_larger_w ) {
|
||
// определяем большую сторону и по ней маштабируем
|
||
if ( $current_size['width'] >= $current_size['height'] ) {
|
||
$new_width = $resize_larger_w;
|
||
$new_height = round( $current_size['height'] * $new_width / $current_size['width'] );
|
||
} else {
|
||
$new_height = $resize_larger_h;
|
||
$new_width = round( $current_size['width'] * $new_height / $current_size['height'] );
|
||
}
|
||
} else {
|
||
// если задана одна из сторон
|
||
if ( ! $resize_larger_w ) {
|
||
// если ширина 0, то рисайзим по высоте
|
||
$new_height = $resize_larger_h;
|
||
$new_width = round( $current_size['width'] * $new_height / $current_size['height'] );
|
||
}
|
||
if ( ! $resize_larger_h ) {
|
||
// если высота 0, то рисайзим по ширине
|
||
$new_width = $resize_larger_w;
|
||
$new_height = round( $current_size['height'] * $new_width / $current_size['width'] );
|
||
}
|
||
}
|
||
|
||
$nl = PHP_EOL;
|
||
$log_message = sprintf( "\tResize from: %sx%s to %sx%s", $current_size['width'], $current_size['height'], $new_width, $new_height ) . $nl;
|
||
$log_message .= sprintf( "\tLarger resize from %sx%s", $resize_larger_w, $resize_larger_h ) . $nl;
|
||
$log_message .= sprintf( "\tAbsolute path: %s", $this->path ) . $nl;
|
||
|
||
$resize_result = $image->resize( $new_width, $new_height, false );
|
||
|
||
if ( is_wp_error( $resize_result ) ) {
|
||
$this->writeLog( sprintf( 'Resize error: %s. Details: %s', $resize_result->get_error_messages(), $log_message ) );
|
||
|
||
return false;
|
||
}
|
||
|
||
$save_result = $image->save( $this->path );
|
||
|
||
if ( is_wp_error( $save_result ) ) {
|
||
$this->writeLog( sprintf( 'Failed to save resized error in db: %s, Details: %s', $save_result->get_error_messages(), $log_message ) );
|
||
|
||
return false;
|
||
}
|
||
|
||
$this->attachment_meta['width'] = $new_width;
|
||
$this->attachment_meta['height'] = $new_height;
|
||
$this->attachment_meta['old_width'] = $current_size['width'];
|
||
$this->attachment_meta['old_height'] = $current_size['height'];
|
||
|
||
wp_update_attachment_metadata( $this->id, $this->attachment_meta );
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Делает резервную копию
|
||
*
|
||
* @return true|WP_Error
|
||
*/
|
||
protected function backup() {
|
||
$backup = WIO_Backup::get_instance();
|
||
|
||
return $backup->backupAttachment( $this );
|
||
}
|
||
|
||
/**
|
||
* Восстанавливает файлы из резервной копии
|
||
*
|
||
* @return true|WP_Error
|
||
*/
|
||
public function restore() {
|
||
$backup = WIO_Backup::get_instance();
|
||
|
||
return $backup->restoreAttachment( $this );
|
||
}
|
||
}
|