first commit

This commit is contained in:
User A0264400
2026-04-01 23:20:16 +03:00
commit a766acdc90
23071 changed files with 4933189 additions and 0 deletions

View File

@@ -0,0 +1,497 @@
<?php
/**
* AJAX обработчик выбора папки
*/
add_action(
'wp_ajax_wriop_browse_dir',
function () {
if ( ! check_ajax_referer( 'bulk_optimization', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
if ( is_main_site() ) {
$base = get_home_path();
$root = wp_normalize_path( $base );
} else {
$up = wp_upload_dir();
$root = wp_normalize_path( $up['basedir'] );
}
$dir = trim( WRIO_Plugin::app()->request->post( 'dir', null, 'rawurldecode' ) );
$multiselect = WRIO_Plugin::app()->request->post( 'multiSelect' );
$multiselect = $multiselect == 'true' ? true : false;
$only_folders = WRIO_Plugin::app()->request->post( 'onlyFolders' );
$only_folders = $only_folders == 'true' ? true : false;
$only_folders = $dir == '/' || $only_folders;
$only_files = WRIO_Plugin::app()->request->post( 'onlyFiles' );
$only_files = $only_files == 'true' ? true : false;
$selected_dir = trailingslashit( $root ) . ( $dir == '/' ? '' : $dir );
// set checkbox if multiSelect set to true
$checkbox = $multiselect ? "<input type='checkbox' />" : null;
$upload_dir = wp_upload_dir();
$upload_dir_path = trailingslashit( str_replace( ABSPATH, '', $upload_dir['basedir'] ) );
$wp_content_dir = trailingslashit( str_replace( ABSPATH, '', WP_CONTENT_DIR ) );
$ngg_path = str_replace( wp_normalize_path( ABSPATH ), '', wrio_get_ngg_galleries_path() );
$shortpixel_path = str_replace( wp_normalize_path( ABSPATH ), '', wrio_get_shortpixel_path() );
$ewww_path = str_replace( wp_normalize_path( ABSPATH ), '', wrio_get_ewww_tools_path() );
$wc_path = str_replace( wp_normalize_path( ABSPATH ), '', wrio_get_wc_logs_path() );
$exclude_dirs = [
$ngg_path,
$shortpixel_path,
$ewww_path,
$wc_path,
$wp_content_dir . 'backup',
$wp_content_dir . 'backups',
$wp_content_dir . 'cache',
$wp_content_dir . 'lang',
$wp_content_dir . 'langs',
$wp_content_dir . 'languages',
$upload_dir_path . 'wio_backup',
$upload_dir_path . 'wrio',
$upload_dir_path . 'wrio-webp-uploads',
// 'wp-admin',
// 'wp-includes'
];
// исключаем все директории /wp-content/uploads/2019 - они уже оптимизируются в медиабиблиотеке.
// с основания WP в 2003 году до текущего года + 1 на всякий случай.
$year = date( 'Y' ) + 1;
for ( $i = 2003; $i <= $year; $i++ ) {
$exclude_dirs[] = $upload_dir_path . $i;
}
if ( file_exists( $selected_dir ) && is_dir( $selected_dir ) ) {
$files = scandir( $selected_dir );
$return_dir = substr( $selected_dir, strlen( $root ) );
natcasesort( $files );
if ( count( $files ) > 2 ) { // The 2 accounts for . and ..
echo "<ul class='jqueryFileTree'>";
$counter = 0;
foreach ( $files as $file ) {
// если в папке очень много файлов, то показываем не все.
if ( $counter++ > 200 ) {
break;
}
// если это папка бекап или другое исключение - пропускаем
if ( in_array( $return_dir . $file, $exclude_dirs ) ) {
continue;
}
$htmlRel = str_replace( "'", '&apos;', $return_dir . $file );
$htmlName = htmlentities( $file );
$ext = preg_replace( '/^.*\./', '', $file );
if ( file_exists( $selected_dir . $file ) && $file != '.' && $file != '..' ) {
// KEEP the spaces in front of the rel values - it's a trick to make WP Hide not replace the wp-content path
if ( is_dir( $selected_dir . $file ) && ( ! $only_files || $only_folders ) ) {
echo "<li class='directory collapsed'>{$checkbox}<a rel=' " . $htmlRel . "/'>" . $htmlName . '</a></li>';
} elseif ( ! $only_folders || $only_files ) {
echo "<li class='file ext_{$ext}'>{$checkbox}<a rel=' " . $htmlRel . "'>" . $htmlName . '</a></li>';
}
}
}
echo '</ul>';
}
}
die();
}
);
/**
* AJAX обработчик добавления папки
*/
add_action(
'wp_ajax_wrio-add-custom-folder',
function () {
if ( ! check_ajax_referer( 'bulk_optimization', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
$path = WRIO_Plugin::app()->request->request( 'path', null, true );
if ( empty( $path ) ) {
wp_die( - 1 );
}
$cf = WRIO_Custom_Folders::get_instance();
$folder = $cf->addFolder( $path );
if ( is_wp_error( $folder ) ) {
wp_send_json_error(
[
'error_message' => $folder->get_error_message(),
'error_code' => $folder->get_error_code(),
]
);
}
wp_send_json_success( $folder->toArray() );
}
);
/**
* AJAX индексация папки
*/
add_action(
'wp_ajax_wrio-scan-folder',
function () {
if ( ! check_ajax_referer( 'bulk_optimization', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
$uid = WRIO_Plugin::app()->request->request( 'uid', null, true );
$offset = WRIO_Plugin::app()->request->request( 'offset', 0, true );
$total = WRIO_Plugin::app()->request->request( 'total', 0, true );
if ( empty( $uid ) ) {
wp_die( - 1 );
}
$max_process_elements = 100; // сколько элементов за итерацию индексирования
$cf = WRIO_Custom_Folders::get_instance();
$folder = $cf->getFolder( $uid );
if ( ! $total ) {
$total = $folder->reCountFiles();
$cf->saveFolders();
}
$processed_count = $folder->indexing( $offset, $max_process_elements );
$offset = $offset + $processed_count;
$results = [
'offset' => $offset,
'total' => $total,
'complete' => false,
'percent' => 0,
];
if ( $total ) {
$results['percent'] = 100 - ( ( $total - $offset ) * 100 / $total );
/**
* Операция индексирования состоит из двух этапов. Проверка существующих файлов и поиск новых файлов.
* Поэтому процент делим на 2 и добавляем 50% т.к. это вторая часть.
*/
$results['percent'] = $results['percent'] / 2 + 50;
}
if ( $offset >= $total ) {
$results['percent'] = 100;
$results['complete'] = true;
}
wp_send_json_success( $results );
}
);
/**
* AJAX обработчик удаления папки
*/
add_action(
'wp_ajax_wriop_remove_folder',
function () {
if ( ! check_ajax_referer( 'bulk_optimization', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
$uid = isset( $_POST['uid'] ) ? $_POST['uid'] : false;
if ( ! $uid ) {
die();
}
$cf = WRIO_Custom_Folders::get_instance();
$cf->removeFolder( $uid );
$cf->saveFolders();
die();
}
);
/**
* AJAX проверка проиндексированных файлов
*/
add_action(
'wp_ajax_wriop_folder_sync_index',
function () {
if ( ! check_ajax_referer( 'bulk_optimization', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
$uid = isset( $_POST['uid'] ) ? $_POST['uid'] : false;
$offset = isset( $_POST['offset'] ) ? intval( $_POST['offset'] ) : 0;
$total = isset( $_POST['total'] ) ? intval( $_POST['total'] ) : 0;
$max_process_elements = 20; // сколько элементов за итерацию индексирования
$cf = WRIO_Custom_Folders::get_instance();
$folder = $cf->getFolder( $uid );
$total = $folder->countIndexedFiles();
$processed_count = $folder->syncIndex( $offset, $max_process_elements );
$offset = $offset + $processed_count;
$results = [
'offset' => $offset,
'total' => $total,
'complete' => false,
'percent' => 0,
];
if ( $total ) {
$results['percent'] = 100 - ( ( $total - $offset ) * 100 / $total );
/**
* Операция индексирования состоит из двух этапов. Проверка существующих файлов и поиск новых файлов.
* Поэтому процент делим на 2. Это первая часть.
*/
$results['percent'] = $results['percent'] / 2;
}
if ( $offset >= $total ) {
$results['percent'] = 100;
$results['complete'] = true;
}
wp_send_json( $results );
}
);
/**
* AJAX массовая оптимизация
*/
/*
add_action( 'wp_ajax_wriop_process_cf_images', function () {
check_admin_referer( 'wio-iph' );
$reset_current_error = (bool) WRIO_Plugin::app()->request->request( 'reset_current_errors' );
// в ajax запросе мы не знаем, получен ли он из мультиадминки или из обычной. Поэтому проверяем параметр, полученный из frontend
if ( isset( $_POST['multisite'] ) and $_POST['multisite'] ) {
$multisite = new WIO_Multisite;
$multisite->initHooks();
}
$cf = WRIO_Custom_Folders::get_instance();
/*$folders = $cf->getFolders();
if ( ! empty( $folders ) ) {
foreach ( (array) $folders as $folder ) {
$folder = $cf->getFolder( $folder->get( 'uid' ) );
$count_files = $folder->countFiles();
$count_indexed_files = $folder->countIndexedFiles();
$test = 'fsdf';
}
}*/
/*
if ( $reset_current_error ) {
// сбрасываем текущие ошибки оптимизации
$cf->resetCurrentErrors();
}
$max_process_per_request = 1;
$optimized_data = $cf->processUnoptimizedImages( $max_process_per_request );
// если изображения закончились - посылаем команду завершения
if ( $optimized_data['remain'] <= 0 ) {
$optimized_data['end'] = true;
}
wp_send_json( $optimized_data );
} );*/
/**
* AJAX массовая оптимизация выбранной папки
*/
/*
add_action( 'wp_ajax_wriop_process_cf_folder_images', function () {
check_admin_referer( 'wio-iph' );
// в ajax запросе мы не знаем, получен ли он из мультиадминки или из обычной. Поэтому проверяем параметр, полученный из frontend
if ( isset( $_POST['multisite'] ) and $_POST['multisite'] ) {
$multisite = new WIO_Multisite;
$multisite->initHooks();
}
$cf = WRIO_Custom_Folders::get_instance();
$max_process_per_request = 1;
add_filter( 'wriop_cf_current_folder', function ( $folder_uid ) {
$folder_uid = isset( $_POST['uid'] ) ? $_POST['uid'] : false;
return $folder_uid;
} );
$optimized_data = $cf->processUnoptimizedImages( $max_process_per_request );
// если изображения закончились - посылаем команду завершения
if ( $optimized_data['remain'] <= 0 ) {
$optimized_data['end'] = true;
}
wp_send_json( $optimized_data );
} );*/
/**
* Переоптимизация cf_image. AJAX
*/
add_action(
'wp_ajax_wio_cf_reoptimize_image',
function () {
if ( ! check_ajax_referer( 'reoptimize', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
$image_id = (int) $_POST['id'];
$backup = WRIOP_Backup::get_instance();
$backup_origin_images = WRIO_Plugin::app()->getPopulateOption( 'backup_origin_images', false );
$cf = WRIO_Custom_Folders::get_instance();
if ( $backup_origin_images and ! $backup->isBackupWritable() ) {
echo $cf->getMediaColumnContent( $image_id );
die();
}
wp_suspend_cache_addition( true );
$default_level = WRIO_Plugin::app()->getPopulateOption( 'image_optimization_level', 'normal' );
$level = isset( $_POST['level'] ) ? sanitize_text_field( $_POST['level'] ) : $default_level;
$optimized_data = $cf->optimizeImage( $image_id, $level );
if ( $optimized_data && isset( $optimized_data['processing'] ) ) {
echo 'processing'; // эту строку не локализировать!
die();
}
echo $cf->getMediaColumnContent( $image_id );
die();
}
);
/**
* Восстановление
*/
add_action(
'wp_ajax_wio_cf_restore_image',
function () {
if ( ! check_ajax_referer( 'restore', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
wp_suspend_cache_addition( true );
$image_id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
$cf = WRIO_Custom_Folders::get_instance();
$cf_image = $cf->getImage( $image_id );
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
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 );
$image_statistics->save();
$folder = $cf->getFolder( $cf_image->get( 'folder_uid' ) );
$folder->reCountOptimizedFiles();
$cf->saveFolders();
}
}
echo $cf->getMediaColumnContent( $image_id );
die();
}
);
/**
* AJAX обработчик восстановления из резервной копии
*/
/*
add_action( 'wp_ajax_wio_cf_restore_backup', function () {
check_admin_referer( 'wio-iph' );
$max_process_per_request = 10; // сколько картинок восстанавливаем за 1 запрос
$total = sanitize_text_field( $_POST['total'] );
if ( isset( $_POST['blog_id'] ) && $_POST['blog_id'] ) {
switch_to_blog( intval( $_POST['blog_id'] ) );
}
$folder_uid = isset( $_POST['uid'] ) ? sanitize_text_field( $_POST['uid'] ) : false;
$cf = WRIO_Custom_Folders::get_instance();
if ( $total == '?' ) {
$total = RIO_Process_Queue::count_by_type_status( 'cf_image', 'success' );
}
$restored_data = $cf->restoreFolderFromBackup( $folder_uid, $max_process_per_request );
if ( isset( $_POST['blog_id'] ) && $_POST['blog_id'] ) {
restore_current_blog();
}
$restored_data['total'] = $total;
if ( $total ) {
$restored_data['percent'] = 100 - ( $restored_data['remain'] * 100 / $total );
} else {
$restored_data['percent'] = 0;
}
// если изображения закончились - посылаем команду завершения
if ( $restored_data['remain'] <= 0 ) {
$restored_data['end'] = true;
}
wp_send_json( $restored_data );
} );*/
/**
* Загружает шаблон для всплывающий окон
*/
/*
add_action( 'wp_ajax_wio_cf_get_template_part', function () {
$template = sanitize_text_field( $_POST['template'] );
$templates = [
'select_folder' => 'select-folder.php',
'restore_folder' => 'restore-folder.php',
'sync_folder' => 'sync-folder.php',
'optimize_folder' => 'optimize-folder.php',
'sync_all_folders' => 'sync-all-folders.php',
'folders_table_body' => 'folders-table-body.php',
];
if ( isset( $templates[ $template ] ) ) {
$template_file = WRIOP_PLUGIN_DIR . '/admin/pages/parts/' . $templates[ $template ];
if ( file_exists( $template_file ) ) {
include( $template_file );
}
}
die();
} );*/
/**
* Загружает шаблон для всплывающий окон
*/
add_action(
'wp_ajax_wio_cf_reload_ui',
function () {
if ( ! check_ajax_referer( 'bulk_optimization', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
$template_file = WRIOP_PLUGIN_DIR . '/admin/pages/parts/folders-table-body.php';
$folders_table = '';
if ( is_file( $template_file ) ) {
ob_start();
include $template_file;
$folders_table = ob_get_contents();
ob_end_clean();
}
$image_statistics = WRIO_Image_Statistic_Folders::get_instance();
$responce = [
'folders_table' => $folders_table,
'statistic' => $image_statistics->load(),
];
wp_send_json( $responce );
}
);

View File

@@ -0,0 +1,71 @@
<?php
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Переоптимизация аттачмента
*/
function wbcr_riop_reoptimizeImage() {
if ( ! check_ajax_referer( 'reoptimize', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
$image_id = (int) $_POST['id'];
$backup = WRIOP_Backup::get_instance();
$backup_origin_images = WRIO_Plugin::app()->getPopulateOption( 'backup_origin_images', false );
$nextgen_gallery = WRIO_Nextgen_Gallery::get_instance();
if ( $backup_origin_images && ! $backup->isBackupWritable() ) {
echo $nextgen_gallery->getMediaColumnContent( $image_id );
die();
}
wp_suspend_cache_addition( true );
$default_level = WRIO_Plugin::app()->getPopulateOption( 'image_optimization_level', 'normal' );
$level = isset( $_POST['level'] ) ? sanitize_text_field( $_POST['level'] ) : $default_level;
$optimized_data = $nextgen_gallery->optimizeNextgenImage( $image_id, $level );
if ( $optimized_data && isset( $optimized_data['processing'] ) ) {
echo 'processing';
die();
}
echo $nextgen_gallery->getMediaColumnContent( $image_id );
die();
}
/**
* Восстановление
*/
function wbcr_riop_restoreImage() {
if ( ! check_ajax_referer( 'restore', false, false ) || ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
}
wp_suspend_cache_addition( true );
$image_id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
$nextgen_gallery = WRIO_Nextgen_Gallery::get_instance();
$nextgen_image = $nextgen_gallery->getNextgenImage( $image_id );
$image_statistics = WRIO_Image_Statistic_Nextgen::get_instance();
if ( $nextgen_image->isOptimized() ) {
$restored = $nextgen_image->restore();
if ( ! is_wp_error( $restored ) ) {
$optimization_data = $nextgen_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 );
$image_statistics->save();
}
}
$nextgen_gallery = WRIO_Nextgen_Gallery::get_instance();
echo $nextgen_gallery->getMediaColumnContent( $image_id );
die();
}

View File

@@ -0,0 +1,4 @@
/**
* Bulk optimization
*/
/*# sourceMappingURL=custom-folders.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"custom-folders.css"}

View File

@@ -0,0 +1,9 @@
/**
* Bulk optimization
*/
#WBCR {
// less code
}

View File

@@ -0,0 +1,318 @@
#wrio-file-tree {
min-height: 100px;
max-height: 400px;
overflow: auto;
padding: 10px;
border: 1px solid #888;
background: #fff url("../img/quick-start-loader.gif") center center no-repeat;
}
div.sp-folder-picker {
margin: 20px 0; /* 15% from the top and centered */
border: 1px solid #888;
max-height: 400px;
overflow: auto;
}
/*UL.jqueryFileTree LI.directory.selected {
background-color: #209fd2;
}*/
UL.jqueryFileTree {
font-family: Verdana, sans-serif;
font-size: 11px;
line-height: 18px;
padding: 0;
margin: 0;
display: none;
background: #fff;
}
UL.jqueryFileTree LI {
list-style: none;
text-align: left;
padding: 0;
padding-left: 20px;
margin: 0;
white-space: nowrap;
}
UL.jqueryFileTree LI.directory {
background: url(../img/file-tree/directory.png) left top no-repeat;
}
UL.jqueryFileTree LI.directory-locked {
background: url(../img/file-tree/directory-lock.png) left top no-repeat;
}
UL.jqueryFileTree LI.expanded {
background: url(../img/file-tree/folder_open.png) left top no-repeat;
}
UL.jqueryFileTree LI.file {
background: url(../img/file-tree/file.png) left top no-repeat;
}
UL.jqueryFileTree LI.file-locked {
background: url(../img/file-tree/file-lock.png) left top no-repeat !important;
}
UL.jqueryFileTree LI.wait {
background: url(../img/file-tree/spinner.gif) left top no-repeat;
}
UL.jqueryFileTree LI.selected > a {
font-weight: bold;
}
UL.jqueryFileTree LI.ext_3gp {
background: url(../img/file-tree/film.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_afp {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_afpa {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_asp {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_aspx {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_avi {
background: url(../img/file-tree/film.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_bat {
background: url(../img/file-tree/application.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_bmp {
background: url(../img/file-tree/picture.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_c {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_cfm {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_cgi {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_com {
background: url(../img/file-tree/application.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_cpp {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_css {
background: url(../img/file-tree/css.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_doc {
background: url(../img/file-tree/doc.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_exe {
background: url(../img/file-tree/application.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_gif {
background: url(../img/file-tree/picture.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_fla {
background: url(../img/file-tree/flash.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_h {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_htm {
background: url(../img/file-tree/html.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_html {
background: url(../img/file-tree/html.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_jar {
background: url(../img/file-tree/java.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_jpg {
background: url(../img/file-tree/picture.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_jpeg {
background: url(../img/file-tree/picture.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_js {
background: url(../img/file-tree/script.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_lasso {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_log {
background: url(../img/file-tree/txt.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_m4p {
background: url(../img/file-tree/music.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_mov {
background: url(../img/file-tree/film.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_mp3 {
background: url(../img/file-tree/music.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_mp4 {
background: url(../img/file-tree/film.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_mpg {
background: url(../img/file-tree/film.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_mpeg {
background: url(../img/file-tree/film.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_ogg {
background: url(../img/file-tree/music.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_ogv {
background: url(../img/file-tree/film.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_pcx {
background: url(../img/file-tree/picture.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_pdf {
background: url(../img/file-tree/pdf.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_php {
background: url(../img/file-tree/php.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_png {
background: url(../img/file-tree/picture.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_ppt {
background: url(../img/file-tree/ppt.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_psd {
background: url(../img/file-tree/psd.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_pl {
background: url(../img/file-tree/script.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_py {
background: url(../img/file-tree/script.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_rb {
background: url(../img/file-tree/ruby.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_rbx {
background: url(../img/file-tree/ruby.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_rhtml {
background: url(../img/file-tree/ruby.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_rpm {
background: url(../img/file-tree/linux.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_ruby {
background: url(../img/file-tree/ruby.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_sql {
background: url(../img/file-tree/db.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_swf {
background: url(../img/file-tree/flash.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_tif {
background: url(../img/file-tree/picture.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_tiff {
background: url(../img/file-tree/picture.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_txt {
background: url(../img/file-tree/txt.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_vb {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_wav {
background: url(../img/file-tree/music.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_webm {
background: url(../img/file-tree/film.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_wmv {
background: url(../img/file-tree/film.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_xls {
background: url(../img/file-tree/xls.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_xml {
background: url(../img/file-tree/code.png) left top no-repeat;
}
UL.jqueryFileTree LI.ext_zip {
background: url(../img/file-tree/zip.png) left top no-repeat;
}
UL.jqueryFileTree A {
color: #333;
text-decoration: none;
display: inline-block;
padding: 0 2px;
cursor: pointer;
}
UL.jqueryFileTree A:hover {
background: #BDF;
}

View File

@@ -0,0 +1,210 @@
.wriop-files-list #preview {
width: 80px;
}
.wriop-files-list #file {
width: 250px;
}
.wriop-files-list #status {
width: 100px;
}
.wriop-files-list #optimization {
width: 200px;
}
/* Filter block */
.wriop-files-list .wp-filter {
padding: 15px 20px;
}
.wriop-files-list .filter-items select {
height: auto;
padding: 6px;
margin-right: 12px;
}
.wriop-files-list .filter-items .button {
height: auto;
padding: 2px 12px 3px;
}
/* Empty table */
.wriop-files-list .no-items td {
padding: 35px;
text-align: center;
font-size: 18px;
}
.wriop-files-list .no-items td a {
text-decoration: underline;
}
/* Th sortable */
.wriop-files-list .sortable a {
color: #000;
}
/* Global links */
.wriop-files-list a {
color: #3694AE;
}
/* Global TDs */
.wriop-files-list tbody td,
.wriop-files-list tbody th,
.wriop-files-list.wriop-files-list tbody .check-column {
vertical-align: top;
text-align: left;
/*padding-top: 20px;
padding-bottom: 20px;*/
color: #626E7B;
}
.wriop-files-list .column-file,
.wriop-files-list .column-folder,
.wriop-files-list .column-status {
padding-top: 28px;
}
/* Col Title */
.wriop-files-list .column-preview strong  {
font-weight: normal;
font-size: 14px;
}
.wriop-files-list .column-preview strong a {
display: inline-flex;
align-items: center;
word-break: break-all;
word-wrap: break-word;
font-weight: normal;
}
.wriop-files-list .filename {
font-size: 12px;
font-weight: bold;
}
.wriop-files-list .media-icon {
position: relative;
width: 60px;
overflow: hidden;
flex-shrink: 0;
}
.media-icon .centered {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
transform: translate(50%, 50%);
}
.media-icon .centered img {
position: absolute;
left: 0;
top: 0;
transform: translate(-50%, -50%);
}
table.media .column-preview .media-icon.landscape img {
max-width: none;
width: auto;
height: 60px;
}
table.media .column-preview .media-icon.portrait img {
width: 60px;
}
/* Optimization datas Col */
.wriop-files-list ul.wriop-datas-list {
font-size: 11px;
}
.wriop-files-list ul.wriop-datas-list .big {
font-size: 13px;
}
.wriop-files-list ul.wriop-datas-list span.wriopy-chart-value {
font-size: 12px;
}
.wriop-files-list ul.wriop-datas-list .wriop-chart-container {
margin-right: 2px;
}
.wriop-files-list ul.wriop-datas-list canvas {
width: 18px !important;
height: 18px !important;
}
/* Optimization Level Col */
.wriop-files-list .optimization_level {
text-align: center;
font-weight: bold;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.02em;
}
.wriopy-files-list .column-optimization_level,
.wriop-files-list .column-optimization_level a {
text-align: center;
}
.wriop-files-list .column-optimization_level a span {
float: none;
display: inline-block;
vertical-align: middle;
}
.wriop-files-list .column-optimization_level .sorting-indicator {
vertical-align: -10px;
}
/* Actions col */
.wriop-files-list .column-actions .button,
.wriop-files-list .column-actions .button-primary {
padding: 5px 20px;
font-size: 14px;
height: auto;
}
.wriop-files-list .column-actions .button-primary {
background: #3694AE;
color: #FFF;
border: 0;
box-shadow: none;
text-shadow: none;
}
.wriop-files-list .column-actions a,
.status a.button-wriop-refresh-status {
display: inline-block;
margin: .3em 0;
font-size: 12px;
font-weight: bold;
}
.wriop-files-list .wriop-status-already_optimized {
font-weight: bold;
color: #8BC34A;
}
.wriop-files-list .column-actions a .dashicons,
.wriop-files-list .column-actions a .dashicons:before,
.status a.button-wriop-refresh-status .dashicons,
.status a.button-wriop-refresh-status .dashicons:before {
margin-right: 2px;
font-size: 17px;
height: 17px;
width: 17px;
}
.wriop-files-list .wio-reoptimize {
margin-top: 15px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,404 @@
/**
* General
* @version 1.0
*/
(function($) {
var customFolders = {
selectedFolder: null,
init: function() {
if( wrio_l18n_bulk_page === undefined || wrio_settings_bulk_page === undefined ) {
console.log('[Error]: Required global variables are not declared.');
return;
}
this.i18n = wrio_l18n_bulk_page;
this.settings = wrio_settings_bulk_page;
this.startOptButton = $('#wrio-start-optimization');
this.registerEvents();
},
registerEvents: function() {
var self = this;
$("#wrio-add-new-folder").on('click', function() {
swal({
title: self.i18n.modal_cf_title,
html: $('#wrio-tmpl-select-custom-folders').html(),
type: '',
customClass: 'wrio-modal wrio-modal-optimization-way',
showCancelButton: true,
showCloseButton: true,
padding: 0,
width: 654,
confirmButtonText: self.i18n.button_select,
cancelButtonText: self.i18n.button_cancel,
reverseButtons: false,
onOpen: function(modal) {
$(modal).find("#wrio-file-tree").fileTree({
script: ajaxurl + '?action=wriop_browse_dir&_wpnonce=' + self.settings.optimization_nonce,
multiFolder: false,
onlyFolders: true
});
$(document).on('click', '#wrio-file-tree .directory > a', function() {
var subPath = $(this).attr("rel").trim();
if( subPath ) {
var fullPath = subPath;
if( fullPath.slice(-1) === '/' ) {
fullPath = fullPath.slice(0, -1);
}
$("#wbcr-rio-selected-path").val(fullPath);
self.selectedFolder = fullPath;
}
});
}
}).then(function() {
self.addNewFolder();
}).catch(swal.noop);
return false;
});
},
addNewFolder: function() {
var self = this;
var data = {
action: 'wrio-add-custom-folder',
path: self.selectedFolder,
_wpnonce: self.settings.optimization_nonce,
};
$.post(ajaxurl, data, function(response) {
if( !response || !response.success ) {
console.log('[Error]: Failed ajax request (Add new folder).');
console.log(data);
console.log(response);
if( response.data && response.data.error_message ) {
// todo: так как фреймворк не используется в аддоне, нужно доработать этот кусок кода. Он не
// может быть скомпилирован.
var noticeId = $.wbcr_factory_clearfy_000.app.showNotice(response.data.error_message, 'danger');
setTimeout(function() {
$.wbcr_factory_clearfy_000.app.hideNotice(noticeId);
}, 5000);
}
return;
}
var tr = $('<tr>'),
td = $('<td>'),
path = $('<span>'),
button = $('<button>'),
compressed_msg;
path.addClass('wrio-table-highlighter').text(response.data.path);
button.addClass('wbcr-rio-remove-folder')
.addClass('btn')
.addClass('btn-default')
.attr('data-confirm', self.i18n.alert_remove_folder)
.html(' <span class="dashicons dashicons-no"></span>');
compressed_msg = self.i18n.compressed_in_folder.replace('%1$d', 0);
compressed_msg = compressed_msg.replace('%2$s', '<span id="wrio-cf-total-' + response.data.uid + '">0</span>');
tr.addClass('wrio-table-item')
.append(td.clone().addClass('wrio-table-spinner'))
.append(td.clone().html(compressed_msg))
.append(td.clone().append(path))
.append(td.clone().attr('data-uid', response.data.uid).append(button));
$('.wbcr-rio-folders-table tbody').append(tr);
self.scanFolder({
action: 'wrio-scan-folder',
uid: response.data.uid,
total: 0,
offset: 0,
_wpnonce: self.settings.optimization_nonce,
}, function(result) {
tr.find('td').eq(0).removeClass('wrio-table-spinner');
tr.find('td').eq(1).find('span').text(result.total);
reload_ui(); // обновляем интерфейс
});
});
},
scanFolder: function(data, callback) {
var self = this;
$.post(ajaxurl, data, function(response) {
console.log(response);
if( !response || !response.success ) {
console.log('[Error]: Failed ajax request (Scan folder).');
console.log(data);
console.log(response);
if( response.data && response.data.error_message ) {
// todo: так как фреймворк не используется в аддоне, нужно доработать этот кусок кода. Он не
// может быть скомпилирован.
var noticeId = $.wbcr_factory_clearfy_000.app.showNotice(response.data.error_message, 'danger');
setTimeout(function() {
$.wbcr_factory_clearfy_000.app.hideNotice(noticeId);
}, 5000);
}
return;
}
data.total = response.data.total;
data.offset = response.data.offset;
$('#wrio-cf-total-' + data.uid).text(data.offset);
if( response.data.complete ) {
callback && callback(response.data);
} else {
self.scanFolder(data, callback);
}
}).fail(function(xhr, status, error) {
// error handling
});
}
};
$(document).ready(function() {
customFolders.init();
});
$(document).on('click', '.wbcr-rio-scan-folder', function() {
$('#wbcr-rio-popup').empty();
tb_show('Sync Folder', '#TB_inline?&width=500&height=200&inlineId=wbcr-rio-popup');
$('#TB_ajaxContent').html('Loading...');
var data = {
action: 'wio_cf_get_template_part',
template: 'sync_folder'
};
var self = $(this);
$.post(ajaxurl, data, function(response) {
$('#TB_ajaxContent').html(response);
var ai_data = {
'action': 'wriop_folder_sync_index',
'uid': self.closest('td').data('uid'),
'total': 0,
'offset': 0,
'_wpnonce': wrio_settings_bulk_page.optimization_nonce,
};
send_indexing_data(ai_data);
});
});
$('.wbcr-rio-scan-all-folders').on('click', function() {
$('#wbcr-rio-popup').empty();
tb_show('Sync All Folders', '#TB_inline?&width=500&height=200&inlineId=wbcr-rio-popup');
$('#TB_ajaxContent').html('Loading...');
var data = {
action: 'wio_cf_get_template_part',
template: 'sync_all_folders'
};
var self = $(this);
$.post(ajaxurl, data, function(response) {
$('#TB_ajaxContent').html(response);
});
});
$(document).on('click', '.wbcr-rio-optimize-folder', function() {
$('#wbcr-rio-popup').empty();
tb_show('Optimize Folder', '#TB_inline?&width=500&height=200&inlineId=wbcr-rio-popup');
$('#TB_ajaxContent').html('Loading...');
var data = {
action: 'wio_cf_get_template_part',
template: 'optimize_folder'
};
var self = $(this);
$.post(ajaxurl, data, function(response) {
$('#TB_ajaxContent').html(response);
var ai_data = {
'uid': self.closest('td').data('uid'),
'action': 'wriop_process_cf_folder_images',
'_wpnonce': $('#wio-iph-nonce').val()
};
send_optimize_post_data(ai_data);
});
});
$(document).on('click', '.wbcr-rio-restore-folder', function() {
if( !confirm($(this).data('confirm')) ) {
return false;
}
$('#wbcr-rio-popup').empty();
tb_show('Restore Folder', '#TB_inline?&width=500&height=200&inlineId=wbcr-rio-popup');
$('#TB_ajaxContent').html('Loading...');
var data = {
action: 'wio_cf_get_template_part',
template: 'restore_folder'
};
var self = $(this);
$.post(ajaxurl, data, function(response) {
$('#TB_ajaxContent').html(response);
var ai_data = {
'total': '?',
'uid': self.closest('td').data('uid'),
'action': 'wio_cf_restore_backup',
'_wpnonce': $('#wio-iph-nonce').val()
};
send_backup_post_data(ai_data);
});
return false;
});
$(document).on('click', '.wbcr-rio-remove-folder', function() {
if( !confirm($(this).data('confirm')) ) {
return false;
}
var data = {
action: 'wriop_remove_folder',
uid: $(this).closest('td').data('uid'),
_wpnonce: wrio_settings_bulk_page.optimization_nonce,
};
$(this).closest('tr').remove();
$.post(ajaxurl, data, function(response) {
reload_ui(); // обновляем интерфейс
});
});
function reload_ui() {
var data = {
action: 'wio_cf_reload_ui',
_wpnonce: wrio_settings_bulk_page.optimization_nonce,
};
$.post(ajaxurl, data, function(response) {
if( response.folders_table ) {
$('.wbcr-rio-folders-table tbody').html(response.folders_table);
}
if( response.statistic ) {
redraw_statistics(response.statistic);
}
});
}
function redraw_statistics(statistic) {
$('#wio-main-chart').attr('data-unoptimized', statistic.unoptimized)
.attr('data-optimized', statistic.optimized)
.attr('data-errors', statistic.error);
$('#wio-total-optimized-attachments').text(statistic.optimized); // optimized
$('#wio-original-size').text(bytesToSize(statistic.original_size));
$('#wio-optimized-size').text(bytesToSize(statistic.optimized_size));
$('#wio-total-optimized-attachments-pct').text(statistic.save_size_percent + '%');
$('#wio-overview-chart-percent').html(statistic.optimized_percent + '<span>%</span>');
$('.wio-total-percent').text(statistic.optimized_percent + '%');
$('#wio-optimized-bar').css('width', statistic.percent_line + '%');
$('#wio-unoptimized-num').text(statistic.unoptimized);
$('#wio-optimized-num').text(statistic.optimized);
$('#wio-error-num').text(statistic.error);
window.wio_chart.data.datasets[0].data[0] = statistic.unoptimized; // unoptimized
window.wio_chart.data.datasets[0].data[1] = statistic.optimized; // optimized
window.wio_chart.data.datasets[0].data[2] = statistic.error; // errors
window.wio_chart.update();
}
function bytesToSize(bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if( bytes == 0 ) {
return '0 Byte';
}
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
if( i == 0 ) {
return bytes + ' ' + sizes[i];
}
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
}
function send_backup_post_data(data) {
$.post(ajaxurl, data, function(response) {
if( !response.end ) {
data.total = response.total;
send_backup_post_data(data);
$('#wio-restore-backup-progress').find('.progress-bar').css('width', response.percent + '%');
} else {
$('#wio-restore-backup-progress').find('.progress-bar').css('width', '100%');
$('#wio-restore-backup-success-msg').show();
$('#wio-restore-backup-progress-msg').hide();
reload_ui(); // обновляем интерфейс
}
});
}
function send_optimize_post_data(data) {
$.post(ajaxurl, data, function(response) {
if( !response.end ) {
send_optimize_post_data(data);
$('#wio-optimize-progress').find('.progress-bar').css('width', response.statistic.optimized_percent + '%');
} else {
$('#wio-optimize-progress').find('.progress-bar').css('width', '100%');
$('#wio-optimize-success-msg').show();
$('#wio-optimize-progress-msg').hide();
reload_ui(); // обновляем интерфейс
}
});
}
function send_indexing_data(data) {
$.post(ajaxurl, data, function(response) {
data.total = response.total;
data.offset = response.offset;
if( response.complete ) {
$('#wio-sync-progress').find('.progress-bar').css('width', '50%');
data.action = 'wriop_folder_indexing';
data.offset = 0;
data.total = 0;
send_check_index_data(data);
} else {
send_indexing_data(data);
$('#wio-sync-progress').find('.progress-bar').css('width', response.percent + '%');
}
});
}
function send_check_index_data(data) {
$.post(ajaxurl, data, function(response) {
data.total = response.total;
data.offset = response.offset;
if( response.complete ) {
$('#wio-sync-progress').find('.progress-bar').css('width', '100%');
$('#wio-sync-success-msg').show();
$('#wio-sync-progress-msg').hide();
reload_ui(); // обновляем интерфейс
} else {
send_check_index_data(data);
$('#wio-sync-progress').find('.progress-bar').css('width', response.percent + '%');
}
});
}
function first_indexing(data) {
$.post(ajaxurl, data, function(response) {
data.total = response.total;
data.offset = response.offset;
$('.wbcr-rio-indexing-counter').text(data.offset);
if( response.complete ) {
$("#wbcr-rio-add-folder").show();
$('#wbcr-rio-indexing-text').hide();
$('#wbcr-rio-indexing-finish-text').show();
reload_ui(); // обновляем интерфейс
} else {
first_indexing(data);
}
});
}
})(jQuery);

View File

@@ -0,0 +1,12 @@
/**
* General
* @version 1.0
*/
(function($) {
'use strict';
// less code
})(jQuery);

View File

@@ -0,0 +1,213 @@
/*
* jQueryFileTree Plugin
*
* @author - Cory S.N. LaViska - A Beautiful Site (http://abeautifulsite.net/) - 24 March 2008
* @author - Dave Rogers - (https://github.com/daverogers/)
*
* Usage: $('.fileTreeDemo').fileTree({ options }, callback )
*
* TERMS OF USE
*
* This plugin is dual-licensed under the GNU General Public License and the MIT License and
* is copyright 2008 A Beautiful Site, LLC.
*/
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
(function($, window) {
var FileTree;
FileTree = (function() {
function FileTree(el, args, callback) {
this.onEvent = bind(this.onEvent, this);
var $el, _this, defaults;
$el = $(el);
_this = this;
defaults = {
root: '/',
script: '/files/filetree',
folderEvent: 'click',
expandSpeed: 500,
collapseSpeed: 500,
expandEasing: 'swing',
collapseEasing: 'swing',
multiFolder: true,
loadMessage: 'Loading...',
errorMessage: 'Unable to get file tree information',
multiSelect: false,
onlyFolders: false,
onlyFiles: false,
preventLinkAction: false
};
this.jqft = {
container: $el
};
this.options = $.extend(defaults, args);
this.callback = callback;
this.data = {};
$el.html('<ul class="jqueryFileTree start"><li class="wait">' + this.options.loadMessage + '<li></ul>');
_this.showTree($el, escape(this.options.root), function() {
return _this._trigger('filetreeinitiated', {});
});
$el.delegate("li a", this.options.folderEvent, _this.onEvent);
}
FileTree.prototype.onEvent = function(event) {
var $ev, _this, callback, jqft, options, ref;
$ev = $(event.target);
options = this.options;
jqft = this.jqft;
_this = this;
callback = this.callback;
_this.data = {};
_this.data.li = $ev.closest('li');
_this.data.type = (ref = _this.data.li.hasClass('directory')) != null ? ref : {
'directory': 'file'
};
_this.data.value = $ev.text();
_this.data.rel = $ev.prop('rel');
_this.data.container = jqft.container;
if (options.preventLinkAction) {
event.preventDefault();
}
if ($ev.parent().hasClass('directory')) {
_this.jqft.container.find('LI.directory').removeClass('selected');
$ev.parent().addClass('selected');
if ($ev.parent().hasClass('collapsed')) {
if (!options.multiFolder) {
$ev.parent().parent().find('UL').slideUp({
duration: options.collapseSpeed,
easing: options.collapseEasing
});
$ev.parent().parent().find('LI.directory').removeClass('expanded').addClass('collapsed');
}
$ev.parent().removeClass('collapsed').addClass('expanded');
$ev.parent().find('UL').remove();
return _this.showTree($ev.parent(), $ev.attr('rel'), function() {
_this._trigger('filetreeexpanded', _this.data);
return callback != null;
});
} else {
return $ev.parent().find('UL').slideUp({
duration: options.collapseSpeed,
easing: options.collapseEasing,
start: function() {
return _this._trigger('filetreecollapse', _this.data);
},
complete: function() {
$ev.parent().removeClass('expanded').addClass('collapsed');
_this._trigger('filetreecollapsed', _this.data);
return callback != null;
}
});
}
} else {
if (!options.multiSelect) {
jqft.container.find('li').removeClass('selected');
$ev.parent().addClass('selected');
} else {
if ($ev.parent().find('input').is(':checked')) {
$ev.parent().find('input').prop('checked', false);
$ev.parent().removeClass('selected');
} else {
$ev.parent().find('input').prop('checked', true);
$ev.parent().addClass('selected');
}
}
_this._trigger('filetreeclicked', _this.data);
return typeof callback === "function" ? callback($ev.attr('rel')) : void 0;
}
};
FileTree.prototype.showTree = function(el, dir, finishCallback) {
var $el, _this, data, handleFail, handleResult, options, result;
$el = $(el);
options = this.options;
_this = this;
$el.addClass('wait');
$(".jqueryFileTree.start").remove();
data = {
dir: dir,
onlyFolders: options.onlyFolders,
onlyFiles: options.onlyFiles,
multiSelect: options.multiSelect
};
handleResult = function(result) {
var li;
$el.find('.start').html('');
$el.removeClass('wait').append(result);
if (options.root === dir) {
$el.find('UL:hidden').show(typeof callback !== "undefined" && callback !== null);
} else {
if (jQuery.easing[options.expandEasing] === void 0) {
console.log('Easing library not loaded. Include jQueryUI or 3rd party lib.');
options.expandEasing = 'swing';
}
$el.find('UL:hidden').slideDown({
duration: options.expandSpeed,
easing: options.expandEasing,
start: function() {
return _this._trigger('filetreeexpand', _this.data);
},
complete: finishCallback
});
}
li = $('[rel="' + decodeURIComponent(dir) + '"]').parent();
if (options.multiSelect && li.children('input').is(':checked')) {
li.find('ul li input').each(function() {
$(this).prop('checked', true);
return $(this).parent().addClass('selected');
});
}
return false;
};
handleFail = function() {
$el.find('.start').html('');
$el.removeClass('wait').append("<p>" + options.errorMessage + "</p>");
return false;
};
if (typeof options.script === 'function') {
result = options.script(data);
if (typeof result === 'string' || result instanceof jQuery) {
return handleResult(result);
} else {
return handleFail();
}
} else {
return $.ajax({
url: options.script,
type: 'POST',
dataType: 'HTML',
data: data
}).done(function(result) {
return handleResult(result);
}).fail(function() {
return handleFail();
});
}
};
FileTree.prototype._trigger = function(eventType, data) {
var $el;
$el = this.jqft.container;
return $el.triggerHandler(eventType, data);
};
return FileTree;
})();
return $.fn.extend({
fileTree: function(args, callback) {
return this.each(function() {
var $this, data;
$this = $(this);
data = $this.data('fileTree');
if (!data) {
$this.data('fileTree', (data = new FileTree(this, args, callback)));
}
if (typeof args === 'string') {
return data[option].apply(data);
}
});
}
});
})(window.jQuery, window);

View File

@@ -0,0 +1,49 @@
<?php
/**
* Обычно в этом файле размещает код, который отвечает за уведомление, совместимость с другими плагинами,
* незначительные функции, которые должны быть выполнены на всех страницах админ панели.
*
* В этом файле должен быть размещен код, которые относится только к области администрирования.
*
* @version 1.0
*/
// Exit if accessed directly
use WRIO\WEBP\HTML\Delivery;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Flush configuration after saving the settings
*
* @param WHM_Plugin $plugin
* @param Wbcr_FactoryPages480_ImpressiveThemplate $page
*
* @return bool
*/
add_action(
'wbcr/factory/pages/impressive/after_form_save',
function ( $plugin, $page ) {
$is_rio_plugin = WRIO_Plugin::app()->getPluginName() == $plugin->getPluginName();
$is_settings_page = 'rio_settings' == $page->id;
$is_apache = WRIO\WEBP\Server::is_apache();
$is_use_htaccess = WRIO\WEBP\Server::server_use_htaccess();
if ( $is_rio_plugin && $is_settings_page ) {
if ( WRIO\WEBP\HTML\Delivery::should_use_converted_images() && WRIO\WEBP\HTML\Delivery::is_redirect_delivery_mode() ) {
if ( $is_apache && $is_use_htaccess ) {
WRIO\WEBP\Server::htaccess_update_webp_rules();
}
return;
}
if ( $is_apache && $is_use_htaccess ) {
WRIO\WEBP\Server::htaccess_clear_webp_rules();
}
}
},
10,
2
);

View File

@@ -0,0 +1,70 @@
<?php
/**
* Used to process different filters.
*
* @version 1.0
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Processing restore back-up of custom folder and Nextgen.
*
* @return array {
* Processing result.
* @type int $remane Count of remained images to be processed.
* @type int $total Count of total processed items.
* }
* @since 1.0.4
*/
add_filter(
'wbcr/rio/backup/restore_filter',
function ( $limit ) {
$remane_count = 0;
$total = 0;
$premium_backup = WRIOP_Backup::get_instance();
if ( wrio_is_active_nextgen_gallery() ) {
/* NextGen*/
$nextgen_restored = $premium_backup->restoreAllNextGen( $limit );
if ( isset( $nextgen_restored['remane'] ) ) {
$remane_count += $nextgen_restored['remane'];
}
$nextgen_count = RIO_Process_Queue::count_by_type_status( 'nextgen', 'success' );
if ( is_numeric( $nextgen_count ) && (int) $nextgen_count > 0 ) {
$total += $nextgen_count;
}
unset( $nextgen_count );
}
/* Custom folders */
$cf_restored = $premium_backup->restoreAllCustomFolders( $limit );
if ( isset( $cf_restored['remane'] ) ) {
$remane_count += $cf_restored['remane'];
}
$cf_count = RIO_Process_Queue::count_by_type_status( 'cf_image', 'success' );
if ( is_numeric( $cf_count ) && (int) $cf_count > 0 ) {
$total += $cf_count;
}
unset( $cf_count );
return [
'remane' => $remane_count,
'total' => $total,
];
}
);

View File

@@ -0,0 +1,87 @@
<?php
/**
* Adds hooks to the main plugin settings page.
*
* @version 1.0
*/
use WRIO\WEBP\HTML\Delivery as WEBP_Delivery;
use WRIO\WEBP\Server;
/**
* Used to save webp/avif conversion options.
*
* @since 1.0.4
*/
add_action(
'wrio/settings_page/berfore_form_save',
function () {
// Get AVIF option - if user tries to enable without premium, force disable
$avif_enabled = WRIO_Plugin::app()->request->post(
WRIO_Plugin::app()->getPrefix() . 'convert_avif_format',
false
);
if ( $avif_enabled && ! wrio_is_license_activate() ) {
WRIO_Plugin::app()->updatePopulateOption( 'convert_avif_format', false );
}
// Check if any conversion is enabled (WebP or AVIF)
$webp_enabled = WRIO_Plugin::app()->request->post(
WRIO_Plugin::app()->getPrefix() . 'convert_webp_format',
false
);
// Only process delivery mode if any conversion is enabled
if ( ! $webp_enabled && ! $avif_enabled ) {
return;
}
$allow_redirection_mode = Server::is_apache() && Server::server_use_htaccess();
$delivery_mode = WRIO_Plugin::app()->request->post( 'wrio_webp_delivery_mode', WEBP_Delivery::PICTURE_DELIVERY_MODE );
if ( WEBP_Delivery::REDIRECT_DELIVERY_MODE === $delivery_mode && ! $allow_redirection_mode ) {
$delivery_mode = WEBP_Delivery::PICTURE_DELIVERY_MODE;
}
WRIO_Plugin::app()->updatePopulateOption( 'webp_delivery_mode', $delivery_mode );
}
);
/**
* This hook prints options for delivering webp images.
*
* @since 1.0.4
*/
add_action(
'wrio/settings_page/conver_webp_options',
function () {
$allow_redirection_mode = Server::is_apache() && Server::server_use_htaccess();
$delivery_mode = WRIO_Plugin::app()->getPopulateOption( 'webp_delivery_mode', WEBP_Delivery::PICTURE_DELIVERY_MODE );
$server = 'unknown';
if ( Server::is_apache() ) {
$server = 'apache';
} elseif ( Server::is_nginx() ) {
$server = 'nginx';
} elseif ( Server::is_iss() ) {
$server = 'iss';
}
// Help
$docs_url = WRIO_Plugin::app()->get_support()->get_tracking_page_url( 'what-is-webp-format-and-how-webp-images-can-speed-up-your-wordpress-website', 'settings-page' );
$view = \WRIO_Views::get_instance( WRIOP_PLUGIN_DIR );
$view->print_template(
'part-settings-page-webp-options',
[
'server' => $server,
'delivery_mode' => $delivery_mode,
'allow_redirection_mode' => $allow_redirection_mode,
'docs_url' => $docs_url,
]
);
}
);

View File

@@ -0,0 +1 @@
<?php

View File

@@ -0,0 +1,204 @@
<?php
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Класс отвечает за работу страницы статистики
*
* @version 1.0
*/
class WRIO_StatisticFolders extends WRIO_StatisticPage {
/**
* {@inheritdoc}
*/
public $id = 'io_folders_statistic';
/**
* {@inheritdoc}
*/
public $internal = true;
/**
* {@inheritdoc}
*/
public $page_parent_page = 'none';
/**
* @var string
*/
public $menu_target = null;
/**
* Use admin.php as base URL instead of menu_target.
*
* @var bool
*/
public $custom_target = true;
/**
* @var string
*/
public $page_menu_dashicon = 'dashicons-images-alt';
/**
* {@inheritdoc}
*/
public $add_link_to_plugin_actions = false;
/**
* {@inheritdoc}
*/
protected $scope = 'custom-folders';
/**
* @since 1.0
* @var WRIO_Views
*/
private $parent_view;
/**
* @param WRIO_Plugin $plugin
*/
public function __construct( WRIO_Plugin $plugin ) {
parent::__construct( $plugin );
$this->parent_view = $this->view;
$this->view = new WRIO_Views( WRIOP_PLUGIN_DIR );
}
/**
* {@inheritdoc}
*/
public function getMenuTitle() {
return __( 'Custom Folders', 'robin-image-optimizer' );
}
/**
* {@inheritdoc}
*/
public function getPageTitle() {
return __( 'Custom Folders', 'robin-image-optimizer' );
}
/**
* Подменяем простраинство имен для меню плагина, если активирован плагин
* Меню текущего плагина будет добавлено в общее меню
*
* @return string
*/
public function getMenuScope() {
if ( $this->clearfy_collaboration ) {
$this->page_parent_page = 'rio_general';
return 'wbcr_clearfy';
}
return 'robin-image-optimizer';
}
/**
* {@inheritdoc}
*/
public function assets( $scripts, $styles ) {
parent::assets( $scripts, $styles );
$this->styles->add( WRIOP_PLUGIN_URL . '/admin/assets/css/jquery-file-tree.css' );
$this->scripts->add( WRIOP_PLUGIN_URL . '/admin/assets/js/jquery-file-tree.js' );
$this->scripts->add( WRIOP_PLUGIN_URL . '/admin/assets/css/custom-folders.css' );
$this->scripts->add( WRIOP_PLUGIN_URL . '/admin/assets/js/custom-folders.js' );
}
/**
* {@inheritdoc}
*/
public function showPageContent() {
$is_premium = wrio_is_license_activate();
$statistics = WRIO_Image_Statistic_Folders::get_instance();
$template_data = [
'is_premium' => $is_premium,
'scope' => $this->scope,
];
// do_action( 'wbcr/rio/multisite_current_blog' );
// Page header
$this->parent_view->print_template(
'part-page-header',
[
'title' => __( 'Image optimization dashboard', 'robin-image-optimizer' ),
'description' => __( 'Monitor image optimization statistics and run on demand or scheduled optimization.', 'robin-image-optimizer' ),
],
$this
);
// Page tabs
$this->parent_view->print_template( 'part-bulk-optimization-tabs', $template_data, $this );
?>
<div class="wbcr-factory-page-group-body" style="padding:0; border-top: 1px solid #d4d4d4;">
<?php
// Servers
$this->parent_view->print_template( 'part-bulk-optimization-servers', $template_data, $this );
// Statistic
$this->parent_view->print_template(
'part-bulk-optimization-statistic',
array_merge(
$template_data,
[
'stats' => $statistics->get(),
]
),
$this
);
// Folders table
$this->view->print_template( 'part-bulk-optimization-table-folders', $template_data, $this );
// Optimization log
$this->parent_view->print_template(
'part-bulk-optimization-log',
array_merge(
$template_data,
[
'process_log' => $statistics->get_last_optimized_images(),
]
),
$this
);
?>
</div>
<script type="text/html" id="wrio-tmpl-bulk-optimization">
<?php $this->parent_view->print_template( 'modal-bulk-optimization' ); ?>
</script>
<script type="text/html" id="wrio-tmpl-select-custom-folders">
<?php $this->view->print_template( 'modal-select-custom-folders' ); ?>
</script>
<?php
// do_action( 'wbcr/rio/multisite_restore_blog' );
}
protected function get_i18n() {
$i18n = parent::get_i18n();
$i18n['modal_cf_title'] = __( 'Select custom folder', 'robin-image-optimizer' );
// $i18n['modal_cf_description'] = __( 'Select a directory for optimization. All nested images and folders will be optimized recursively.', 'robin-image-optimizer' );
$i18n['button_select'] = __( 'Select', 'robin-image-optimizer' );
$i18n['button_cancel'] = __( 'Cancel', 'robin-image-optimizer' );
$i18n['button_remove'] = __( 'Remove', 'robin-image-optimizer' );
$i18n['alert_remove_folder'] = __( 'Exclude directory from optimization?', 'robin-image-optimizer' );
// translators: %d is the number of images found
$i18n['found_images'] = __( 'Selected directory is being indexed. Found %d images.', 'robin-image-optimizer' );
$i18n['scan_complete'] = __( 'Indexing complete. Directory successfully added and ready for optimization.', 'robin-image-optimizer' );
// translators: %1$d is the number of compressed images, %2$s is the total number of images
$i18n['compressed_in_folder'] = __( 'Compressed %1$d of %2$s<br>images', 'robin-image-optimizer' );
$i18n['optimization_complete'] = __( 'All images from custom folders are optimized.', 'robin-image-optimizer' );
return $i18n;
}
}

View File

@@ -0,0 +1,111 @@
<?php
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Класс отвечает за работу страницы статистики
*
* @version 1.0
*/
class WRIO_StatisticNextgenPage extends WRIO_StatisticPage {
/**
* {@inheritdoc}
*/
public $id = 'io_nextgen_gallery_statistic';
/**
* {@inheritdoc}
*/
public $page_menu_dashicon = 'dashicons-images-alt';
/**
* {@inheritdoc}
*/
public $internal = true;
/**
* @var string
*/
public $menu_target = null;
/**
* Use admin.php as base URL instead of menu_target.
*
* @var bool
*/
public $custom_target = true;
/**
* none - to hide page from plugin menu
* {@inheritdoc}
*/
public $page_parent_page = 'none';
/**
* {@inheritdoc}
*/
public $add_link_to_plugin_actions = false;
/**
* {@inheritdoc}
*/
protected $scope = 'nextgen-gallery';
/**
* @param WRIO_Plugin $plugin
*/
public function __construct( WRIO_Plugin $plugin ) {
$this->plugin = $plugin;
parent::__construct( $plugin );
}
/**
* {@inheritdoc}
*/
public function getMenuTitle() {
return __( 'NextGen Gallery', 'robin-image-optimizer' );
}
/**
* {@inheritdoc}
*/
public function getPageTitle() {
return __( 'NextGen Gallery', 'robin-image-optimizer' );
}
/**
* Подменяем простраинство имен для меню плагина, если активирован плагин
* Меню текущего плагина будет добавлено в общее меню
*
* @return string
*/
public function getMenuScope() {
if ( $this->clearfy_collaboration ) {
$this->page_parent_page = 'rio_general';
return 'wbcr_clearfy';
}
return 'robin-image-optimizer';
}
/**
* {@inheritdoc}
*/
public function assets( $scripts, $styles ) {
parent::assets( $scripts, $styles );
}
/**
* @since 1.3.0
* @return object|\WRIO_Image_Statistic
*/
protected function get_statisctic_data() {
return WRIO_Image_Statistic_Nextgen::get_instance();
}
}

View File

@@ -0,0 +1,37 @@
<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>

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

File diff suppressed because one or more lines are too long

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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)! ' );
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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' );

View File

@@ -0,0 +1,489 @@
<?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 ) {
/**
* @var RIOP_WebP_Extra_Data $extra_data
*/
$extra_data = $model->get_extra_data();
if ( $extra_data === null ) {
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 );
}
}
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 );
$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 .= $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 ) );
}
}
$file_contents = file_get_contents( $source_file_path );
$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(),
];
}
$response = wp_remote_post(
$url,
[
'timeout' => 60,
'headers' => $headers,
'body' => $body,
]
);
delete_transient( $transient_string );
return $response;
}
/**
* 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"', $response_json->error ) );
}
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 ( \Exception $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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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';
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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( "Failed getting converted url. Unsupported image format or no conversion enabled\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( "Failed getting converted url. Image is 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( "Failed getting converted url. Unable to find origin image\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( "Failed getting converted url. No converted image found\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;
}
}

View File

@@ -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);
}
}

View File

@@ -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 );
}
}

View File

@@ -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, 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;
}
}

View File

@@ -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)' );
}
}
}
}

View File

@@ -0,0 +1,5 @@
{
"require": {
"rosell-dk/dom-util-for-webp": "^0.3.0"
}
}

View 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": []
}

View File

@@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit84320a6a225dd65e40820c51b32fdca4::getLoader();

View File

@@ -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;
}

View 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;
}
}

View File

@@ -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.

View File

@@ -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',
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -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'),
);

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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',
),
),
);

View File

@@ -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;

View File

@@ -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": {
}
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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';
}

Some files were not shown because too many files have changed in this diff Show More