Featherwell: an overview with the bulk jobs, the list of images * with their format and savings, and the settings. Plus a column in the * Media Library list. * * @package Featherwell */ if ( ! defined( 'ABSPATH' ) ) { exit; } class Featherwell_Admin { const SLUG = 'featherwell'; const CAP = 'manage_options'; const PER_PAGE = 20; public function __construct() { add_action( 'admin_menu', array( $this, 'menu' ) ); add_action( 'admin_post_featherwell_settings', array( $this, 'handle_settings' ) ); add_action( 'admin_post_featherwell_one', array( $this, 'handle_one' ) ); add_filter( 'manage_media_columns', array( $this, 'add_column' ) ); add_action( 'manage_media_custom_column', array( $this, 'column' ), 10, 2 ); add_filter( 'plugin_action_links_' . plugin_basename( FEATHERWELL_FILE ), array( $this, 'action_links' ) ); } public function menu() { $hook = add_media_page( __( 'Featherwell', 'featherwell' ), __( 'Featherwell', 'featherwell' ), self::CAP, self::SLUG, array( $this, 'render' ) ); add_action( 'admin_print_styles-' . $hook, function () { wp_enqueue_style( 'featherwell-admin', FEATHERWELL_URL . 'assets/admin.css', array(), FEATHERWELL_VERSION ); } ); add_action( 'admin_print_scripts-' . $hook, function () { wp_enqueue_script( 'featherwell-admin', FEATHERWELL_URL . 'assets/admin.js', array(), FEATHERWELL_VERSION, true ); wp_localize_script( 'featherwell-admin', 'featherwellJob', array( 'ajax' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( Featherwell_Jobs::NONCE ), 'job' => Featherwell_Jobs::view( Featherwell_Jobs::current() ), 'i18n' => array( 'paused' => __( 'Paused. You can continue at any time, also after leaving this page.', 'featherwell' ), 'finished' => __( 'Finished.', 'featherwell' ), 'error' => __( 'The server did not answer. Click Continue to try again.', 'featherwell' ), 'confirm' => array( 'undo' => __( 'Put every converted image back in its original format? Files that something links to are kept.', 'featherwell' ), 'cleanup' => __( 'Remove old copies that nothing links to? Your original uploads are never removed, so every image can still be rebuilt.', 'featherwell' ), ), ), 'refresh' => self::url(), ) ); } ); } /** * @param array $links Links. * @return array */ public function action_links( $links ) { array_unshift( $links, '' . esc_html__( 'Open', 'featherwell' ) . '' ); return $links; } /** * @param array $args Query args. * @return string */ public static function url( $args = array() ) { return add_query_arg( array_merge( array( 'page' => self::SLUG ), $args ), admin_url( 'upload.php' ) ); } // ------------------------------------------------------------------ // Screens // ------------------------------------------------------------------ public function render() { if ( ! current_user_can( self::CAP ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display only. $view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'overview'; $tabs = array( 'overview' => __( 'Overview', 'featherwell' ), 'images' => __( 'Images', 'featherwell' ), 'settings' => __( 'Settings', 'featherwell' ), ); if ( ! isset( $tabs[ $view ] ) ) { $view = 'overview'; } ?>

notice(); ?> view_images(); } elseif ( 'settings' === $view ) { $this->view_settings(); } else { $this->view_overview(); } ?>
0 ? 100 - round( 100 * $t['after'] / $t['before'] ) : 0; ?>

server_text() ); ?>

