I'm looking for feedback on a WordPress plugin I developed for automatically converting images to WebP and AVIF formats. The goal is to optimize image loading performance.
The plugin has the following key features:
Automatically converts images on upload to WebP and AVIF formats using predefined quality settings. Uses imagecreatefromjpeg/png/gif and imagewebp/imageavif functions.
Adds custom fields in media library for per-image quality control of JPEG, WebP, and AVIF.
Bulk conversion utility to convert all existing media library images. Shows progress bar during conversion.
Option to bulk delete all WebP/AVIF images.
Fallback to original image formats if WebP/AVIF is not supported by the browser.
Uses best practices like namespace organization, OOP structure, and exception handling.
Main files:
public/class-web-avif-converter.php - Main plugin class
public/src/hooks/* - WordPress hooks for handling uploads and edits
public/src/generator-utils.php - Functions for image conversion
public/src/utils/* - Helper utilities
The full code is pasted below. I would greatly appreciate any feedback on:
- Overall code structure and organization
- Code quality, best practices
- Performance optimizations or improvements
- Any bugs or issues you see
- Any other suggestions!
class-web-avif-converter.php
<?php
/**
* The plugin bootstrap file
*
* This file is read by WordPress to generate the plugin information in the plugin
* admin area. This file also includes all of the dependencies used by the plugin,
* registers the activation and deactivation functions, and defines a function
* that starts the plugin.
*
* @link https://rekurencja.com/
* @since 1.0.0
* @package Web_Avif_Converter
*
* @wordpress-plugin
* Plugin Name: WebP & Avif Converter
* Plugin URI: https://images.rekurencja.com
* Description: Fast and simple plugin to automatically convert and serve WebP & AVIF images.
* Version: 1.0.0
* Author: Rekurencja
* Author URI: https://rekurencja.com/
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: web-avif-converter
* Domain Path: /languages
*/
// If this file is called directly, abort.
if (!defined('WPINC')) {
die;
}
define('PHP_REQUIRED_VERSION', '8.1.0');
define('PHP_VERSION_OK', version_compare(phpversion(), PHP_REQUIRED_VERSION, '>='));
define('DEFAULT_QUALITY', 82);
require plugin_dir_path(__FILE__) . 'public/src/utils/utils.php';
require plugin_dir_path(__FILE__) . 'public/src/hooks/activation_hooks.php';
require plugin_dir_path(__FILE__) . 'public/src/hooks/attachment_hooks.php';
require plugin_dir_path(__FILE__) . 'public/src/utils/generator-utils.php';
// exceptions
require plugin_dir_path(__FILE__) . 'public/src/exceptions/exceptions.php';
// Include admin.php for admin page functionality
if (is_admin()) {
require plugin_dir_path(__FILE__) . 'public/src/admin.php';
}
register_activation_hook(__FILE__, 'WebAvifConverter\Hooks\activate_web_avif_converter');
register_deactivation_hook(__FILE__, 'WebAvifConverter\Hooks\deactivate_web_avif_converter');
add_filter('attachment_fields_to_edit', 'add_image_quality_field', 10, 2);
add_filter('manage_media_columns', 'add_image_quality_column');
add_action('manage_media_custom_column', 'display_image_quality_column', 10, 2);
add_filter('jpeg_quality', 'get_jpeg_quality');
function get_jpeg_quality($quality)
{
return get_option('wac_quality_jpeg', 82);
}
use WebAvifConverter\Hooks;
use WebAvifConverter\Utils;
function add_image_quality_column($columns)
{
$columns['quality_webp'] = 'WebP Quality';
$columns['quality_avif'] = 'Avif Quality';
$columns['quality_jpeg'] = 'Jpeg Quality';
return $columns;
}
function add_image_quality_field($form_fields, $post)
{
// wp_die( print_r( $form_fields ));
$form_fields['quality_webp'] = generate_quality_field($post->ID, 'quality_webp', 'Quality of WEBP');
$form_fields['quality_avif'] = generate_quality_field($post->ID, 'quality_avif', 'Quality of AVIF');
$form_fields['quality_jpeg'] = generate_quality_field($post->ID, 'quality_jpeg', 'Quality of JPEG');
return $form_fields;
}
function determine_image_extension($image_url)
{
$extension = pathinfo($image_url, PATHINFO_EXTENSION);
return strtolower($extension);
}
function generate_quality_field($post_id, $field_name, $field_label)
{
// get post metadata
$metadata = wp_get_attachment_metadata($post_id);
// echo $post_id;
// wp_die(print_r($metadata));
if (isset(wp_get_attachment_metadata($post_id)[$field_name])) {
$field_value = wp_get_attachment_metadata($post_id)[$field_name];
$placeholder_value = $field_value;
} else {
$field_value = '';
$placeholder_value = 'Quality is not set';
}
$input_html = "<input type='number' min='0' max='100' step='1' ";
$input_html .= "name='attachments[$post_id][$field_name]' ";
$input_html .= "value='" . esc_attr($field_value ? $field_value : '') . "' ";
$input_html .= "placeholder='" . esc_attr($placeholder_value) . "'/>";
$field = array(
'label' => $field_label,
'input' => 'html',
'helps' => 'Enter a value between 1 and 100 for image quality (100 = best quality, 1 = worst quality)',
'html' => $input_html,
);
return $field;
}
function display_image_quality_column($column_name, $attachment_id)
{
$allowed_columns = ['quality_avif', 'quality_webp', 'quality_jpeg'];
if (in_array($column_name, $allowed_columns)) {
$attachment_metadata = wp_get_attachment_metadata($attachment_id);
if (isset($attachment_metadata[$column_name])) {
echo $attachment_metadata[$column_name];
} else {
echo 'N/A';
}
}
}
add_action('admin_menu', 'webp_avif_bulk_convert_menu');
function webp_avif_bulk_convert_menu()
{
add_submenu_page(
'options-general.php',
'WebP & Avif Converter',
'WebP & Avif Converter',
'manage_options',
'webp_avif_bulk_convert',
'webp_avif_bulk_convert_page'
);
}
function get_images_size_info()
{
// loop through all images
$images = get_posts(
array(
'post_type' => 'attachment',
'post_mime_type' => 'image',
'numberposts' => -1,
)
);
$images_size_info = [
"total_original_size" => 0,
"total_avif_size" => 0,
"total_webp_size" => 0,
];
echo "<div class='summary-wrapper'>";
foreach ($images as $image) {
try {
// Determine the extension
$extension = determine_image_extension($image->guid);
// Handle unsupported extensions
if ($extension === 'svg' || $extension === 'gif') {
throw new FilesizeUnavailableException($extension, 'Unsupported extension: ' . $extension);
}
$metadata = wp_get_attachment_metadata($image->ID);
$cur_image_size_info = [
"original_size" => isset($metadata['filesize']) ? $metadata['filesize'] : 0,
"avif_size" => 0,
"webp_size" => 0,
];
if (isset($metadata['sizes'])) {
foreach ($metadata['sizes'] as $size_name => $size) {
$cur_image_size_info["original_size"] += isset($size['filesize']) ? $size['filesize'] : 0;
}
}
if (isset($metadata['sizes_avif'])) {
foreach ($metadata['sizes_avif'] as $size_name => $size) {
$cur_image_size_info["avif_size"] += isset($size['filesize']) ? $size['filesize'] : 0;
}
}
if (isset($metadata['sizes_webp'])) {
foreach ($metadata['sizes_webp'] as $size_name => $size) {
$cur_image_size_info["webp_size"] += isset($size['filesize']) ? $size['filesize'] : 0;
}
}
if (!isset($metadata['sizes'])) {
throw new FilesizeUnavailableException('Unknown', 'Filesize information not available for this file.');
}
$images_size_info['total_original_size'] += $cur_image_size_info['original_size'];
$images_size_info['total_avif_size'] += $cur_image_size_info['avif_size'];
$images_size_info['total_webp_size'] += $cur_image_size_info['webp_size'];
echo "<div class='summary-item'>";
echo "<img src='" . $image->guid . "' alt='Image ID: " . $image->ID . "' width='150px'>";
echo "<h4>Image ID: " . $image->ID . "</h4>";
echo "<p>Image Title: <b>" . $image->post_title . "</b></p>";
echo "<p>Original size: " . size_format($cur_image_size_info['original_size']) . " </p>";
echo "<p>Avif size: " . size_format($cur_image_size_info['avif_size']) . "</p>";
echo "<p>Webp size: " . size_format($cur_image_size_info['webp_size']) . "</p>";
echo "</div>";
} catch (FilesizeUnavailableException $e) {
echo "Filesize information not available for image ID " . $image->ID . ". " . $e->getMessage();
} catch (Exception $e) {
echo "An error occurred: " . $e->getMessage();
}
}
echo "</div>";
echo "<div class='total-summary-item'>";
echo "<h4>Total Summary:</h4>";
echo "<p>Total Original Image Size: " . size_format($images_size_info['total_original_size']) . "</p>";
echo "<p>Total AVIF Image Size: " . size_format($images_size_info['total_avif_size']) . "</p>";
echo "<p>Total WebP Image Size: " . size_format($images_size_info['total_webp_size']) . "</p>";
echo "</div>";
return $images_size_info;
}
function webp_avif_bulk_convert_page()
{
if (isset($_POST['submit'])) {
$quality_webp = isset($_POST['quality_webp']) ? intval($_POST['quality_webp']) : DEFAULT_QUALITY;
$quality_avif = isset($_POST['quality_avif']) ? intval($_POST['quality_avif']) : DEFAULT_QUALITY;
$quality_jpeg = isset($_POST['quality_jpeg']) ? intval($_POST['quality_jpeg']) : DEFAULT_QUALITY;
if ($_POST['submit'] === 'Set') {
echo '<div class="success alert conversion-wrapper" id="alert" [class.visible]="isVisible">';
echo '<div class="content">';
update_option('wac_quality_webp', $quality_webp);
update_option('wac_quality_avif', $quality_avif);
update_option('wac_quality_jpeg', $quality_jpeg);
if (isset($_POST['regenerate']) && $_POST['regenerate'] === 'on') {
echo '<span class="closebtn icon" onclick="this.parentElement.style.display=\'none\';"></span>';
echo '<div class="progress-bar" id="progress-bar" style="width: 0%">0%;
</div>';
Hooks\update_all_attachments_quality($quality_webp, $quality_avif, $quality_jpeg);
echo '<p class="notification notification-good">
All images have been converted to WebP and Avif formats.
</p>';
}
} else if ($_POST['submit'] === 'Delete') {
echo '<div class="success alert conversion-wrapper" id="alert" [class.visible]="isVisible">';
echo '<div class="content">';
Hooks\delete_all_attachments_avif_and_webp();
echo '<p class="notification notification-bad">
All WebP and Avif images have been deleted.
</p>';
}
echo '</div>';
echo '</div>';
}
?>
<div class="image-conversion-wrapper">
<h1 class="conversion-heading">WebP & Avif Converter </h1>
<form method="post">
<?php $isPhpVersionOk = true; ?> <!-- Define PHP_VERSION_OK -->
<fieldset <?php echo $isPhpVersionOk ? '' : 'disabled' ?>>
<p class="conversion-description">Tool for WebP and Avif format conversion of all images in the
uploads/media library directory. <br>
<b>Choose quality</b> - choose the quality of the converted images (the lower the quality, the smaller
the size) <br>
<b>Delete function</b> - deletes all WebP and Avif images from the uploads/media library directory.
<br>
<b>Convert function</b> - converts all images in the uploads/media library directory to WebP and Avif
formats. <br>
<b>Note: </b> This tool will not convert images that are not in the uploads/media library directory.<br>
<?php echo Utils\get_php_version_info(); ?>
</p>
<div class="conversion-options">
<label class="option-label">Quality of WEBP: (0 - 100%)</label>
<input type="number" name="quality_webp" value="<?php echo get_option('wac_quality_webp', DEFAULT_QUALITY); ?>" min="0" max="100">
<label class="option-label">Quality of AVIF: (0 - 100%)</label>
<input type="number" name="quality_avif" value="<?php echo get_option('wac_quality_avif', DEFAULT_QUALITY); ?>" min="0" max="100">
<label class="option-label">Quality of JPEG: (0 - 100%)</label>
<input type="number" name="quality_jpeg" value="<?php echo get_option('wac_quality_jpeg', DEFAULT_QUALITY); ?>" min="0" max="100">
<input type="checkbox" name="regenerate" id="regenerate" checked>
<label for="regenerate" class="option-label">
Update qualities of existing images
</label>
<div class="toggle-summary_w">
<button id="toggle-summary">Show / Hide Podsumowanie</button>
</div>
<div class="conversion-summary-toggle">
<?php
// You need to define the get_images_size_info function or include the file that contains it.
get_images_size_info();
?>
</div>
</div>
<div class="conversion-buttons">
<input type="submit" name="submit" value="Set" class="convert-button">
<input type="submit" name="submit" value="Delete" class="delete-button">
</div>
</fieldset>
</form>
</div>
<?php
}
/**
* Runs the main functionality of the plugin.
*/
function run_web_avif_converter()
{
// The 'Web_Avif_Converter' class code is included in the 'includes/class-web-avif-converter.php' file.
require plugin_dir_path(__FILE__) . 'includes/class-web-avif-converter.php';
// echo print_r
$plugin = new Web_Avif_Converter();
$plugin->run();
}
run_web_avif_converter();
attachment_hooks.php
<?php
namespace WebAvifConverter\Hooks;
use WebAvifConverter\Utils;
use WebAvifConverter\GeneratorUtils;
// function p($a, $die = 0){
// echo '<pre>';
// echo print_r($a);
// echo '</pre>';
// if ($die) { wp_die(); }
// }
apply_filters('jepg_quality', get_option('wac_quality_jpeg', DEFAULT_QUALITY));
add_filter('wp_generate_attachment_metadata', 'WebAvifConverter\Hooks\convert_images_on_generate_attachment_metadata', 10, 2);
function convert_images_on_generate_attachment_metadata($metadata, $attachment_id)
{
if (!isset($metadata['file'])) { return $metadata; }
$extension = pathinfo($metadata['file'], PATHINFO_EXTENSION);
if (!in_array($extension, ['jpg', 'jpeg', 'png'])) { return $metadata; }
$metadata['quality_jpeg'] = get_option('wac_quality_jpeg', DEFAULT_QUALITY);
wp_update_attachment_metadata($attachment_id, $metadata);
// Generating JPEGs with adjusted quality is handled by jepg_quality filter
GeneratorUtils\generate_webp_sizes($attachment_id, get_option('wac_quality_webp', DEFAULT_QUALITY));
GeneratorUtils\generate_avif_sizes($attachment_id, get_option('wac_quality_avif', DEFAULT_QUALITY));
return wp_get_attachment_metadata($attachment_id);
}
add_action('edit_attachment', 'WebAvifConverter\Hooks\save_image_quality_metadata');
function save_image_quality_metadata($attachment_id)
{
$metadata = wp_get_attachment_metadata($attachment_id);
$filed_names = ['quality_jpeg', 'quality_avif', 'quality_webp' ];
foreach ($filed_names as $filed_name) {
if (empty($_REQUEST['attachments'][$attachment_id][$filed_name])) { continue; }
$quality = intval($_REQUEST['attachments'][$attachment_id][$filed_name]);
if ($quality < 1 || $quality > 100 || isset($metadata[$filed_name]) && $quality === $metadata[$filed_name]) { continue; }
if ($filed_name === 'quality_jpeg') { GeneratorUtils\generate_jpeg_sizes($attachment_id, $quality); }
if ($filed_name === 'quality_webp') { GeneratorUtils\generate_webp_sizes($attachment_id, $quality); }
if ($filed_name === 'quality_avif') { GeneratorUtils\generate_avif_sizes($attachment_id, $quality); }
}
}
function update_attachment_quality($attachment_id, $quality_webp = DEFAULT_QUALITY, $quality_avif = DEFAULT_QUALITY, $quality_jpeg = DEFAULT_QUALITY)
{
$metadata = wp_get_attachment_metadata($attachment_id);
if (empty($metadata) || !isset($metadata['file']) || !isset($metadata['sizes'])) {
return; // No metadata found for this attachment.
}
if(!isset($metadata['quality_jpeg']) || intval($metadata['quality_jpeg']) !== $quality_jpeg){
GeneratorUtils\generate_jpeg_sizes($attachment_id, $quality_jpeg);
}
if(!isset($metadata['quality_webp']) || intval($metadata['quality_webp']) !== $quality_webp){
GeneratorUtils\generate_webp_sizes($attachment_id, $quality_webp);
}
if(!isset($metadata['quality_avif']) || intval($metadata['quality_avif']) !== $quality_avif){
GeneratorUtils\generate_avif_sizes($attachment_id, $quality_avif);
}
}
function update_all_attachments_quality($quality_webp, $quality_avif, $quality_jpeg)
{
Utils\update_progress_bar(0);
$attachments = get_posts(array(
'post_type' => 'attachment',
'numberposts' => -1,
'post_status' => null,
'exclude' => get_post_thumbnail_id()
));
$total_attachments = count($attachments);
$progress = 0;
foreach ($attachments as $attachment) {
$progress++;
$percent = intval($progress / $total_attachments * 100);
Utils\update_progress_bar($percent);
update_attachment_quality($attachment->ID, $quality_webp, $quality_avif, $quality_jpeg);
}
}
add_filter('delete_attachment', 'WebAvifConverter\Hooks\delete_attachment_files');
function delete_attachment_files($attachment_id)
{
$metadata = wp_get_attachment_metadata($attachment_id);
if (empty($metadata)) { return; }
$upload_dir = wp_upload_dir()['basedir'];
if (array_key_exists('file_webp', $metadata)) {
Utils\safe_unlink($upload_dir . '/' . $metadata['file_webp']);
unset($metadata['file_webp']);
}
if (array_key_exists('file_avif', $metadata)) {
Utils\safe_unlink($upload_dir . '/' . $metadata['file_avif']);
unset($metadata['file_avif']);
}
$attachment_subdir = dirname($metadata['file']);
$attachment_files_dir = $upload_dir . '/' . $attachment_subdir . '/';
if (array_key_exists('sizes_avif', $metadata)) {
foreach ($metadata['sizes_avif'] as $size) {
Utils\safe_unlink($attachment_files_dir . $size['file']);
}
unset($metadata['sizes_avif']);
}
if (array_key_exists('quality_webp', $metadata)){
unset($metadata['quality_webp']);
}
if (array_key_exists('quality_avif', $metadata)){
unset($metadata['quality_avif']);
}
if (array_key_exists('sizes_webp', $metadata)) {
foreach ($metadata['sizes_webp'] as $size) {
Utils\safe_unlink($attachment_files_dir . $size['file']);
}
unset($metadata['sizes_webp']);
}wp_update_attachment_metadata($attachment_id, $metadata);
}
function delete_all_attachments_avif_and_webp()
{
Utils\update_progress_bar(0);
$attachments = get_posts(array(
'post_type' => 'attachment',
'numberposts' => -1,
'post_mime_type' => 'image',
'exclude' => get_post_thumbnail_id()
));
$total_attachments = count($attachments);
$progress = 0;
foreach ($attachments as $attachment) {
delete_attachment_files($attachment->ID);
$progress++;
$percent = intval($progress / $total_attachments * 100) . "%";
Utils\update_progress_bar($percent);
}
}
generator-utils.php
<?php
namespace WebAvifConverter\GeneratorUtils;
/**
* Convert image to WebP format
*
* @param string $path Path to the image file.
* @param int $quality Image quality (0 to 100, default is 80).
*
* @return string|void Returns the relative path to the converted WebP image or void if unable to create an image resource.
*/
function p($a, $die = 0){
echo '<pre>';
echo print_r($a);
echo '</pre>';
if ($die) { wp_die(); }
}
function generate_jpeg(string $path, int $quality = 80){
$output_path = dirname($path);
$extension = pathinfo($path, PATHINFO_EXTENSION);
$filename = basename($path);
$image = null;
if ($extension === 'jpeg' || $extension === 'jpg') {
$image = imagecreatefromjpeg($path);
} elseif ($extension === 'png') {
$image = imagecreatefrompng($path);
} elseif ($extension === 'gif') {
$image = imagecreatefromgif($path);
} else {
return; // Unsupported format.
}
if (!$image) {
return; // Unable to create image resource, possibly unsupported format.
}
imagejpeg($image, $output_path . '/' . $filename , $quality);
imagedestroy($image);
$directories = explode('/', dirname($path));
$upload_subdir = implode('/', array_slice($directories, -2));
return $upload_subdir . '/' . $filename;
}
function generate_webp(string $path, int $quality = 80)
{
$output_path = dirname($path);
$extension = pathinfo($path, PATHINFO_EXTENSION);
$filename = basename($path, '.' . $extension);
$image = null;
if ($extension === 'jpeg' || $extension === 'jpg') {
$image = imagecreatefromjpeg($path);
} elseif ($extension === 'png') {
$image = imagecreatefrompng($path);
} elseif ($extension === 'gif') {
$image = imagecreatefromgif($path);
}
if (!$image) {
return; // Unable to create image resource, possibly unsupported format.
}
imagewebp($image, $output_path . '/' . $filename . '.webp', $quality);
imagedestroy($image);
$directories = explode('/', dirname($path));
$upload_subdir = implode('/', array_slice($directories, -2));
return $upload_subdir . '/' . $filename . '.webp';
}
/**
* Convert image to AVIF format
*
* @param string $path Path to the image file.
* @param int $quality Image quality (0 to 100, default is 80).
*
* @return string|void Returns the relative path to the converted AVIF image or void if unable to create an image resource.
*/
function generate_avif(string $path, int $quality = 80)
{
$output_path = dirname($path);
$extension = pathinfo($path, PATHINFO_EXTENSION);
$filename = basename($path, '.' . $extension);
$image = null;
if ($extension === 'jpeg' || $extension === 'jpg') {
$image = imagecreatefromjpeg($path);
} elseif ($extension === 'png') {
$image = imagecreatefrompng($path);
} elseif ($extension === 'gif') {
$image = imagecreatefromgif($path);
} else {
return; // Unsupported format.
}
if (!$image) {
return; // Unable to create image resource, possibly unsupported format.
}
imageavif($image, $output_path . '/' . $filename . '.avif', $quality);
imagedestroy($image);
$directories = explode('/', dirname($path));
$upload_subdir = implode('/', array_slice($directories, -2));
return $upload_subdir . '/' . $filename . '.avif';
}
function generate_jpeg_sizes($attachment_id, $quality){
$metadata = wp_get_attachment_metadata($attachment_id);
if (empty($metadata)) { return; }
$original_image_path = wp_get_upload_dir()['basedir'] . '/' . $metadata['file'];
$image_subdir_path = wp_get_upload_dir()['basedir'] . '/' . dirname($metadata['file']);
$metadata['quality_jpeg'] = $quality;
// skip svg files path info
if (pathinfo($original_image_path, PATHINFO_EXTENSION) === 'svg') {
return;
}
// p($metadata['sizes'], 1);
foreach($metadata['sizes'] as $size_name => $size){
$subsize_path = $image_subdir_path . '/' . $size['file'];
$image = wp_get_image_editor($subsize_path);
$image->set_quality( $quality );
$saved = $image->save($subsize_path);
$metadata['sizes'][$size_name]['filesize'] = filesize($subsize_path);
}
wp_update_attachment_metadata($attachment_id, $metadata);
};
function generate_webp_sizes($attachment_id, $quality){
$metadata = wp_get_attachment_metadata($attachment_id);
if (empty($metadata)) { return; }
$image_subdir_path = wp_get_upload_dir()['basedir'] . '/' . dirname($metadata['file']);
$metadata['quality_webp'] = $quality;
$metadata['sizes_webp'] = [];
$metadata['file_webp'] = generate_webp(
wp_get_upload_dir()['basedir'] . '/' . $metadata['file'],
$quality
);
$metadata['webp_filesize'] = filesize(wp_get_upload_dir()['basedir'] . '/'. $metadata['file_webp']);
$original_image_path = wp_get_upload_dir()['basedir'] . '/' . $metadata['file'];
// skip svg files path info
if (pathinfo($original_image_path, PATHINFO_EXTENSION) === 'svg') {
return;
}
foreach($metadata['sizes'] as $size_name => $size){
$original_subsize_path = $image_subdir_path . '/' . $size['file'];
//create or overwrite the subsize image
$subsize_subpath = generate_webp($original_subsize_path, $quality);
$metadata['sizes_webp'][$size_name] = [
'file' => basename($subsize_subpath),
'width' => $size['width'],
'height' => $size['height'],
'mime-type' => 'image/webp',
'filesize' => filesize(wp_get_upload_dir()['basedir'] . '/' . $subsize_subpath)
];
}
wp_update_attachment_metadata($attachment_id, $metadata);
};
function generate_avif_sizes($attachment_id, $quality){
$metadata = wp_get_attachment_metadata($attachment_id);
if (empty($metadata)) { return; }
$image_subdir_path = wp_get_upload_dir()['basedir'] . '/' . dirname($metadata['file']);
$metadata['quality_avif'] = $quality;
$metadata['sizes_avif'] = [];
$metadata['file_avif'] = generate_avif(
wp_get_upload_dir()['basedir'] . '/' . $metadata['file'],
$quality
);
$metadata['avif_filesize'] = filesize(wp_get_upload_dir()['basedir'] . '/'. $metadata['file_avif']);
$original_image_path = wp_get_upload_dir()['basedir'] . '/' . $metadata['file'];
// skip svg files path info
if (pathinfo($original_image_path, PATHINFO_EXTENSION) === 'svg') {
return;
}
foreach($metadata['sizes'] as $size_name => $size){
$original_subsize_path = $image_subdir_path . '/' . $size['file'];
//create or overwrite the subsize image
$subsize_subpath = generate_avif($original_subsize_path, $quality);
$metadata['sizes_avif'][$size_name] = [
'file' => basename($subsize_subpath),
'width' => $size['width'],
'height' => $size['height'],
'mime-type' => 'image/avif',
'filesize' => filesize(wp_get_upload_dir()['basedir'] . '/' . $subsize_subpath)
];
}
wp_update_attachment_metadata($attachment_id, $metadata);
};
utils.php
<?php
namespace WebAvifConverter\Utils;
/**
* Helper function for deleting files.
*/
function safe_unlink($path)
{
if (file_exists($path)) {
unlink($path);
}
}
function update_progress_bar($percent) {
$percent = strval($percent);
echo "<script>
var progressBar = document.getElementById('progress-bar');
progressBar.style.width = '$percent%';
progressBar.innerHTML = '$percent%';
</script>";
ob_flush();
flush();
}
function get_php_version_info()
{
if(version_compare(phpversion(), PHP_REQUIRED_VERSION, '>=')){
return 'PHP Version is: <span class="php-version-good">' . phpversion() . '</span><b> version >= ' . PHP_REQUIRED_VERSION . '</b> is required.';
} else {
return 'PHP Version: <span class="php-version-bad">' . phpversion() . '</span> is too low <.>version >= ' . PHP_REQUIRED_VERSION . '</b> is required.';
}
}
-
2\$\begingroup\$ The full code has multiple files combined in one block, right? It may be helpful to have the files separated with headers above each one, like in this example. \$\endgroup\$Sᴀᴍ Onᴇᴌᴀ– Sᴀᴍ Onᴇᴌᴀ ♦2023年10月05日 15:36:56 +00:00Commented Oct 5, 2023 at 15:36
1 Answer 1
Foreword
I haven't use Wordpress in quite some time and when I did I only really added a plugin to log data in an external database. I recently installed Wordpress locally but didn't get it running so I honestly haven't had a chance to add the converter and try it out.
General feedback
The code is not too difficult to read. Two of the files contain a docblocks. The first one is for the bootstrap the file. There appear to be four docblocks above functions. Only two of them list parameters and possible return values. If the goal is to distribute/share the plugin then it would likely be best to add docblocks above all functions describing what the functions achieve as well as parameters, return values, possible exceptions, etc.
It may or may not have been a goal to have the code in line with a style guide but it mostly adheres to PSR-12. For class-web-avif-converter.php
there are only a few warnings - e.g. using else if
instead of elseif
, and lines longer than 120 characters. For attachment_hooks
there are numerous other warnings and the same is true for generator-utils.php.
The description lists the main files:
public/class-web-avif-converter.php - Main plugin class public/src/hooks/* - WordPress hooks for handling uploads and edits public/src/generator-utils.php - Functions for image conversion public/src/utils/* - Helper utilities
I was expecting to see a class definition in public/class-web-avif-converter.php
but apparently there is no class defined there. I do see it has this comment near the end of the file within function run_web_avif_converter()
:
// The 'Web_Avif_Converter' class code is included in the 'includes/class-web-avif-converter.php' file.
Perhaps a more appropriate name for public/class-web-avif-converter.php
would be public/web-avif-converter-plugin.php
Some of the code is repetitive - e.g. the functions generate_jpeg()
, generate_webp()
and generate_avif()
all achieve very similar things and have many repeated (or at least similar). One could abstract some of the common functionality out to separate functions.
There don't appear to be unit/feature/end-to-end tests. If those exist then it would be good to include those in sub-sequent posts. If those do not exist then it would be good to create and maintain those. I haven't gone through it but I see there is a handbook page for plugin integration tests on the wordpress.org site.
Suggestions
Add type declarations to function arguments and return values
Type declarations can help ensure that functions are called with the correct type of arguments and also contribute to the readability and documentation. There do appear to be some type declarations for some functions in generator-utils.php though most functions in other files have no type declarations.
Use the null coalescing operator to simplify logic
For example - in display_image_quality_column()
:
if (isset($attachment_metadata[$column_name])) { echo $attachment_metadata[$column_name]; } else { echo 'N/A'; }
Can be simplified to the following using the null coalescing operator:
echo $attachment_metadata[$column_name] ?? 'N/A';
Obviously there is one less function call and if the function was called numerous times there may be a noticeable change in performance but it may not be much.
In the screenshot below is a comparison of using the null-coalescing operator on the left compared to using isset()
with a ternary operator. Obviously it is a trivial snippet of code but it shows there are fewer number of operations using the null-coalescing operator.
Similarly in the foreach
in get_images_size_info()
is this assignment:
$cur_image_size_info = [ "original_size" => isset($metadata['filesize']) ? $metadata['filesize'] : 0, "avif_size" => 0, "webp_size" => 0, ];
The ternary can be simplified to using the null-coalescing operator:
$cur_image_size_info = [
"original_size" => $metadata['filesize'] ?? 0,
"avif_size" => 0,
"webp_size" => 0,
];
The same also applies to the four lines inside the conditional loops that add file size values to the elements in cur_image_size_info
.
Utilize array index inside foreach
instead of updating counter
In function update_all_attachments_quality()
there is an increment of counter.
$progress = 0; foreach ($attachments as $attachment) { $progress++; $percent = intval($progress / $total_attachments * 100); Utils\update_progress_bar($percent); update_attachment_quality($attachment->ID, $quality_webp, $quality_avif, $quality_jpeg); }
The increment of $progress
could be switched to pre-fix increment and consolidated in the line where $percent
is assigned:
$percent = intval(++$progress / $total_attachments * 100);
However, $progress
can be eliminated by using the second form of foreach
:
foreach ($attachments as $index => $attachment) {
$percent = intval(($index + 1)/ $total_attachments * 100);
Utils\update_progress_bar($percent);
update_attachment_quality($attachment->ID, $quality_webp, $quality_avif, $quality_jpeg);
}
The same applies to delete_all_attachments_avif_and_webp()
.
Use short echo tags to simplify output within HTML
The short echo tag <?=
can be used in place of <?php echo
. It appears there are five places that occurs - mostly in webp_avif_bulk_convert_page()
.
else
following a conditional return
can be removed
In get_php_version_info()
the else
keyword can be removed which allows for the indentation level of the last return
to be decreased.
Explore related questions
See similar questions with these tags.