Файловый менеджер - Редактировать - /var/www/xthruster/html/wp-content/uploads/flags/modules.tar
Назад
core/module.php 0000644 00000021076 14717617543 0007517 0 ustar 00 <?php namespace ImageOptimization\Modules\Core; use ImageOptimization\Modules\Optimization\{ Rest\Cancel_Bulk_Optimization, Rest\Optimize_Bulk, }; use ImageOptimization\Modules\Backups\Rest\{ Restore_All, Remove_Backups, }; use ImageOptimization\Classes\{ Migration\Migration_Manager, Module_Base, Utils }; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Module_Base { public function get_name(): string { return 'core'; } public static function component_list() : array { return [ 'Pointers', 'Migrations', 'Conflicts', 'User_Feedback', 'Not_Connected', ]; } private function render_top_bar() { ?> <div id="image-optimization-top-bar"></div> <?php } private function render_app() { ?> <div class="clear"></div> <div id="image-optimization-app"></div> <?php } public function maybe_add_quota_reached_notice() { // @var ImageOptimizer/Modules/ConnectManager/Module $module = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); if ( ! $module->connect_instance->get_connect_status() || $module->connect_instance->images_left() > 0 ) { return; } ?> <div class="notice notice-warning notice image-optimizer__notice image-optimizer__notice--warning"> <p> <b> <?php esc_html_e( 'You’ve reached your plan quota.', 'image-optimization' ); ?> </b> <span> <?php esc_html_e( 'You have no images left to optimize in your current plan.', 'image-optimization' ); ?> <a href="https://go.elementor.com/io-quota-upgrade/"> <?php esc_html_e( 'Upgrade plan now', 'image-optimization' ); ?> </a> </span> </p> </div> <?php } public function maybe_add_url_mismatch_notice() { // @var ImageOptimizer/Modules/ConnectManager/Module $module = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); if ( $module->connect_instance->is_valid_home_url() ) { return; } ?> <div class="notice notice-error notice image-optimizer__notice image-optimizer__notice--error"> <p> <b> <?php esc_html_e( 'Your license key does not match your current domain, causing a mismatch.', 'image-optimization' ); ?> </b> <span> <?php esc_html_e( 'This is most likely due to a change in the domain URL of your site (including HTTP/SSL migration).', 'image-optimization' ); ?> <button type="button" onclick="document.dispatchEvent( new Event( 'image-optimizer/auth/url-mismatch-modal/open' ) );"> <?php esc_html_e( 'Fix mismatched URL', 'image-optimization' ); ?> </button> </span> </p> </div> <?php } public function add_plugin_links( $links, $plugin_file_name ): array { if ( ! str_ends_with( $plugin_file_name, '/image-optimization.php' ) ) { return (array) $links; } $custom_links = [ 'settings' => sprintf( '<a href="%s">%s</a>', admin_url( 'admin.php?page=' . \ImageOptimization\Modules\Settings\Module::SETTING_BASE_SLUG ), esc_html__( 'Settings', 'image-optimization' ) ), ]; // @var ImageOptimizer/Modules/ConnectManager/Module $module = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); if ( $module->connect_instance->is_connected() ) { $custom_links['upgrade'] = sprintf( '<a href="%s" style="color: #524CFF; font-weight: 700;" target="_blank" rel="noopener noreferrer">%s</a>', 'https://go.elementor.com/io-plugins-upgrade/', esc_html__( 'Upgrade', 'image-optimization' ) ); } else { $custom_links['connect'] = sprintf( '<a href="%s" style="color: #524CFF; font-weight: 700;">%s</a>', admin_url( 'admin.php?page=' . \ImageOptimization\Modules\Settings\Module::SETTING_BASE_SLUG ), esc_html__( 'Connect', 'image-optimization' ) ); } return array_merge( $custom_links, $links ); } public function enqueue_global_assets() { wp_enqueue_style( 'image-optimization-admin-fonts', 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap', [], IMAGE_OPTIMIZATION_VERSION ); wp_enqueue_style( 'image-optimization-core-style-admin', $this->get_css_assets_url( 'style-admin', 'assets/build/' ), [], IMAGE_OPTIMIZATION_VERSION, ); } /** * Enqueue styles and scripts */ private function enqueue_scripts() { $asset_file = require IMAGE_OPTIMIZATION_ASSETS_PATH . 'build/admin.asset.php'; foreach ( $asset_file['dependencies'] as $style ) { wp_enqueue_style( $style ); } wp_enqueue_script( 'image-optimization-admin', $this->get_js_assets_url( 'admin' ), array_merge( $asset_file['dependencies'], [ 'wp-util' ] ), $asset_file['version'], true ); wp_localize_script( 'image-optimization-admin', 'imageOptimizerAppSettings', [ 'siteUrl' => wp_parse_url( get_site_url(), PHP_URL_HOST ), 'thumbnailSizes' => wp_get_registered_image_subsizes(), 'isDevelopment' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, ] ); /** * @var ImageOptimizer\Modules\ConnectManager\Module $module */ $module = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); $is_connect_on_fly = $module->connect_instance->get_is_connect_on_fly(); $connect_email = $module->connect_instance->get_connect_data()['user']['email'] ?? null; $show_reset = ! $module->connect_instance->is_connected() && ( $module->connect_instance->get_client_id() || $module->connect_instance->get_client_secret() ); wp_localize_script( 'image-optimization-admin', 'imageOptimizerUserData', [ 'isConnectOnFly' => $is_connect_on_fly, 'isConnected' => $module->connect_instance->is_connected(), 'isActivated' => $module->connect_instance->is_activated(), 'isUrlMismatch' => ! $module->connect_instance->is_valid_home_url(), 'planData' => $module->connect_instance->is_activated() ? $module->connect_instance->get_connect_status() : null, 'licenseKey' => $module->connect_instance->is_activated() ? $module->connect_instance->get_activation_state() : null, 'imagesLeft' => $module->connect_instance->is_activated() ? $module->connect_instance->images_left() : null, 'isOwner' => $module->connect_instance->is_connected() ? $module->connect_instance->user_is_subscription_owner() : null, 'subscriptionEmail' => $connect_email ? $connect_email : null, 'showResetButton' => $show_reset, 'wpRestNonce' => wp_create_nonce( 'wp_rest' ), 'disconnect' => wp_create_nonce( 'wp_rest' ), 'authInitNonce' => wp_create_nonce( $module->connect_instance->connect_init_nonce() ), 'authDisconnectNonce' => wp_create_nonce( $module->connect_instance->disconnect_nonce() ), 'authDeactivateNonce' => wp_create_nonce( $module->connect_instance->deactivate_nonce() ), 'authGetSubscriptionsNonce' => wp_create_nonce( $module->connect_instance->get_subscriptions_nonce() ), 'authActivateNonce' => wp_create_nonce( $module->connect_instance->activate_nonce() ), 'versionNonce' => wp_create_nonce( $module->connect_instance->version_nonce() ), 'removeBackupsNonce' => wp_create_nonce( Remove_Backups::NONCE_NAME ), 'restoreAllImagesNonce' => wp_create_nonce( Restore_All::NONCE_NAME ), 'optimizeBulkNonce' => wp_create_nonce( Optimize_Bulk::NONCE_NAME ), 'cancelBulkOptimizationNonce' => wp_create_nonce( Cancel_Bulk_Optimization::NONCE_NAME ), ] ); wp_set_script_translations( 'image-optimization-admin', 'image-optimization' ); } private function should_render(): bool { return ( Utils::is_media_page() || Utils::is_plugin_page() ) && Utils::user_is_admin(); } /** * Module constructor. */ public function __construct() { $this->register_components(); add_action( 'action_scheduler_init', [ Migration_Manager::class, 'init' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_global_assets' ] ); add_filter( 'plugin_action_links', [ $this, 'add_plugin_links' ], 10, 2 ); add_action('current_screen', function () { if ( ! $this->should_render() ) { return; } add_action( 'admin_notices', [ $this, 'maybe_add_quota_reached_notice' ] ); add_action( 'admin_notices', [ $this, 'maybe_add_url_mismatch_notice' ] ); if ( Utils::is_media_page() ) { add_action('in_admin_header', function () { $this->render_top_bar(); }); add_action('all_admin_notices', function () { $this->render_app(); }); } if ( Utils::is_plugin_page() ) { add_action('in_admin_header', function () { $this->render_top_bar(); }); } add_action('admin_enqueue_scripts', function () { $this->enqueue_scripts(); }); }); } } core/components/conflicts.php 0000644 00000005523 14717617543 0012402 0 ustar 00 <?php namespace ImageOptimization\Modules\Core\Components; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Conflicts { private array $conflicting_plugins; private const CONFLICTING_PLUGINS = [ 'imagify/imagify.php' => 'Imagify', 'optimole-wp/optimole-wp.php' => 'Image optimization service by Optimole', 'ewww-image-optimizer/ewww-image-optimizer.php' => 'EWWW Image Optimizer', 'ewww-image-optimizer-cloud/ewww-image-optimizer-cloud.php' => 'EWWW Image Optimizer Cloud', 'kraken-image-optimizer/kraken.php' => 'Kraken Image Optimizer', 'shortpixel-image-optimiser/wp-shortpixel.php' => 'ShortPixel Image Optimizer', 'wp-smushit/wp-smush.php' => 'Smush', 'wp-smush-pro/wp-smush.php' => 'Smush PRO', 'tiny-compress-images/tiny-compress-images.php' => 'TinyPNG - JPEG, PNG & WebP image compression', ]; public function render_notice(): void { $conflicting_plugins_names = $this->conflicting_plugins; ?> <div class="notice notice-warning image-optimizer__notice image-optimizer__notice--warning"> <p> <b> <?php esc_html_e( 'Image optimizer has detected multiple active image optimization plugins.', 'image-optimization' ); ?> </b> <span> <?php esc_html_e( 'Having more than one may result in unexpected results.', 'image-optimization' ); ?> </span> </p> <form action="<?php echo esc_url( admin_url( 'plugins.php' ) ); ?>" method="post" style="margin:0.5em 0;padding:2px"> <span style="margin-inline-end: 8px;"><?php echo esc_html( join( ', ', $conflicting_plugins_names ) ); ?></span> <input type="hidden" name="action" value="deactivate-selected"> <?php foreach ( array_keys( $this->conflicting_plugins ) as $plugin ) { ?> <input type="hidden" name="checked[]" value="<?php echo esc_attr( $plugin ); ?>"> <?php } ?> <input type="hidden" name="_wpnonce" value="<?php echo esc_attr( wp_create_nonce( 'bulk-plugins' ) ); ?>"> <input type="submit" style="border:none;background-color:transparent;text-decoration:underline;cursor:pointer;font-size:13px;color:#135e96;" value="<?php esc_html_e( 'Deactivate All', 'image-optimization' ); ?>"> </form> </div> <?php } public function get_conflicting_plugins(): array { $plugins = get_option( 'active_plugins' ); $conflicting_plugins_file_names = array_keys( self::CONFLICTING_PLUGINS ); $output = []; foreach ( $plugins as $plugin_file_name ) { if ( in_array( $plugin_file_name, $conflicting_plugins_file_names, true ) ) { $output[ $plugin_file_name ] = self::CONFLICTING_PLUGINS[ $plugin_file_name ]; } } return $output; } public function __construct() { $this->conflicting_plugins = $this->get_conflicting_plugins(); if ( ! empty( $this->conflicting_plugins ) ) { add_action( 'admin_notices', [ $this, 'render_notice' ] ); } } } core/components/pointers.php 0000644 00000002625 14717617543 0012261 0 ustar 00 <?php namespace ImageOptimization\Modules\Core\Components; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Pointers { const DISMISSED_POINTERS_META_KEY = 'image_optimizer_dismissed_pointers'; public function dismiss_pointers() { if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'image-optimization-pointer-dismissed' ) ) { wp_send_json_error( [ 'message' => 'Invalid nonce' ] ); } $pointer = sanitize_text_field( $_POST['data']['pointer'] ) ?? null; if ( empty( $pointer ) ) { wp_send_json_error( [ 'message' => 'The pointer id must be provided' ] ); } $pointer = explode( ',', $pointer ); $user_dismissed_meta = get_user_meta( get_current_user_id(), self::DISMISSED_POINTERS_META_KEY, true ); if ( ! $user_dismissed_meta ) { $user_dismissed_meta = []; } foreach ( $pointer as $item ) { $user_dismissed_meta[ $item ] = true; } update_user_meta( get_current_user_id(), self::DISMISSED_POINTERS_META_KEY, $user_dismissed_meta ); wp_send_json_success( [] ); } public static function is_dismissed( string $slug ): bool { $meta = (array) get_user_meta( get_current_user_id(), self::DISMISSED_POINTERS_META_KEY, true ); return key_exists( $slug, $meta ); } public function __construct() { add_action( 'wp_ajax_image_optimizer_pointer_dismissed', [ $this, 'dismiss_pointers' ] ); } } core/components/not-connected.php 0000644 00000007555 14717617543 0013165 0 ustar 00 <?php namespace ImageOptimization\Modules\Core\Components; use ImageOptimization\Classes\Image\Image_Query_Builder; use ImageOptimization\Classes\Utils; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Not_Connected { const NOT_CONNECTED_NOTICE_SLUG = 'image-optimizer-not-connected'; public function render_not_connected_notice() { if ( Pointers::is_dismissed( self::NOT_CONNECTED_NOTICE_SLUG ) ) { return; } ?> <div class="notice notice-info notice is-dismissible image-optimizer__notice image-optimizer__notice--pink" data-notice-slug="<?php echo esc_attr( self::NOT_CONNECTED_NOTICE_SLUG ); ?>"> <div class="image-optimizer__icon-block"> <svg width="32" height="32" fill="none" role="presentation"> <rect width="32" height="32" fill="#FF7BE5" rx="16"/> <path fill="#fff" d="M10.508 4.135a.125.125 0 0 0-.236 0l-1.183 3.42a.125.125 0 0 1-.078.078L5.553 8.8a.125.125 0 0 0 0 .237l3.458 1.166a.125.125 0 0 1 .078.078l1.183 3.42a.125.125 0 0 0 .236 0l1.182-3.42a.125.125 0 0 1 .078-.078l3.458-1.166a.125.125 0 0 0 0-.237l-3.458-1.167a.125.125 0 0 1-.078-.077l-1.182-3.421ZM17.425 12.738v3.683l-4.073 4.582L26.495 9.598a.125.125 0 0 1 .207.094v14.851a.125.125 0 0 1-.125.125H5.874a.125.125 0 0 1-.09-.212l11.427-11.805a.125.125 0 0 1 .214.087Z"/> </svg> </div> <p> <b> <?php esc_html_e( 'Image Optimizer is not connected right now. To start optimizing your images, please connect your account.', 'image-optimization' ); ?> </b> <span> <a href="<?php echo admin_url( 'admin.php?page=' . \ImageOptimization\Modules\Settings\Module::SETTING_BASE_SLUG . '&action=connect' ); ?>"> <?php esc_html_e( 'Connect now', 'image-optimization' ); ?> </a> </span> </p> </div> <script> const onNotConnectedNoticeClose = () => { const pointer = '<?php echo esc_js( self::NOT_CONNECTED_NOTICE_SLUG ); ?>'; return wp.ajax.post( 'image_optimizer_pointer_dismissed', { data: { pointer, }, nonce: '<?php echo esc_js( wp_create_nonce( 'image-optimization-pointer-dismissed' ) ); ?>', } ); } jQuery( document ).ready( function( $ ) { setTimeout(() => { const $closeButton = $( '[data-notice-slug="<?php echo esc_js( self::NOT_CONNECTED_NOTICE_SLUG ); ?>"] .notice-dismiss' ) $closeButton .first() .on( 'click', onNotConnectedNoticeClose ) $( '[data-notice-slug="<?php echo esc_js( self::NOT_CONNECTED_NOTICE_SLUG ); ?>"] a' ) .first() .on( 'click', function ( e ) { e.preventDefault(); onNotConnectedNoticeClose().promise().done(() => { window.open( $( this ).attr( 'href' ), '_blank' ).focus(); $closeButton.click(); }); }) }, 0); } ); </script> <?php } public function add_media_menu_badge( $parent_file ) { global $menu; foreach ( $menu as &$item ) { if ( 'upload.php' === $item[2] ) { $item[0] .= ' <span class="update-plugins count-1"><span class="plugin-count">1</span></span>'; break; } } return $parent_file; } public function __construct() { add_action('current_screen', function () { if ( ! Utils::user_is_admin() ) { return; } // @var ImageOptimizer/Modules/ConnectManager/Module $module = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); if ( $module->connect_instance->is_connected() || ! $module->connect_instance->is_valid_home_url() ) { return; } add_filter( 'parent_file', [ $this, 'add_media_menu_badge' ] ); if ( Utils::is_media_page() || Utils::is_plugin_page() || Utils::is_single_attachment_page() || Utils::is_media_upload_page() || Utils::is_wp_dashboard_page() || Utils::is_wp_updates_page() ) { add_action( 'admin_notices', [ $this, 'render_not_connected_notice' ] ); } }); } } core/components/migrations.php 0000644 00000003003 14717617543 0012561 0 ustar 00 <?php namespace ImageOptimization\Modules\Core\Components; use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Migration\{ Migration_Manager, Migration_Meta, }; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Migrations { /** * @async * @param string $migration_name * * @return bool */ public function run_migration( string $migration_name ): bool { $migration_class = Migration_Manager::get_migration( $migration_name ); if ( ! $migration_class ) { Logger::log( Logger::LEVEL_ERROR, "Migration class for `$migration_name` does not exist." ); return false; } if ( ! method_exists( $migration_class, 'run' ) || ! is_callable( [ $migration_class, 'run' ] ) ) { Logger::log( Logger::LEVEL_ERROR, "The run method does not exist or is not static in the class `$migration_class`." ); return false; } try { $migration_class::run(); ( new Migration_Meta() ) ->add_migration_passed( $migration_name ) ->save(); Logger::log( Logger::LEVEL_INFO, "The migration `$migration_name` successfully executed." ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, "Error while running the migration `$migration_name`: " . $t->getMessage() ); return false; } return true; } public function __construct() { add_action( Async_Operation_Hook::DB_MIGRATION, [ $this, 'run_migration' ] ); } } core/components/user-feedback.php 0000644 00000016701 14717617543 0013116 0 ustar 00 <?php namespace ImageOptimization\Modules\Core\Components; use ImageOptimization\Classes\Image\Image_Query_Builder; use ImageOptimization\Classes\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class User_Feedback { public const NOTICE_FEEDBACK_LINK = 'https://go.elementor.com/io-wp-dash-notice-review'; public const FOOTER_FEEDBACK_LINK = 'https://go.elementor.com/io-wp-dash-footer-review'; private const FIRST_IMAGE_OPTIMIZED_FEEDBACK_SLUG = 'image-optimizer-first-image-optimized-feedback'; private const THIRD_OF_IMAGES_OPTIMIZED_FEEDBACK_SLUG = 'image-optimizer-third-images-optimized-feedback'; /** * Checks if the notice after the first optimization should be rendered. * * @param bool $check_location * * @return bool */ public function should_render_first_optimized_notice( bool $check_location = true ): bool { if ( Pointers::is_dismissed( self::FIRST_IMAGE_OPTIMIZED_FEEDBACK_SLUG ) ) { return false; } $is_wrong_page = ( ! Utils::is_media_page() && ! Utils::is_plugin_page() && ! Utils::is_single_attachment_page() ) || Utils::is_plugin_settings_page(); if ( $check_location && $is_wrong_page ) { return false; } $optimized_image_query = ( new Image_Query_Builder() ) ->return_optimized_images() ->set_paging_size( 1 ) ->execute(); if ( ! $optimized_image_query->post_count ) { return false; } return true; } /** * Checks if the notice should be rendered after the one third of images were optimized. * * @param bool $check_location * * @return bool */ public function should_render_third_optimized_notice( bool $check_location = true ): bool { if ( Pointers::is_dismissed( self::THIRD_OF_IMAGES_OPTIMIZED_FEEDBACK_SLUG ) ) { return false; } if ( $check_location && ! Utils::is_plugin_settings_page() ) { return false; } $images_query = ( new Image_Query_Builder() ) ->execute(); $optimized_images_query = ( new Image_Query_Builder() ) ->return_optimized_images() ->execute(); if ( ! $optimized_images_query->post_count ) { return false; } $optimized_percentage = round( $optimized_images_query->post_count / $images_query->post_count * 100 ); if ( $optimized_percentage < 33.33 ) { return false; } return true; } /** * Renders the notice after the first optimization. * * @return void */ public function render_first_optimized_notice() { ?> <div class="notice is-dismissible notice-success notice image-optimizer__notice image-optimizer__notice--success image-optimizer__notice--feedback" data-notice-slug="<?php echo esc_attr( self::FIRST_IMAGE_OPTIMIZED_FEEDBACK_SLUG ); ?>"> <p> <b> <?php esc_html_e( 'Your image has been optimized!', 'image-optimization' ); ?> </b> <span> <?php printf( __( 'If you enjoyed using Image Optimizer, consider leaving a <a href="%1$s" aria-label="%2$s" target="_blank" rel="noopener noreferrer">★★★★★</a> review to spread the word.', 'image-optimization' ), esc_url( self::NOTICE_FEEDBACK_LINK ), esc_attr__( 'Five stars', 'image-optimization' ) ); ?> </span> </p> </div> <script> const onClose = () => { const pointer = '<?php echo $this->should_render_third_optimized_notice( false ) ? esc_js( join( ',', [ static::FIRST_IMAGE_OPTIMIZED_FEEDBACK_SLUG, static::THIRD_OF_IMAGES_OPTIMIZED_FEEDBACK_SLUG ] ) ) : esc_js( static::FIRST_IMAGE_OPTIMIZED_FEEDBACK_SLUG ) ?>'; return wp.ajax.post( 'image_optimizer_pointer_dismissed', { data: { pointer, }, nonce: '<?php echo esc_js( wp_create_nonce( 'image-optimization-pointer-dismissed' ) ); ?>', } ); } jQuery( document ).ready( function( $ ) { setTimeout(() => { const $closeButton = $( '[data-notice-slug="<?php echo esc_js( self::FIRST_IMAGE_OPTIMIZED_FEEDBACK_SLUG ); ?>"] .notice-dismiss' ) $closeButton .first() .on( 'click', onClose ) $( '[data-notice-slug="<?php echo esc_js( self::FIRST_IMAGE_OPTIMIZED_FEEDBACK_SLUG ); ?>"] a' ) .first() .on( 'click', function ( e ) { e.preventDefault(); onClose().promise().done(() => { window.open( $( this ).attr( 'href' ), '_blank' ).focus(); $closeButton.click(); }); }) }, 0); } ); </script> <?php } /** * Renders the notice after the one third of images were optimized. * * @return void */ public function render_third_optimized_notice() { ?> <div class="notice is-dismissible notice-success notice image-optimizer__notice image-optimizer__notice--success image-optimizer__notice--feedback" data-notice-slug="<?php echo esc_attr( self::THIRD_OF_IMAGES_OPTIMIZED_FEEDBACK_SLUG ); ?>"> <p> <b> <?php esc_html_e( 'Thanks for using Image Optimizer!', 'image-optimization' ); ?> </b> <span> <?php printf( __( 'If you\'ve enjoyed it, consider leaving a <a href="%1$s" aria-label="%2$s" target="_blank" rel="noopener noreferrer">★★★★★</a> review to spread the word.', 'image-optimization' ), esc_url( self::NOTICE_FEEDBACK_LINK ), esc_attr__( 'Five stars', 'image-optimization' ) ); ?> </span> </p> </div> <script> const onClose = () => { const pointer = '<?php echo $this->should_render_first_optimized_notice( false ) ? esc_js( join( ',', [ static::FIRST_IMAGE_OPTIMIZED_FEEDBACK_SLUG, static::THIRD_OF_IMAGES_OPTIMIZED_FEEDBACK_SLUG ] ) ) : esc_js( static::THIRD_OF_IMAGES_OPTIMIZED_FEEDBACK_SLUG ) ?>'; return wp.ajax.post( 'image_optimizer_pointer_dismissed', { data: { pointer, }, nonce: '<?php echo esc_js( wp_create_nonce( 'image-optimization-pointer-dismissed' ) ); ?>', } ); } jQuery( document ).ready( function( $ ) { setTimeout(() => { const $closeButton = $( '[data-notice-slug="<?php echo esc_js( self::THIRD_OF_IMAGES_OPTIMIZED_FEEDBACK_SLUG ); ?>"] .notice-dismiss' ); $closeButton .first() .on( 'click', onClose) $( '[data-notice-slug="<?php echo esc_js( self::THIRD_OF_IMAGES_OPTIMIZED_FEEDBACK_SLUG ); ?>"] a' ) .first() .on( 'click', function ( e ) { e.preventDefault(); onClose().promise().done(() => { window.open( $( this ).attr( 'href' ), '_blank' ).focus(); $closeButton.click(); }); }) }, 0); } ); </script> <?php } /** * Renders the admin footer feedback notice. * * @return void */ public function add_leave_feedback_footer_text(): void { printf( __( '<b>Found Image Optimizer helpful?</b> Leave us a <a href="%1$s" aria-label="%2$s" target="_blank" rel="noopener noreferrer">★★★★★</a> rating!', 'image-optimization' ), esc_url( self::FOOTER_FEEDBACK_LINK ), esc_attr__( 'Five stars', 'image-optimization' ) ); } public function __construct() { add_action('current_screen', function () { if ( ! Utils::user_is_admin() ) { return; } if ( $this->should_render_first_optimized_notice() ) { add_action( 'admin_notices', [ $this, 'render_first_optimized_notice' ] ); } if ( $this->should_render_third_optimized_notice() ) { add_action( 'admin_notices', [ $this, 'render_third_optimized_notice' ] ); } if ( Utils::is_media_page() || Utils::is_plugin_page() || Utils::is_single_attachment_page() ) { add_filter( 'admin_footer_text', [ $this, 'add_leave_feedback_footer_text' ] ); } }); } } connect-manager/classes/connect-runner.php 0000644 00000004375 14717617543 0014743 0 ustar 00 <?php namespace ImageOptimization\Modules\ConnectManager\Classes; use ImageOptimization\Modules\ConnectManager\Components\Connect_Interface; if ( ! defined( 'ABSPATH' ) ) { exit; // exit if accessed directly } class Connect_Runner { public $connect; public function __construct( Connect_Interface $connect ) { $this->connect = $connect; } public function is_connected() { return $this->connect->is_connected(); } public function is_activated() : bool { return $this->connect->is_activated(); } public function is_valid_home_url() : bool { return $this->connect->is_valid_home_url(); } public function get_connect_status() { return $this->connect->get_connect_status(); } public function get_connect_data( bool $force = false ): array { return $this->connect->get_connect_data( $force ); } public function update_usage_data( $new_usage_data ): void { $this->connect->update_usage_data( $new_usage_data ); } public function get_activation_state() : string { return $this->connect->get_activation_state(); } public function get_access_token() { return $this->connect->get_access_token(); } public function get_client_id(): string { return $this->connect->get_client_id(); } public function get_client_secret(): string { return $this->connect->get_client_secret(); } public function images_left(): int { return $this->connect->images_left(); } public function user_is_subscription_owner(): bool { return $this->connect->user_is_subscription_owner(); } // Legacy Nonces. public function connect_init_nonce(): string { return $this->connect->connect_init_nonce(); } public function disconnect_nonce(): string { return $this->connect->disconnect_nonce(); } public function deactivate_nonce(): string { return $this->connect->deactivate_nonce(); } public function get_subscriptions_nonce(): string { return $this->connect->get_subscriptions_nonce(); } public function activate_nonce(): string { return $this->connect->activate_nonce(); } public function version_nonce(): string { return $this->connect->version_nonce(); } public function get_is_connect_on_fly(): bool { return $this->connect->get_is_connect_on_fly(); } public function refresh_token(): void { $this->connect->refresh_token(); } } connect-manager/module.php 0000644 00000003040 14717617543 0011617 0 ustar 00 <?php namespace ImageOptimization\Modules\ConnectManager; use ImageOptimization; use ImageOptimization\Classes\{ Module_Base, }; use ImageOptimization\Modules\ConnectManager\Classes\Connect_Runner; use ImageOptimization\Modules\ConnectManager\Components\Legacy_Connect; use ImageOptimization\Modules\ConnectManager\Components\Connect; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Module */ class Module extends Module_Base { /** * Connect instance * * @var connect_instance */ public $connect_instance; /** * Get module name. * Retrieve the module name. * @access public * @return string Module name. */ public function get_name() { return 'connect-manager'; } /** * component_list * @return string[] */ public static function component_list() : array { return [ 'Legacy_Connect', 'Connect', ]; } public function __construct() { // Register components. $this->register_components(); // Load Connect Manager. add_action( 'plugins_loaded', [ $this, 'load_connect_manager' ] ); } /** * Load Connect Manager * * Load the correct version of Connect Manager based on whether * the user is already connected using legacy version or not. * * @return void */ public function load_connect_manager() { if ( ImageOptimization\Modules\Connect\Module::is_active() ) { $this->connect_instance = new Connect_Runner( new Connect() ); } else { $this->connect_instance = new Connect_Runner( new Legacy_Connect() ); } } } connect-manager/components/connect.php 0000644 00000010110 14717617543 0014144 0 ustar 00 <?php namespace ImageOptimization\Modules\ConnectManager\Components; use ImageOptimization\Modules\Connect\{ Module as ConnectModule, Classes\Data, Classes\Service, Classes\Utils as ConnectUtils, Rest\Authorize, Rest\Version }; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Utils; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // exit if accessed directly } class Connect implements Connect_Interface { const STATUS_CHECK_TRANSIENT = 'image_optimizer_status_check'; public function is_connected() : bool { return ConnectModule::is_connected(); } public function is_activated() : bool { return ConnectModule::is_connected(); } public function is_valid_home_url() : bool { return ConnectUtils::is_valid_home_url(); } public function get_connect_status() { if ( ! $this->is_connected() ) { Logger::log( Logger::LEVEL_INFO, 'Status check error. Reason: User is not connected' ); return null; } $cached_status = get_transient( self::STATUS_CHECK_TRANSIENT ); if ( $cached_status ) { return $cached_status; } try { $response = Utils::get_api_client()->make_request( 'POST', 'status/check' ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Status check error. Reason: ' . $t->getMessage() ); return null; } if ( ! isset( $response->status ) ) { Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' ); return null; } if ( ! empty( $response->site_url ) && Data::get_home_url() !== $response->site_url ) { Data::set_home_url( $response->site_url ); } set_transient( self::STATUS_CHECK_TRANSIENT, $response, MINUTE_IN_SECONDS * 5 ); return $response; } public function get_connect_data( bool $force = false ): array { $data = get_transient( self::STATUS_CHECK_TRANSIENT ); $user = []; // Return empty array if transient does not exist or is expired. if ( ! $data ) { return $user; } // Return if user property does not exist in the data object. if ( ! property_exists( $data, 'user' ) ) { return $user; } if ( $data->user->email ) { $user = [ 'user' => [ 'email' => $data->user->email, ], ]; } return $user; } public function update_usage_data( $new_usage_data ): void { $connect_status = $this->get_connect_status(); if ( ! isset( $new_usage_data->allowed ) || ! isset( $new_usage_data->used ) ) { return; } if ( 0 === $new_usage_data->allowed - $new_usage_data->used ) { $connect_status->status = 'expired'; } $connect_status->quota = $new_usage_data->allowed; $connect_status->used_quota = $new_usage_data->used; set_transient( self::STATUS_CHECK_TRANSIENT, $connect_status, MINUTE_IN_SECONDS * 5 ); } public function get_activation_state() : string { /** * Returning true because the license key is * not used for deactivation in connect. */ return true; } public function get_access_token() { return Data::get_access_token(); } public function get_client_id(): string { return Data::get_client_id(); } public function get_client_secret(): string { return Data::get_client_secret(); } public function images_left(): int { $plan_data = $this->get_connect_status(); if ( empty( $plan_data ) ) { return 0; } $quota = $plan_data->quota; $used_quota = $plan_data->used_quota; return max( $quota - $used_quota, 0 ); } public function user_is_subscription_owner(): bool { return Data::user_is_subscription_owner(); } // Nonces. public function connect_init_nonce(): string { return Authorize::NONCE_NAME; } public function disconnect_nonce(): string { return Authorize::NONCE_NAME; } public function deactivate_nonce(): string { return Authorize::NONCE_NAME; } public function get_subscriptions_nonce(): string { return Authorize::NONCE_NAME; } public function activate_nonce(): string { return Authorize::NONCE_NAME; } public function version_nonce(): string { return Version::NONCE_NAME; } public function get_is_connect_on_fly(): bool { return true; } public function refresh_token(): void { Service::refresh_token(); } } connect-manager/components/legacy-connect.php 0000644 00000004105 14717617543 0015415 0 ustar 00 <?php namespace ImageOptimization\Modules\ConnectManager\Components; use ImageOptimization\Modules\Oauth\{ Components\Connect, Classes\Data, Rest\Activate, Rest\Connect_Init, Rest\Deactivate, Rest\Disconnect, Rest\Get_Subscriptions, Rest\Version, }; if ( ! defined( 'ABSPATH' ) ) { exit; // exit if accessed directly } class Legacy_Connect implements Connect_Interface { public function is_connected() : bool { return Connect::is_connected(); } public function is_activated() : bool { return Connect::is_activated(); } public function is_valid_home_url() : bool { return true; } public function get_connect_status() { return Connect::get_connect_status(); } public function get_connect_data( bool $force = false ): array { return Data::get_connect_data( $force ); } public function update_usage_data( $new_usage_data ): void { Connect::update_usage_data( $new_usage_data ); } public function get_activation_state() : string { return Data::get_activation_state(); } public function get_access_token() { return Data::get_access_token(); } public function get_client_id(): string { return Data::get_client_id(); } public function get_client_secret(): string { return Data::get_client_secret(); } public function images_left(): int { return Data::images_left(); } public function user_is_subscription_owner(): bool { return Data::user_is_subscription_owner(); } // Legacy Nonces. public function connect_init_nonce(): string { return Connect_Init::NONCE_NAME; } public function disconnect_nonce(): string { return Disconnect::NONCE_NAME; } public function deactivate_nonce(): string { return Deactivate::NONCE_NAME; } public function get_subscriptions_nonce(): string { return Get_Subscriptions::NONCE_NAME; } public function activate_nonce(): string { return Activate::NONCE_NAME; } public function version_nonce(): string { return Version::NONCE_NAME; } public function get_is_connect_on_fly(): bool { return false; } // Placeholder function for legacy connect. public function refresh_token(): void { } } connect-manager/components/connect-interface.php 0000644 00000002156 14717617543 0016115 0 ustar 00 <?php namespace ImageOptimization\Modules\ConnectManager\Components; if ( ! defined( 'ABSPATH' ) ) { exit; // exit if accessed directly } interface Connect_Interface { // Connect. public function is_connected(): bool; public function is_activated() : bool; public function is_valid_home_url() : bool; public function get_connect_status(); public function get_connect_data( bool $force = false ): array; public function update_usage_data( $new_usage_data ): void; public function get_activation_state() : string; public function get_access_token(); public function get_client_id(): string; public function get_client_secret(): string; public function images_left(): int; public function user_is_subscription_owner(): bool; // Nonces. public function connect_init_nonce(): string; public function disconnect_nonce(): string; public function deactivate_nonce(): string; public function get_subscriptions_nonce(): string; public function activate_nonce(): string; public function version_nonce(): string; public function get_is_connect_on_fly(): bool; public function refresh_token(): void; } stats/classes/route-base.php 0000644 00000001353 14717617543 0012137 0 ustar 00 <?php namespace ImageOptimization\Modules\Stats\Classes; use ImageOptimization\Classes\Route; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Route_Base extends Route { protected $auth = true; protected string $path = ''; public function get_methods(): array { return []; } public function get_endpoint(): string { return 'stats/' . $this->get_path(); } public function get_path(): string { return $this->path; } public function get_name(): string { return ''; } public function get_permission_callback( WP_REST_Request $request ): bool { $valid = $this->permission_callback( $request ); return $valid && user_can( $this->current_user_id, 'manage_options' ); } } stats/classes/stats.php 0000644 00000007472 14717617543 0011237 0 ustar 00 <?php namespace ImageOptimization\Modules\Stats\Classes; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Queries\Image_Optimization_Operation_Query, Queries\Operation_Query, }; use ImageOptimization\Classes\Image\Image_Query_Builder; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Stats { public static function calculate_global_stats(): array { $bulk_optimization_operation_status = self::get_bulk_optimization_status(); $bulk_optimization_operation_id = Async_Operation::OPERATION_STATUS_RUNNING === $bulk_optimization_operation_status ? self::get_bulk_optimization_active_operation_id() : null; return [ 'optimization_stats' => Optimization_Stats::get_image_stats(), 'bulk_optimization_status' => $bulk_optimization_operation_status, 'bulk_optimization_operation_id' => $bulk_optimization_operation_id, 'bulk_restoring_status' => self::get_bulk_restoring_status(), 'bulk_backup_removing_status' => self::get_bulk_backup_removing_status(), 'backups_exist' => self::backups_exist(), ]; } private static function get_bulk_optimization_status(): string { $active_query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::OPTIMIZE_BULK ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING, ] ) ->set_limit( 1 ); $active_operations = Async_Operation::get( $active_query ); if ( empty( $active_operations ) ) { return Async_Operation::OPERATION_STATUS_NOT_STARTED; } $operation_id = $active_operations[0]->get_args()['operation_id']; $cancelled_query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::OPTIMIZE_BULK ) ->set_status( Async_Operation::OPERATION_STATUS_CANCELED ) ->set_bulk_operation_id( $operation_id ) ->set_limit( 1 ); $cancelled_operations = Async_Operation::get( $cancelled_query ); if ( ! empty( $cancelled_operations ) ) { return Async_Operation::OPERATION_STATUS_CANCELED; } return Async_Operation::OPERATION_STATUS_RUNNING; } private static function get_bulk_optimization_active_operation_id(): ?string { $active_query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::OPTIMIZE_BULK ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING, ] ) ->set_limit( 1 ); $active_operation = Async_Operation::get( $active_query ); if ( empty( $active_operation ) ) { return null; } return $active_operation[0]->get_args()['operation_id']; } private static function get_bulk_restoring_status(): string { $active_query = ( new Operation_Query() ) ->set_hook( Async_Operation_Hook::RESTORE_MANY_IMAGES ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING, ] ) ->set_limit( 1 ); $active_operations = Async_Operation::get( $active_query ); return ! empty( $active_operations ) ? Async_Operation::OPERATION_STATUS_RUNNING : Async_Operation::OPERATION_STATUS_NOT_STARTED; } private static function get_bulk_backup_removing_status(): string { $active_query = ( new Operation_Query() ) ->set_hook( Async_Operation_Hook::REMOVE_MANY_BACKUPS ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING, ] ) ->set_limit( 1 ); $active_operations = Async_Operation::get( $active_query ); return ! empty( $active_operations ) ? Async_Operation::OPERATION_STATUS_RUNNING : Async_Operation::OPERATION_STATUS_NOT_STARTED; } private static function backups_exist(): bool { $query = ( new Image_Query_Builder() ) ->set_paging_size( 1 ) ->return_images_only_with_backups() ->execute(); return $query->post_count > 0; } } stats/classes/optimization-stats.php 0000644 00000015337 14717617543 0013762 0 ustar 00 <?php namespace ImageOptimization\Modules\Stats\Classes; use ImageOptimization\Classes\Image\{ Image, Image_Meta, Image_Query_Builder, WP_Image_Meta, Exceptions\Invalid_Image_Exception }; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Async_Operation_Queue, Exceptions\Async_Operation_Exception, Queries\Operation_Query, }; use ImageOptimization\Classes\File_System\{ Exceptions\File_System_Operation_Error, File_System, }; use ImageOptimization\Classes\Logger; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_Validation_Error; use ImageOptimization\Modules\Optimization\Classes\Validate_Image; use ImageOptimization\Modules\Settings\Classes\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimization_Stats { const PAGING_SIZE = 25000; const STATS_CALCULATION_DELAY = 60 * 15; /** * Returns image stats. * If the library is too big, it queries images in chunks. * * @return array{total_image_count: int, optimized_image_count: int, current_image_size: int, initial_image_size: * int} */ public static function get_image_stats( ?int $image_id = null, ?bool $no_cache = false ): array { // No caching needed if we check a specific image. if ( $image_id ) { $output = self::get_image_stats_chunk( 1, $image_id ); unset( $output['pages'] ); return $output; } // Look at the stored data first. If it's still valid -- use it. $stats = self::get_stored_stats(); if ( ! $no_cache && ( time() - $stats['updated_at'] ) <= self::STATS_CALCULATION_DELAY ) { unset( $stats['updated_at'] ); return $stats; } // Otherwise, recalculate the stats. $output = self::get_image_stats_chunk( 1, $image_id ); $pages_count = $output['pages']; if ( $pages_count <= 1 ) { unset( $output['pages'] ); return $output; } if ( $pages_count > 1 && Async_Operation::OPERATION_STATUS_NOT_STARTED === self::get_stats_calculation_status() ) { try { Async_Operation::create( Async_Operation_Hook::CALCULATE_OPTIMIZATION_STATS, [ 'page' => 2, 'pages_count' => $pages_count, 'output' => $output, ], Async_Operation_Queue::STATS ); } catch ( Async_Operation_Exception $aoe ) { Logger::log( Logger::LEVEL_ERROR, 'Error while creating a stats calculation task: ' . $aoe->getMessage() ); } } unset( $stats['updated_at'] ); return $stats; } /** * @return array{pages: int, total_image_count: int, optimized_image_count: int, current_image_size: int, * initial_image_size: int} */ public static function get_image_stats_chunk( int $paged = 1, ?int $image_id = null ): array { $output = [ 'pages' => 1, 'total_image_count' => 0, 'optimized_image_count' => 0, 'current_image_size' => 0, 'initial_image_size' => 0, ]; $query = ( new Image_Query_Builder() ) ->set_paging_size( self::PAGING_SIZE ) ->set_current_page( $paged ); if ( $image_id ) { $query->set_image_ids( [ $image_id ] ); } $query = $query->execute(); $output['pages'] = (int) $query->max_num_pages; foreach ( $query->posts as $attachment_id ) { try { Validate_Image::is_valid( $attachment_id ); $wp_meta = new WP_Image_Meta( $attachment_id ); } catch ( Invalid_Image_Exception | Image_Validation_Error $ie ) { continue; } $meta = new Image_Meta( $attachment_id ); $image_sizes = $wp_meta->get_size_keys(); $current_sizes = self::filter_only_enabled_sizes( $image_sizes ); $optimized_sizes = self::filter_only_enabled_sizes( $meta->get_optimized_sizes() ); $output['total_image_count'] += count( $current_sizes ); $output['optimized_image_count'] += count( $optimized_sizes ); foreach ( $image_sizes as $image_size ) { $output['current_image_size'] += self::calculate_current_image_file_size( $attachment_id, $wp_meta, $image_size ); $output['initial_image_size'] += self::calculate_initial_image_file_size( $attachment_id, $meta, $wp_meta, $image_size ); } } return $output; } private static function calculate_current_image_file_size( int $image_id, WP_Image_Meta $wp_meta, string $image_size ): int { $size_from_meta = $wp_meta->get_file_size( $image_size ); if ( $size_from_meta ) { return $size_from_meta; } try { return File_System::size( ( new Image( $image_id ) )->get_file_path( $image_size ) ); } catch ( File_System_Operation_Error $e ) { return 0; } } private static function calculate_initial_image_file_size( int $image_id, Image_Meta $meta, WP_Image_Meta $wp_meta, string $image_size ): int { $size_from_meta = $meta->get_original_file_size( $image_size ) ?? $wp_meta->get_file_size( $image_size ); if ( $size_from_meta ) { return $size_from_meta; } try { return File_System::size( ( new Image( $image_id ) )->get_file_path( $image_size ) ); } catch ( File_System_Operation_Error $e ) { return 0; } } private static function filter_only_enabled_sizes( array $size_keys ): array { $enabled_sizes = Settings::get( Settings::CUSTOM_SIZES_OPTION_NAME ); if ( 'all' === $enabled_sizes ) { return array_filter( $size_keys, fn( string $size_key ) => ! str_starts_with( $size_key, 'elementor_' ) ); } return array_filter($size_keys, function( string $size ) use ( $enabled_sizes ) { if ( Image::SIZE_FULL === $size ) { return true; } if ( in_array( $size, $enabled_sizes, true ) ) { return true; } return false; }); } /** * Retrieves stats data. * * @return array{total_image_count: ?int, optimized_image_count: ?int, current_image_size: ?int, initial_image_size: * ?int, updated_at: ?int} */ public static function get_stored_stats(): array { $default = [ 'total_image_count' => null, 'optimized_image_count' => null, 'current_image_size' => null, 'initial_image_size' => null, 'updated_at' => null, ]; return json_decode( get_option( 'image_optimizer_optimization_stats', json_encode( $default ) ), ARRAY_A ); } /** * Updates the optimization stats with fresh values. * * @param array $stats * * @return bool */ public static function set_stored_stats( array $stats ) { $stats['updated_at'] = time(); return update_option( 'image_optimizer_optimization_stats', json_encode( $stats ) ); } private static function get_stats_calculation_status(): string { $active_query = ( new Operation_Query() ) ->set_hook( Async_Operation_Hook::CALCULATE_OPTIMIZATION_STATS ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING, ] ) ->set_limit( 1 ); $active_operations = Async_Operation::get( $active_query ); return ! empty( $active_operations ) ? Async_Operation::OPERATION_STATUS_RUNNING : Async_Operation::OPERATION_STATUS_NOT_STARTED; } } stats/rest/get-stats.php 0000644 00000001241 14717617543 0011320 0 ustar 00 <?php namespace ImageOptimization\Modules\Stats\Rest; use ImageOptimization\Modules\Stats\Classes\{ Route_Base, Stats }; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Get_Stats extends Route_Base { protected string $path = ''; public function get_name(): string { return 'get-stats'; } public function get_methods(): array { return [ 'GET' ]; } public function GET() { try { return $this->respond_success_json( Stats::calculate_global_stats() ); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } stats/module.php 0000644 00000001103 14717617543 0007712 0 ustar 00 <?php namespace ImageOptimization\Modules\Stats; use ImageOptimization\Classes\Module_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Module_Base { public function get_name(): string { return 'stats'; } public static function component_list(): array { return [ 'Optimization_Stats_Handler', ]; } public static function routes_list() : array { return [ 'Get_Stats', ]; } /** * Module constructor. */ public function __construct() { $this->register_routes(); $this->register_components(); } } stats/components/optimization-stats-handler.php 0000644 00000003001 14717617543 0016106 0 ustar 00 <?php namespace ImageOptimization\Modules\Stats\Components; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Async_Operation_Queue, Exceptions\Async_Operation_Exception, }; use ImageOptimization\Classes\Logger; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimization_Stats_Handler { /** * @async * * @param $page * @param $pages_count * @param $output * * @return void */ public function handle_stats_chunk( $page, $pages_count, $output ) { $chunk = Optimization_Stats::get_image_stats_chunk( $page ); foreach ( array_keys( $chunk ) as $key ) { if ( isset( $output[ $key ] ) ) { $output[ $key ] += $chunk[ $key ]; continue; } $output[ $key ] = $chunk[ $key ]; } if ( $page < $pages_count ) { try { Async_Operation::create( Async_Operation_Hook::CALCULATE_OPTIMIZATION_STATS, [ 'page' => $page + 1, 'pages_count' => $pages_count, 'output' => $output, ], Async_Operation_Queue::STATS ); } catch ( Async_Operation_Exception $aoe ) { Logger::log( Logger::LEVEL_ERROR, 'Error while creating a stats calculation task: ' . $aoe->getMessage() ); } } else { unset( $output['pages'] ); Optimization_Stats::set_stored_stats( $output ); } } public function __construct() { add_action( Async_Operation_Hook::CALCULATE_OPTIMIZATION_STATS, [ $this, 'handle_stats_chunk' ], 10, 3 ); } } optimization/classes/route-base.php 0000644 00000001574 14717617543 0013534 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Route; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Route_Base extends Route { protected $auth = true; protected string $path = ''; public function get_methods(): array { return []; } public function get_endpoint(): string { return 'optimize/' . $this->get_path(); } public function get_path(): string { return $this->path; } public function get_name(): string { return ''; } public function post_permission_callback( WP_REST_Request $request ): bool { return $this->get_permission_callback( $request ); } public function get_permission_callback( WP_REST_Request $request ): bool { $valid = $this->permission_callback( $request ); return $valid && user_can( $this->current_user_id, 'manage_options' ); } } optimization/classes/optimize-image.php 0000644 00000030626 14717617543 0014406 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\File_System\{ Exceptions\File_System_Operation_Error, File_System, }; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Classes\Image\{ Exceptions\Image_Backup_Creation_Error, Exceptions\Invalid_Image_Exception, Image, Image_Backup, Image_Conversion, Image_DB_Update, Image_Dimensions, Image_Meta, Image_Status, WP_Image_Meta }; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Utils; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\Classes\{ Exceptions\Image_File_Already_Exists_Error, Exceptions\Image_Optimization_Error, Exceptions\Bulk_Token_Expired_Error, Exceptions\Image_Already_Optimized_Error, }; use ImageOptimization\Modules\Settings\Classes\Settings; use Throwable; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * The class is responsible for the optimization logic itself. It gets an image file, runs * backup process if needed, sends a file to the service, stores the result and updates image meta. * * This class is used by manual, bulk and on-upload optimization flows. */ class Optimize_Image { private const IMAGE_OPTIMIZE_ENDPOINT = 'image/optimize'; protected ?Image $image; protected WP_Image_Meta $wp_meta; protected string $initiator; protected ?string $bulk_token; private string $current_image_path; private string $current_image_size; private bool $keep_backups; private array $current_size_duplicates; protected Image_Conversion $image_conversion; private bool $is_reoptimize; /** * @throws Quota_Exceeded_Error|Bulk_Token_Expired_Error|Image_File_Already_Exists_Error|Image_Optimization_Error */ public function optimize(): void { $sizes_enabled = Settings::get( Settings::CUSTOM_SIZES_OPTION_NAME ); $sizes_exist = $this->wp_meta->get_size_keys(); Logger::log( Logger::LEVEL_INFO, "Start optimization of {$this->image->get_id()}" ); foreach ( $sizes_exist as $size_exist ) { // If some image sizes optimization is disabled in settings, we check if the current one is still enabled if ( 'all' !== $sizes_enabled && Image::SIZE_FULL !== $size_exist && ! in_array( $size_exist, $sizes_enabled, true ) ) { continue; } // Elementor editor generates thumbnails we don't need to optimize. if ( str_starts_with( $size_exist, 'elementor_' ) ) { continue; } $image_meta = new Image_Meta( $this->image->get_id() ); // If the current size was already optimized -- ignore it. if ( ! $this->is_reoptimize && in_array( $size_exist, $image_meta->get_optimized_sizes(), true ) ) { Logger::log( Logger::LEVEL_INFO, "Size `$size_exist` is already optimized" ); continue; } if ( ! file_exists( $this->image->get_file_path( $size_exist ) ) ) { Logger::log( Logger::LEVEL_ERROR, "Can't access file for size `$size_exist`" ); throw new Image_Optimization_Error( esc_html__( 'File is missing. Verify the upload', 'image-optimization' ) ); } $this->current_image_size = $size_exist; $this->current_size_duplicates = $this->wp_meta->get_size_duplicates( $size_exist ); $this->current_image_path = $this->image->get_file_path( $size_exist ); $this->optimize_current_size(); $this->current_image_size = ''; $this->current_size_duplicates = []; $this->current_image_path = ''; } $this->mark_as_optimized(); if ( ! $this->keep_backups ) { Image_Backup::remove( $this->image->get_id() ); } Logger::log( Logger::LEVEL_INFO, "End optimization of {$this->image->get_id()}" ); } private function optimize_current_size(): void { try { $original_path = $this->current_image_path; $response = $this->send_file(); $optimized_size = (int) $response->optimizedSize; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $optimized_image_binary = base64_decode( $response->image, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $this->replace_image_file( $optimized_image_binary ); $this->update_attachment_meta( $optimized_size ); if ( $original_path !== $this->current_image_path ) { $this->update_posts( $original_path, $this->current_image_path ); } // This should only be updated after meta $this->update_attachment_post(); } catch ( Image_Already_Optimized_Error $iao ) { // If we can't optimize it further, just update the meta $original_size = $this->wp_meta->get_file_size( $this->current_image_size ) ?? File_System::size( $this->image->get_file_path( $this->current_image_size ) ); $this->update_attachment_meta( $original_size, true ); $this->update_attachment_post( true ); } catch ( Bulk_Token_Expired_Error | Quota_Exceeded_Error | Image_File_Already_Exists_Error $e ) { throw $e; } catch ( Throwable $t ) { // In case of anything else throw new Image_Optimization_Error( $t->getMessage() ); } } private function send_file() { $connect_status = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->get_connect_status(); $headers = [ 'access_token' => $connect_status->access_token ?? '', ]; if ( $this->bulk_token ) { $headers['x-elementor-bulk-token'] = $this->bulk_token; } $optimization_options = [ 'compression_level' => Settings::get( Settings::COMPRESSION_LEVEL_OPTION_NAME ), 'convert_to_format' => $this->image_conversion->get_current_conversion_option(), 'strip_exif' => Settings::get( Settings::STRIP_EXIF_METADATA_OPTION_NAME ), ]; if ( Settings::get( Settings::RESIZE_LARGER_IMAGES_OPTION_NAME ) ) { $optimization_options['resize'] = Settings::get( Settings::RESIZE_LARGER_IMAGES_SIZE_OPTION_NAME ); } $image_key = wp_generate_password( 15, false ); $response = Utils::get_api_client()->make_request( 'POST', self::IMAGE_OPTIMIZE_ENDPOINT, [ 'initiator' => $this->initiator, 'image_url' => $this->image->get_url( $this->current_image_size ), 'image_key' => $image_key, 'checksum' => md5_file( $this->current_image_path ), 'attachment_id' => $this->image->get_id(), 'attachment_parent_id' => Image::SIZE_FULL === $this->current_image_size ? 0 : $this->image->get_id(), 'image_optimization_settings' => base64_encode( wp_json_encode( $optimization_options ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode ], $headers, $this->current_image_path, 'image' ); if ( isset( $response->stats ) ) { Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->update_usage_data( $response->stats ); } if ( ! isset( $response->imageKey ) || $image_key !== $response->imageKey ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase Logger::log( Logger::LEVEL_ERROR, "Image key must be $image_key, instead got $response->imageKey" ); throw new Image_Optimization_Error( esc_html__( 'Service response is incorrect', 'image-optimization' ) ); } $received_file_hash = md5( base64_decode( $response->image, true ) ); if ( ! isset( $response->checksum ) || $received_file_hash !== $response->checksum ) { Logger::log( Logger::LEVEL_ERROR, "Image key must be $response->checksum, instead calculated $received_file_hash" ); throw new Image_Optimization_Error( esc_html__( 'Service response is incorrect', 'image-optimization' ) ); } return $response; } /** * @throws File_System_Operation_Error|Image_Backup_Creation_Error|Image_File_Already_Exists_Error */ private function replace_image_file( string $file_data ): void { $path = $this->current_image_path; // If we have backups disabled, we want to make sure we can download and save new file before we // remove an existing one. $tmp_path = File_Utils::replace_extension( $path, 'tmp' ); File_System::put_contents( $tmp_path, $file_data ); if ( $this->image_conversion->is_enabled() ) { $converted_path = File_Utils::replace_extension( $tmp_path, $this->image_conversion->get_current_file_extension() ); $original_is_already_converted = strtolower( $path ) === strtolower( $converted_path ); if ( ! $original_is_already_converted && File_System::exists( $converted_path ) ) { throw new Image_File_Already_Exists_Error(); } // We want to keep both original as a fallback and optimized webp to prevent 404s if ( ! $original_is_already_converted ) { $this->keep_backups = true; ( new Image_Meta( $this->image->get_id() ) ) ->set_image_backup_path( $this->current_image_size, $this->current_image_path ) ->save(); } if ( $original_is_already_converted ) { Image_Backup::create( $this->image->get_id(), $this->current_image_size, $this->current_image_path ); } File_System::move( $tmp_path, $converted_path, true ); $path = $converted_path; } else { Image_Backup::create( $this->image->get_id(), $this->current_image_size, $this->current_image_path ); File_System::move( $tmp_path, $path, true ); } if ( Image::SIZE_FULL === $this->current_image_size ) { // Drop WP caches update_attached_file( $this->image->get_id(), $path ); } // Updating to the correct value $this->current_image_path = $path; } /** * Updates attachment records in the `wp_posts` table. * * @return void */ private function update_attachment_post( ?bool $is_fully_optimized = false ) { $update_query = []; if ( $this->image_conversion->is_enabled() && ! $is_fully_optimized ) { $attachment_object = $this->image->get_attachment_object(); $update_query['guid'] = File_Utils::replace_extension( $attachment_object->guid, $this->image_conversion->get_current_file_extension() ); $update_query['post_mime_type'] = $this->image_conversion->get_current_mime_type(); } $update_query['post_modified'] = current_time( 'mysql' ); $update_query['post_modified_gmt'] = current_time( 'mysql', true ); $this->image->update_attachment( $update_query ); } /** * Updates attachment records in the `wp_postmeta` table. * * @param int $optimized_size * @param bool|null $is_fully_optimized * * @return void */ private function update_attachment_meta( int $optimized_size, ?bool $is_fully_optimized = false ) { $meta = new Image_Meta( $this->image->get_id() ); $dimensions = Image_Dimensions::get_by_path( $this->current_image_path ); $sizes_to_update = [ $this->current_image_size, ...$this->current_size_duplicates ]; foreach ( $sizes_to_update as $size ) { $meta ->set_compression_level( Settings::get( Settings::COMPRESSION_LEVEL_OPTION_NAME ) ) ->add_optimized_size( $size ) ->add_original_data( $size, $this->wp_meta->get_size_data( $size ) ); $this->wp_meta ->set_width( $size, $dimensions->width ) ->set_height( $size, $dimensions->height ) ->set_file_size( $size, $optimized_size ); if ( $this->image_conversion->is_enabled() && ! $is_fully_optimized ) { $this->wp_meta ->set_file_path( $size, $this->current_image_path ) ->set_mime_type( $size, $this->image_conversion->get_current_mime_type() ); } } $meta->save(); $this->wp_meta->save(); } /** * If we change an image extension, we should walk through the wp_posts table and update all the * hardcoded image links to prevent 404s. * * @param string $old_path Previous image path * @param string $new_path Current image path * * @return void */ private function update_posts( string $old_path, string $new_path ) { Image_DB_Update::update_posts_table_urls( File_Utils::get_url_from_path( $old_path ), File_Utils::get_url_from_path( $new_path ) ); } /** * Changes image status after all image sizes were optimized. * * @return void */ private function mark_as_optimized() { ( new Image_Meta( $this->image->get_id() ) ) ->set_status( Image_Status::OPTIMIZED ) ->set_error_type( null ) ->save(); } /** * @throws Invalid_Image_Exception */ public function __construct( int $image_id, string $initiator, ?string $bulk_token = null, ?bool $is_reoptimize = false ) { $this->image = new Image( $image_id ); $this->wp_meta = new WP_Image_Meta( $image_id, $this->image ); $this->initiator = $initiator; $this->bulk_token = $bulk_token; $this->is_reoptimize = $is_reoptimize; $this->image_conversion = new Image_Conversion(); $this->keep_backups = Settings::get( Settings::BACKUP_ORIGINAL_IMAGES_OPTION_NAME ); } } optimization/classes/optimization-status.php 0000644 00000003470 14717617543 0015532 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Image\{ Image_Meta, Image_Optimization_Error_Type, Image_Status }; use ImageOptimization\Modules\Oauth\Classes\Data; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimization_Status { /** * * * @param int[] $image_ids * @return array */ public static function get_images_optimization_statuses( array $image_ids ): array { $output = []; $images_left = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->images_left(); foreach ( $image_ids as $image_id ) { $meta = new Image_Meta( $image_id ); $data = []; $status = $meta->get_status(); $data['status'] = $status; if ( Image_Status::OPTIMIZED === $status ) { $data['stats'] = Optimization_Stats::get_image_stats( $image_id ); } if ( Image_Status::RESTORING_FAILED === $status ) { $data['message'] = esc_html__( 'Image restoring failed', 'image-optimization' ); } if ( Image_Status::OPTIMIZATION_FAILED === $status ) { $error_type = $meta->get_error_type() ?? Image_Optimization_Error_Type::GENERIC; $error_message = Optimization_Error_Message::get_optimization_error_message( $error_type ); $data['message'] = $error_message; $data['images_left'] = $images_left; } if ( Image_Status::REOPTIMIZING_FAILED === $status ) { $error_type = $meta->get_error_type() ?? Image_Optimization_Error_Type::GENERIC; $error_message = Optimization_Error_Message::get_reoptimization_error_message( $error_type ); $data['message'] = $error_message; $data['images_left'] = $images_left; } $output[ $image_id ] = $data; } return $output; } } optimization/classes/exceptions/image-already-optimized-error.php 0000644 00000000571 14717617543 0021475 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * The service responses with the error when the optimized image size >= than the original size. */ class Image_Already_Optimized_Error extends Exception { protected $message = 'Image already optimized'; } optimization/classes/exceptions/bulk-token-obtaining-error.php 0000644 00000000420 14717617543 0021006 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Token_Obtaining_Error extends Exception { protected $message = 'Bulk token obtaining error'; } optimization/classes/exceptions/image-optimization-error.php 0000644 00000000414 14717617543 0020574 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Optimization_Error extends Exception { protected $message = 'Image optimization error'; } optimization/classes/exceptions/image-validation-error.php 0000644 00000000410 14717617543 0020174 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Validation_Error extends Exception { protected $message = 'Image validation error'; } optimization/classes/exceptions/image-optimization-already-in-progress-error.php 0000644 00000000464 14717617543 0024466 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Optimization_Already_In_Progress_Error extends Exception { protected $message = 'Image optimization already in progress error'; } optimization/classes/exceptions/bulk-token-expired-error.php 0000644 00000000414 14717617543 0020477 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Token_Expired_Error extends Exception { protected $message = 'Bulk token expired error'; } optimization/classes/exceptions/image-file-already-exists-error.php 0000644 00000000443 14717617543 0021723 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_File_Already_Exists_Error extends Exception { protected $message = 'Image file with this name already exists'; } optimization/classes/exceptions/image-optimization-retryable-error.php 0000644 00000000427 14717617543 0022567 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Optimization_Retryable_Error extends Exception { protected $message = 'Image optimization error'; } optimization/classes/bulk-optimization-controller.php 0000644 00000031335 14717617543 0017326 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Async_Operation_Queue, Exceptions\Async_Operation_Exception, Queries\Image_Optimization_Operation_Query }; use ImageOptimization\Classes\Image\{ Exceptions\Invalid_Image_Exception, Image, Image_Meta, Image_Optimization_Error_Type, Image_Query_Builder, Image_Status, WP_Image_Meta }; use ImageOptimization\Classes\File_System\Exceptions\File_System_Operation_Error; use ImageOptimization\Classes\File_System\File_System; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Utils; use ImageOptimization\Modules\Oauth\Classes\Data; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Bulk_Token_Obtaining_Error; use ImageOptimization\Modules\Optimization\Components\Exceptions\Bulk_Optimization_Token_Not_Found_Error; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; use ImageOptimization\Plugin; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Optimization_Controller { private const OBTAIN_TOKEN_ENDPOINT = 'image/bulk-token'; public static function reschedule_bulk_optimization() { self::cancel_bulk_optimization(); self::find_images_and_schedule_optimization(); } public static function reschedule_bulk_reoptimization() { self::cancel_bulk_reoptimization(); self::find_optimized_images_and_schedule_reoptimization(); } /** * Cancels pending bulk optimization operations. * * @return void * @throws Async_Operation_Exception */ public static function cancel_bulk_optimization(): void { $query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::OPTIMIZE_BULK ) // It's risky to cancel in-progress operations at that point, so we cancel only the pending ones. ->set_status( Async_Operation::OPERATION_STATUS_PENDING ) ->set_limit( -1 ); $operations = Async_Operation::get( $query ); foreach ( $operations as $operation ) { $image_id = $operation->get_args()['attachment_id']; Async_Operation::cancel( $operation->get_id() ); ( new Image_Meta( $image_id ) )->delete(); } } /** * Cancels pending bulk re-optimization operations. * * @return void * @throws Async_Operation_Exception */ public static function cancel_bulk_reoptimization(): void { $query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::REOPTIMIZE_BULK ) // It's risky to cancel in-progress operations at that point, so we cancel only the pending ones. ->set_status( Async_Operation::OPERATION_STATUS_PENDING ) ->set_limit( -1 ); $operations = Async_Operation::get( $query ); foreach ( $operations as $operation ) { $image_id = $operation->get_args()['attachment_id']; Async_Operation::cancel( $operation->get_id() ); ( new Image_Meta( $image_id ) )->delete(); } } /** * Looks for all non-optimized images and creates a bulk operation for each of them. * Also, obtains bulk token and passes it to a newly created operation. * * @return void * * @throws Quota_Exceeded_Error|Invalid_Image_Exception */ public static function find_images_and_schedule_optimization(): void { $images = self::find_images( ( new Image_Query_Builder() ) ->return_not_optimized_images(), true ); if ( ! $images['total_images_count'] ) { return; } $operation_id = wp_generate_password( 10, false ); try { $bulk_token = self::obtain_bulk_token( $images['total_images_count'] ); self::set_bulk_operation_token( $operation_id, $bulk_token ); } catch ( Bulk_Token_Obtaining_Error $e ) { $bulk_token = null; } foreach ( $images['attachments_in_quota'] as $attachment_id ) { $meta = new Image_Meta( $attachment_id ); if ( null === $bulk_token ) { $meta ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->save(); continue; } try { Async_Operation::create( Async_Operation_Hook::OPTIMIZE_BULK, [ 'attachment_id' => $attachment_id, 'operation_id' => $operation_id, ], Async_Operation_Queue::OPTIMIZE ); $meta ->set_status( Image_Status::OPTIMIZATION_IN_PROGRESS ) ->save(); } catch ( Async_Operation_Exception $aoe ) { $meta ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->save(); continue; } } } /** * Looks for already optimized images with backups and creates a bulk operation for each of them. * Also, obtains bulk token and passes it to a newly created operation. * * @return void * * @throws Quota_Exceeded_Error|Invalid_Image_Exception */ public static function find_optimized_images_and_schedule_reoptimization(): void { $images = self::find_images( ( new Image_Query_Builder() ) ->return_optimized_images() ); if ( ! $images['total_images_count'] ) { return; } $operation_id = wp_generate_password( 10, false ); try { $bulk_token = self::obtain_bulk_token( $images['total_images_count'] ); self::set_bulk_operation_token( $operation_id, $bulk_token ); } catch ( Bulk_Token_Obtaining_Error $e ) { $bulk_token = null; } foreach ( $images['attachments_in_quota'] as $attachment_id ) { $meta = new Image_Meta( $attachment_id ); if ( null === $bulk_token ) { $meta ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->save(); continue; } try { Async_Operation::create( Async_Operation_Hook::REOPTIMIZE_BULK, [ 'attachment_id' => $attachment_id, 'operation_id' => $operation_id, ], Async_Operation_Queue::OPTIMIZE ); $meta ->set_status( Image_Status::REOPTIMIZING_IN_PROGRESS ) ->save(); } catch ( Async_Operation_Exception $aoe ) { $meta ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->save(); continue; } } foreach ( $images['attachments_out_of_quota'] as $attachment_id ) { ( new Image_Meta( $attachment_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } } /** * Looks for images for bulk optimization operations based on a query passed and the quota left. * * @param Image_Query_Builder $query Image query to execute. * @param bool $limit_to_quota If true, it limits image query to the quota left. * @return array{total_images_count: int, attachments_in_quota: array, attachments_out_of_quota: array} * * @throws Invalid_Image_Exception * @throws Quota_Exceeded_Error */ private static function find_images( Image_Query_Builder $query, bool $limit_to_quota = false ): array { $output = [ 'total_images_count' => 0, 'attachments_in_quota' => [], 'attachments_out_of_quota' => [], ]; $images_left = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->images_left(); if ( ! $images_left ) { throw new Quota_Exceeded_Error( __( 'Images quota exceeded', 'image-optimization' ) ); } if ( $limit_to_quota ) { $query->set_paging_size( $images_left ); } $wp_query = $query->execute(); if ( ! $wp_query->post_count ) { return $output; } foreach ( $wp_query->posts as $attachment_id ) { try { Validate_Image::is_valid( $attachment_id ); $wp_meta = new WP_Image_Meta( $attachment_id ); } catch ( Invalid_Image_Exception | Exceptions\Image_Validation_Error $ie ) { continue; } $sizes_count = count( $wp_meta->get_size_keys() ); if ( $output['total_images_count'] + $sizes_count <= $images_left ) { $output['total_images_count'] += $sizes_count; $output['attachments_in_quota'][] = $attachment_id; } else { break; } } $output['attachments_out_of_quota'] = array_diff( $wp_query->posts, $output['attachments_in_quota'] ); return $output; } /** * Looks for the bulk token in transients. * * @param string $operation_id Bulk optimization operation id * * @return string|null Bulk token. * * @throws Bulk_Optimization_Token_Not_Found_Error */ public static function get_bulk_operation_token( string $operation_id ): ?string { $bulk_token = get_transient( "image_optimizer_bulk_token_$operation_id" ); if ( ! $bulk_token ) { throw new Bulk_Optimization_Token_Not_Found_Error( "There is no token found for the operation $operation_id" ); } return $bulk_token; } /** * Saves bulk optimization token to transients for a day. * * @param string $operation_id Bulk optimization operation id * @param string $bulk_token Bulk optimization token * @return void */ public static function set_bulk_operation_token( string $operation_id, string $bulk_token ): void { set_transient( "image_optimizer_bulk_token_$operation_id", $bulk_token, HOUR_IN_SECONDS ); } /** * Sends a request to the BE to obtain bulk optimization token. * It prevents obtaining a token for each and every optimization operation. * * @return string * * @throws Bulk_Token_Obtaining_Error */ private static function obtain_bulk_token( int $images_count ): ?string { try { $response = Utils::get_api_client()->make_request( 'POST', self::OBTAIN_TOKEN_ENDPOINT, [ 'images_count' => $images_count, ] ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Error while sending bulk token request: ' . $t->getMessage() ); throw new Bulk_Token_Obtaining_Error( $t->getMessage() ); } return $response->token ?? null; } /** * Checks if there is a bulk optimization operation in progress. * If there is at least a single active bulk optimization operation it returns true, otherwise false. * * @return bool * @throws Async_Operation_Exception */ public static function is_optimization_in_progress(): bool { $query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::OPTIMIZE_BULK ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING ] ) ->set_limit( 1 ) ->return_ids(); return ! empty( Async_Operation::get( $query ) ); } /** * Checks if there is a bulk re-optimization operation in progress. * If there is at least a single active bulk re-optimization operation it returns true, otherwise false. * * @return bool * @throws Async_Operation_Exception */ public static function is_reoptimization_in_progress(): bool { $query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::REOPTIMIZE_BULK ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING ] ) ->set_limit( 1 ) ->return_ids(); return ! empty( Async_Operation::get( $query ) ); } /** * Retrieves the bulk optimization process status. * * @return array{status: string, stats: array} * @throws Async_Operation_Exception */ public static function get_status(): array { $stats = Optimization_Stats::get_image_stats(); $output = [ 'status' => 'not-started', 'percentage' => round( $stats['optimized_image_count'] / $stats['total_image_count'] * 100 ), ]; $active_query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::OPTIMIZE_BULK ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING ] ) ->set_limit( -1 ); if ( empty( Async_Operation::get( $active_query ) ) ) { return $output; } $output['status'] = 'in-progress'; return $output; } /** * Returns latest operations for the bulk optimization screen. * * @param string|null $operation_id * * @return array * @throws Async_Operation_Exception */ public static function get_processed_images( string $operation_id ): array { $output = []; $query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::OPTIMIZE_BULK ) ->set_bulk_operation_id( $operation_id ) ->set_limit( 50 ); $operations = Async_Operation::get( $query ); foreach ( $operations as $operation ) { $image_id = $operation->get_args()['attachment_id']; $image = new Image( $image_id ); try { $stats = Optimization_Stats::get_image_stats( $image_id ); } catch ( Invalid_Image_Exception $iie ) { continue; } catch ( Throwable $t ) { $original_file_size = 0; $current_file_size = 0; } $output[] = [ 'id' => $operation->get_id(), 'status' => $operation->get_status() === Async_Operation::OPERATION_STATUS_COMPLETE ? ( new Image_Meta( $image_id ) )->get_status() : $operation->get_status(), 'image_name' => $image->get_attachment_object()->post_title, 'image_id' => $image_id, 'thumbnail_url' => $image->get_url( 'thumbnail' ), 'original_file_size' => $stats['initial_image_size'], 'current_file_size' => $stats['current_image_size'], ]; } return $output; } } optimization/classes/single-optimization.php 0000644 00000004556 14717617543 0015476 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Async_Operation_Queue, Exceptions\Async_Operation_Exception, Queries\Image_Optimization_Operation_Query }; use ImageOptimization\Classes\Image\{ Image_Meta, Image_Status, }; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_Optimization_Already_In_Progress_Error; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Single_Optimization { /** * @throws Throwable|Async_Operation_Exception */ public static function optimize_many( array $image_ids, bool $is_reoptimize = false ): void { foreach ( $image_ids as $image_id ) { try { self::schedule_single_optimization( $image_id, $is_reoptimize ); } catch ( Image_Optimization_Already_In_Progress_Error $ioe ) { continue; } catch ( Throwable $t ) { throw $t; } } } /** * @throws Throwable|Async_Operation_Exception|Image_Optimization_Already_In_Progress_Error */ public static function schedule_single_optimization( int $image_id, bool $is_reoptimize = false ): void { if ( self::is_optimization_in_progress( $image_id ) ) { throw new Image_Optimization_Already_In_Progress_Error( esc_html__( 'Optimization is already in progress', 'image-optimization' ) ); } $meta = new Image_Meta( $image_id ); try { $meta ->set_status( $is_reoptimize ? Image_Status::REOPTIMIZING_IN_PROGRESS : Image_Status::OPTIMIZATION_IN_PROGRESS ) ->save(); Async_Operation::create( $is_reoptimize ? Async_Operation_Hook::REOPTIMIZE_SINGLE : Async_Operation_Hook::OPTIMIZE_SINGLE, [ 'attachment_id' => $image_id ], Async_Operation_Queue::OPTIMIZE ); } catch ( Throwable $t ) { $meta ->set_status( $is_reoptimize ? Image_Status::REOPTIMIZING_FAILED : Image_Status::OPTIMIZATION_FAILED ) ->save(); throw $t; } } /** * @throws Async_Operation_Exception */ private static function is_optimization_in_progress( int $image_id ): bool { $query = ( new Image_Optimization_Operation_Query() ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING ] ) ->set_image_id( $image_id ) ->return_ids(); $operations = Async_Operation::get( $query ); return count( $operations ) > 0; } } optimization/classes/validate-image.php 0000644 00000005726 14717617543 0014342 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Image\{ Exceptions\Invalid_Image_Exception, Image, Image_Meta, WP_Image_Meta }; use ImageOptimization\Classes\File_System\Exceptions\File_System_Operation_Error; use ImageOptimization\Classes\File_System\File_System; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_Validation_Error; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Validate_Image { public const MAX_FILE_SIZE = 10 * 1024 * 1024; /** * Returns true if $image_id provided associated with an image that can be optimized. * * @param int $image_id Attachment id. * * @return true * @throws Image_Validation_Error * @throws Invalid_Image_Exception */ public static function is_valid( int $image_id ): bool { $attachment_object = get_post( $image_id ); if ( ! $attachment_object ) { throw new Image_Validation_Error( __( 'Can\'t optimize this file. If the issue persists, Contact Support', 'image-optimization' ) ); } if ( ! wp_attachment_is_image( $attachment_object ) || ! in_array( $attachment_object->post_mime_type, Image::get_supported_mime_types(), true ) || ( in_array( $attachment_object->post_mime_type, Image::get_mime_types_cannot_be_optimized(), true ) && ! get_post_meta( $image_id, Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, true ) ) ) { throw new Image_Validation_Error( self::prepare_supported_formats_list_error() ); } if ( ! file_exists( get_attached_file( $image_id ) ) ) { throw new Image_Validation_Error( esc_html__( 'File is missing. Verify the upload', 'image-optimization' ) ); } $wp_meta = new WP_Image_Meta( $image_id ); try { $image_size = $wp_meta->get_file_size( Image::SIZE_FULL ) ?? File_System::size( ( new Image( $image_id ) )->get_file_path( Image::SIZE_FULL ) ); } catch ( File_System_Operation_Error $e ) { throw new Image_Validation_Error( esc_html__( 'File is missing. Verify the upload', 'image-optimization' ) ); } if ( $image_size > self::MAX_FILE_SIZE ) { throw new Image_Validation_Error( sprintf( __( 'File is too large. Max size is %s', 'image-optimization' ), File_Utils::format_file_size( self::MAX_FILE_SIZE, 0 ), ) ); } return true; } /** * Prepares the error message for the unsupported file formats. * * @return string The error message. */ private static function prepare_supported_formats_list_error(): string { $formats = Image::get_supported_formats(); $formats = array_filter( $formats, fn ( $format) => ! in_array( $format, Image::get_formats_cannot_be_optimized(), true ) ); $last_item = strtoupper( array_pop( $formats ) ); $formats_list = join( ', ', array_map( 'strtoupper', $formats ) ); return sprintf( __( 'Unsupported file format. Only %1$s, or %2$s are supported', 'image-optimization' ), $formats_list, $last_item ); } } optimization/classes/optimization-error-message.php 0000644 00000002503 14717617543 0016756 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Image\Image_Optimization_Error_Type; use TypeError; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimization_Error_Message { public static function get_reoptimization_error_message( string $error_type ) { if ( Image_Optimization_Error_Type::GENERIC === $error_type ) { return esc_html__( 'Image reoptimizing failed', 'image-optimization' ); } return self::get_optimization_error_message( $error_type ); } public static function get_optimization_error_message( string $error_type ) { if ( ! in_array( $error_type, Image_Optimization_Error_Type::get_values(), true ) ) { throw new TypeError( "Error type $error_type is not a part of Image_Optimization_Error_Type values" ); } $messages = [ Image_Optimization_Error_Type::FILE_ALREADY_EXISTS => esc_html__( 'File with this name already exists', 'image-optimization' ), Image_Optimization_Error_Type::QUOTA_EXCEEDED => esc_html__( 'Plan quota reached', 'image-optimization' ), Image_Optimization_Error_Type::GENERIC => esc_html__( 'Optimization error', 'image-optimization' ), ]; if ( isset( $messages[ $error_type ] ) ) { return $messages[ $error_type ]; } return $messages[ Image_Optimization_Error_Type::GENERIC ]; } } optimization/rest/optimize-single-image.php 0000644 00000003414 14717617543 0015200 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Classes\Image\Image; use ImageOptimization\Modules\Oauth\Components\Connect; use ImageOptimization\Modules\Optimization\Classes\{ Route_Base, Single_Optimization, Validate_Image, }; use Throwable; use WP_REST_Request; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimize_Single_Image extends Route_Base { const NONCE_NAME = 'image-optimization-optimize-image'; const IMAGE_ID_PARAM = 'imageId'; protected string $path = 'image'; public function get_name(): string { return 'optimize-single-image'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( ! Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->is_activated() ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid activation', 'image-optimization' ), 'code' => 'unauthorized', ]); } $is_reoptimize = (bool) $request->get_param( 'reoptimize' ); $image_id = (int) $request->get_param( self::IMAGE_ID_PARAM ); if ( empty( $image_id ) ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid image id', 'image-optimization' ), 'code' => 'bad_request', ]); } try { Validate_Image::is_valid( $image_id ); Single_Optimization::schedule_single_optimization( $image_id, $is_reoptimize ); return $this->respond_success_json(); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } optimization/rest/optimization-status.php 0000644 00000002271 14717617543 0015050 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Modules\Optimization\Classes\Optimization_Status as Optimization_Status_Controller; use ImageOptimization\Modules\Optimization\Classes\Route_Base; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimization_Status extends Route_Base { protected string $path = 'status'; public function get_name(): string { return 'optimization-status'; } public function get_methods(): array { // There might be a huge amount of ids in some cases, so decided to keep it as POST return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { try { $body = json_decode( $request->get_body() ); $image_ids = $body->image_ids ?? null; if ( empty( $image_ids ) ) { return $this->respond_success_json([ 'status' => [], ]); } return $this->respond_success_json([ 'status' => Optimization_Status_Controller::get_images_optimization_statuses( $image_ids ), ]); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } optimization/rest/cancel-bulk-optimization.php 0000644 00000003164 14717617543 0015707 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Modules\Optimization\Classes\{ Bulk_Optimization_Controller, Route_Base, }; use ImageOptimization\Modules\Oauth\Components\Connect; use Throwable; use WP_REST_Request; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Cancel_Bulk_Optimization extends Route_Base { const NONCE_NAME = 'image-optimization-cancel-bulk-optimization'; protected string $path = 'bulk/cancel'; public function get_name(): string { return 'cancel-bulk-optimization'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( ! Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->is_activated() ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid activation', 'image-optimization' ), 'code' => 'unauthorized', ]); } try { $is_in_progress = Bulk_Optimization_Controller::is_optimization_in_progress(); if ( ! $is_in_progress ) { return $this->respond_error_json([ 'message' => esc_html__( 'Bulk optimization is not in progress', 'image-optimization' ), 'code' => 'forbidden', ]); } Bulk_Optimization_Controller::cancel_bulk_optimization(); return $this->respond_success_json(); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } optimization/rest/optimize-bulk.php 0000644 00000005321 14717617543 0013573 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Classes\Async_Operation\Exceptions\Async_Operation_Exception; use ImageOptimization\Modules\Optimization\Classes\{ Bulk_Optimization_Controller, Route_Base, }; use ImageOptimization\Classes\Image\Exceptions\Invalid_Image_Exception; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Oauth\Components\Connect; use Throwable; use WP_REST_Request; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimize_Bulk extends Route_Base { const NONCE_NAME = 'image-optimization-optimize-bulk'; protected string $path = 'bulk'; public function get_name(): string { return 'optimize-bulk'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( ! Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->is_activated() ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid activation', 'image-optimization' ), 'code' => 'unauthorized', ]); } $is_reoptimize = (bool) $request->get_param( 'reoptimize' ); try { if ( $is_reoptimize ) { return $this->handle_bulk_reoptimization(); } else { return $this->handle_bulk_optimization(); } } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } /** * @return \WP_Error|\WP_REST_Response * @throws Async_Operation_Exception|Invalid_Image_Exception|Quota_Exceeded_Error */ private function handle_bulk_optimization() { $is_in_progress = Bulk_Optimization_Controller::is_optimization_in_progress(); if ( $is_in_progress ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Bulk optimization is already in progress', 'image-optimization' ), 'code' => 'forbidden', ] ); } Bulk_Optimization_Controller::find_images_and_schedule_optimization(); return $this->respond_success_json(); } /** * @throws Async_Operation_Exception|Quota_Exceeded_Error */ private function handle_bulk_reoptimization() { $is_in_progress = Bulk_Optimization_Controller::is_reoptimization_in_progress(); if ( $is_in_progress ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Bulk re-optimization is already in progress', 'image-optimization' ), 'code' => 'forbidden', ] ); } Bulk_Optimization_Controller::find_optimized_images_and_schedule_reoptimization(); return $this->respond_success_json(); } } optimization/rest/get-bulk-optimization-images.php 0000644 00000002163 14717617543 0016502 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Modules\Optimization\Classes\{ Bulk_Optimization_Controller, Route_Base, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Get_Bulk_Optimization_Images extends Route_Base { protected string $path = 'bulk/images'; public function get_name(): string { return 'bulk-optimization-images'; } public function get_methods(): array { return [ 'GET' ]; } public function GET( WP_REST_Request $request ) { try { $operation_id = $request->get_param( 'operation_id' ); if ( empty( $operation_id ) ) { return $this->respond_error_json([ 'message' => esc_html__( 'Missed operation id', 'image-optimization' ), 'code' => 'bad_request', ]); } $images = Bulk_Optimization_Controller::get_processed_images( $operation_id ); return $this->respond_success_json([ 'images' => $images, ]); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } optimization/module.php 0000644 00000004563 14717617543 0011317 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization; use ImageOptimization\Classes\Module_Base; use ImageOptimization\Modules\Backups\Rest\Restore_Single; use ImageOptimization\Modules\Optimization\Rest\Optimize_Single_Image; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Module_Base { public function get_name(): string { return 'optimization'; } public static function routes_list() : array { return [ 'Optimize_Single_Image', 'Optimize_Bulk', 'Get_Bulk_Optimization_Images', 'Optimization_Status', 'Cancel_Bulk_Optimization', ]; } public static function component_list() : array { return [ 'Avif_Compatibility', 'Media_Control', 'Single_Optimization', 'Upload_Optimization', 'Bulk_Optimization', 'List_View_Pointer', 'Admin_Bulk_Actions', 'Admin_Filter', ]; } /** * Enqueue styles and scripts */ public function enqueue_scripts() { $asset_file = include IMAGE_OPTIMIZATION_ASSETS_PATH . 'build/control.asset.php'; foreach ( $asset_file['dependencies'] as $style ) { wp_enqueue_style( $style ); } wp_enqueue_style( 'image-optimization-control', $this->get_css_assets_url( 'control' ), [], IMAGE_OPTIMIZATION_VERSION, ); wp_enqueue_script( 'image-optimization-control', $this->get_js_assets_url( 'control' ), $asset_file['dependencies'], IMAGE_OPTIMIZATION_VERSION, true ); wp_set_script_translations( 'image-optimization-control', 'image-optimization' ); wp_localize_script( 'image-optimization-control', 'imageOptimizerControlSettings', [ 'optimizeSingleImageNonce' => wp_create_nonce( Optimize_Single_Image::NONCE_NAME ), 'restoreSingleImageNonce' => wp_create_nonce( Restore_Single::NONCE_NAME ), ] ); } public static function load_template( $path, $name, $args = [] ): bool { $templates_path = sprintf( '%s/templates/%s/%s.php', dirname( __FILE__ ), $path, $name ); try { load_template( $templates_path, false, $args ); } catch ( Throwable $t ) { return false; } return true; } /** * Module constructor. */ public function __construct() { $this->register_components(); $this->register_routes(); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } } optimization/components/avif-compatibility.php 0000644 00000004106 14717617543 0016004 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Image\Image_Dimensions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Avif_Compatibility { public function is_avif_supported(): bool { return in_array( 'image/avif', get_allowed_mime_types(), true ); } public function add_to_supported_types( $mime_types ) { $mime_types['avif'] = 'image/avif'; return $mime_types; } public function add_to_supported_mime_types( $mime_types ) { $mime_types['image/avif'] = 'avif'; return $mime_types; } public function mark_as_displayable( $result, $path ) { if ( str_ends_with( $path, '.avif' ) ) { return true; } return $result; } public function fix_avif_images( $metadata, $attachment_id ) { if ( empty( $metadata ) ) { return $metadata; } $attachment = get_post( $attachment_id ); if ( ! $attachment || is_wp_error( $attachment ) || 'image/avif' !== $attachment->post_mime_type ) { return $metadata; } if ( ( ! empty( $metadata['width'] ) && ( 0 !== $metadata['width'] ) ) && ( ! empty( $metadata['height'] ) && 0 !== $metadata['height'] ) ) { return $metadata; } $file = get_attached_file( $attachment_id ); if ( ! $file ) { return $metadata; } $dimensions = Image_Dimensions::get_by_path( $file ); $metadata['width'] = $dimensions->width; $metadata['height'] = $dimensions->height; if ( empty( $metadata['file'] ) ) { $metadata['file'] = _wp_relative_upload_path( $file ); } if ( empty( $metadata['sizes'] ) ) { $metadata['sizes'] = []; } return $metadata; } public function __construct() { if ( $this->is_avif_supported() ) { return; } add_filter( 'upload_mimes', [ $this, 'add_to_supported_types' ] ); add_filter( 'mime_types', [ $this, 'add_to_supported_types' ] ); add_filter( 'getimagesize_mimes_to_exts', [ $this, 'add_to_supported_mime_types' ] ); add_filter( 'file_is_displayable_image', [ $this, 'mark_as_displayable' ], 10, 2 ); add_filter( 'wp_generate_attachment_metadata', [ $this, 'fix_avif_images' ], 1, 3 ); } } optimization/components/upload-optimization.php 0000644 00000006140 14717617543 0016220 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Async_Operation_Queue, }; use ImageOptimization\Classes\Image\{ Image, Image_Meta, Image_Optimization_Error_Type, Image_Status }; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_File_Already_Exists_Error; use ImageOptimization\Modules\Optimization\Classes\Optimize_Image; use ImageOptimization\Modules\Settings\Classes\Settings; use Throwable; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Upload_Optimization { public function handle_upload( int $attachment_id ) { if ( ! Settings::get( Settings::OPTIMIZE_ON_UPLOAD_OPTION_NAME ) ) { return; } // @var ImageOptimizer/Modules/ConnectManager/Module $module = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); if ( ! $module->connect_instance->is_connected() || ! $module->connect_instance->is_activated() ) { return; } $attachment_object = get_post( $attachment_id ); // TODO: Check how we can use Validate_Image::is_valid() here if ( ! wp_attachment_is_image( $attachment_object ) || ! in_array( $attachment_object->post_mime_type, Image::get_supported_mime_types(), true ) || ( in_array( $attachment_object->post_mime_type, Image::get_mime_types_cannot_be_optimized(), true ) && ! get_post_meta( $attachment_id, Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, true ) ) ) { return; } $meta = new Image_Meta( $attachment_id ); try { $meta ->set_status( Image_Status::OPTIMIZATION_IN_PROGRESS ) ->save(); Async_Operation::create( Async_Operation_Hook::OPTIMIZE_ON_UPLOAD, [ 'attachment_id' => $attachment_id ], Async_Operation_Queue::OPTIMIZE ); } catch ( Throwable $t ) { $meta ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->save(); } } /** @async */ public function optimize_image_on_upload( int $image_id ) { try { $oi = new Optimize_Image( $image_id, 'upload', ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Optimization error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } } public function __construct() { add_action( 'add_attachment', [ $this, 'handle_upload' ] ); add_action( Async_Operation_Hook::OPTIMIZE_ON_UPLOAD, [ $this, 'optimize_image_on_upload' ] ); } } optimization/components/admin-filter.php 0000644 00000005414 14717617543 0014566 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Image\Image_Meta; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin_Filter { public function add_filter( string $post_type ) { if ( 'attachment' !== $post_type ) { return; } $options = [ '' => __( 'All Media Files', 'image-optimization' ), 'optimized' => __( 'Optimized', 'image-optimization' ), 'not-optimized' => __( 'Unoptimized', 'image-optimization' ), 'in-progress' => __( 'In progress', 'image-optimization' ), 'failed' => __( 'Errors', 'image-optimization' ), ]; $current_value = $this->get_current_filter(); ?> <label class="screen-reader-text" for="image-optimization-filter"> <?php esc_html_e( 'Filter by optimization status', 'image-optimization' ); ?> </label> <select class="image-optimization-filter" id="image-optimization-filter" name="image-optimization-filter"> <?php foreach ( $options as $value => $title ) { printf( '<option value="%s" %s>%s</option>', esc_attr( $value ), selected( $value, $current_value, false ), esc_html( $title ) ); } ?> </select> <?php } public function handle_filter( $query ) { global $pagenow; if ( 'upload.php' !== $pagenow ) { return; } $current_value = $this->get_current_filter(); if ( empty( $current_value ) ) { return; } $meta_query = empty( $query->get( 'meta_query' ) ) ? [] : $query->get( 'meta_query' ); $meta_query[] = [ [ 'key' => '_wp_attachment_metadata', // Images without this field considered invalid 'compare' => 'EXISTS', ], ]; switch ( $current_value ) { case 'not-optimized': $meta_query[] = [ 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, 'compare' => 'NOT EXISTS', ]; break; case 'optimized': $meta_query[] = [ 'compare' => 'LIKE', 'value' => '"status";s:9:"optimized"', 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ]; break; case 'in-progress': $meta_query[] = [ 'compare' => 'LIKE', 'value' => '-in-progress"', // Covers both optimization and restoring 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ]; break; case 'failed': $meta_query[] = [ 'compare' => 'LIKE', 'value' => '-failed"', // Covers both optimization and restoring 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ]; break; } $query->set( 'meta_query', $meta_query ); } private function get_current_filter(): string { return sanitize_text_field( wp_unslash( $_GET['image-optimization-filter'] ?? '' ) ); } public function __construct() { add_filter( 'restrict_manage_posts', [ $this, 'add_filter' ] ); add_filter( 'parse_query', [ $this, 'handle_filter' ] ); } } optimization/components/list-view-pointer.php 0000644 00000003734 14717617543 0015617 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Modules\Core\Components\Pointers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class List_View_Pointer { const CURRENT_POINTER_SLUG = 'image-optimizer-list-view'; public function admin_print_script() { if ( $this->is_dismissed() ) { return; } wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); $pointer_content = '<h3>' . esc_html__( 'Switch to list view', 'image-optimization' ) . '</h3>'; $pointer_content .= '<p>' . esc_html__( 'Get the most out of your optimizing options. Use the List view to quickly optimize your uploaded images with Image Optimizer.', 'image-optimization' ) . '</p>'; $allowed_tags = [ 'h3' => [], 'p' => [], ]; ?> <script> jQuery( document ).ready( function( $ ) { setTimeout(() => { $( '#wp-media-grid div.media-toolbar.wp-filter' ).first().pointer( { content: '<?php echo wp_kses( $pointer_content, $allowed_tags ); ?>', pointerClass: 'image-optimization-list-view-pointer', position: { edge: 'top', align: '<?php echo is_rtl() ? 'right' : 'left'; ?>', }, close() { wp.ajax.post( 'image_optimizer_pointer_dismissed', { data: { pointer: '<?php echo esc_attr( static::CURRENT_POINTER_SLUG ); ?>', }, nonce: '<?php echo esc_attr( wp_create_nonce( 'image-optimization-pointer-dismissed' ) ); ?>', } ); } } ).pointer( 'open' ); }, 0) } ); </script> <style> .image-optimization-list-view-pointer .wp-pointer-arrow { inset-inline-start: 15px; } </style> <?php } private function is_dismissed(): bool { $meta = (array) get_user_meta( get_current_user_id(), Pointers::DISMISSED_POINTERS_META_KEY, true ); return key_exists( static::CURRENT_POINTER_SLUG, $meta ); } public function __construct() { add_action( 'in_admin_header', [ $this, 'admin_print_script' ] ); } } optimization/components/exceptions/bulk-optimization-token-not-found-error.php 0000644 00000000447 14717617543 0024232 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Optimization_Token_Not_Found_Error extends Exception { protected $message = 'Bulk optimization token not found'; } optimization/components/single-optimization.php 0000644 00000005405 14717617543 0016220 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook; use ImageOptimization\Classes\Image\{ Image, Image_Meta, Image_Optimization_Error_Type, Image_Restore, Image_Status }; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_File_Already_Exists_Error; use ImageOptimization\Modules\Optimization\Classes\Optimize_Image; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Single_Optimization { /** @async */ public function optimize_single_image( int $image_id ) { try { $oi = new Optimize_Image( $image_id, 'manual', ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Optimization error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } } /** @async */ public function reoptimize_single_image( int $image_id ) { try { $image = new Image( $image_id ); if ( $image->can_be_restored() ) { Image_Restore::restore( $image_id, true ); } $oi = new Optimize_Image( $image_id, 'manual', null, true ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Reoptimizing error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } } public function __construct() { add_action( Async_Operation_Hook::OPTIMIZE_SINGLE, [ $this, 'optimize_single_image' ] ); add_action( Async_Operation_Hook::REOPTIMIZE_SINGLE, [ $this, 'reoptimize_single_image' ] ); } } optimization/components/bulk-optimization.php 0000644 00000013234 14717617543 0015673 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook; use ImageOptimization\Classes\Image\{ Image, Image_Meta, Image_Optimization_Error_Type, Image_Restore, Image_Status }; use ImageOptimization\Classes\Async_Operation\Exceptions\Async_Operation_Exception; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Utils; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\{ Classes\Exceptions\Bulk_Token_Expired_Error, Classes\Exceptions\Image_File_Already_Exists_Error, Classes\Optimize_Image, Classes\Bulk_Optimization_Controller, Components\Exceptions\Bulk_Optimization_Token_Not_Found_Error, }; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Optimization { const BULK_OPTIMIZATION_BASE_SLUG = 'image-optimization-bulk-optimization'; const BULK_OPTIMIZATION_CAPABILITY = 'manage_options'; public function render_app() { ?> <!-- The hack required to wrap WP notifications --> <div class="wrap"> <h1 style="display: none;" role="presentation"></h1> </div> <div id="image-optimization-app"></div> <?php } public function register_page() { add_media_page( __( 'Bulk Optimization', 'image-optimization' ), __( 'Bulk Optimization', 'image-optimization' ), self::BULK_OPTIMIZATION_CAPABILITY, self::BULK_OPTIMIZATION_BASE_SLUG, [ $this, 'render_app' ], 7 ); } /** @async */ public function optimize_bulk( int $image_id, string $operation_id ) { try { $bulk_token = Bulk_Optimization_Controller::get_bulk_operation_token( $operation_id ); $oi = new Optimize_Image( $image_id, 'bulk', $bulk_token ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Bulk_Token_Expired_Error | Bulk_Optimization_Token_Not_Found_Error $bte ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::NOT_OPTIMIZED ) ->save(); Bulk_Optimization_Controller::reschedule_bulk_optimization(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Optimization error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } finally { Optimization_Stats::get_image_stats( null, true ); } } /** @async */ public function reoptimize_bulk( int $image_id, string $operation_id ) { try { $image = new Image( $image_id ); if ( $image->can_be_restored() ) { Image_Restore::restore( $image_id, true ); } $bulk_token = Bulk_Optimization_Controller::get_bulk_operation_token( $operation_id ); $oi = new Optimize_Image( $image_id, 'bulk', $bulk_token, true ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Bulk_Token_Expired_Error | Bulk_Optimization_Token_Not_Found_Error $bte ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::NOT_OPTIMIZED ) ->save(); Bulk_Optimization_Controller::reschedule_bulk_reoptimization(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Reoptimization error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } finally { Optimization_Stats::get_image_stats( null, true ); } } /** * Renders the bulk optimization notice * * @return void */ public function render_bulk_optimization_notice() { try { $is_in_progress = Bulk_Optimization_Controller::is_optimization_in_progress(); } catch ( Async_Operation_Exception $aoe ) { $is_in_progress = false; } ?> <div class="notice notice-info notice image-optimizer__notice image-optimizer__notice--info image-optimizer__notice--bulk-tip" style="display: <?php echo $is_in_progress ? 'block' : 'none'; ?>"> <p> <b> <?php esc_html_e( 'Heads up!', 'image-optimization' ); ?> </b> <span> <?php esc_html_e( 'Bulk optimizing may take a lot of processing and server time, depending on the number of images. Your site will still work smoothly until the processing is all done, without any downtime.', 'image-optimization' ); ?> </span> </p> </div> <?php } public function __construct() { add_action( 'admin_menu', [ $this, 'register_page' ] ); add_action( Async_Operation_Hook::OPTIMIZE_BULK, [ $this, 'optimize_bulk' ], 10, 2 ); add_action( Async_Operation_Hook::REOPTIMIZE_BULK, [ $this, 'reoptimize_bulk' ], 10, 2 ); add_action('current_screen', function () { if ( Utils::is_bulk_optimization_page() ) { add_filter( 'admin_footer_text', [ $this, 'render_bulk_optimization_notice' ] ); } }); } } optimization/components/media-control.php 0000644 00000021044 14717617543 0014745 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Image\{ Exceptions\Invalid_Image_Exception, Image, Image_Conversion, Image_Conversion_Option, Image_Meta, Image_Optimization_Error_Type, Image_Status }; use ImageOptimization\Modules\Oauth\Components\{ Exceptions\Auth_Error, }; use ImageOptimization\Modules\Optimization\{ Classes\Exceptions\Image_Validation_Error, Classes\Optimization_Error_Message, Classes\Validate_Image, Module, }; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Modules\Settings\Classes\Settings; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; use Throwable; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * The class is responsible for rendering optimization control in all views. */ class Media_Control { const COLUMN_ID = 'image_optimization'; const META_BOX_ID = 'image_optimization_meta_box'; const DETAILS_MODAL_FIELD_ID = 'image_optimization_modal'; /** * Adds a new optimization column to the library list view. * * @param array $columns * @return array */ public function add_optimization_column( array $columns ): array { if ( empty( $columns ) ) { return $columns; } $columns[ self::COLUMN_ID ] = esc_html__( 'Image optimization', 'image-optimization' ); return $columns; } /** * Renders optimization controls in the library list view. * * @param string $column_name * @param int $attachment_id * @return void */ public function render_optimization_column( string $column_name, int $attachment_id ) { if ( self::COLUMN_ID !== $column_name ) { return; } $this->render_optimization_control( 'list-view', $attachment_id ); } /** * Adds optimization control as media meta box. * * @return void */ public function add_optimization_meta_box() { add_meta_box( self::META_BOX_ID, __( 'Image optimization', 'image-optimization' ), function( $post ) { $this->render_optimization_control( 'meta-box', $post->ID ); }, 'attachment', 'side', ); } /** * Adds optimization control to media modals. * * @param array $form_fields * @param \WP_Post $post * * @return array */ public function add_media_modal_details( array $form_fields, \WP_Post $post ): array { $form_fields[ self::DETAILS_MODAL_FIELD_ID ] = [ 'label' => '', 'input' => 'hidden', 'value' => $this->get_optimization_control_html( 'details-view', $post->ID ), 'required' => false, ]; return $form_fields; } /** * Returns optimization control as an HTML string. * * @param string $context Control placement context. One of ['list-view', 'meta-box', 'details-view']. * @param int $image_id Attachment id. * @return false|string */ public function get_optimization_control_html( string $context, int $image_id ) { ob_start(); $this->render_optimization_control( $context, $image_id ); $html = ob_get_contents(); ob_end_clean(); return $html; } /** * Renders the optimization control for the current image in the current context. * * @param string $context Control placement context. One of ['list-view', 'meta-box', 'details-view']. * @param int $image_id Attachment id. * @return void Renders the control. */ public function render_optimization_control( string $context, int $image_id ) { $global_context = [ 'image_id' => $image_id, 'can_be_restored' => false, ]; // @var ImageOptimizer/Modules/ConnectManager/Module $module = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); try { if ( ! $module->connect_instance->is_connected() || ! $module->connect_instance->is_activated() ) { throw new Auth_Error( 'You have to activate your license to use Image Optimizer' ); } Validate_Image::is_valid( $image_id ); $image = new Image( $image_id ); $meta = new Image_Meta( $image->get_id() ); if ( Image_Status::OPTIMIZED === $meta->get_status() ) { $global_context['can_be_restored'] = $image->can_be_restored(); } else { $ic = new Image_Conversion(); $path = $image->get_file_path( Image::SIZE_FULL ); $backups_enabled = Settings::get( Settings::BACKUP_ORIGINAL_IMAGES_OPTION_NAME ); $conversion_enabled = $ic->is_enabled(); $needs_conversion = $conversion_enabled && strtolower( File_Utils::get_extension( $path ) ) !== $ic->get_current_file_extension(); $global_context['can_be_restored'] = ( $needs_conversion && $conversion_enabled ) || $backups_enabled; } switch ( $meta->get_status() ) { case Image_Status::OPTIMIZATION_IN_PROGRESS: Module::load_template( $context, 'loading', array_merge( $global_context, [ 'action' => 'optimize', ] ) ); break; case Image_Status::REOPTIMIZING_IN_PROGRESS: Module::load_template( $context, 'loading', array_merge( $global_context, [ 'action' => 'reoptimize', ] ) ); break; case Image_Status::RESTORING_IN_PROGRESS: Module::load_template( $context, 'loading', array_merge( $global_context, [ 'action' => 'restore', ] ) ); break; case Image_Status::OPTIMIZED: $stats = Optimization_Stats::get_image_stats( $image_id ); $saved = [ 'relative' => max( 100 - round( $stats['current_image_size'] / $stats['initial_image_size'] * 100 ), 0 ), 'absolute' => $stats['initial_image_size'] - $stats['current_image_size'], ]; $is_losseless_and_webp = Image_Conversion_Option::WEBP === Settings::get( Settings::CONVERT_TO_FORMAT_OPTION_NAME ) && 'lossless' === Settings::get( Settings::COMPRESSION_LEVEL_OPTION_NAME ); Module::load_template( $context, 'optimized', array_merge( $global_context, [ 'sizes_optimized_count' => $stats['optimized_image_count'], 'saved' => $saved, 'is_losseless_and_webp' => $is_losseless_and_webp, ] ) ); break; case Image_Status::OPTIMIZATION_FAILED: $error_type = $meta->get_error_type() ?? Image_Optimization_Error_Type::GENERIC; $error_message = Optimization_Error_Message::get_optimization_error_message( $error_type ); $images_left = $module->connect_instance->images_left(); Module::load_template( $context, 'error', array_merge( $global_context, [ 'message' => $error_message, 'optimization_error_type' => $error_type, 'images_left' => $images_left, 'allow_retry' => true, 'action' => 'optimize', ] ) ); break; case Image_Status::REOPTIMIZING_FAILED: $error_type = $meta->get_error_type() ?? Image_Optimization_Error_Type::GENERIC; $error_message = Optimization_Error_Message::get_reoptimization_error_message( $error_type ); $images_left = $module->connect_instance->images_left(); Module::load_template( $context, 'error', array_merge( $global_context, [ 'message' => $error_message, 'optimization_error_type' => $error_type, 'images_left' => $images_left, 'allow_retry' => true, 'action' => 'reoptimize', ] ) ); break; case Image_Status::RESTORING_FAILED: Module::load_template( $context, 'error', array_merge( $global_context, [ 'message' => esc_html__( 'Image restoring error', 'image-optimization' ), 'allow_retry' => true, 'action' => 'restore', ] ) ); break; default: Module::load_template( $context, 'not-optimized', $global_context ); } } catch ( Invalid_Image_Exception | Image_Validation_Error $iie ) { Module::load_template( $context, 'error', array_merge( $global_context, [ 'action' => 'error', 'message' => $iie->getMessage(), 'allow_retry' => false, ] ) ); } catch ( Auth_Error $ae ) { Module::load_template( $context, 'error', array_merge( $global_context, [ 'action' => 'error', 'message' => esc_html__( 'N/A', 'image-optimization' ), 'allow_retry' => false, ] ) ); } catch ( Throwable $t ) { Module::load_template( $context, 'error', array_merge( $global_context, [ 'action' => 'error', 'message' => esc_html__( 'Internal server error', 'image-optimization' ), 'allow_retry' => false, ] ) ); } } public function __construct() { add_filter( 'manage_upload_columns', [ $this, 'add_optimization_column' ] ); add_action( 'manage_media_custom_column', [ $this, 'render_optimization_column' ], 10, 2 ); add_action( 'add_meta_boxes_attachment', [ $this, 'add_optimization_meta_box' ] ); add_filter( 'attachment_fields_to_edit', [ $this, 'add_media_modal_details' ], 10, 2 ); } } optimization/components/admin-bulk-actions.php 0000644 00000002343 14717617543 0015672 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use ImageOptimization\Modules\Backups\Classes\Restore_Images; use ImageOptimization\Modules\Optimization\Classes\Single_Optimization as Single_Optimization_Controller; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin_Bulk_Actions { public function add_bulk_actions( array $bulk_actions ): array { $bulk_actions['image-optimization-optimize'] = esc_html__( 'Optimize', 'image-optimization' ); $bulk_actions['image-optimization-restore'] = esc_html__( 'Restore original', 'image-optimization' ); return $bulk_actions; } public function handle_bulk_actions( $redirect_url, $action, $post_ids ): string { if ( 'image-optimization-optimize' === $action ) { Single_Optimization_Controller::optimize_many( $post_ids ); } if ( 'image-optimization-restore' === $action ) { Restore_Images::find_and_schedule_restoring( $post_ids ); } return $redirect_url; } public function __construct() { add_filter( 'bulk_actions-upload', [ $this, 'add_bulk_actions' ] ); add_filter( 'handle_bulk_actions-upload', [ $this, 'handle_bulk_actions' ], 10, 3 ); } } optimization/templates/details-view/optimized.php 0000644 00000006307 14717617543 0016427 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use ImageOptimization\Classes\File_Utils; ?> <div class="image-optimization-control image-optimization-control--details-view image-optimization-control--optimized" data-image-optimization-context="details-view" data-image-optimization-status="optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Optimized', 'image-optimization' ); ?> </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Image sizes optimized', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php echo esc_html( $args['sizes_optimized_count'] ); ?> </span> </span> <span class="setting image-optimization-setting"> <?php if ( 0 === $args['saved']['absolute'] ) { ?> <span class="name image-optimization-control__property"></span> <span class="name image-optimization-control__property-value" style="max-width: 50%;"> <?php if ( $args['is_losseless_and_webp'] ) { echo esc_html_e( 'Requested WebP format is larger than the original file. Switch to Lossy in settings to convert this image in an optimized manner.', 'image-optimization' ); } else { echo esc_html_e( 'Image is fully optimized', 'image-optimization' ); } ?> </span> <?php } else { ?> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Overall saving', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php printf( esc_html__( '%1$s%% (%2$s)', 'image-optimization' ), esc_html( $args['saved']['relative'] ), esc_html( File_Utils::format_file_size( $args['saved']['absolute'], 1 ) ) ); ?> </span> <?php } ?> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button class="button button-link image-optimization-control__button image-optimization-control__button--reoptimize" type="button"> <?php esc_html_e( 'Reoptimize', 'image-optimization' ); ?> </button> </span> </span> <?php if ( $args['can_be_restored'] ) { ?> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button class="button button-link image-optimization-control__button image-optimization-control__button--restore-original" type="button"> <?php esc_html_e( 'Restore original', 'image-optimization' ); ?> </button> </span> </span> <?php } ?> </div> optimization/templates/details-view/error.php 0000644 00000004230 14717617543 0015545 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--details-view image-optimization-control--error" data-image-optimization-context="details-view" data-image-optimization-status="error" data-image-allow-retry="<?php echo esc_attr( $args['allow_retry'] ); ?>" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Error', 'image-optimization' ); ?> </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Reason', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php echo esc_html( $args['message'] ); ?> </span> </span> <?php if ( $args['allow_retry'] ) { ?> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <?php if ( isset( $args['images_left'] ) && 0 === $args['images_left'] ) { ?> <a class="button button-secondary button-large image-optimization-control__button" href="https://go.elementor.com/io-panel-upgrade/" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'Upgrade', 'image-optimization' ); ?> </a> <?php } else { ?> <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> <?php esc_html_e( 'Try again', 'image-optimization' ); ?> </button> <?php } ?> </span> </span> <?php } ?> </div> optimization/templates/details-view/not-optimized.php 0000644 00000002402 14717617543 0017215 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--details-view image-optimization-control--not-optimized" data-image-optimization-context="details-view" data-image-optimization-status="not-optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Not optimized', 'image-optimization' ); ?> </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> <?php esc_html_e( 'Optimize now', 'image-optimization' ); ?> </button> </span> </span> </div> optimization/templates/details-view/loading.php 0000644 00000002220 14717617543 0016026 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--details-view image-optimization-control--loading" data-image-optimization-context="details-view" data-image-optimization-status="loading" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php esc_html_e( 'In Progress', 'image-optimization' ); ?> </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--spinner"> <span class="spinner is-active"></span> </span> </span> </div> optimization/templates/list-view/optimized.php 0000644 00000004072 14717617543 0015752 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use ImageOptimization\Classes\File_Utils; ?> <div class="image-optimization-control image-optimization-control--list-view image-optimization-control--optimized" data-image-optimization-context="list-view" data-image-optimization-status="optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Image sizes optimized', 'image-optimization' ); ?>: <span><?php echo esc_html( $args['sizes_optimized_count'] ); ?></span> </p> <p class="image-optimization-control__property"> <?php if ( 0 === $args['saved']['absolute'] ) { ?> <span> <?php if ( $args['is_losseless_and_webp'] ) { echo esc_html_e( 'Requested WebP format is larger than the original file. Switch to Lossy in settings to convert this image in an optimized manner.', 'image-optimization' ); } else { echo esc_html_e( 'Image is fully optimized', 'image-optimization' ); } ?> </span> <?php } else { ?> <?php esc_html_e( 'Overall saving', 'image-optimization' ); ?>: <span> <?php printf( esc_html__( '%1$s%% (%2$s)', 'image-optimization' ), esc_html( $args['saved']['relative'] ), esc_html( File_Utils::format_file_size( $args['saved']['absolute'], 1 ) ) ); ?> </span> <?php } ?> </p> <div class="image-optimization-control__buttons-wrapper"> <?php if ( $args['can_be_restored'] ) { ?> <button type="button" class="button button-secondary image-optimization-control__button image-optimization-control__button--restore-original"> <?php esc_html_e( 'Restore original', 'image-optimization' ); ?> </button> <?php } ?> <button type="button" class="button button-secondary image-optimization-control__button image-optimization-control__button--reoptimize"> <?php esc_html_e( 'Reoptimize', 'image-optimization' ); ?> </button> </div> </div> optimization/templates/list-view/error.php 0000644 00000003362 14717617543 0015100 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--list-view image-optimization-control--error" data-image-optimization-context="list-view" data-image-optimization-status="error" data-image-allow-retry="<?php echo esc_attr( $args['allow_retry'] ); ?>" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <?php $message = esc_html( $args['message'] ); if ( false === $args['allow_retry'] ) { $message_chunks = explode( '. ', $message, 2 ); $message = "<span class='image-optimization-control__error-title'>{$message_chunks[0]}</span>"; if ( isset( $message_chunks[1] ) ) { $message .= "<span class='image-optimization-control__error-subtitle'>{$message_chunks[1]}</span>"; } } ?> <span class='image-optimization-control__error-message'><?php echo $message; ?></span> <?php if ( $args['allow_retry'] ) { ?> <?php if ( isset( $args['images_left'] ) && 0 === $args['images_left'] ) { ?> <a class="button button-secondary button-large image-optimization-control__button" href="https://go.elementor.com/io-panel-upgrade/" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'Upgrade', 'image-optimization' ); ?> </a> <?php } else { ?> <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> <?php esc_html_e( 'Try again', 'image-optimization' ); ?> </button> <?php } ?> <?php } ?> </div> optimization/templates/list-view/not-optimized.php 0000644 00000001266 14717617543 0016552 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--list-view image-optimization-control--not-optimized" data-image-optimization-context="list-view" data-image-optimization-status="not-optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> <?php esc_html_e( 'Optimize now', 'image-optimization' ); ?> </button> </div> optimization/templates/list-view/loading.php 0000644 00000002143 14717617543 0015360 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--list-view image-optimization-control--loading" data-image-optimization-context="list-view" data-image-optimization-status="loading" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <button class="button button-secondary image-optimization-control__button image-optimization-control__button--optimize" disabled=""> <span class="spinner is-active"></span> <?php switch ( $args['action'] ) { case 'restore': esc_html_e( 'Restoring…', 'image-optimization' ); break; case 'optimize': esc_html_e( 'Optimizing…', 'image-optimization' ); break; case 'reoptimize': esc_html_e( 'Reoptimizing…', 'image-optimization' ); break; default: esc_html_e( 'Loading…', 'image-optimization' ); } ?> </button> </div> optimization/templates/meta-box/optimized.php 0000644 00000004713 14717617543 0015545 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use ImageOptimization\Classes\File_Utils; ?> <div class="image-optimization-control image-optimization-control--meta-box image-optimization-control--optimized" data-image-optimization-context="meta-box" data-image-optimization-status="optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Optimized', 'image-optimization' ); ?> </span> </p> <p class="image-optimization-control__property"> <?php esc_html_e( 'Image sizes optimized', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php echo esc_html( $args['sizes_optimized_count'] ); ?> </span> </p> <p class="image-optimization-control__property"> <?php if ( 0 === $args['saved']['absolute'] ) { ?> <span class="image-optimization-control__property-value"> <?php if ( $args['is_losseless_and_webp'] ) { echo esc_html_e( 'Requested WebP format is larger than the original file. Switch to Lossy in settings to convert this image in an optimized manner.', 'image-optimization' ); } else { echo esc_html_e( 'Image is fully optimized', 'image-optimization' ); } ?> </span> <?php } else { ?> <?php esc_html_e( 'Overall saving', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php printf( esc_html__( '%1$s%% (%2$s)', 'image-optimization' ), esc_html( $args['saved']['relative'] ), esc_html( File_Utils::format_file_size( $args['saved']['absolute'], 1 ) ) ); ?> </span> <?php } ?> </p> <div class="image-optimization-control__action-button-wrapper"> <?php if ( $args['can_be_restored'] ) { ?> <button class="button button-link image-optimization-control__button image-optimization-control__button--restore-original" type="button"> <?php esc_html_e( 'Restore original', 'image-optimization' ); ?> </button> <?php } ?> <button class="button button-link image-optimization-control__button image-optimization-control__button--reoptimize" type="button"> <?php esc_html_e( 'Reoptimize', 'image-optimization' ); ?> </button> </div> </div> optimization/templates/meta-box/error.php 0000644 00000003476 14717617543 0014677 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--meta-box image-optimization-control--error" data-image-optimization-context="meta-box" data-image-optimization-status="error" data-image-allow-retry="<?php echo esc_attr( $args['allow_retry'] ); ?>" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Error', 'image-optimization' ); ?> </span> </p> <p class="image-optimization-control__property"> <?php esc_html_e( 'Reason', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php echo esc_html( $args['message'] ); ?> </span> </p> <?php if ( $args['allow_retry'] ) { ?> <div class="image-optimization-control__action-button-wrapper"> <?php if ( isset( $args['images_left'] ) && 0 === $args['images_left'] ) { ?> <a class="button button-secondary button-large image-optimization-control__button" href="https://go.elementor.com/io-panel-upgrade/" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'Upgrade', 'image-optimization' ); ?> </a> <?php } else { ?> <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> <?php esc_html_e( 'Try again', 'image-optimization' ); ?> </button> <?php } ?> </div> <?php } ?> </div> optimization/templates/meta-box/not-optimized.php 0000644 00000001775 14717617543 0016350 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--meta-box image-optimization-control--not-optimized" data-image-optimization-context="meta-box" data-image-optimization-status="not-optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Not optimized', 'image-optimization' ); ?> </span> </p> <div class="image-optimization-control__action-button-wrapper"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> <?php esc_html_e( 'Optimize now', 'image-optimization' ); ?> </button> </div> </div> optimization/templates/meta-box/loading.php 0000644 00000001616 14717617543 0015155 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--meta-box image-optimization-control--loading" data-image-optimization-context="meta-box" data-image-optimization-status="loading" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php esc_html_e( 'In Progress', 'image-optimization' ); ?> </span> </p> <div class="image-optimization-control__action-spinner-wrapper"> <span class="spinner is-active"></span> </div> </div> optimization/assets/css/control.css 0000644 00000006244 14717617543 0013603 0 ustar 00 .image-optimization-control--list-view { display: flex; justify-content: flex-start; align-items: center; min-height: 105px; } .image-optimization-control--list-view.image-optimization-control--error { justify-content: center; align-items: flex-start; } .image-optimization-control__buttons-wrapper { margin-block-start: 8px; } .image-optimization-control__buttons-wrapper :not(:last-of-type) { margin-inline-end: 8px; } .image-optimization-control--error { flex-direction: column; } .image-optimization-control--optimized { flex-direction: column; align-items: flex-start; } .image-optimization-control__error-message { display: flex; flex-direction: column; font-family: Roboto, sans-serif; font-size: 14px; font-weight: 600; line-height: 24px; margin-block-end: 15px; max-width: 200px; } .image-optimization-control__error-title { margin-block-end: 10px; } .image-optimization-control__error-subtitle { color: #DC2626; } .image-optimization-control__button { font-family: Roboto, sans-serif; font-weight: 500; border-radius: 4px; } .image-optimization-control__button--optimize > .spinner { float: left; padding: 0; margin-block-start: 4px; margin-left: 0; margin-block-end: 0; } .image-optimization-control--error .image-optimization-control__button--try-again, .image-optimization-control--error .image-optimization-control__button--try-again:hover { color: #DC2626; border-color: #DC2626; background: transparent; } .image-optimization-control__property { margin: 9px 0; } .image-optimization-control__property:last-of-type { margin-block-end: 0; } .image-optimization-control .image-optimization-setting { margin-block-end: 0; } .image-optimization-control__property-value, .image-optimization-control--details-view .setting .image-optimization-control__property-value { font-weight: 600; font-size: 13px; line-height: 24px; } .image-optimization-control--details-view .setting .image-optimization-control__property-value { margin: 6px 3px 3px; text-align: start; } .image-optimization-control--details-view .setting .image-optimization-control__property-value--button { margin: 0 0 0 3px; font-weight: initial; } .image-optimization-control--details-view .setting .image-optimization-control__property-value--spinner { display: flex; justify-content: center; margin: 25px 0 0 3px; } .image-optimization-control__action-button-wrapper { margin-block-start: 37px; display: flex; width: 100%; justify-content: flex-end; } .image-optimization-control__action-button-wrapper .button-link:not(:last-of-type) { margin-inline-end: 24px; } .image-optimization-control__action-button-wrapper .button-link, .image-optimization-control--details-view .image-optimization-control__property-value--button .button-link { text-decoration: none; } .image-optimization-control__action-button-wrapper .button-link:hover, .image-optimization-control--details-view .image-optimization-control__property-value--button .button-link:hover { background-color: transparent; } .image-optimization-control__action-spinner-wrapper { height: 75px; display: flex; justify-content: center; align-items: center; } div .components-tooltip { max-width: 165px; text-align: start; } optimization/assets/js/classes/api.js 0000644 00000002375 14717617543 0014002 0 ustar 00 import apiFetch from '@wordpress/api-fetch'; import APIError from './exceptions/APIError'; const v1Prefix = '/image-optimizer/v1'; class API { static async request( { path, data, method = 'POST' } ) { try { const response = await apiFetch( { path, method, data, } ); if ( ! response.success ) { throw new APIError( response.data.message ); } return response.data; } catch ( e ) { if ( e instanceof APIError ) { throw e; } else { throw new APIError( e.message ); } } } static async optimizeSingleImage( { imageId, reoptimize = false } ) { return API.request( { path: `${ v1Prefix }/optimize/image`, data: { imageId, reoptimize, 'image-optimization-optimize-image': window?.imageOptimizerControlSettings?.optimizeSingleImageNonce, }, } ); } static async restoreSingleImage( imageId ) { return API.request( { path: `${ v1Prefix }/backups/restore/${ imageId }`, data: { 'image-optimization-restore-single': window?.imageOptimizerControlSettings?.restoreSingleImageNonce, }, } ); } static async getOptimizationStatus( imageIds ) { return API.request( { path: `${ v1Prefix }/optimize/status`, data: { image_ids: imageIds, }, } ); } } export default API; optimization/assets/js/classes/extend-views.js 0000644 00000004231 14717617543 0015644 0 ustar 00 class ExtendViews { constructor() { this.init(); } init() { this.extendAttachmentDetails(); this.extendAttachmentDetailsTwoColumn(); } // #tmpl-attachment-details extendAttachmentDetails() { if ( ! wp?.media?.view?.Attachment?.Details ) { return; } wp.media.view.Attachment.Details = wp.media.view.Attachment.Details.extend( { template( view ) { const html = wp.media.template( 'attachment-details' )( view ); if ( this.model.attributes.type !== 'image' ) { return html; } const content = document.createElement( 'div' ); content.innerHTML = html; const optimizationControl = this.getOptimizationControlHTML( view.compat.item ); if ( ! optimizationControl ) { return content.innerHTML; } content.innerHTML += optimizationControl; return content.innerHTML; }, getOptimizationControlHTML( compatData ) { const tempElement = document.createElement( 'div' ); tempElement.innerHTML = compatData; return tempElement.querySelector( 'input[name*="[image_optimization_modal]"]' )?.value; }, } ); } // #tmpl-attachment-details-two-column extendAttachmentDetailsTwoColumn() { if ( ! wp?.media?.view?.Attachment?.Details?.TwoColumn ) { return; } wp.media.view.Attachment.Details.TwoColumn = wp.media.view.Attachment.Details.TwoColumn.extend( { template( view ) { const html = wp.media.template( 'attachment-details-two-column' )( view ); if ( this.model.attributes.type !== 'image' ) { return html; } const content = document.createElement( 'div' ); content.innerHTML = html; const optimizationControl = this.getOptimizationControlHTML( view.compat.item ); if ( ! optimizationControl ) { return content.innerHTML; } const settings = content.querySelector( '.settings' ); settings.innerHTML += optimizationControl; return content.innerHTML; }, getOptimizationControlHTML( compatData ) { const tempElement = document.createElement( 'div' ); tempElement.innerHTML = compatData; return tempElement.querySelector( 'input[name*="[image_optimization_modal]"]' )?.value; }, } ); } } export default ExtendViews; optimization/assets/js/classes/exceptions/APIError.js 0000644 00000000207 14717617543 0017025 0 ustar 00 class APIError extends Error { constructor( message ) { super( message ); this.name = 'APIError'; } } export default APIError; optimization/assets/js/classes/control/control-sync.js 0000644 00000004760 14717617543 0017343 0 ustar 00 import API from '../api'; import { SELECTORS } from '../../constants'; import ControlMeta from './control-meta'; import ControlStates from './control-states'; class ControlSync { async run() { const controls = document.querySelectorAll( SELECTORS.controlWrapperSelector ); if ( ! controls.length ) { return; } const imageIds = this.mapImageIds( controls ); const { status } = await API.getOptimizationStatus( imageIds ); controls.forEach( ( controlWrapper ) => { const imageId = new ControlMeta( controlWrapper ).getImageId(); const currentStatus = new ControlMeta( controlWrapper ).getStatus(); const imageData = status[ imageId ]; const controlStates = new ControlStates( controlWrapper ); if ( currentStatus === imageData.status ) { return; } if ( controlWrapper.dataset.isFrozen === 'true' ) { controlWrapper.dataset.isFrozen = false; return; } if ( currentStatus === 'error' && ! new ControlMeta( controlWrapper ).allowRetry() ) { return; } switch ( imageData.status ) { case 'optimization-in-progress': controlStates.renderLoading( 'optimize' ); break; case 'reoptimizing-in-progress': controlStates.renderLoading( 'reoptimize' ); break; case 'restoring-in-progress': controlStates.renderLoading( 'restore' ); break; case 'not-optimized': controlStates.renderNotOptimized(); break; case 'optimized': const statsData = { sizesOptimized: imageData.stats.optimized_image_count, saved: { absolute: imageData.stats.initial_image_size - imageData.stats.current_image_size, relative: Math.max( 100 - Math.round( imageData.stats.current_image_size / imageData.stats.initial_image_size * 100 ), 0 ), }, }; controlStates.renderOptimized( statsData ); break; case 'optimization-failed': controlStates.renderError( { message: imageData.message, imagesLeft: imageData.images_left, action: 'optimize', } ); break; case 'reoptimizing-failed': controlStates.renderError( { message: imageData.message, imagesLeft: imageData.images_left, action: 'reoptimize', } ); break; case 'restoring-failed': controlStates.renderError( { message: imageData.message, action: 'restore', } ); } } ); } mapImageIds( nodes ) { return Array.prototype.map.call( nodes, ( node ) => new ControlMeta( node ).getImageId(), ); } } export default ControlSync; optimization/assets/js/classes/control/control-states.js 0000644 00000004767 14717617543 0017701 0 ustar 00 import listViewTemplates from '../../templates/list-view'; import metaBoxTemplates from '../../templates/meta-box'; import detailsViewTemplates from '../../templates/details-view'; import { SELECTORS } from '../../constants'; import ControlMeta from './control-meta'; class ControlStates { constructor( controlWrapper ) { this.controlWrapper = controlWrapper; this.context = new ControlMeta( controlWrapper ).getContext(); this.action = new ControlMeta( controlWrapper ).getAction(); this.canBeRestored = new ControlMeta( controlWrapper ).canBeRestored(); this.templates = { 'list-view': listViewTemplates, 'meta-box': metaBoxTemplates, 'details-view': detailsViewTemplates, }; } renderNotOptimized( data ) { this.controlWrapper.className = this.mixControlContextClass( SELECTORS.controlNotOptimizedClassName ); this.controlWrapper.innerHTML = this.getTemplates().notOptimizedTemplate( data ); this.controlWrapper.dataset.imageOptimizationStatus = 'not-optimized'; } renderOptimized( data ) { const canBeRestored = this.canBeRestored && data?.saved?.absolute !== 0; this.controlWrapper.className = this.mixControlContextClass( SELECTORS.controlOptimizedClassName ); this.controlWrapper.innerHTML = this.getTemplates().optimizedTemplate( { ...data, canBeRestored } ); this.controlWrapper.dataset.imageOptimizationStatus = 'optimized'; } renderError( { message, imagesLeft, action } ) { this.controlWrapper.className = this.mixControlContextClass( SELECTORS.controlErrorClassName ); this.controlWrapper.innerHTML = this.getTemplates().errorTemplate( message, imagesLeft ); this.controlWrapper.dataset.imageOptimizationAction = action; this.controlWrapper.dataset.imageOptimizationStatus = 'error'; } renderLoading( action ) { this.controlWrapper.className = this.mixControlContextClass( SELECTORS.controlLoadingClassName ); this.controlWrapper.innerHTML = this.getTemplates().loadingTemplate( action ); this.controlWrapper.dataset.imageOptimizationStatus = 'loading'; } getTemplates() { const template = this.templates[ this.context ]; if ( ! template ) { throw new Error( `No templates found for the context ${ this.context }` ); } return template; } mixControlContextClass( className ) { const contextClassName = SELECTORS.controlWrapper[ this.context ]; if ( ! contextClassName ) { throw new Error( `No context className found for the context ${ this.context }` ); } return `${ className } ${ contextClassName }`; } } export default ControlStates; optimization/assets/js/classes/control/control-meta.js 0000644 00000001415 14717617543 0017307 0 ustar 00 class ControlMeta { constructor( controlNode ) { this.controlNode = controlNode; } getImageId() { return this.controlNode.dataset?.imageOptimizationImageId ? parseInt( this.controlNode.dataset?.imageOptimizationImageId, 10 ) : null; } getAction() { return this.controlNode.dataset?.imageOptimizationAction || null; } getContext() { return this.controlNode.dataset?.imageOptimizationContext || null; } getStatus() { return this.controlNode.dataset?.imageOptimizationStatus || null; } canBeRestored() { const value = this.controlNode.dataset?.imageOptimizationCanBeRestored; if ( ! value ) { return null; } return '1' === value; } allowRetry() { return this.controlNode.dataset?.allowRetry || null; } } export default ControlMeta; optimization/assets/js/constants/index.js 0000644 00000002640 14717617543 0014712 0 ustar 00 export const UPGRADE_LINK = 'https://go.elementor.com/io-panel-upgrade/'; export const SELECTORS = Object.freeze( { optimizeButtonSelector: '.image-optimization-control__button--optimize', reoptimizeButtonSelector: '.image-optimization-control__button--reoptimize', tryAgainOptimizeButtonSelector: '[data-image-optimization-action="optimize"] .image-optimization-control__button--try-again', tryAgainReoptimizeButtonSelector: '[data-image-optimization-action="reoptimize"] .image-optimization-control__button--try-again', tryAgainRestoreButtonSelector: '[data-image-optimization-action="restore"] .image-optimization-control__button--try-again', controlWrapperSelector: '.image-optimization-control', controlNotOptimizedClassName: 'image-optimization-control image-optimization-control--not-optimized', controlLoadingClassName: 'image-optimization-control image-optimization-control--loading', controlOptimizedClassName: 'image-optimization-control image-optimization-control--optimized', controlErrorClassName: 'image-optimization-control image-optimization-control--error', controlWrapper: { 'list-view': 'image-optimization-control--list-view', 'meta-box': 'image-optimization-control--meta-box', 'details-view': 'image-optimization-control--details-view', }, restoreButtonSelector: '.image-optimization-control__button--restore-original', loadingControlsSelector: '[data-image-optimization-status="loading"]', } ); optimization/assets/js/module.js 0000644 00000000467 14717617543 0013061 0 ustar 00 import '../css/control.css'; import ExtendViews from './classes/extend-views'; import OptimizationControl from './control'; class Module { constructor() { this.init(); } init() { new ExtendViews(); new OptimizationControl(); } } document.addEventListener( 'DOMContentLoaded', () => new Module() ); optimization/assets/js/utils/index.js 0000644 00000001632 14717617543 0014036 0 ustar 00 import { __, sprintf } from '@wordpress/i18n'; export const formatFileSize = ( fileSizeInBytes, decimals = 2 ) => { const sizes = [ // translators: %s: file size in bytes __( '%s Bytes', 'image-optimization' ), // translators: %s: file size in kilobytes __( '%s Kb', 'image-optimization' ), // translators: %s: file size in megabytes __( '%s Mb', 'image-optimization' ), // translators: %s: file size in gigabytes __( '%s Gb', 'image-optimization' ), ]; if ( ! fileSizeInBytes ) { // translators: %s: file size in bytes return sprintf( __( '%s Bytes', 'image-optimization' ), 0 ); } const currentScale = Math.floor( Math.log( fileSizeInBytes ) / Math.log( 1024 ) ); const formattedValue = parseFloat( ( fileSizeInBytes / Math.pow( 1024, currentScale ) ).toFixed( decimals ) ); // eslint-disable-next-line @wordpress/valid-sprintf return sprintf( sizes[ currentScale ], formattedValue ); }; optimization/assets/js/templates/details-view/index.js 0000644 00000012620 14717617543 0017270 0 ustar 00 import { __ } from '@wordpress/i18n'; import { escapeHTML } from '@wordpress/escape-html'; import { formatFileSize } from '../../utils'; import { UPGRADE_LINK } from '../../constants'; const notOptimizedTemplate = () => { return ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ __( 'Not optimized', 'image-optimization' ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> ${ __( 'Optimize now', 'image-optimization' ) } </button> </span> </span> `; }; const loadingTemplate = () => { return ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ __( 'In Progress', 'image-optimization' ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--spinner"> <span class="spinner is-active"></span> </span> </span> `; }; const errorTemplate = ( message, imagesLeft ) => { return ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ __( 'Error', 'image-optimization' ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Reason', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ escapeHTML( message ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> ${ imagesLeft === 0 ? `<a class="button button-secondary button-large image-optimization-control__button" href="${ UPGRADE_LINK }" target="_blank" rel="noopener noreferrer"> ${ __( 'Upgrade', 'image-optimization' ) } </a> ` : ` <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> ${ __( 'Try again', 'image-optimization' ) } </button>` } </span> </span> `; }; const optimizedTemplate = ( data ) => { const absoluteValue = formatFileSize( data?.saved?.absolute, 1 ); return ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ __( 'Optimized', 'image-optimization' ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Image sizes optimized', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ data?.sizesOptimized } </span> </span> <span class="setting image-optimization-setting"> ${ data?.saved?.absolute !== 0 ? `<span class="name image-optimization-control__property"> ${ __( 'Overall saving', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ data?.saved?.relative }% (${ absoluteValue }) </span>` : `<span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value"> ${ __( 'Image is fully optimized', 'image-optimization' ) } </span>` } </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button class="button button-link image-optimization-control__button image-optimization-control__button--reoptimize" type="button"> ${ __( 'Reoptimize', 'image-optimization' ) } </button> </span> </span> ${ data?.canBeRestored ? ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button class="button button-link image-optimization-control__button image-optimization-control__button--restore-original" type="button"> ${ __( 'Restore original', 'image-optimization' ) } </button> </span> </span> ` : '' } `; }; const detailsViewTemplates = Object.freeze( { notOptimizedTemplate, loadingTemplate, errorTemplate, optimizedTemplate, } ); export default detailsViewTemplates; optimization/assets/js/templates/list-view/index.js 0000644 00000005736 14717617543 0016630 0 ustar 00 import { __ } from '@wordpress/i18n'; import { escapeHTML } from '@wordpress/escape-html'; import { formatFileSize } from '../../utils'; import { UPGRADE_LINK } from '../../constants'; const notOptimizedTemplate = () => { return ` <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> ${ __( 'Optimize now', 'image-optimization' ) } </button> `; }; const loadingTemplate = ( action ) => { let buttonText; switch ( action ) { case 'restore': buttonText = __( 'Restoring…', 'image-optimization' ); break; case 'optimize': buttonText = __( 'Optimizing…', 'image-optimization' ); break; case 'reoptimize': buttonText = __( 'Reoptimizing…', 'image-optimization' ); break; default: buttonText = __( 'Loading…', 'image-optimization' ); } return ` <button class="button button-secondary image-optimization-control__button image-optimization-control__button--optimize" disabled=""> <span class="spinner is-active"></span> ${ buttonText } </button> `; }; const errorTemplate = ( message, imagesLeft ) => { return ` <span class="image-optimization-control__error-message">${ escapeHTML( message ) }</span> ${ imagesLeft === 0 ? `<a class="button button-secondary button-large image-optimization-control__button" href="${ UPGRADE_LINK }" target="_blank" rel="noopener noreferrer"> ${ __( 'Upgrade', 'image-optimization' ) } </a> ` : ` <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> ${ __( 'Try again', 'image-optimization' ) } </button>` } `; }; const optimizedTemplate = ( data ) => { const absoluteValue = formatFileSize( data?.saved?.absolute, 1 ); return ` <p class="image-optimization-control__property"> ${ __( 'Image sizes optimized', 'image-optimization' ) }: <span>${ data?.sizesOptimized }</span> </p> <p class="image-optimization-control__property"> ${ data?.saved?.absolute !== 0 ? `${ __( 'Overall saving', 'image-optimization' ) }: <span>${ data?.saved?.relative }% (${ absoluteValue })</span>` : `<span>${ __( 'Image is fully optimized', 'image-optimization' ) }</span>` } </p> <div class="image-optimization-control__buttons-wrapper"> ${ data?.canBeRestored ? ` <button type="button" class="button button-secondary image-optimization-control__button image-optimization-control__button--restore-original"> ${ __( 'Restore original', 'image-optimization' ) } </button> ` : '' } <button type="button" class="button button-secondary image-optimization-control__button image-optimization-control__button--reoptimize"> ${ __( 'Reoptimize', 'image-optimization' ) } </button> </div> `; }; const listViewTemplates = Object.freeze( { notOptimizedTemplate, loadingTemplate, errorTemplate, optimizedTemplate, } ); export default listViewTemplates; optimization/assets/js/templates/meta-box/index.js 0000644 00000007540 14717617543 0016414 0 ustar 00 import { __ } from '@wordpress/i18n'; import { escapeHTML } from '@wordpress/escape-html'; import { formatFileSize } from '../../utils'; import { UPGRADE_LINK } from '../../constants'; const notOptimizedTemplate = () => { return ` <p class="image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ __( 'Not optimized', 'image-optimization' ) } </span> </p> <div class="image-optimization-control__action-button-wrapper"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> ${ __( 'Optimize now', 'image-optimization' ) } </button> </div> `; }; const loadingTemplate = () => { return ` <p class="image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ __( 'In Progress', 'image-optimization' ) } </span> </p> <div class="image-optimization-control__action-spinner-wrapper"> <span class="spinner is-active"></span> </div> `; }; const errorTemplate = ( message, imagesLeft ) => { return ` <p class="image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ __( 'Error', 'image-optimization' ) } </span> </p> <p class="image-optimization-control__property"> ${ __( 'Reason', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ escapeHTML( message ) } </span> </p> <div class="image-optimization-control__action-button-wrapper"> ${ imagesLeft === 0 ? `<a class="button button-secondary button-large image-optimization-control__button" href="${ UPGRADE_LINK }" target="_blank" rel="noopener noreferrer"> ${ __( 'Upgrade', 'image-optimization' ) } </a> ` : ` <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> ${ __( 'Try again', 'image-optimization' ) } </button>` } </div> `; }; const optimizedTemplate = ( data ) => { const absoluteValue = formatFileSize( data?.saved?.absolute, 1 ); return ` <p class="image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ __( 'Optimized', 'image-optimization' ) } </span> </p> <p class="image-optimization-control__property"> ${ __( 'Image sizes optimized', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ data?.sizesOptimized } </span> </p> <p class="image-optimization-control__property"> ${ data?.saved?.absolute !== 0 ? ` <span class="image-optimization-control__property-value"> ${ __( 'Overall saving', 'image-optimization' ) }: ${ data?.saved?.relative }% (${ absoluteValue }) </span> ` : ` <span class="image-optimization-control__property-value"> ${ __( 'Image is fully optimized', 'image-optimization' ) } </span> ` } </p> <div class="image-optimization-control__action-button-wrapper"> ${ data?.canBeRestored ? ` <button class="button button-link image-optimization-control__button image-optimization-control__button--restore-original" type="button"> ${ __( 'Restore original', 'image-optimization' ) } </button> ` : '' } <button class="button button-link image-optimization-control__button image-optimization-control__button--reoptimize" type="button"> ${ __( 'Reoptimize', 'image-optimization' ) } </button> </div> `; }; const metaBoxTemplates = Object.freeze( { notOptimizedTemplate, loadingTemplate, errorTemplate, optimizedTemplate, } ); export default metaBoxTemplates; optimization/assets/js/control.js 0000644 00000006050 14717617543 0013246 0 ustar 00 import { __ } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; import { SELECTORS } from './constants'; import API from './classes/api'; import ControlSync from './classes/control/control-sync'; import ControlStates from './classes/control/control-states'; import ControlMeta from './classes/control/control-meta'; class OptimizationControl { constructor() { this.controlSyncRequestInProgress = false; this.init(); this.controlSync = new ControlSync(); } init() { this.initEventListeners(); setInterval( () => this.runStatusCheckLoop(), 5000 ); } async runStatusCheckLoop() { if ( this.controlSyncRequestInProgress ) { return; } this.controlSyncRequestInProgress = true; await this.controlSync.run(); this.controlSyncRequestInProgress = false; } initEventListeners() { document.addEventListener( 'click', ( e ) => this.handleOptimizeButtonClick( e ) ); document.addEventListener( 'click', ( e ) => this.handleReoptimizeButtonClick( e ) ); document.addEventListener( 'click', ( e ) => this.handleRestoreButtonClick( e ) ); } async handleOptimizeButtonClick( e ) { if ( ! e.target.closest( `${ SELECTORS.optimizeButtonSelector }, ${ SELECTORS.tryAgainOptimizeButtonSelector }` ) ) { return; } speak( __( 'Optimization is in progress', 'image-optimization' ), 'assertive' ); const controlWrapper = e.target.closest( SELECTORS.controlWrapperSelector ); const states = new ControlStates( controlWrapper ); states.renderLoading( 'optimize' ); try { controlWrapper.dataset.isFrozen = true; await API.optimizeSingleImage( { imageId: new ControlMeta( controlWrapper ).getImageId(), reoptimize: false, } ); } catch ( error ) { states.renderError( error ); } } async handleReoptimizeButtonClick( e ) { if ( ! e.target.closest( `${ SELECTORS.reoptimizeButtonSelector }, ${ SELECTORS.tryAgainReoptimizeButtonSelector }` ) ) { return; } speak( __( 'Reoptimizing is in progress', 'image-optimization' ), 'assertive' ); const controlWrapper = e.target.closest( SELECTORS.controlWrapperSelector ); const states = new ControlStates( controlWrapper ); states.renderLoading( 'reoptimize' ); try { controlWrapper.dataset.isFrozen = true; await API.optimizeSingleImage( { imageId: new ControlMeta( controlWrapper ).getImageId(), reoptimize: true, } ); } catch ( error ) { states.renderError( error ); } } async handleRestoreButtonClick( e ) { if ( ! e.target.closest( `${ SELECTORS.restoreButtonSelector }, ${ SELECTORS.tryAgainRestoreButtonSelector }` ) ) { return; } speak( __( 'Image restoring is in progress', 'image-optimization' ), 'assertive' ); const controlWrapper = e.target.closest( SELECTORS.controlWrapperSelector ); const states = new ControlStates( controlWrapper ); states.renderLoading( 'restore' ); try { controlWrapper.dataset.isFrozen = true; await API.restoreSingleImage( new ControlMeta( controlWrapper ).getImageId() ); } catch ( error ) { states.renderError( error ); } } } export default OptimizationControl; oauth/classes/route-base.php 0000644 00000002361 14717617543 0012121 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Classes; use ImageOptimization\Classes\Route; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Route_Base extends Route { const SITE_URL = 'https://my.elementor.com/connect/v1/'; protected $auth = true; protected string $path = ''; public function get_methods(): array { return []; } public function get_endpoint(): string { return 'connect/' . $this->get_path(); } public function get_path(): string { return $this->path; } public function get_name(): string { return ''; } public function get_permission_callback( \WP_REST_Request $request ): bool { $valid = $this->permission_callback( $request ); return $valid && user_can( $this->current_user_id, 'manage_options' ); } public function verify_nonce( $nonce = '', $name = '' ) { if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), $name ) ) { wp_die( 'Invalid nonce', 'image-optimization' ); } } public function verify_nonce_and_capability( $nonce = '', $name = '', $capability = 'manage_options' ) { $this->verify_nonce( $nonce, $name ); if ( ! current_user_can( $capability ) ) { wp_die( 'You do not have sufficient permissions to access this page.' ); } } } oauth/classes/data.php 0000644 00000010706 14717617543 0010766 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Classes; use ImageOptimization\Modules\Oauth\Components\Connect; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Class Data */ class Data { const CONNECT_CLIENT_DATA_OPTION_NAME = 'image_optimizer_client_data'; const CONNECT_DATA_OPTION_NAME = 'image_optimizer_connect_data'; const OPTION_CONNECT_SITE_KEY = 'image_optimizer_site_key'; const OPTION_CONNECT_STATE = 'image_optimizer_connect_state'; const OPTION_ACTIVATION_STATE = 'image_optimizer_activation_state'; const OPTION_OWNER_USER_ID = 'image_optimizer_owner_user_id'; /** * set_client_data * @param $client_id * @param $auth_secret */ public static function set_client_data( $client_id, $auth_secret ) { update_option( self::CONNECT_CLIENT_DATA_OPTION_NAME, [ 'client_id' => $client_id, 'auth_secret' => $auth_secret, ], false ); } /** * get_client_data * @return array */ public static function get_client_data(): array { return get_option( self::CONNECT_CLIENT_DATA_OPTION_NAME, [ 'client_id' => '', 'auth_secret' => '', ] ); } /** * get_client_id * @return string */ public static function get_client_id(): string { return self::get_client_data()['client_id'] ?? ''; } /** * get_client_secret * @return string */ public static function get_client_secret(): string { return self::get_client_data()['auth_secret'] ?? ''; } /** * get_connect_data * * @param bool $force * * @return array|null */ public static function get_connect_data( bool $force = false ): array { static $connect_data = null; if ( $connect_data === null || $force ) { $connect_data = array_merge( [ 'access_token' => '', 'access_token_secret' => '', 'last_update' => 0, 'token_type' => 'bearer', 'user' => [], 'version' => 1, ], get_option( self::CONNECT_DATA_OPTION_NAME, [] ) ); } return $connect_data; } /** * set_connect_data * * @param array $data */ public static function set_connect_data( array $data = [] ): bool { $data['last_update'] = time(); $data['version'] = 1; update_option( self::OPTION_OWNER_USER_ID, get_current_user_id() ); return update_option( self::CONNECT_DATA_OPTION_NAME, $data ); } /** * get_access_token * @return false|mixed */ public static function get_access_token() { return self::get_connect_data()['access_token'] ?? false; } /** * get_connect_state * * @param bool $force * * @return false|mixed|null|string */ public static function get_connect_state( bool $force = false ) { $state = get_option( static::OPTION_CONNECT_STATE ); if ( ! $state || $force ) { $state = wp_generate_password( 12, false ); update_option( static::OPTION_CONNECT_STATE, $state, false ); } return $state; } /** * get_site_key * @return false|mixed|string|null */ public static function get_site_key() { $site_key = get_option( static::OPTION_CONNECT_SITE_KEY ); if ( ! $site_key ) { $site_key = md5( uniqid( wp_generate_password() ) ); update_option( static::OPTION_CONNECT_SITE_KEY, $site_key, false ); } return $site_key; } /** * delete_connect_state */ public static function delete_connect_state(): bool { return delete_option( static::OPTION_CONNECT_STATE ); } public static function get_activation_state(): string { return get_option( self::OPTION_ACTIVATION_STATE, '' ); } public static function set_activation_state( $state ): bool { return update_option( self::OPTION_ACTIVATION_STATE, $state ); } public static function delete_activation_state(): bool { return delete_option( self::OPTION_ACTIVATION_STATE ); } /** * reset connect data */ public static function reset(): void { self::delete_connect_state(); self::delete_activation_state(); delete_option( self::OPTION_OWNER_USER_ID ); delete_option( static::CONNECT_DATA_OPTION_NAME ); delete_option( self::CONNECT_CLIENT_DATA_OPTION_NAME ); delete_transient( Connect::STATUS_CHECK_TRANSIENT ); } public static function images_left(): int { $plan_data = Connect::get_connect_status(); if ( empty( $plan_data ) ) { return 0; } $quota = $plan_data->quota; $used_quota = $plan_data->used_quota; return max( $quota - $used_quota, 0 ); } public static function user_is_subscription_owner(): bool { $owner_id = (int) get_option( self::OPTION_OWNER_USER_ID ); return get_current_user_id() === $owner_id; } } oauth/rest/activate.php 0000644 00000003042 14717617543 0011170 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Rest; use ImageOptimization\Modules\Oauth\{ Classes\Route_Base, Components\Connect, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Activate extends Route_Base { const NONCE_NAME = 'image-optimization-activate-subscription'; protected string $path = 'activate'; public function get_name(): string { return 'activate'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( ! Connect::is_connected() ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Please connect first', 'image-optimization' ), 'code' => 'forbidden', ] ); } if ( Connect::is_activated() ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Already activated', 'image-optimization' ), 'code' => 'bad_request', ] ); } if ( ! $request->get_param( 'license_key' ) ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Missing license key', 'image-optimization' ), 'code' => 'bad_request', ] ); } $license_key = $request->get_param( 'license_key' ); try { Connect::activate( $license_key ); return $this->respond_success_json(); } catch ( Throwable $t ) { return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } oauth/rest/version.php 0000644 00000001155 14717617543 0011060 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Rest; use ImageOptimization\Modules\Oauth\{ Classes\Route_Base, Components\Connect, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Version extends Route_Base { const NONCE_NAME = 'image-optimization-version'; protected string $path = 'version'; public function get_name(): string { return 'version'; } public function get_methods(): array { return [ 'GET' ]; } public function GET( WP_REST_Request $request ) { return $this->respond_success_json( [ 'version' => 1, ] ); } } oauth/rest/disconnect.php 0000644 00000001651 14717617543 0011525 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Rest; use ImageOptimization\Modules\Oauth\{ Classes\Data, Classes\Route_Base, Components\Connect }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Disconnect extends Route_Base { const NONCE_NAME = 'image-optimization-disconnect'; protected string $path = 'disconnect'; public function get_name(): string { return 'disconnect'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); try { Connect::disconnect(); } catch ( Throwable $t ) { Data::reset(); return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } return $this->respond_success_json(); } } oauth/rest/get-subscriptions.php 0000644 00000002217 14717617543 0013057 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Rest; use ImageOptimization\Modules\Oauth\{ Classes\Route_Base, Components\Connect, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Get_Subscriptions extends Route_Base { const NONCE_NAME = 'image-optimization-get-subscription'; protected string $path = 'get-subscriptions'; public function get_name(): string { return 'get_subscriptions'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( ! Connect::is_connected() ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Please connect first', 'image-optimization' ), 'code' => 'forbidden', ] ); } try { $subscriptions = Connect::get_subscriptions(); return $this->respond_success_json( $subscriptions ); } catch ( Throwable $t ) { return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } oauth/rest/connect-init.php 0000644 00000002155 14717617543 0011766 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Rest; use ImageOptimization\Modules\Oauth\{ Classes\Route_Base, Components\Connect, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Connect_Init extends Route_Base { const NONCE_NAME = 'image-optimization-connect'; protected string $path = 'init'; public function get_name(): string { return 'connect-init'; } public function get_methods(): array { return [ 'GET' ]; } public function get( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( Connect::is_connected() ) { return $this->respond_error_json( [ 'message' => esc_html__( 'You are already connected', 'image-optimization' ), 'code' => 'forbidden', ] ); } try { $connect_url = Connect::initialize_connect(); return $this->respond_success_json( $connect_url ); } catch ( Throwable $t ) { return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } oauth/rest/deactivate.php 0000644 00000002637 14717617543 0011512 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Rest; use ImageOptimization\Modules\Oauth\{ Classes\Data, Classes\Route_Base, Components\Connect }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Deactivate extends Route_Base { const NONCE_NAME = 'image-optimization-deactivate-subscription'; protected string $path = 'deactivate'; public function get_name(): string { return 'deactivate'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( ! Connect::is_connected() ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Please connect first', 'image-optimization' ), 'code' => 'forbidden', ] ); } if ( ! $request->get_param( 'license_key' ) ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Missing license key', 'image-optimization' ), 'code' => 'bad_request', ] ); } $license_key = $request->get_param( 'license_key' ); try { Connect::deactivate( $license_key ); return $this->respond_success_json(); } catch ( Throwable $t ) { Data::delete_activation_state(); return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } oauth/module.php 0000644 00000001615 14717617543 0007704 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth; use ImageOptimization\Classes\Module_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Class Module */ class Module extends Module_Base { /** * Get module name. * Retrieve the module name. * @access public * @return string Module name. */ public function get_name(): string { return 'oauth'; } public static function routes_list() : array { return [ 'Connect_Init', 'Disconnect', 'Get_Subscriptions', 'Activate', 'Deactivate', 'Version', ]; } public static function component_list() : array { return [ 'Connect', 'Checkpoint', 'Connect_Pointer', ]; } public static function is_active() : bool { return ! empty( get_option( 'image_optimizer_client_data' ) ); } public function __construct() { $this->register_components(); $this->register_routes(); } } oauth/components/connect.php 0000644 00000023246 14717617543 0012241 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Components; use ImageOptimization\Modules\Oauth\{ Classes\Route_Base, Components\Exceptions\Auth_Error, Classes\Data, }; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Utils; use ImageOptimization\Modules\Settings\Module as Settings_Module; use stdClass; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Class Connect */ class Connect { const API_URL = 'https://my.elementor.com/api/connect/v1'; const STATUS_CHECK_TRANSIENT = 'image_optimizer_status_check'; /** * is_connected * @return bool */ public static function is_connected(): bool { return ! empty( Data::get_connect_data()['access_token'] ); } /** * is_activated * @return bool */ public static function is_activated(): bool { return ! empty( Data::get_activation_state() ); } /** * maybe_handle_admin_connect_page * @return bool */ public static function maybe_handle_admin_connect_page(): bool { if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'nonce_actionget_token' ) ) { return false; } $args = [ 'page' => 'elementor-connect', 'app' => 'library', 'action' => 'get_token', 'state' => Data::get_connect_state(), ]; foreach ( $args as $key => $value ) { if ( ! isset( $_GET[ $key ] ) || $_GET[ $key ] !== $value ) { return false; } } if ( ! isset( $_GET['nonce'] ) || ! isset( $_GET['code'] ) ) { return false; } return true; } /** * handle_elementor_connect_admin */ public function handle_elementor_connect_admin(): void { // validate args if ( ! self::maybe_handle_admin_connect_page() ) { return; } // validate nonce if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'nonce_actionget_token' ) ) { wp_die( 'Nonce verification failed', 'image-optimization' ); } $token_response = wp_remote_request( self::API_URL . '/get_token', [ 'method' => 'POST', 'body' => [ 'app' => 'library', 'grant_type' => 'authorization_code', 'client_id' => Data::get_client_id(), 'code' => sanitize_text_field( $_GET['code'] ), ], ] ); if ( is_wp_error( $token_response ) ) { wp_die( $token_response->get_error_message(), 'image-optimization' ); } $data = json_decode( wp_remote_retrieve_body( $token_response ), true ); Data::set_connect_data( $data ); do_action( Checkpoint::ON_CONNECT ); // cleanup Data::delete_connect_state(); wp_redirect( add_query_arg( [ 'page' => Settings_Module::SETTING_BASE_SLUG, 'connected' => 'true', ], admin_url( 'admin.php' ) ) ); die(); } /** * Gets required connection data from the service and generates a connect link. * * @return string User connect link * * @throws Auth_Error */ public static function initialize_connect(): string { try { $response = wp_remote_request( self::API_URL . '/library/get_client_id', [ 'method' => 'POST', 'body' => [ 'local_id' => get_current_user_id(), 'site_key' => Data::get_site_key(), 'app' => 'library', 'home_url' => trailingslashit( home_url() ), 'source' => 'image-optimizer', ], ] ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Error while sending connection initialization request: ' . $t->getMessage() ); throw new Auth_Error( $t->getMessage() ); } $data = json_decode( wp_remote_retrieve_body( $response ) ); if ( ! isset( $data->client_id ) || ! isset( $data->auth_secret ) ) { Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server: client id or auth secret are undefined' ); throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) ); } Data::set_client_data( $data->client_id, $data->auth_secret ); return add_query_arg( [ 'utm_source' => 'image-optimizer-panel', 'utm_campaign' => 'image-optimizer', 'utm_medium' => 'wp-dash', 'source' => 'generic', 'action' => 'authorize', 'response_type' => 'code', 'client_id' => $data->client_id, 'auth_secret' => $data->auth_secret, 'state' => Data::get_connect_state( true ), 'redirect_uri' => rawurlencode( add_query_arg( [ 'page' => 'elementor-connect', 'app' => 'library', 'action' => 'get_token', 'nonce' => wp_create_nonce( 'nonce_action' . 'get_token' ), ], admin_url( 'admin.php' ) ) ), 'may_share_data' => 0, 'reconnect_nonce' => wp_create_nonce( 'nonce_action' . 'reconnect' ), ], Route_Base::SITE_URL . 'library' ); } /** * Disconnects a user and removes connection data from the DB. */ public static function disconnect() { do_action( Checkpoint::ON_DISCONNECT ); try { return wp_remote_request( self::API_URL . '/disconnect', [ 'method' => 'POST', 'body' => [ 'app' => 'library', 'home_url' => trailingslashit( home_url() ), 'client_id' => Data::get_client_id(), 'access_token' => Data::get_access_token(), ], ] ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Error while sending disconnection request: ' . $t->getMessage() ); throw new Auth_Error( $t->getMessage() ); } finally { Data::reset(); } } /** * Sends an activation request and stores activation data in the DB. * * @param $license_key string License key to activate with. * @return mixed * * @throws Auth_Error */ public static function activate( string $license_key ) { try { $response = Utils::get_api_client()->make_request( 'POST', 'activation/activate', [], [ 'key' => $license_key ] ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Error while sending activation request: ' . $t->getMessage() ); throw new Auth_Error( $t->getMessage() ); } if ( ! isset( $response->id ) ) { Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' ); throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) ); } Data::set_activation_state( $license_key ); do_action( Checkpoint::ON_ACTIVATE, $license_key ); return $response; } /** * Deactivate specific license and remove activation data from the DB. * * @param $license_key string License key to deactivate. * * @return mixed * * @throws Auth_Error */ public static function deactivate( string $license_key ) { do_action( Checkpoint::ON_DEACTIVATE, $license_key ); try { $response = Utils::get_api_client()->make_request( 'POST', 'activation/deactivate', [ 'key' => $license_key, ] ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Error while sending deactivation request: ' . $t->getMessage() ); throw new Auth_Error( $t->getMessage() ); } finally { Data::delete_activation_state(); } if ( ! isset( $response->id ) ) { Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' ); throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) ); } return $response; } /** * Fetches and returns a list of available licenses for a specific user. * * @return array Available subscriptions or an empty array * * @throws Auth_Error */ public static function get_subscriptions(): array { try { $response = Utils::get_api_client()->make_request( 'POST', 'activation/get-subscriptions' ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Error while fetching subscriptions: ' . $t->getMessage() ); throw new Auth_Error( $t->getMessage() ); } if ( ! isset( $response->subscriptions ) ) { Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' ); throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) ); } return $response->subscriptions; } public static function get_connect_status() { if ( ! self::is_connected() ) { Logger::log( Logger::LEVEL_INFO, 'Status getting error. Reason: User is not connected' ); return null; } $cached_status = get_transient( self::STATUS_CHECK_TRANSIENT ); if ( $cached_status ) { return $cached_status; } $status = self::check_connect_status(); set_transient( self::STATUS_CHECK_TRANSIENT, $status, MINUTE_IN_SECONDS * 5 ); return $status; } private static function check_connect_status() { if ( ! self::is_connected() ) { Logger::log( Logger::LEVEL_INFO, 'Status check error. Reason: User is not connected' ); return null; } try { $response = Utils::get_api_client()->make_request( 'POST', 'status/check' ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Status check error. Reason: ' . $t->getMessage() ); return null; } if ( ! isset( $response->status ) ) { Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' ); return null; } return $response; } public static function update_usage_data( stdClass $new_usage_data ) { $connect_status = self::get_connect_status(); if ( ! isset( $new_usage_data->allowed ) || ! isset( $new_usage_data->used ) ) { return; } if ( 0 === $new_usage_data->allowed - $new_usage_data->used ) { $connect_status->status = 'expired'; } $connect_status->quota = $new_usage_data->allowed; $connect_status->used_quota = $new_usage_data->used; set_transient( self::STATUS_CHECK_TRANSIENT, $connect_status, MINUTE_IN_SECONDS * 5 ); } public function __construct() { // handle connect if elementor is active add_action( 'load-elementor_page_elementor-connect', [ $this, 'handle_elementor_connect_admin' ], 9 ); // handle connect if elementor is not active add_action( '_admin_menu', [ $this, 'handle_elementor_connect_admin' ] ); } } oauth/components/connect-pointer.php 0000644 00000003417 14717617543 0013715 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Components; use ImageOptimization\Modules\Core\Components\Pointers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Connect_Pointer { const CURRENT_POINTER_SLUG = 'image-optimization-auth-connect'; public function admin_print_script() { if ( Connect::is_connected() ) { return; } wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); $pointer_content = '<h3>' . esc_html__( 'Start by connecting your license', 'image-optimization' ) . '</h3>'; $pointer_content .= '<p>' . esc_html__( 'You’re one click away from improving your site’s performance dramatically!', 'image-optimization' ) . '</p>'; ?> <script> jQuery( document ).ready( function( $ ) { console.log( $( '.image-optimization-stats-connect-button' ) ); const intervalId = setInterval( () => { if ( ! $( '.image-optimization-stats-connect-button' ).length ) { return; } clearInterval(intervalId); $( '.image-optimization-stats-connect-button' ).first().pointer( { content: '<?php echo $pointer_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>', pointerClass: 'image-optimization-auth-connect-pointer', position: { edge: 'top', align: <?php echo is_rtl() ? "'left'" : "'right'"; ?>, }, } ).pointer( 'open' ); }, 100 ); } ); </script> <style> .image-optimization-auth-connect-pointer .wp-pointer-arrow { inset-block-start: 4px; inset-inline-start: 78%; } .image-optimization-auth-connect-pointer .wp-pointer-arrow-inner { inset-block-start: 10px; } </style> <?php } public function __construct() { add_action( 'in_admin_header', [ $this, 'admin_print_script' ] ); } } oauth/components/exceptions/auth-error.php 0000644 00000000354 14717617543 0015054 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Components\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Auth_Error extends Exception { protected $message = 'Auth error'; } oauth/components/checkpoint.php 0000644 00000002377 14717617543 0012741 0 ustar 00 <?php namespace ImageOptimization\Modules\Oauth\Components; use ImageOptimization\Classes\Utils; use ImageOptimization\Modules\Oauth\Classes\Data; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Class Checkpoint */ class Checkpoint { const ON_CONNECT = 'image-optimizer-connect'; const ON_DISCONNECT = 'image-optimizer-disconnect'; const ON_ACTIVATE = 'image-optimizer-activate'; const ON_DEACTIVATE = 'image-optimizer-deactivate'; /** * event * * @param array $event_data */ public static function event( array $event_data = [] ): void { $event_name = current_action(); // only allow specific events if ( ! in_array( $event_name, self::get_checkpoints() ) ) { return; } $response = Utils::get_api_client()->make_request( 'POST', 'status/checkpoint', [ 'event_name' => $event_name, 'event_data' => $event_data, ] ); } /** * get_checkpoints * @return string[] */ public static function get_checkpoints(): array { return [ self::ON_DISCONNECT, self::ON_CONNECT, self::ON_ACTIVATE, self::ON_DEACTIVATE, ]; } public function __construct() { foreach ( self::get_checkpoints() as $checkpoint ) { add_action( $checkpoint, [ __CLASS__, 'event' ], 10, 0 ); } } } settings/classes/settings.php 0000644 00000004016 14717617543 0012432 0 ustar 00 <?php namespace ImageOptimization\Modules\Settings\Classes; use ImageOptimization\Classes\Image\Image_Conversion_Option; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Settings { public const COMPRESSION_LEVEL_OPTION_NAME = 'image_optimizer_compression_level'; public const OPTIMIZE_ON_UPLOAD_OPTION_NAME = 'image_optimizer_optimize_on_upload'; /** @deprecated Use self::CONVERT_TO_FORMAT_OPTION_NAME instead */ public const CONVERT_TO_WEBP_OPTION_NAME = 'image_optimizer_convert_to_webp'; public const CONVERT_TO_FORMAT_OPTION_NAME = 'image_optimizer_convert_to_format'; public const RESIZE_LARGER_IMAGES_OPTION_NAME = 'image_optimizer_resize_larger_images'; public const RESIZE_LARGER_IMAGES_SIZE_OPTION_NAME = 'image_optimizer_resize_larger_images_size'; public const STRIP_EXIF_METADATA_OPTION_NAME = 'image_optimizer_exif_metadata'; public const BACKUP_ORIGINAL_IMAGES_OPTION_NAME = 'image_optimizer_original_images'; public const CUSTOM_SIZES_OPTION_NAME = 'image_optimizer_custom_sizes'; /** * Returns plugin settings data by option name typecasted to an appropriate data type. * * @param string $option_name * @return mixed */ public static function get( string $option_name ) { $data = get_option( $option_name ); switch ( $option_name ) { case self::RESIZE_LARGER_IMAGES_SIZE_OPTION_NAME: return (int) $data; case self::RESIZE_LARGER_IMAGES_OPTION_NAME: case self::STRIP_EXIF_METADATA_OPTION_NAME: case self::BACKUP_ORIGINAL_IMAGES_OPTION_NAME: return (bool) $data; // TODO: [Stability] Remove this fallback after a few versions case self::CONVERT_TO_WEBP_OPTION_NAME: $new_option = get_option( self::CONVERT_TO_FORMAT_OPTION_NAME ); if ( Image_Conversion_Option::WEBP === $new_option ) { return true; } return false; case self::CUSTOM_SIZES_OPTION_NAME: if ( 'all' === $data ) { return $data; } if ( empty( $data ) ) { return []; } return explode( ',', $data ); default: return $data; } } } settings/module.php 0000644 00000006552 14717617543 0010431 0 ustar 00 <?php namespace ImageOptimization\Modules\Settings; use ImageOptimization\Classes\Image\Image_Conversion_Option; use ImageOptimization\Classes\Module_Base; use ImageOptimization\Modules\Settings\Classes\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Module extends Module_Base { const SETTING_PREFIX = 'image_optimizer_'; const SETTING_GROUP = 'image_optimizer_settings'; const SETTING_BASE_SLUG = 'image-optimization-settings'; const SETTING_CAPABILITY = 'manage_options'; public function get_name(): string { return 'settings'; } public static function component_list() : array { return [ 'Settings_Pointer', ]; } public static function get_options() : array { return [ 'compression_level' => [ 'default' => 'lossy' ], 'optimize_on_upload' => [ 'type' => 'boolean', 'default' => true, ], 'resize_larger_images' => [ 'type' => 'boolean', 'default' => true, ], 'resize_larger_images_size' => [ 'type' => 'integer', 'default' => 1920, ], 'exif_metadata' => [ 'type' => 'boolean', 'default' => true, ], 'original_images' => [ 'type' => 'boolean', 'default' => true, ], 'convert_to_format' => [ 'type' => 'string', 'default' => Image_Conversion_Option::WEBP, ], 'custom_sizes' => [ 'type' => 'string', 'default' => 'all', ], ]; } public function register_options() { $options = $this->get_options(); foreach ( $options as $key => &$args ) { $args['type'] = $args['type'] ?? 'string'; $args['show_in_rest'] = $args['show_in_rest'] ?? true; $args['default'] = $args['default'] ?? ''; register_setting( self::SETTING_GROUP, self::SETTING_PREFIX . $key, $args ); // Set defaults add_option( self::SETTING_PREFIX . $key, $args['default'] ); } } public function render_app() { ?> <!-- The hack required to wrap WP notifications --> <div class="wrap"> <h1 style="display: none;" role="presentation"></h1> </div> <div id="image-optimization-app"></div> <?php } public function register_page() { add_media_page( __( 'Image Optimizer', 'image-optimization' ), __( 'Image Optimizer', 'image-optimization' ), self::SETTING_CAPABILITY, self::SETTING_BASE_SLUG, [ $this, 'render_app' ], 6 ); } /** * The handler converts an old CONVERT_TO_WEBP option to the new CONVERT_TO_FORMAT option. * TODO: [Stability] Remove this fallback after all users updated * * @return void */ public function maybe_migrate_legacy_conversion_option() { $legacy_convert_to_webp = get_option( Settings::CONVERT_TO_WEBP_OPTION_NAME, null ); if ( is_null( $legacy_convert_to_webp ) ) { return; } if ( '1' === $legacy_convert_to_webp ) { update_option( Settings::CONVERT_TO_FORMAT_OPTION_NAME, Image_Conversion_Option::WEBP, false ); } if ( '0' === $legacy_convert_to_webp ) { update_option( Settings::CONVERT_TO_FORMAT_OPTION_NAME, Image_Conversion_Option::ORIGINAL, false ); } delete_option( Settings::CONVERT_TO_WEBP_OPTION_NAME ); } public function __construct() { $this->register_components(); add_action( 'admin_init', [ $this, 'register_options' ] ); add_action( 'rest_api_init', [ $this, 'register_options' ] ); add_action( 'admin_init', [ $this, 'maybe_migrate_legacy_conversion_option' ] ); add_action( 'admin_menu', [ $this, 'register_page' ] ); } } settings/components/settings-pointer.php 0000644 00000004632 14717617543 0014644 0 ustar 00 <?php namespace ImageOptimization\Modules\Settings\Components; use ImageOptimization\Modules\Core\Components\Pointers; use ImageOptimization\Modules\Settings\Module; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Settings_Pointer { const CURRENT_POINTER_SLUG = 'image-optimizer-settings'; public function admin_print_script() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( $this->is_dismissed() ) { return; } wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); $pointer_content = '<h3>' . esc_html__( 'Image Optimization settings', 'image-optimization' ) . '</h3>'; $pointer_content .= '<p>' . esc_html__( 'Head over to the Image Optimization Settings to fine-tune how your media uploads are managed.', 'image-optimization' ) . '</p>'; $pointer_content .= sprintf( '<p><a class="button button-primary image-optimization-pointer-settings-link" href="%s">%s</a></p>', admin_url( 'admin.php?page=' . Module::SETTING_BASE_SLUG ), esc_html__( 'Take me there', 'image-optimization' ) ); $allowed_tags = [ 'h3' => [], 'p' => [], 'a' => [ 'class' => [], 'href' => [], ], ]; ?> <script> const onClose = () => { return wp.ajax.post( 'image_optimizer_pointer_dismissed', { data: { pointer: '<?php echo esc_attr( static::CURRENT_POINTER_SLUG ); ?>', }, nonce: '<?php echo esc_attr( wp_create_nonce( 'image-optimization-pointer-dismissed' ) ); ?>', } ); } jQuery( document ).ready( function( $ ) { $( '#menu-media' ).pointer( { content: '<?php echo wp_kses( $pointer_content, $allowed_tags ); ?>', position: { edge: <?php echo is_rtl() ? "'right'" : "'left'"; ?>, align: 'center' }, close: onClose } ).pointer( 'open' ); $( '.image-optimization-pointer-settings-link' ).first().on( 'click', function( e ) { e.preventDefault(); $(this).attr( 'disabled', true ); onClose().promise().done(() => { location = $(this).attr( 'href' ); }); }) } ); </script> <?php } public function is_dismissed(): bool { $meta = (array) get_user_meta( get_current_user_id(), Pointers::DISMISSED_POINTERS_META_KEY, true ); return key_exists( static::CURRENT_POINTER_SLUG, $meta ); } public function __construct() { add_action( 'in_admin_header', [ $this, 'admin_print_script' ] ); } } backups/classes/route-base.php 0000644 00000001357 14717617543 0012435 0 ustar 00 <?php namespace ImageOptimization\Modules\Backups\Classes; use ImageOptimization\Classes\Route; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Route_Base extends Route { protected $auth = true; protected string $path = ''; public function get_methods(): array { return []; } public function get_endpoint(): string { return 'backups/' . $this->get_path(); } public function get_path(): string { return $this->path; } public function get_name(): string { return ''; } public function get_permission_callback( WP_REST_Request $request ): bool { $valid = $this->permission_callback( $request ); return $valid && user_can( $this->current_user_id, 'manage_options' ); } } backups/classes/remove-all-backups.php 0000644 00000001462 14717617543 0014055 0 ustar 00 <?php namespace ImageOptimization\Modules\Backups\Classes; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Async_Operation_Queue, }; use ImageOptimization\Classes\Image\Image_Query_Builder; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Remove_All_Backups { private const CHUNK_SIZE = 100; public static function find_and_schedule_removing(): void { $query = ( new Image_Query_Builder() ) ->return_images_only_with_backups() ->execute(); $attachment_ids = $query->posts; $chunks = array_chunk( $attachment_ids, self::CHUNK_SIZE ); foreach ( $chunks as $chunk ) { Async_Operation::create( Async_Operation_Hook::REMOVE_MANY_BACKUPS, [ 'attachment_ids' => $chunk ], Async_Operation_Queue::BACKUP ); } } } backups/classes/restore-images.php 0000644 00000005072 14717617543 0013313 0 ustar 00 <?php namespace ImageOptimization\Modules\Backups\Classes; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Async_Operation_Queue, }; use ImageOptimization\Classes\Image\{ Image_Meta, Image_Query_Builder, Image_Status }; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Restore_Images { private const CHUNK_SIZE = 100; /** * Schedules a single restoring operation. * * @param int $image_id * @return void * @throws Throwable * @throws \ImageOptimization\Classes\Async_Operation\Exceptions\Async_Operation_Exception */ public static function schedule_single_restoring( int $image_id ): void { $meta = new Image_Meta( $image_id ); try { $meta ->set_status( Image_Status::RESTORING_IN_PROGRESS ) ->save(); Async_Operation::create( Async_Operation_Hook::RESTORE_SINGLE_IMAGE, [ 'attachment_id' => $image_id ], Async_Operation_Queue::RESTORE ); } catch ( Throwable $t ) { $meta ->set_status( Image_Status::RESTORING_FAILED ) ->save(); throw $t; } } /** * Schedules restoring operations for all valid images with backups created. * * @param array $image_ids * * @return void */ public static function find_and_schedule_restoring( array $image_ids = [] ): void { $query = ( new Image_Query_Builder() ) ->return_images_only_with_backups(); if ( ! empty( $image_ids ) ) { $query->set_image_ids( $image_ids ); } $query = $query->execute(); $attachment_ids = $query->posts; $chunks = array_chunk( $attachment_ids, self::CHUNK_SIZE ); foreach ( $chunks as $chunk ) { try { Async_Operation::create( Async_Operation_Hook::RESTORE_MANY_IMAGES, [ 'attachment_ids' => $chunk ], Async_Operation_Queue::RESTORE ); self::mark_chunk_as_in_progress( $chunk ); } catch ( Throwable $t ) { self::fail_restoring_for_chunk( $chunk ); } } } /** * Updates chunk images optimization status to in-progress. * * @param int[] $chunk * @return void */ private static function mark_chunk_as_in_progress( array $chunk ) { foreach ( $chunk as $image_id ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::RESTORING_IN_PROGRESS ) ->save(); } } /** * Updates chunk images optimization status to failed. * * @param int[] $chunk * @return void */ private static function fail_restoring_for_chunk( array $chunk ) { foreach ( $chunk as $image_id ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::RESTORING_FAILED ) ->save(); } } } backups/rest/restore-all.php 0000644 00000001640 14717617543 0012133 0 ustar 00 <?php namespace ImageOptimization\Modules\Backups\Rest; use ImageOptimization\Modules\Backups\Classes\{ Restore_Images, Route_Base, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Restore_All extends Route_Base { const NONCE_NAME = 'image-optimization-restore-all'; protected string $path = 'restore'; public function get_name(): string { return 'restore-all'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); try { Restore_Images::find_and_schedule_restoring(); return $this->respond_success_json(); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } backups/rest/remove-backups.php 0000644 00000001655 14717617543 0012633 0 ustar 00 <?php namespace ImageOptimization\Modules\Backups\Rest; use ImageOptimization\Modules\Backups\Classes\{ Route_Base, Remove_All_Backups, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Remove_Backups extends Route_Base { const NONCE_NAME = 'image-optimization-remove-backups'; protected string $path = ''; public function get_name(): string { return 'remove-backups'; } public function get_methods(): array { return [ 'DELETE' ]; } public function DELETE( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); try { Remove_All_Backups::find_and_schedule_removing(); return $this->respond_success_json(); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } backups/rest/restore-single.php 0000644 00000002454 14717617543 0012650 0 ustar 00 <?php namespace ImageOptimization\Modules\Backups\Rest; use ImageOptimization\Modules\Optimization\Classes\Validate_Image; use ImageOptimization\Modules\Backups\Classes\{ Restore_Images, Route_Base, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Restore_Single extends Route_Base { const NONCE_NAME = 'image-optimization-restore-single'; protected string $path = 'restore/(?P<image_id>\d+)'; public function get_name(): string { return 'restore-single'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); $image_id = (int) $request->get_param( 'image_id' ); if ( empty( $image_id ) ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Invalid image id', 'image-optimization' ), 'code' => 'internal_server_error', ] ); } try { Validate_Image::is_valid( $image_id ); Restore_Images::schedule_single_restoring( $image_id ); return $this->respond_success_json(); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } backups/module.php 0000644 00000001210 14717617543 0010203 0 ustar 00 <?php namespace ImageOptimization\Modules\Backups; use ImageOptimization\Classes\Module_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Module_Base { public function get_name(): string { return 'backups'; } public static function routes_list() : array { return [ 'Remove_Backups', 'Restore_Single', 'Restore_All', ]; } public static function component_list() : array { return [ 'Handle_Backups_Removing', 'Restore_Images', ]; } /** * Module constructor. */ public function __construct() { $this->register_components(); $this->register_routes(); } } backups/components/handle-backups-removing.php 0000644 00000001561 14717617543 0015621 0 ustar 00 <?php namespace ImageOptimization\Modules\Backups\Components; use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook; use ImageOptimization\Classes\Image\Image_Backup; use WP_Post; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Handle_Backups_Removing { public function remove_backups_on_attachment_removing( int $attachment_id, WP_Post $attachment_post ) { if ( ! wp_attachment_is_image( $attachment_post ) ) { return; } Image_Backup::remove( $attachment_id ); } /** @async */ public function remove_many_backups( array $attachment_ids ) { Image_Backup::remove_many( $attachment_ids ); } public function __construct() { add_action( 'delete_attachment', [ $this, 'remove_backups_on_attachment_removing' ], 10, 2 ); add_action( Async_Operation_Hook::REMOVE_MANY_BACKUPS, [ $this, 'remove_many_backups' ] ); } } backups/components/restore-images.php 0000644 00000002055 14717617543 0014041 0 ustar 00 <?php namespace ImageOptimization\Modules\Backups\Components; use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook; use ImageOptimization\Classes\Image\{ Image_Meta, Image_Restore, Image_Status, }; use ImageOptimization\Classes\Logger; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Restore_Images { /** @async */ public function restore_image( int $image_id ) { try { Image_Restore::restore( $image_id ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Image restoring error: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::RESTORING_FAILED ) ->save(); throw $t; } } /** @async */ public function restore_many_images( array $attachment_ids ) { Image_Restore::restore_many( $attachment_ids ); } public function __construct() { add_action( Async_Operation_Hook::RESTORE_SINGLE_IMAGE, [ $this, 'restore_image' ] ); add_action( Async_Operation_Hook::RESTORE_MANY_IMAGES, [ $this, 'restore_many_images' ] ); } } connect/classes/route-base.php 0000644 00000001645 14717617543 0012436 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Classes; use ImageOptimization\Classes\Rest\Route; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Route_Base */ class Route_Base extends Route { protected bool $override = false; protected $auth = true; protected string $path = ''; public function get_methods(): array { return []; } public function get_endpoint(): string { return 'connect/' . $this->get_path(); } public function get_path(): string { return $this->path; } public function get_name(): string { return ''; } public function post_permission_callback( \WP_REST_Request $request ): bool { return $this->get_permission_callback( $request ); } public function get_permission_callback( \WP_REST_Request $request ): bool { $valid = $this->permission_callback( $request ); return $valid && user_can( $this->current_user_id, 'manage_options' ); } } connect/classes/grant-types.php 0000644 00000000524 14717617543 0012640 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class GrantTypes */ class GrantTypes extends Basic_Enum { const CLIENT_CREDENTIALS = 'client_credentials'; const AUTHORIZATION_CODE = 'authorization_code'; const REFRESH_TOKEN = 'refresh_token'; } connect/classes/config.php 0000644 00000001157 14717617543 0011633 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Config */ class Config { const APP_NAME = 'image-optimizer'; const APP_PREFIX = 'image_optimizer'; const APP_REST_NAMESPACE = 'image_optimizer'; const BASE_URL = 'https://my.elementor.com/connect'; const ADMIN_PAGE = 'upload.php?page=image-optimization-settings'; const APP_TYPE = 'app_io'; const SCOPES = 'openid offline_access'; const STATE_NONCE = 'image_optimizer_auth_nonce'; /** * Connect mode * accepts 'site' or 'user' */ const CONNECT_MODE = 'site'; } connect/classes/data.php 0000644 00000021555 14717617543 0011303 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Data */ class Data { const CLIENT_ID = '_client_id'; const CLIENT_SECRET = '_client_secret'; const ACCESS_TOKEN = '_access_token'; const REFRESH_TOKEN = '_refresh_token'; const TOKEN_ID = '_token_id'; const SUBSCRIPTION_ID = '_subscription_id'; const OPTION_OWNER_USER_ID = '_owner_user_id'; const HOME_URL = '_home_url'; /** * get_option * @param $option_name * @param $default * * @return false|mixed|null */ public static function get_option( $option_name, $default ) { return get_option( Config::APP_PREFIX . $option_name, $default ); } /** * set_option * @param $option_name * @param $option_value * @param $auto_load * * @return bool */ public static function set_option( $option_name, $option_value, $auto_load = false ): bool { return update_option( Config::APP_PREFIX . $option_name, $option_value, $auto_load ); } /** * delete_option * @param $option_name * * @return bool */ public static function delete_option( $option_name ) : bool { return delete_option( Config::APP_PREFIX . $option_name ); } /** * get_user_data * @param $user_id * @param $data_name * @param mixed|bool $default * * @return false|mixed */ public static function get_user_data( $user_id, $data_name, $default = false ) { $data = get_user_meta( $user_id, Config::APP_PREFIX . $data_name, true ); return empty( $data ) ? $default : $data; } /** * set_user_data * @param $user_id * @param $data_name * @param $value * * @return bool|int */ public static function set_user_data( $user_id, $data_name, $value ) { return update_user_meta( $user_id, Config::APP_PREFIX . $data_name, $value ); } /** * delete_user_data * @param $user_id * @param $data_name * * @return bool */ public static function delete_user_data( $user_id, $data_name ): bool { return delete_user_meta( $user_id, Config::APP_PREFIX . $data_name ); } /** * get_connect_mode_data * @param ...$data * * @return false|mixed|null|string */ public static function get_connect_mode_data( ...$data ) { if ( Config::CONNECT_MODE === 'site' ) { return self::get_option( ...$data ); } $user_id = get_current_user_id(); return self::get_user_data( ...( [ $user_id, ...$data ] ) ); } /** * set_connect_mode_data * @param ...$data * * @return bool|int */ public static function set_connect_mode_data( ...$data ) { if ( Config::CONNECT_MODE === 'site' ) { return self::set_option( ...$data ); } $user_id = get_current_user_id(); return self::set_user_data( ...( [ $user_id, ...$data ] ) ); } /** * get_client_id * * @return string|bool|mixed */ public static function get_client_id() { return self::get_connect_mode_data( self::CLIENT_ID, false ); } /** * get_client_secret * @return false|mixed|string|null */ public static function get_client_secret() { return self::get_connect_mode_data( self::CLIENT_SECRET, false ); } /** * set_client_id * @param $value * * @return bool */ public static function set_client_id( $value ): bool { return self::set_connect_mode_data( self::CLIENT_ID, $value ); } public static function get_subscription_id() { return self::get_connect_mode_data( self::SUBSCRIPTION_ID, false ); } public static function set_subscription_id( $value ): bool { return self::set_connect_mode_data( self::SUBSCRIPTION_ID, $value ); } /** * set_client_secret * @param $value * * @return bool */ public static function set_client_secret( $value ): bool { return self::set_connect_mode_data( self::CLIENT_SECRET, $value ); } /** * get_access_token * @return false|mixed|string|null */ public static function get_access_token() { return self::get_connect_mode_data( self::ACCESS_TOKEN, false ); } public static function get_token_id() { return self::get_connect_mode_data( self::TOKEN_ID, false ); } /** * get_refresh_token * @return false|mixed|string|null */ public static function get_refresh_token() { return self::get_connect_mode_data( self::REFRESH_TOKEN, false ); } public static function get_home_url() { $raw = self::get_connect_mode_data( self::HOME_URL, false ); $is_base64 = base64_encode( base64_decode( $raw, true ) ) === $raw; return $is_base64 ? base64_decode( $raw ) : $raw; } /** * set_home_url * * Stores home URL as a base64 string to avoid migration/stg tools from overriding value */ public static function set_home_url( ?string $home_url = null ): bool { $home_url = $home_url ?? home_url(); return self::set_connect_mode_data( self::HOME_URL, base64_encode( $home_url ) ); } /** * set_user_is_owner_option */ public static function set_user_is_owner_option( $value ) { return self::set_connect_mode_data( self::OPTION_OWNER_USER_ID, $value ); } /** * get_user_is_owner_option */ public static function get_user_is_owner_option() { return self::get_connect_mode_data( self::OPTION_OWNER_USER_ID, false ); } /** * fetch_option * direct query to avoid cache and race condition issues * * @param $option_name * @param $default * * @return mixed|null */ public static function fetch_option( $option_name, $default = null ) { global $wpdb; $cache_buster = wp_generate_uuid4(); $option = $wpdb->get_col( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s AND %s = %s LIMIT 1", $option_name, $cache_buster, $cache_buster ) ); if ( ! empty( $option ) ) { return $option[0]; } return $default; } /** * insert_option_uniquely * used to insert option if not there already * direct query to avoid cache and race condition issues * @param $option_name * @param $option_value * * @return bool */ public static function insert_option_uniquely( $option_name, $option_value ): bool { global $wpdb; $cache_buster = wp_generate_uuid4(); $result = $wpdb->query( $wpdb->prepare( "INSERT INTO $wpdb->options (option_name, option_value, autoload) SELECT * FROM (SELECT %s, %s, 'no') AS tmp WHERE NOT EXISTS ( SELECT option_name FROM $wpdb->options WHERE option_name = %s AND option_value = %s AND %s = %s ) LIMIT 1", $option_name, $option_value, $option_name, $option_value, $cache_buster, $cache_buster ) ); if ( false === $result || 0 === $result ) { // false means query failed, 0 means no row inserted because it exists return false; } return true; } /** * User is subscription owner. * * Check if current user is subscription owner. * * @return boolean */ public static function user_is_subscription_owner(): bool { $owner_id = (int) self::get_connect_mode_data( self::OPTION_OWNER_USER_ID, false ); return get_current_user_id() === $owner_id; } public static function ensure_reset_connect() { if ( Config::CONNECT_MODE === 'site' ) { self::delete_option( self::ACCESS_TOKEN ); self::delete_option( self::REFRESH_TOKEN ); self::delete_option( self::TOKEN_ID ); self::delete_option( self::SUBSCRIPTION_ID ); self::delete_option( self::OPTION_OWNER_USER_ID ); self::delete_option( self::HOME_URL ); delete_transient( 'image_optimizer_status_check' ); } else { $user_id = get_current_user_id(); self::delete_user_data( $user_id, self::ACCESS_TOKEN ); self::delete_user_data( $user_id, self::REFRESH_TOKEN ); self::delete_user_data( $user_id, self::TOKEN_ID ); self::delete_user_data( $user_id, self::SUBSCRIPTION_ID ); self::delete_user_data( $user_id, self::OPTION_OWNER_USER_ID ); self::delete_user_data( $user_id, self::HOME_URL ); delete_transient( 'image_optimizer_status_check' ); } } /** * clear_session */ public static function clear_session() { if ( Config::CONNECT_MODE === 'site' ) { self::delete_option( self::CLIENT_ID ); self::delete_option( self::CLIENT_SECRET ); self::delete_option( self::ACCESS_TOKEN ); self::delete_option( self::REFRESH_TOKEN ); self::delete_option( self::TOKEN_ID ); self::delete_option( self::SUBSCRIPTION_ID ); self::delete_option( self::OPTION_OWNER_USER_ID ); self::delete_option( self::HOME_URL ); delete_transient( 'image_optimizer_status_check' ); } else { $user_id = get_current_user_id(); self::delete_user_data( $user_id, self::CLIENT_ID ); self::delete_user_data( $user_id, self::CLIENT_SECRET ); self::delete_user_data( $user_id, self::ACCESS_TOKEN ); self::delete_user_data( $user_id, self::REFRESH_TOKEN ); self::delete_user_data( $user_id, self::TOKEN_ID ); self::delete_user_data( $user_id, self::SUBSCRIPTION_ID ); self::delete_user_data( $user_id, self::OPTION_OWNER_USER_ID ); self::delete_user_data( $user_id, self::HOME_URL ); delete_transient( 'image_optimizer_status_check' ); } } } connect/classes/exceptions/service-exception.php 0000644 00000000371 14717617543 0016200 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Service_Exception extends Exception { protected $message = 'Service Exception'; } connect/classes/service.php 0000644 00000016276 14717617543 0012036 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Classes; use ImageOptimization\Modules\Connect\Classes\Exceptions\Service_Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Service */ class Service { const REFRESH_TOKEN_LOCK = '_connect_refresh_token'; /** * Registers new client and returns client ID * * @return string * @throws Service_Exception */ public static function register_client(): string { $clients_url = Utils::get_clients_url(); if ( ! $clients_url ) { throw new Service_Exception( 'Missing client registration URL' ); } $client_data = self::request( $clients_url, [ 'method' => 'POST', 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => wp_json_encode( [ 'redirect_uri' => Utils::get_redirect_uri(), 'app_type' => Config::APP_TYPE, ] ), ], 201 ); $client_id = $client_data['client_id'] ?? null; $client_secret = $client_data['client_secret'] ?? null; Data::set_client_id( $client_id ); Data::set_client_secret( $client_secret ); Data::set_home_url(); return $client_id; } /** * Deactivate license * * @return void * @throws Service_Exception */ public static function deactivate_license(): void { $client_id = Data::get_client_id(); if ( ! $client_id ) { throw new Service_Exception( 'Missing client ID' ); } $deactivation_url = Utils::get_deactivation_url( $client_id ); if ( ! $deactivation_url ) { throw new Service_Exception( 'Missing deactivation URL' ); } $access_token = Data::get_access_token(); if ( ! $access_token ) { throw new Service_Exception( 'Missing access token' ); } $refresh_token = Data::get_refresh_token(); if ( ! $refresh_token ) { throw new Service_Exception( 'Missing refresh token' ); } self::request( $deactivation_url, [ 'method' => 'DELETE', 'headers' => [ 'Authorization' => "Bearer {$access_token}", ], ], 204 ); self::get_token( 'refresh_token', $refresh_token ); } /** * disconnect * * @return void * @throws Service_Exception */ public static function disconnect(): void { $sessions_url = Utils::get_sessions_url(); if ( ! $sessions_url ) { throw new Service_Exception( 'Missing sessions URL' ); } $access_token = Data::get_access_token(); if ( ! $access_token ) { throw new Service_Exception( 'Missing access token' ); } self::request( $sessions_url, [ 'method' => 'DELETE', 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => "Bearer {$access_token}", ], ], 204 ); Data::clear_session(); } /** * Get token & optionally save to user * * @param string $grant_type * * @param string|null $credential * * @return array * @throws Service_Exception */ public static function get_token( string $grant_type, ?string $credential = null, ?bool $update = true ): array { $token_url = Utils::get_token_url(); if ( ! $token_url ) { throw new Service_Exception( 'Missing token URL' ); } $client_id = Data::get_client_id(); $client_secret = Data::get_client_secret(); if ( empty( $client_id ) || empty( $client_secret ) ) { throw new Service_Exception( 'Missing client ID or secret' ); } $body = [ 'grant_type' => $grant_type, 'redirect_uri' => Utils::get_redirect_uri(), ]; switch ( $grant_type ) { case GrantTypes::AUTHORIZATION_CODE: $body['code'] = $credential; break; case GrantTypes::REFRESH_TOKEN: $body[ GrantTypes::REFRESH_TOKEN ] = $credential; break; case GrantTypes::CLIENT_CREDENTIALS: $body['redirect_uri'] = Utils::get_redirect_uri( Data::get_home_url() ); break; default: throw new Service_Exception( 'Invalid grant type' ); } $data = self::request( $token_url, [ 'method' => 'POST', 'headers' => [ 'x-elementor-apps' => Config::APP_NAME, 'Authorization' => 'Basic ' . base64_encode( "{$client_id}:{$client_secret}" ), ], 'body' => $body, ] ); if ( $update ) { Data::set_connect_mode_data( Data::TOKEN_ID, $data['id_token'] ?? null ); Data::set_connect_mode_data( Data::ACCESS_TOKEN, $data['access_token'] ?? null ); Data::set_connect_mode_data( Data::REFRESH_TOKEN, $data['refresh_token'] ?? null ); Data::set_connect_mode_data( Data::OPTION_OWNER_USER_ID, get_current_user_id() ); } return $data; } public static function jwt_decode( $payload ): string { static $jwks = null; $jwks_url = Utils::get_jwks_url(); if ( ! $jwks_url ) { return __( 'Missing JWKS URL' ); } if ( ! $jwks ) { $jwks = self::request( $jwks_url, [ 'method' => 'GET', ] ); } if ( ! class_exists( 'JWT' ) ) { require_once IMAGE_OPTIMIZATION_PATH . 'vendor/autoload.php'; } try { $decoded = \Firebase\JWT\JWT::decode( $payload, \Firebase\JWT\JWK::parseKeySet( $jwks ) ); return json_encode( $decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); } catch ( \Throwable $th ) { if ( $th instanceof \Firebase\JWT\ExpiredException ) { self::get_token( GrantTypes::REFRESH_TOKEN, Data::get_refresh_token() ); return self::jwt_decode( $payload ); } return $th->getMessage(); } } /** * @param string $url * @param array $args * @param int $valid_response_code * * @return array|null * @throws Service_Exception */ public static function request( string $url, array $args, int $valid_response_code = 200 ): ?array { $args['timeout'] = 30; $response = wp_remote_request( $url, $args ); if ( is_wp_error( $response ) ) { error_log( '[Connect Service]: ' . $response->get_error_message() ); throw new Service_Exception( $response->get_error_message() ); } if ( $valid_response_code !== wp_remote_retrieve_response_code( $response ) ) { error_log( '[Connect Service]: invalid status code' . wp_remote_retrieve_response_code( $response ) ); throw new Service_Exception( wp_remote_retrieve_body( $response ) ); } return json_decode( wp_remote_retrieve_body( $response ), true ); } public static function refresh_token() { $lock_key = Config::APP_NAME . self::REFRESH_TOKEN_LOCK; $last_token = Data::fetch_option( $lock_key, '' ); $current_refresh_token = Data::get_refresh_token(); if ( ! empty( $last_token ) && $last_token === $current_refresh_token ) { sleep( 1 ); return; } delete_option( $lock_key ); $locked = Data::insert_option_uniquely( $lock_key, $current_refresh_token ); if ( ! $locked ) { sleep( 1 ); return; } self::get_token( GrantTypes::REFRESH_TOKEN, $current_refresh_token ); } /** * @throws Service_Exception */ public static function update_redirect_uri(): void { $client_id = Data::get_client_id(); if ( ! $client_id ) { throw new Service_Exception( 'Missing client ID' ); } $client_patch_url = Utils::get_clients_patch_url( $client_id ); [ 'access_token' => $access_token ] = self::get_token( GrantTypes::CLIENT_CREDENTIALS, null, false ); self::request( $client_patch_url, [ 'method' => 'PATCH', 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => "Bearer {$access_token}", ], 'body' => json_encode( [ 'redirect_uri' => Utils::get_redirect_uri(), ] ), ] ); self::refresh_token(); Data::set_home_url(); } } connect/classes/basic-enum.php 0000644 00000001313 14717617543 0012403 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Classes; use ReflectionClass; use ReflectionException; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Basic_Enum { private static array $entries = []; /** * @throws ReflectionException */ public static function get_values(): array { return array_values( self::get_entries() ); } /** * @throws ReflectionException */ protected static function get_entries(): array { $caller = get_called_class(); if ( ! array_key_exists( $caller, self::$entries ) ) { $reflect = new ReflectionClass( $caller ); self::$entries[ $caller ] = $reflect->getConstants(); } return self::$entries[ $caller ]; } } connect/classes/utils.php 0000644 00000005536 14717617543 0011533 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Classes; use ImageOptimization\Modules\Connect\Classes\Exceptions\Service_Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Utils */ class Utils { /** * get_clients_url * @return string */ public static function get_clients_url(): string { return self::get_base_url() . '/api/v1/clients'; } /** * get_redirect_uri * @return string */ public static function get_redirect_uri( string $domain = '' ): string { if ( false !== strpos( Config::ADMIN_PAGE, '?page=' ) ) { $admin_url = admin_url( Config::ADMIN_PAGE ); } else { $admin_url = admin_url( 'options-general.php?page=' . Config::ADMIN_PAGE ); } if ( $domain ) { $parsed_url = parse_url( $admin_url ); $path = $parsed_url['path'] . ( isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '' ); return rtrim( $domain, '/' ) . $path; } return $admin_url; } public static function get_auth_url(): string { return self::get_base_url() . '/v1/oauth2/auth'; } /** * Get full authorization URL with all required parameters * * @param string $client_id * * @return string * @throws Service_Exception */ public static function get_authorize_url( string $client_id ): string { return add_query_arg( [ 'client_id' => $client_id, 'redirect_uri' => rawurlencode( self::get_redirect_uri() ), 'response_type' => 'code', 'scope' => Config::SCOPES, 'state' => wp_create_nonce( Config::STATE_NONCE ), ], self::get_auth_url() ); } /** * get_deactivation_url * @param string $client_id * * @return string */ public static function get_deactivation_url( string $client_id ): string { return self::get_base_url() . "/api/v1/clients/{$client_id}/activation"; } public static function get_jwks_url(): string { return self::get_base_url() . '/v1/.well-known/jwks.json'; } /** * get_sessions_url * @return string */ public static function get_sessions_url(): string { return self::get_base_url() . '/api/v1/session'; } public static function get_token_url(): string { return self::get_base_url() . '/api/v1/oauth2/token'; } /** * Get clients URL * * @param string $client_id * * @return string */ public static function get_clients_patch_url( string $client_id ): string { return self::get_base_url() . "/api/v1/clients/{$client_id}"; } /** * get_base_url * @return string */ public static function get_base_url(): string { return apply_filters( 'image_optimizer_connect_get_base_url', Config::BASE_URL ); } /** * is_valid_home_url * @return bool */ public static function is_valid_home_url(): bool { static $valid = null; if ( null === $valid ) { if ( empty( Data::get_home_url() ) ) { $valid = true; } else { $valid = Data::get_home_url() === home_url(); } } return $valid; } } connect/rest/authorize.php 0000644 00000004610 14717617543 0011715 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Rest; use ImageOptimization\Modules\Connect\Classes\{ Data, Route_Base, Service, Utils }; use ImageOptimization\Modules\Connect\Module as Connect; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Authorize */ class Authorize extends Route_Base { public string $path = 'authorize'; public const NONCE_NAME = 'wp_rest'; public function get_methods(): array { return [ 'POST' ]; } public function get_name(): string { return 'authorize'; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( Connect::is_connected() && Utils::is_valid_home_url() ) { return $this->respond_error_json( [ 'message' => esc_html__( 'You are already connected', 'image-optimization' ), 'code' => 'forbidden', ] ); } try { $client_id = Data::get_client_id(); if ( ! $client_id ) { $client_id = Service::register_client(); } if ( ! Utils::is_valid_home_url() ) { if ( $request->get_param( 'update_redirect_uri' ) ) { Service::update_redirect_uri(); } else { return $this->respond_error_json( [ 'message' => esc_html__( 'Connected domain mismatch', 'image-optimization' ), 'code' => 'forbidden', ] ); } } $authorize_url = Utils::get_authorize_url( $client_id ); $additional_source_campaign = []; $image_optimization_campaign = get_transient( 'elementor_image_optimization_campaign' ); if ( ! empty( $image_optimization_campaign['source'] ) ) { $additional_source_campaign['utm_source'] = $image_optimization_campaign['source']; } if ( ! empty( $image_optimization_campaign['medium'] ) ) { $additional_source_campaign['utm_medium'] = $image_optimization_campaign['medium']; } if ( ! empty( $image_optimization_campaign['campaign'] ) ) { $additional_source_campaign['utm_campaign'] = $image_optimization_campaign['campaign']; } if ( ! empty( $additional_source_campaign ) ) { $authorize_url = add_query_arg( $additional_source_campaign, $authorize_url ); } return $this->respond_success_json( $authorize_url ); } catch ( Throwable $t ) { return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } connect/rest/version.php 0000644 00000001531 14717617543 0011367 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Rest; use ImageOptimization\Modules\Connect\Classes\{ Data, Route_Base, Service, Utils }; use ImageOptimization\Modules\Connect\Module as Connect; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Version */ class Version extends Route_Base { public string $path = 'version'; public const NONCE_NAME = 'wp_rest'; public function get_methods(): array { return [ 'GET' ]; } public function get_name(): string { return 'version'; } public function GET( WP_REST_Request $request ) { try { return $this->respond_success_json([ 'version' => 2, ]); } catch ( Throwable $t ) { return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } connect/rest/disconnect.php 0000644 00000001361 14717617543 0012034 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Rest; use ImageOptimization\Modules\Connect\Classes\{ Data, Route_Base, Service }; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Disconnect */ class Disconnect extends Route_Base { public string $path = 'disconnect'; public function get_methods(): array { return [ 'POST' ]; } public function get_name(): string { return 'disconnect'; } public function POST() { try { Service::disconnect(); return $this->respond_success_json(); } catch ( Throwable $t ) { Data::ensure_reset_connect(); return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } connect/rest/deactivate-and-disconnect.php 0000644 00000001755 14717617543 0014712 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Rest; use ImageOptimization\Modules\Connect\Classes\{ Data, Route_Base, Service }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Disconnect */ class Deactivate_And_Disconnect extends Route_Base { public string $path = 'deactivate_and_disconnect'; public function get_methods(): array { return [ 'POST' ]; } public function get_name(): string { return 'deactivate_and_disconnect'; } public function POST( WP_REST_Request $request ) { try { if ( $request->get_param( 'clear_session' ) ) { Data::clear_session(); return $this->respond_success_json(); } Service::deactivate_license(); Service::disconnect(); return $this->respond_success_json(); } catch ( Throwable $t ) { Data::ensure_reset_connect(); return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } connect/rest/switch-domain.php 0000644 00000002333 14717617543 0012451 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Rest; use ImageOptimization\Modules\Connect\Classes\{ Data, Route_Base, Service, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Switch_Domain */ class Switch_Domain extends Route_Base { public string $path = 'switch_domain'; public const NONCE_NAME = 'wp_rest'; public function get_methods(): array { return [ 'POST' ]; } public function get_name(): string { return 'switch_domain'; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); try { $client_id = Data::get_client_id(); if ( ! $client_id ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Client ID not found', 'image-optimization' ), 'code' => 'bad_request', ] ); } Service::update_redirect_uri(); return $this->respond_success_json( [ 'message' => esc_html__( 'Domain updated!', 'image-optimization' ), ] ); } catch ( Throwable $t ) { return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } connect/rest/deactivate.php 0000644 00000001371 14717617543 0012015 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Rest; use ImageOptimization\Modules\Connect\Classes\{ Data, Route_Base, Service }; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Deactivate */ class Deactivate extends Route_Base { public string $path = 'deactivate'; public function get_methods(): array { return [ 'POST' ]; } public function get_name(): string { return 'deactivate'; } public function POST() { try { Service::deactivate_license(); return $this->respond_success_json(); } catch ( Throwable $t ) { Data::ensure_reset_connect(); return $this->respond_error_json( [ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ] ); } } } connect/module.php 0000644 00000002314 14717617543 0010212 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect; use ImageOptimization\Classes\Module_Base; use ImageOptimization\Modules\Connect\Classes\{ Data, Utils, }; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Module */ class Module extends Module_Base { /** * Get module name. * Retrieve the module name. * @access public * @return string Module name. */ public function get_name() { return 'connect'; } /** * component_list * @return string[] */ public static function component_list() : array { return [ 'Handler', ]; } /** * routes_list * @return string[] */ public static function routes_list() : array { return [ 'Authorize', 'Disconnect', 'Deactivate', 'Deactivate_And_Disconnect', 'Version', 'Switch_Domain', ]; } public static function is_connected() : bool { return ! ! Data::get_access_token() && Utils::is_valid_home_url(); } public static function is_active() : bool { // TODO: Add login to check if the function should be active or not. return empty( get_option( 'image_optimizer_client_data' ) ); } public function __construct() { $this->register_components(); $this->register_routes(); } } connect/components/handler.php 0000644 00000003336 14717617543 0012534 0 ustar 00 <?php namespace ImageOptimization\Modules\Connect\Components; use ImageOptimization\Modules\Connect\Classes\{ Config, Data, GrantTypes, Service, Utils, }; use ImageOptimization\Classes\Services\Client; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Handler */ class Handler { private function should_handle_auth_code(): bool { global $plugin_page; $page_slug = explode( 'page=', Config::ADMIN_PAGE ); $is_connect_admin_page = false; if ( ! empty( $page_slug[1] ) && $page_slug[1] === $plugin_page ) { $is_connect_admin_page = true; } if ( ! $is_connect_admin_page && Config::ADMIN_PAGE === $plugin_page ) { $is_connect_admin_page = true; } if ( ! $is_connect_admin_page ) { return false; } $code = $_GET['code'] ?? null; $state = $_GET['state'] ?? null; if ( empty( $code ) || empty( $state ) ) { return false; } return true; } private function validate_nonce( $state ) { if ( ! wp_verify_nonce( $state, Config::STATE_NONCE ) ) { wp_die( 'Invalid state' ); } } public function handle_auth_code() { if ( ! $this->should_handle_auth_code() ) { return; } $code = sanitize_text_field( $_GET['code'] ); $state = sanitize_text_field( $_GET['state'] ); // Check if the state is valid $this->validate_nonce( $state ); // Exchange the code for an access token and store it Service::get_token( GrantTypes::AUTHORIZATION_CODE, $code ); // Makes sure we won't stick in the mismatch limbo Data::set_home_url(); // Redirect to the redirect URI wp_redirect( Utils::get_redirect_uri() ); exit; } /** * Handler constructor. */ public function __construct() { add_action( 'admin_init', [ $this, 'handle_auth_code' ] ); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.3-4ubuntu2.24 | Генерация страницы: 0.02 |
proxy
|
phpinfo
|
Настройка