__( 'All images', 'featherwell' ), 'ready' => __( 'Ready', 'featherwell' ), 'converted' => __( 'Converted', 'featherwell' ), 'skipped' => __( 'Skipped', 'featherwell' ), 'undone' => __( 'Undone', 'featherwell' ), ); if ( ! isset( $filters[ $show ] ) ) { $show = 'all'; } $query = new WP_Query( $this->images_query( $show, $paged ) ); ?> posts ) : ?> posts as $post ) : ?> image_row( $post ); ?>
max_num_pages; if ( $pages > 1 ) { echo '
'; echo wp_kses_post( paginate_links( array( 'base' => add_query_arg( 'paged', '%#%', self::url( array( 'view' => 'images', 'show' => $show ) ) ), 'format' => '', 'current' => $paged, 'total' => $pages, ) ) ); echo '
'; } } /** * @param string $show Filter. * @param int $paged Page. * @return array */ private function images_query( $show, $paged ) { $args = array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'posts_per_page' => self::PER_PAGE, 'paged' => $paged, 'orderby' => 'ID', 'order' => 'DESC', ); $has = function ( $key ) { return array( 'key' => $key, 'compare' => 'EXISTS', ); }; $not = function ( $key ) { return array( 'key' => $key, 'compare' => 'NOT EXISTS', ); }; switch ( $show ) { case 'converted': $args['meta_query'] = array( $has( Featherwell_Library::RECORD ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin list, paged. break; case 'skipped': $args['meta_query'] = array( $has( Featherwell_Library::SKIP ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin list, paged. break; case 'undone': $args['meta_query'] = array( $has( Featherwell_Library::UNDONE ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin list, paged. break; case 'ready': $args['post_mime_type'] = Featherwell_Settings::source_mimes(); $args['meta_query'] = array( $not( Featherwell_Library::RECORD ), $not( Featherwell_Library::SKIP ), $not( Featherwell_Library::UNDONE ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin list, paged. break; } return $args; } /** * @param WP_Post $post Attachment. */ private function image_row( $post ) { $id = (int) $post->ID; $status = Featherwell_Library::status( $id ); $record = get_post_meta( $id, Featherwell_Library::RECORD, true ); $file = (string) get_post_meta( $id, '_wp_attached_file', true ); $labels = array( 'converted' => __( 'Converted', 'featherwell' ), 'modern' => __( 'Already WebP or AVIF', 'featherwell' ), 'ready' => __( 'Ready', 'featherwell' ), 'skipped' => __( 'Skipped', 'featherwell' ), 'undone' => __( 'Undone', 'featherwell' ), 'other' => __( 'Not converted (format)', 'featherwell' ), ); ?> '' ) ); ?> post_title ? $post->post_title : wp_basename( $file ) ); ?>
one_button( $id, 'undo', __( 'Undo', 'featherwell' ) ); ?> one_button( $id, 'convert', __( 'Convert', 'featherwell' ) ); ?>
'JPEG', 'jpeg' => 'JPEG', 'jpe' => 'JPEG', 'png' => 'PNG', 'webp' => 'WebP', 'avif' => 'AVIF', 'gif' => 'GIF', ); return isset( $labels[ $ext ] ) ? $labels[ $ext ] : strtoupper( (string) $ext ); } // ------------------------------------------------------------------ // Media Library column // ------------------------------------------------------------------ /** * @param array $columns Columns. * @return array */ public function add_column( $columns ) { $columns['featherwell'] = __( 'Format', 'featherwell' ); return $columns; } /** * @param string $column Column. * @param int $id Attachment. */ public function column( $column, $id ) { if ( 'featherwell' !== $column || ! wp_attachment_is_image( $id ) ) { return; } $status = Featherwell_Library::status( $id ); $format = self::format_label( $status['format'] ); $before = (int) get_post_meta( $id, Featherwell_Library::BEFORE, true ); $after = (int) get_post_meta( $id, Featherwell_Library::AFTER, true ); if ( $before && $after ) { /* translators: 1: format, 2: percent */ echo esc_html( sprintf( __( '%1$s, %2$s%% lighter', 'featherwell' ), $format, number_format_i18n( 100 - round( 100 * $after / $before ) ) ) ); } else { echo esc_html( $format ); } } // ------------------------------------------------------------------ // Actions // ------------------------------------------------------------------ private function notice() { $key = 'featherwell_notice_' . get_current_user_id(); $notice = get_transient( $key ); if ( ! is_array( $notice ) ) { return; } delete_transient( $key ); printf( '

%2$s

', esc_attr( 'error' === $notice['type'] ? 'error' : 'success' ), esc_html( $notice['message'] ) ); } /** * @param string $message Message. * @param string $type success or error. */ private static function flash( $message, $type = 'success' ) { set_transient( 'featherwell_notice_' . get_current_user_id(), array( 'message' => $message, 'type' => $type, ), MINUTE_IN_SECONDS ); } /** * @param string $action Nonce action. */ private function guard( $action ) { if ( ! current_user_can( self::CAP ) ) { wp_die( esc_html__( 'You do not have permission to do this.', 'featherwell' ), '', array( 'response' => 403 ) ); } check_admin_referer( $action ); } public function handle_settings() { $this->guard( 'featherwell_settings' ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- Checked in guard(). Featherwell_Settings::save( array( 'format' => isset( $_POST['format'] ) ? sanitize_key( wp_unslash( $_POST['format'] ) ) : 'webp', 'quality' => isset( $_POST['quality'] ) ? absint( $_POST['quality'] ) : 82, 'max_size' => isset( $_POST['max_size'] ) ? absint( $_POST['max_size'] ) : 2560, 'png' => ! empty( $_POST['png'] ), 'rewrite' => ! empty( $_POST['rewrite'] ), 'dimensions' => ! empty( $_POST['dimensions'] ), ) ); // phpcs:enable self::flash( __( 'Settings saved.', 'featherwell' ) ); wp_safe_redirect( self::url( array( 'view' => 'settings' ) ) ); exit; } public function handle_one() { $this->guard( 'featherwell_one' ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- Checked in guard(). $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; $do = isset( $_POST['do'] ) ? sanitize_key( wp_unslash( $_POST['do'] ) ) : ''; // phpcs:enable if ( ! $id || 'attachment' !== get_post_type( $id ) || ! current_user_can( 'edit_post', $id ) ) { wp_die( esc_html__( 'You do not have permission to do this.', 'featherwell' ), '', array( 'response' => 403 ) ); } $title = get_the_title( $id ); if ( 'undo' === $do ) { $done = Featherwell_Library::undo( $id ); /* translators: %s: image title */ $ok = sprintf( __( '"%s" is back in its original format.', 'featherwell' ), $title ); } else { delete_post_meta( $id, Featherwell_Library::SKIP ); $done = Featherwell_Library::convert( $id ); $ok = is_wp_error( $done ) ? '' : sprintf( /* translators: 1: image title, 2: before, 3: after */ __( '"%1$s" converted: %2$s became %3$s.', 'featherwell' ), $title, size_format( $done['before'], 1 ), size_format( $done['after'], 1 ) ); } if ( is_wp_error( $done ) ) { /* translators: 1: image title, 2: reason */ self::flash( sprintf( __( '"%1$s" was not changed: %2$s', 'featherwell' ), $title, $done->get_error_message() ), 'error' ); } else { self::flash( $ok ); } $back = wp_get_referer(); wp_safe_redirect( $back ? $back : self::url( array( 'view' => 'images' ) ) ); exit; } }