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(
'',
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;
}
}