How to display Last Updated Date in WordPress
👋 When your content is consistently updated, Google trusts it more, rewarding you with better rankings, and users feel a significantly higher level of trust! But how can we achieve this? This is precisely where the importance to display Last Updated Date information for articles and products reveals itself. Today, we’re going to talk about a fantastic WordPress plugin that makes this process as easy as pie and offers a host of other exciting features. Ready? Let’s dive in! 🤩
Why use Display Last Updated Date Information
Building User Trust ⭐⭐⭐
Imagine you’ve written a comprehensive article on the “Best Smartphones of 2025.” Naturally, you’ll need to update this article periodically with new information, fresh models, and price changes. When a user lands on your page and sees that the last edit was made just yesterday, their trust in your content increases significantly. This signals that your content is alive and dynamic, always providing the latest information.
This plugin provides shortcodes like [real_post] for articles and [real_price] for products, allowing you to easily show the actual date of the last modification. This feature is not only beneficial for the user but also sends a positive signal to Google that your page is active and up-to-date, which is a major plus for SEO.So, don’t underestimate the significance of your ability to display Last Updated Date details!
Displaying a “Fake” Last Updated Date
Some content, like foundational tutorials or product pages, has an “Evergreen” nature and doesn’t require daily updates. However, to give users the impression that this content is also fresh, you can use the feature to display Last Updated Date information fictitiously.
With the shortcodes [fake_post] and [fake_price], this plugin allows you to set a random update date (between 1 and 21 days ago) or a fixed date for your content. This clever trick gives your content a sense of newness without any actual changes and can boost your click-through rate (CTR) in search results
Note: Be sure to genuinely update your core content to maintain user trust. This is a powerful tool, but only when used correctly!
Key Features of this Last Updated Date Plugin
Customize the Style to Display Last Updated Date Your Way
One of the greatest strengths of this plugin is its incredible flexibility in customizing the appearance of the update dates.
-
Change text and background colors (you can even use attractive gradients!).
-
Set font sizes separately for desktop and mobile to ensure an excellent user experience on any device.
-
Control the margin and padding of the date display box so it fits perfectly where you want without disrupting your site’s layout.
-
Enable visually appealing features like glassmorphism backgrounds and animated backdrops to make your update dates stand out and attract users. 🤩
-
You can even enable or disable various icons and change their colors.
This means you have complete control over the details, ensuring that the way you display Last Updated Date information is exactly what you envision. This level of customization is truly exceptional and helps strengthen your site’s branding. 💪
Smart Display with WooCommerce Compatibility
If you run a WordPress eCommerce store using WooCommerce, this plugin has great news for you! 🥳 It is fully compatible with WooCommerce, including localized versions. This means you can easily enable the feature to display Last Updated Date details for your product prices as well.
But the plugin’s intelligence doesn’t stop there. Sometimes, you may not want the update date to appear for all content or products. For example:
-
Out-of-stock products 🚫
-
Products without a price or listed as “Call for Price” 📞
-
Pre-order products 📦
-
Specific product or post categories 🏷️
-
Specific product or post links 🔗
This plugin allows you to set exception rules, specifying conditions under which the last updated date should not be displayed. This feature gives you more precise control over your site’s content, allowing you to show these dates only where they are truly needed. This leads to more optimized and professional content management. 🧠
Ease of Use: Just a Simple Shortcode
One of WordPress’s best qualities is its simplicity, and this plugin follows that philosophy perfectly. You don’t need complex coding knowledge to use its features. Simply place the relevant shortcodes wherever you want in your articles or product pages.
[real_post] to show the actual update date of an article.
[real_price] to show the actual update date of a product’s price.
[publish_time] to show the original publication time.
[fake_post] to show a fake update date for an article.
[fake_price] to show a fake update date for a product’s price.
[publish_date] to show the original publication date.
Even if you’re a beginner, you can take full advantage of this plugin and implement the function to display Last Updated Date information on your site in the best way possible. 🚀
Free Plugin to Display Update Date
If you’d like to test a free version, you can do so by placing the following code into the WPCode plugin. This will allow you to familiarize yourself with the settings panel. By inserting the shortcode [real-post-date], you can display the publication date of your posts, products, or pages anywhere you choose. (Note: Displaying the last updated date is a pro feature).
-
Install the WPCode plugin from the WordPress repository.
-
Create a new snippet with a custom name and set the type to PHP Snippet.
-
Copy the necessary code, activate it, and you’re all set.
Free plugin web pro : insert code in WPcode plugin
<?php
/**
* Plugin Name: WebP Free
* Plugin URI: https://mrbarati.com/forms/
* Description: Free plugin for converting images to WebP format and managing alt text with limited features.
* Version: 0.0
* Author: Mohandes Barati
* Author URI: https://mrbarati.com
* License: GPLv3
* Requires at least: 6.0
* Requires PHP: 7.4
*/
if (!defined('ABSPATH')) {
exit;
}
final class WebP_Free {
private static $instance = null;
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->setup_hooks();
}
private function setup_hooks() {
register_activation_hook(__FILE__, [__CLASS__, 'activate']);
register_deactivation_hook(__FILE__, [__CLASS__, 'deactivate']);
register_uninstall_hook(__FILE__, [__CLASS__, 'uninstall']);
add_action('admin_menu', [$this, 'register_admin_menu']);
add_filter('plugin_action_links_' . plugin_basename(__FILE__), [$this, 'add_settings_link']);
add_action('admin_head-settings_page_webp-free', [$this, 'inject_admin_assets']);
add_action('wp_ajax_webpfree_scan_images', [$this, 'ajax_scan_images']);
add_action('wp_ajax_webpfree_update_images', [$this, 'ajax_update_images']);
add_action('wp_ajax_webpfree_load_images_table', [$this, 'ajax_load_images_table']);
add_action('wp_ajax_webpfree_save_alt_texts', [$this, 'ajax_save_alt_texts']);
add_action('wp_ajax_webpfree_convert_to_webp', [$this, 'ajax_convert_to_webp']);
add_action('wp_ajax_webpfree_save_settings', [$this, 'ajax_save_settings']);
add_action('wp_ajax_webpfree_clear_logs', [$this, 'ajax_clear_logs']);
add_action('wp_ajax_webpfree_refresh_nonce', [$this, 'ajax_refresh_nonce']);
add_action('wp_ajax_webpfree_refresh_logs', [$this, 'ajax_refresh_logs']);
add_filter('the_content', [$this, 'replace_images_with_webp_in_content']);
}
public static function activate() {
if (!function_exists('wp_get_image_editor')) {
wp_die('Error: The plugin requires the wp_get_image_editor function. Please ensure that the GD or Imagick library is installed on your server.');
}
add_option('webpfree_formats', ['jpeg', 'png', 'jpg']);
add_option('webpfree_quality', 80);
add_option('webpfree_logs', []);
}
public static function deactivate() {
delete_option('webpfree_formats');
delete_option('webpfree_quality');
delete_option('webpfree_logs');
}
public static function uninstall() {
global $wpdb;
delete_option('webpfree_formats');
delete_option('webpfree_quality');
delete_option('webpfree_logs');
$wpdb->delete($wpdb->postmeta, ['meta_key' => '_webp_converted_to']);
}
private function clear_caches() {
if (function_exists('rocket_clean_domain')) {
rocket_clean_domain();
}
if (class_exists('LiteSpeed_Cache')) {
do_action('litespeed_purge_all');
}
wp_cache_flush();
}
private function add_log($message) {
$logs = get_option('webpfree_logs', []);
$new_log = [
'message' => sanitize_text_field($message),
'time' => current_time('mysql')
];
array_unshift($logs, $new_log);
$logs = array_slice($logs, 0, 40);
update_option('webpfree_logs', $logs);
}
public function add_settings_link($links) {
$settings_link = '<a href="' . esc_url(admin_url('options-general.php?page=webp-free')) . '">Settings</a>';
array_unshift($links, $settings_link);
return $links;
}
public function register_admin_menu() {
add_options_page(
'WebP Free Settings',
'WebP Free',
'manage_options',
'webp-free',
[$this, 'render_settings_page']
);
}
public function inject_admin_assets() {
$this->embed_css();
$this->embed_js();
}
private function embed_css() {
?>
<style>
.rtl .nav-tab-wrapper {
margin-bottom: 20px;
background: linear-gradient(135deg, #2271b1 0%, #1a5d93 100%);
padding: 5px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.rtl .nav-tab {
background: #fff;
color: #004aad;
border: none;
margin: 5px;
padding: 10px 20px;
border-radius: 5px;
transition: all 0.3s ease;
font-weight: bold;
font-size: 16px;
}
.rtl .nav-tab:hover, .rtl .nav-tab.nav-tab-active {
background: #004aad;
color: #fff;
}
.rtl .tab-content {
display: none;
padding: 25px;
background: #fff;
border: 1px solid #c3c4c7;
border-top: 0;
border-radius: 0 0 8px 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
font-size: 15px;
}
.rtl .tab-content.active { display: block; }
.rtl .tab-content h2 {
color: #004aad;
font-weight: bold;
font-size: 21px;
}
.rtl .tab-content p, .rtl .tab-content li {
font-size: 15px;
line-height: 1.8;
color: #333;
}
.rtl .result-container, .rtl .log-box { margin-top: 20px; }
.rtl .actions-bar { margin-bottom: 15px; display: flex; gap: 10px; align-items: center; }
.rtl #webp-image-list, .rtl .widefat { margin-top: 20px; }
.rtl #webp-image-list th.check-column { padding: 8px 0; }
.rtl #webp-image-list td, .rtl #webp-image-list th { vertical-align: middle; }
.rtl #webp-image-list tbody tr { height: 70px; }
.rtl #webp-image-list img { max-height: 60px; width: auto; max-width: 100%; border-radius: 3px; box-shadow: 0 0 5px rgba(0,0,0,0.1); }
.rtl td.editable-alt { cursor: pointer; position: relative; padding: 8px; }
.rtl .alt-text-input { display: none; width: calc(100% - 110px); padding: 5px; vertical-align: middle; }
.rtl .save-single-alt { display: none; background-color: #4CAF50; color: #fff; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer; margin-right: 5px; vertical-align: middle; }
.rtl .cancel-single-alt { display: none; background-color: #FF0000; color: #fff; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer; margin-right: 5px; vertical-align: middle; }
.rtl .max-ten-warning { color: #FF0000; }
#webpfree-toast-container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 99999; width: 380px; max-width: 90%; }
.ir-toast { font-family: inherit; font-size: 15px; background-color: #fff; color: #1d2327; padding: 20px; margin-bottom: 10px; border-right-width: 5px; border-right-style: solid; border-radius: 4px; box-shadow: 0 5px 25px rgba(0,0,0,0.2); display: flex; align-items: flex-start; gap: 12px; opacity: 0; transform: scale(0.9); transition: all 0.3s ease-out; }
.ir-toast.show { opacity: 1; transform: scale(1); }
.ir-toast .ir-toast-icon { font-size: 20px; line-height: 1.4; }
.ir-toast .ir-toast-message { flex-grow: 1; line-height: 1.6; }
.ir-toast .ir-toast-message b { font-weight: 600; }
.ir-toast .ir-toast-close { background: none; border: none; font-size: 22px; cursor: pointer; color: #777; padding: 0 5px; }
.ir-toast.success { border-right-color: #4CAF50; }
.ir-toast.success .ir-toast-icon { color: #4CAF50; }
.ir-toast.error { border-right-color: #F44336; }
.ir-toast.error .ir-toast-icon { color: #F44336; }
#log-container { max-height: 400px; overflow-y: auto; background: #f9f9f9; border: 1px solid #ddd; padding: 10px; }
#log-container ul { margin: 0; padding: 0; list-style: none; }
#log-container li { background: #fff; border-bottom: 1px solid #eee; padding: 8px 12px; font-size: 15px; display: flex; justify-content: space-between; align-items: center; }
#log-container li:last-child { border-bottom: none; }
#log-container li::before { content: counters(item, ".") ". "; counter-increment: item; margin-left: 10px; }
.log-time { color: #777; font-size: 14px; }
#help h3 { margin-top: 25px; border-bottom: 2px solid #004aad; padding-bottom: 5px; color: #004aad; font-size: 20px; }
#help p { line-height: 1.7; }
#webp-pro-btn { display: inline-block; padding: 15px 30px; font-size: 15px; font-weight: bold; background-color: #2271b1; color: #fff; text-decoration: none; border-radius: 5px; margin-top: 20px; margin-left: 10px; }
#webp-pro-btn:hover { background-color: #1a5d93; }
.progress-container { margin-top: 15px; display: none; }
.progress-bar { width: 100%; background-color: #f3f3f3; border-radius: 5px; overflow: hidden; }
.progress-bar-fill { height: 20px; background-color: #4CAF50; width: 0%; transition: width 0.3s ease-in-out; }
.progress-text { margin-top: 5px; font-size: 15px; color: #333; }
#image-table-container > p { color: #FF0000; font-weight: bold; font-size: 15px; }
.rtl td.editable-alt[title] { color: #FF0000; font-weight: bold; }
</style>
<?php
}
private function embed_js() {
$localized_data = [
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('webpfree_nonce'),
'i18n' => [
'loading' => 'Loading...',
'error' => 'An unexpected error occurred!',
'saving' => 'Saving...',
'scan' => 'Scan and display reports',
'scanning' => 'Scanning...',
'startUpdate' => 'Start Bulk Update',
'updating' => 'Processing...',
'convertingSelected' => 'Convert Selected to WebP',
'converting' => 'Converting...',
'saveAltChanges' => 'OK',
'cancelAltChanges' => 'Cancel',
'clearing' => 'Clearing...',
'progress' => 'Progress: %s%% (Processing %s of %s images)'
]
];
?>
<script>
jQuery(document).ready(function($) {
let webpFree = <?php echo wp_json_encode($localized_data); ?>;
const i18n = webpFree.i18n;
function showToast(message, isSuccess = true, duration = 4000) {
const container = $('#webpfree-toast-container');
if (container.length === 0) {
$('body').append('<div id="webpfree-toast-container"></div>');
}
const icon = isSuccess ? '✔' : '✖';
const toastClass = isSuccess ? 'success' : 'error';
const toast = $(`<div class="ir-toast ${toastClass}"><span class="ir-toast-icon">${icon}</span><div class="ir-toast-message">${message}</div><button class="ir-toast-close" title="Close">×</button></div>`);
$('#webpfree-toast-container').append(toast);
setTimeout(() => { toast.addClass('show'); }, 100);
const autoClose = setTimeout(() => { toast.removeClass('show').on('transitionend', () => toast.remove()); }, duration);
toast.find('.ir-toast-close').on('click', () => {
clearTimeout(autoClose);
toast.removeClass('show').on('transitionend', () => toast.remove());
});
}
async function refreshNonce() {
try {
const response = await $.post(webpFree.ajaxurl, { action: 'webpfree_refresh_nonce' });
if (response.success && response.data.new_nonce) {
webpFree.nonce = response.data.new_nonce;
return true;
}
} catch (e) {
console.error('Nonce refresh failed:', e);
showToast(i18n.error, false);
}
return false;
}
$('.nav-tab').on('click', function(e) {
e.preventDefault();
const target = $(this).attr('href');
$('.nav-tab').removeClass('nav-tab-active');
$(this).addClass('nav-tab-active');
$('.tab-content').removeClass('active');
$(target).addClass('active');
if (target === '#webp') {
loadImagesTable();
showToast('Free version: Limited to displaying and converting 14 images.', true, 4000);
} else if (target === '#update') {
showToast('Free version: Limited to updating 14 images.', true, 4000);
} else if (target === '#logs') {
$('#log-container').html('<p>To view logs, click the scan button.</p>');
showToast('Free version: Recent operation logs.', true, 4000);
} else if (target === '#stats') {
scanImages();
showToast('Free version: Display image statistics.', true, 4000);
} else {
showToast('Free version: Limited features.', true, 4000);
}
});
if (window.location.hash && $('.nav-tab[href="' + window.location.hash + '"]').length) {
$('.nav-tab[href="' + window.location.hash + '"]').trigger('click');
} else {
$('.nav-tab-wrapper a:first').trigger('click');
}
function scanImages() {
$('#stats-table-container').html('<p>' + i18n.loading + '</p>');
$.post(webpFree.ajaxurl, { action: 'webpfree_scan_images', nonce: webpFree.nonce })
.done(res => {
if (res.success) {
$('#stats-table-container').html(res.data.table);
} else {
showToast(res.data.message || i18n.error, false);
}
})
.fail(() => showToast(i18n.error, false));
}
$('#scan-images').on('click', function() {
const btn = $(this).prop('disabled', true).text(i18n.scanning);
scanImages();
btn.prop('disabled', false).text(i18n.scan);
});
function refreshLogs() {
$('#log-container').html('<p>' + i18n.loading + '</p>');
$.post(webpFree.ajaxurl, { action: 'webpfree_refresh_logs', nonce: webpFree.nonce })
.done(res => {
if (res.success) {
$('#log-container').html(res.data.logs);
showToast('Logs loaded successfully.', true);
} else {
showToast(res.data.message || i18n.error, false);
}
})
.fail(() => showToast(i18n.error, false));
}
$('#scan-logs').on('click', function() {
const btn = $(this).prop('disabled', true).text(i18n.scanning);
refreshLogs();
btn.prop('disabled', false).text(i18n.scan);
});
$('#start-processing').on('click', async function() {
if (!await refreshNonce()) return;
const btn = $(this).prop('disabled', true).text(i18n.updating);
const batchSize = 14;
const progressContainer = $('#progress-container');
const progressBar = progressContainer.find('.progress-bar-fill');
const progressText = progressContainer.find('.progress-text');
progressContainer.show();
let totalImages = 0;
try {
const countResponse = await $.post(webpFree.ajaxurl, {
action: 'webpfree_scan_images',
nonce: webpFree.nonce,
count_only: true
});
if (countResponse.success && countResponse.data.total_without_alt) {
totalImages = Math.min(countResponse.data.total_without_alt, 14);
} else {
showToast(i18n.error, false);
btn.prop('disabled', false).text(i18n.startUpdate);
progressContainer.hide();
return;
}
} catch (e) {
showToast(i18n.error, false);
btn.prop('disabled', false).text(i18n.startUpdate);
progressContainer.hide();
return;
}
let processed = 0;
let updatedCount = 0;
async function processBatch(offset) {
try {
const response = await $.post(webpFree.ajaxurl, {
action: 'webpfree_update_images',
nonce: webpFree.nonce,
offset: offset,
limit: batchSize
});
if (response.success) {
processed += response.data.processed;
updatedCount += response.data.updated;
const percentage = Math.min((processed / totalImages) * 100, 100).toFixed(1);
progressBar.css('width', percentage + '%');
progressText.text(i18n.progress.replace('%s', percentage).replace('%s', processed).replace('%s', totalImages));
if (processed < totalImages && processed < 14) {
await processBatch(offset + batchSize);
} else {
showToast(`Bulk update: ${updatedCount} alt texts updated.`, true);
btn.prop('disabled', false).text(i18n.startUpdate);
progressContainer.hide();
}
} else {
showToast(response.data.message || i18n.error, false);
btn.prop('disabled', false).text(i18n.startUpdate);
progressContainer.hide();
}
} catch (e) {
showToast(i18n.error, false);
btn.prop('disabled', false).text(i18n.startUpdate);
progressContainer.hide();
}
}
await processBatch(0);
});
function loadImagesTable() {
$('#image-table-container').html('<p>' + i18n.loading + '</p>');
$.post(webpFree.ajaxurl, {
action: 'webpfree_load_images_table',
nonce: webpFree.nonce
})
.done(res => {
if (res.success) {
$('#image-table-container').html(res.data.table);
} else {
showToast(res.data.message || i18n.error, false);
}
})
.fail(() => showToast(i18n.error, false));
}
$('#image-table-container').on('change', '#select-all-images', function() {
$('#image-table-container .image-select').prop('checked', this.checked).trigger('change');
});
$('#image-table-container').on('change', '.image-select', function() {
$('#convert-webp').prop('disabled', $('#image-table-container .image-select:checked').length === 0);
});
$('#image-table-container').on('dblclick', '.editable-alt', function() {
const cell = $(this);
cell.find('.alt-text-display').hide();
cell.find('.alt-text-input, .save-single-alt, .cancel-single-alt').show();
cell.find('.alt-text-input').focus().data('original-value', cell.find('.alt-text-input').val());
});
$('#image-table-container').on('click', '.save-single-alt', async function() {
if (!await refreshNonce()) return;
const btn = $(this);
const cell = btn.closest('.editable-alt');
const input = cell.find('.alt-text-input');
const display = cell.find('.alt-text-display');
const imageId = cell.closest('tr').data('id');
const newText = input.val();
btn.text('...').prop('disabled', true);
const updateData = { id: imageId, text: newText };
$.post(webpFree.ajaxurl, {
action: 'webpfree_save_alt_texts',
nonce: webpFree.nonce,
updates: [updateData]
})
.done(res => {
if (res.success) {
display.text(newText);
showToast(res.data.message, true);
} else {
showToast(res.data.message || i18n.error, false);
}
})
.fail(() => showToast(i18n.error, false))
.always(() => {
input.hide();
btn.hide().text(i18n.saveAltChanges).prop('disabled', false);
cell.find('.cancel-single-alt').hide();
display.show();
});
});
$('#image-table-container').on('click', '.cancel-single-alt', function() {
const cell = $(this).closest('.editable-alt');
const input = cell.find('.alt-text-input');
const display = cell.find('.alt-text-display');
input.val(input.data('original-value')).hide();
$(this).hide();
cell.find('.save-single-alt').hide();
display.show();
});
$('#convert-webp').on('click', async function() {
if (!await refreshNonce()) return;
const btn = $(this);
const imageIds = $('#image-table-container .image-select:checked').map((_, el) => $(el).val()).get();
if (imageIds.length > 0) {
btn.prop('disabled', true).text(i18n.converting);
$.post(webpFree.ajaxurl, {
action: 'webpfree_convert_to_webp',
nonce: webpFree.nonce,
image_ids: imageIds
})
.done(res => {
if (res.success) {
loadImagesTable();
$.post(webpFree.ajaxurl, { action: 'webpfree_clear_caches', nonce: webpFree.nonce });
}
showToast(res.data.message, res.success);
})
.fail(() => showToast(i18n.error, false))
.always(() => {
btn.prop('disabled', false).text(i18n.convertingSelected);
if ($('#select-all-images').length) $('#select-all-images').prop('checked', false);
$('#convert-webp').prop('disabled', true);
});
}
});
$('#save-settings').on('click', async function() {
if (!await refreshNonce()) return;
const btn = $(this).prop('disabled', true).text(i18n.saving);
$.post(webpFree.ajaxurl, {
action: 'webpfree_save_settings',
nonce: webpFree.nonce,
settings: $('#bulk-webp-settings').serialize()
})
.done(res => {
if (res.success) {
$.post(webpFree.ajaxurl, { action: 'webpfree_clear_caches', nonce: webpFree.nonce });
}
showToast(res.data.message, res.success);
})
.fail(() => showToast(i18n.error, false))
.always(() => btn.prop('disabled', false).text('Save Settings'));
});
$('#clear-logs-btn').on('click', async function() {
if (!await refreshNonce()) return;
if (!confirm('Are you sure you want to clear all logs? This action is irreversible.')) return;
const btn = $(this).prop('disabled', true).text(i18n.clearing);
$.post(webpFree.ajaxurl, { action: 'webpfree_clear_logs', nonce: webpFree.nonce })
.done(res => {
if (res.success) $('#log-container').html('<p>Logs cleared successfully.</p>');
showToast(res.data.message, res.success);
})
.fail(() => showToast(i18n.error, false))
.always(() => btn.prop('disabled', false).text('Clear All Logs'));
});
});
</script>
<?php
}
public function render_settings_page() {
$formats = get_option('webpfree_formats', ['jpeg', 'png', 'jpg']);
$quality = get_option('webpfree_quality', 80);
?>
<div class="wrap">
<div id="webpfree-toast-container"></div>
<h1>WebP Free Settings</h1>
<h2 class="nav-tab-wrapper">
<a href="#stats" class="nav-tab">Statistics</a>
<a href="#update" class="nav-tab">Bulk Update</a>
<a href="#webp" class="nav-tab">Convert to WebP</a>
<a href="#bulk-webp" class="nav-tab">Settings</a>
<a href="#logs" class="nav-tab">Logs</a>
<a href="#help" class="nav-tab">Help</a>
</h2>
<div id="stats" class="tab-content">
<h2>Media Library Statistics</h2>
<button id="scan-images" class="button button-primary" aria-label="Scan and display image statistics">Scan and Display Statistics</button>
<div id="stats-table-container" class="result-container"></div>
</div>
<div id="update" class="tab-content">
<h2>Automatic Alt Text Update</h2>
<p>This operation processes images without alt text in small batches and displays progress with a progress bar: Note: An unexpected error means you don't have an image without alt text, and this does not mean a system error!</p>
<button id="start-processing" class="button button-primary" aria-label="Start bulk updating alt texts">Start Bulk Update</button>
<div id="progress-container" class="progress-container">
<div class="progress-bar">
<div class="progress-bar-fill"></div>
</div>
<div class="progress-text">Progress: 0% (Processing 0 of 0 images)</div>
</div>
</div>
<div id="webp" class="tab-content">
<h2>Selective WebP Conversion and Alt Text Editing</h2>
<div class="notice notice-warning inline"><p><strong>Warning:</strong> To avoid server overload, it is recommended to select a maximum of <span class="max-ten-warning">10</span> images for conversion at a time.</p></div>
<div class="actions-bar"><button id="convert-webp" class="button button-primary" disabled aria-label="Convert selected images to WebP format">Convert Selected to WebP</button></div>
<div id="image-table-container"></div>
</div>
<div id="bulk-webp" class="tab-content">
<h2>WebP Conversion Settings</h2>
<form id="bulk-webp-settings">
<table class="form-table">
<tr>
<th scope="row"><label for="quality">Image Quality:</label></th>
<td>
<input type="number" id="quality" name="quality" value="<?php echo esc_attr($quality); ?>" min="10" max="100" class="small-text" />
<p class="description">This number determines the output image quality percentage (between 10 and 100). A lower number means smaller size and lower quality. The recommended value for a balance between size and quality is 80.</p>
</td>
</tr>
<tr>
<th scope="row">Convertible Formats:</th>
<td>
<fieldset>
<label><input type="checkbox" name="formats[]" value="jpeg" <?php checked(in_array('jpeg', $formats)); ?>> JPEG</label>
<label><input type="checkbox" name="formats[]" value="jpg" <?php checked(in_array('jpg', $formats)); ?>> JPG</label>
<label><input type="checkbox" name="formats[]" value="png" <?php checked(in_array('png', $formats)); ?>> PNG</label>
</fieldset>
</td>
</tr>
</table>
<p class="submit"><button type="button" id="save-settings" class="button button-secondary" aria-label="Save plugin settings">Save Settings</button></p>
</form>
</div>
<div id="logs" class="tab-content">
<h2>Operation Logs</h2>
<p>To view logs, click the "Scan and Display Reports" button.</p>
<div class="actions-bar">
<button id="scan-logs" class="button button-primary" aria-label="Scan and display operation logs">Scan and Display Logs</button>
<button id="clear-logs-btn" class="button button-danger" aria-label="Clear all logs">Clear All Logs</button>
</div>
<div id="log-container" class="result-container"></div>
</div>
<div id="help" class="tab-content">
<h2>Comprehensive Plugin Guide</h2>
<p>This plugin is a powerful tool for optimizing your site's images by managing alt text and converting them to the modern WebP format.</p>
<a href="https://mrbarati.com/webp-pro/" id="webp-pro-btn" target="_blank" rel="noopener" aria-label="Purchase Webp Pro version">Purchase Webp Pro Version</a>
<h3>Statistics Tab</h3>
<p>This section provides an overview of your media library's image status. By clicking the "Scan" button, you can view detailed statistics on the total number of images, the number of JPG and PNG images, the number of WebP images, and the status of alt texts.</p>
<h3>Bulk Update Tab</h3>
<p>This tool processes up to 14 images without alt text in batches. Alt text is generated based on priority (post title, image title, file name).</p>
<h3>Convert to WebP Tab</h3>
<p>Up to 14 JPG and PNG images are displayed. You can select images, edit alt text, and convert them to WebP.</p>
<h3>Settings Tab</h3>
<p>You can adjust WebP image quality and convertible formats.</p>
<h3>Logs Tab</h3>
<p>All important operations are recorded here with date and time.</p>
</div>
</div>
<?php
}
public function ajax_scan_images() {
check_ajax_referer('webpfree_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Access denied.']);
}
global $wpdb;
$total_images = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_mime_type LIKE 'image/%'");
$jpeg_images = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'attachment' AND (post_mime_type = 'image/jpeg' OR post_mime_type = 'image/jpg')");
$png_images = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_mime_type = 'image/png'");
$webp_images = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_mime_type = 'image/webp'");
$images_with_alt = (int) $wpdb->get_var("SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = 'attachment' AND p.post_mime_type LIKE 'image/%' AND pm.meta_key = '_wp_attachment_image_alt' AND pm.meta_value != ''");
$images_without_alt = $total_images - $images_with_alt;
if (isset($_POST['count_only']) && $_POST['count_only']) {
wp_send_json_success(['total_without_alt' => $images_without_alt]);
}
$percent_with_alt = ($total_images > 0) ? round(($images_with_alt / $total_images) * 100, 1) : 0;
$percent_without_alt = ($total_images > 0) ? 100 - $percent_with_alt : 0;
ob_start();
?>
<table class="widefat striped">
<thead>
<tr>
<th>Metric</th>
<th>Count</th>
<th>Percentage</th>
</tr>
</thead>
<tbody>
<tr><td>Total Media Library Images</td><td><?php echo esc_html(number_format_i18n($total_images)); ?></td><td>100%</td></tr>
<tr><td>Number of JPG/JPEG Images</td><td><?php echo esc_html(number_format_i18n($jpeg_images)); ?></td><td>-</td></tr>
<tr><td>Number of PNG Images</td><td><?php echo esc_html(number_format_i18n($png_images)); ?></td><td>-</td></tr>
<tr><td>Number of WebP Images</td><td><?php echo esc_html(number_format_i18n($webp_images)); ?></td><td>-</td></tr>
<tr><td>Images with Alt Text</td><td><?php echo esc_html(number_format_i18n($images_with_alt)); ?></td><td><?php echo esc_html($percent_with_alt); ?>%</td></tr>
<tr><td>Images without Alt Text</td><td><?php echo esc_html(number_format_i18n($images_without_alt)); ?></td><td><?php echo esc_html($percent_without_alt); ?>%</td></tr>
</tbody>
</table>
<?php
wp_send_json_success(['table' => ob_get_clean()]);
}
public function ajax_refresh_logs() {
check_ajax_referer('webpfree_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Access denied.']);
}
$logs = get_option('webpfree_logs', []);
ob_start();
if (empty($logs)) {
echo '<p>No logs found.</p>';
} else {
?>
<ul style="counter-reset: item;">
<?php foreach ($logs as $log) : ?>
<li>
<span class="log-message"><?php echo esc_html($log['message']); ?></span>
<span class="log-time"><?php echo esc_html(wp_date('Y/m/d H:i:s', strtotime($log['time']))); ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php
}
wp_send_json_success(['logs' => ob_get_clean()]);
}
public function ajax_update_images() {
check_ajax_referer('webpfree_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Access denied.']);
}
global $wpdb;
$offset = isset($_POST['offset']) ? max(0, intval($_POST['offset'])) : 0;
$limit = min(14, max(1, isset($_POST['limit']) ? intval($_POST['limit']) : 14));
$images = $wpdb->get_results($wpdb->prepare(
"SELECT p.ID, p.post_parent, p.post_title, p.guid
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_wp_attachment_image_alt'
WHERE p.post_type = 'attachment'
AND p.post_mime_type LIKE 'image/%%'
AND (pm.meta_value IS NULL OR pm.meta_value = '')
LIMIT %d OFFSET %d",
$limit, $offset
));
if (empty($images)) {
wp_send_json_success([
'message' => 'No images found for update.',
'processed' => 0,
'updated' => 0
]);
return;
}
$updated_count = 0;
foreach ($images as $image) {
$alt_text = '';
if ($image->post_parent > 0) {
$alt_text = get_the_title($image->post_parent);
}
if (empty(trim($alt_text))) {
$alt_text = $image->post_title;
}
if (empty(trim($alt_text))) {
$alt_text = pathinfo($image->guid, PATHINFO_FILENAME);
}
$alt_text = ucwords(str_replace(['-', '_'], ' ', $alt_text));
if (update_post_meta($image->ID, '_wp_attachment_image_alt', sanitize_text_field($alt_text))) {
$updated_count++;
}
}
if ($updated_count > 0) {
$this->add_log(sprintf('Bulk update: %s alt texts updated.', number_format_i18n($updated_count)));
$this->clear_caches();
}
wp_send_json_success([
'message' => sprintf('%s images successfully updated.', number_format_i18n($updated_count)),
'processed' => count($images),
'updated' => $updated_count
]);
}
public function ajax_load_images_table() {
check_ajax_referer('webpfree_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Access denied.']);
}
$query_args = [
'post_type' => 'attachment',
'post_status' => 'inherit',
'posts_per_page' => 14,
'post_mime_type' => ['image/jpeg', 'image/png', 'image/jpg'],
'meta_query' => [
[
'key' => '_webp_converted_to',
'compare' => 'NOT EXISTS',
],
],
'orderby' => 'date',
'order' => 'DESC',
];
$query = new WP_Query($query_args);
ob_start();
if ($query->have_posts()) {
?>
<table class="widefat striped" id="webp-image-list">
<thead>
<tr>
<th class="check-column"><input type="checkbox" id="select-all-images"></th>
<th>Preview</th>
<th>File Name</th>
<th>Alt Text (Double-click to edit)</th>
<th>Size (KB)</th>
</tr>
</thead>
<tbody>
<?php
while ($query->have_posts()) : $query->the_post();
$image_id = get_the_ID();
$file_path = get_attached_file($image_id);
$size_kb = (file_exists($file_path)) ? round(filesize($file_path) / 1024, 1) : '-';
$alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true);
$filename = wp_basename(get_the_guid());
$display_filename = (mb_strlen($filename) > 22) ? mb_substr($filename, 0, 22) . '...' : $filename;
?>
<tr data-id="<?php echo esc_attr($image_id); ?>">
<th class="check-column"><input type="checkbox" class="image-select" value="<?php echo esc_attr($image_id); ?>"></th>
<td><?php echo wp_get_attachment_image($image_id, [60, 60], true); ?></td>
<td title="<?php echo esc_attr($filename); ?>"><?php echo esc_html($display_filename); ?></td>
<td class="editable-alt" title="Double-click to edit">
<span class="alt-text-display"><?php echo esc_html($alt_text); ?></span>
<input type="text" class="alt-text-input" value="<?php echo esc_attr($alt_text); ?>">
<button class="save-single-alt">OK</button>
<button class="cancel-single-alt">Cancel</button>
</td>
<td><?php echo esc_html($size_kb); ?></td>
</tr>
<?php
endwhile;
wp_reset_postdata();
?>
</tbody>
</table>
<?php
} else {
?>
<div class="notice notice-info inline">
<p>Congratulations! All eligible images have been converted to WebP format.</p>
</div>
<?php
}
wp_send_json_success(['table' => ob_get_clean()]);
}
public function ajax_save_alt_texts() {
check_ajax_referer('webpfree_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Access denied.']);
}
if (!isset($_POST['updates']) || !is_array($_POST['updates'])) {
wp_send_json_error(['message' => 'Invalid data.']);
}
$updates = (array) $_POST['updates'];
$updated_count = 0;
foreach ($updates as $update) {
$image_id = isset($update['id']) ? intval($update['id']) : 0;
$alt_text = isset($update['text']) ? sanitize_text_field(wp_unslash($update['text'])) : '';
if ($image_id > 0 && current_user_can('edit_post', $image_id)) {
if (update_post_meta($image_id, '_wp_attachment_image_alt', $alt_text)) {
$updated_count++;
$this->add_log(sprintf('Alt text for image ID %d changed to "%s".', $image_id, $alt_text));
$this->clear_caches();
}
}
}
if ($updated_count > 0) {
wp_send_json_success(['message' => sprintf('Alt text saved for %s images.', number_format_i18n($updated_count))]);
} else {
wp_send_json_error(['message' => 'No changes to save.']);
}
}
public function ajax_convert_to_webp() {
check_ajax_referer('webpfree_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Access denied.']);
}
$image_ids = isset($_POST['image_ids']) ? array_map('intval', (array) $_POST['image_ids']) : [];
if (empty($image_ids)) {
wp_send_json_error(['message' => 'No images selected for conversion.']);
}
$success_count = 0;
$failed_count = 0;
foreach ($image_ids as $image_id) {
$result = $this->convert_and_create_webp_attachment($image_id);
if ($result) {
$success_count++;
} else {
$failed_count++;
}
}
$message = sprintf(
'Operation completed: <br><b>Success:</b> %s images <br><b>Failed:</b> %s images',
number_format_i18n($success_count),
number_format_i18n($failed_count)
);
if ($success_count > 0) {
$this->add_log(sprintf('Bulk conversion: %d images converted to WebP.', $success_count));
$this->clear_caches();
}
wp_send_json_success(['message' => $message]);
}
public function ajax_save_settings() {
check_ajax_referer('webpfree_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Access denied.']);
}
$settings = [];
if (isset($_POST['settings'])) {
parse_str($_POST['settings'], $settings);
}
$quality = isset($settings['quality']) ? absint($settings['quality']) : 80;
$formats = isset($settings['formats']) && is_array($settings['formats']) ? array_map('sanitize_text_field', $settings['formats']) : [];
if ($quality < 10 || $quality > 100) {
wp_send_json_error(['message' => 'Image quality must be between 10 and 100.']);
return;
}
if (empty($formats)) {
wp_send_json_error(['message' => 'At least one format must be selected.']);
return;
}
update_option('webpfree_quality', $quality);
update_option('webpfree_formats', $formats);
$this->add_log('Plugin settings saved.');
$this->clear_caches();
wp_send_json_success(['message' => 'Settings saved successfully.']);
}
public function ajax_clear_logs() {
check_ajax_referer('webpfree_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Access denied.']);
}
delete_option('webpfree_logs');
wp_send_json_success(['message' => 'All logs cleared successfully.']);
}
public function ajax_clear_caches() {
check_ajax_referer('webpfree_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Access denied.']);
}
$this->clear_caches();
wp_send_json_success(['message' => 'Caches cleared successfully.']);
}
public function ajax_refresh_nonce() {
if (!current_user_can('manage_options')) {
wp_send_json_error();
}
wp_send_json_success(['new_nonce' => wp_create_nonce('webpfree_nonce')]);
}
private function convert_and_create_webp_attachment($original_attachment_id) {
if (!function_exists('wp_get_image_editor') || !function_exists('wp_generate_attachment_metadata') || !function_exists('wp_insert_attachment')) {
$this->add_log(sprintf('Error: Required functions for converting image ID %d are not available.', $original_attachment_id));
return false;
}
$original_file_path = get_attached_file($original_attachment_id);
if (!$original_file_path || !file_exists($original_file_path)) {
$this->add_log(sprintf('Error: Image file ID %d not found.', $original_attachment_id));
return false;
}
$editor = wp_get_image_editor($original_file_path);
if (is_wp_error($editor)) {
$this->add_log(sprintf('Error: Image editor for ID %d encountered an error: %s', $original_attachment_id, $editor->get_error_message()));
return false;
}
$editor->set_quality(get_option('webpfree_quality', 80));
$webp_saved = $editor->save($editor->generate_filename('webp'), 'image/webp');
if (is_wp_error($webp_saved) || !isset($webp_saved['path'])) {
$this->add_log(sprintf('Error: Saving WebP image for ID %d failed: %s', $original_attachment_id, is_wp_error($webp_saved) ? $webp_saved->get_error_message() : 'Unknown error'));
return false;
}
$webp_path = $webp_saved['path'];
$original_post = get_post($original_attachment_id);
$attachment_data = [
'post_mime_type' => $webp_saved['mime-type'],
'post_title' => $original_post->post_title . ' (WebP)',
'post_content' => $original_post->post_content,
'post_status' => 'inherit'
];
$webp_attachment_id = wp_insert_attachment($attachment_data, $webp_path, 0, true);
if (is_wp_error($webp_attachment_id)) {
@unlink($webp_path);
$this->add_log(sprintf('Error: Creating WebP attachment for image ID %d failed: %s', $original_attachment_id, $webp_attachment_id->get_error_message()));
return false;
}
if (file_exists(ABSPATH . 'wp-admin/includes/image.php')) {
require_once ABSPATH . 'wp-admin/includes/image.php';
wp_update_attachment_metadata($webp_attachment_id, wp_generate_attachment_metadata($webp_attachment_id, $webp_path));
}
$original_alt = get_post_meta($original_attachment_id, '_wp_attachment_image_alt', true);
if (!empty($original_alt)) {
update_post_meta($webp_attachment_id, '_wp_attachment_image_alt', $original_alt);
}
update_post_meta($original_attachment_id, '_webp_converted_to', $webp_attachment_id);
$this->add_log(sprintf('Image "%s" (ID: %d) converted to WebP (ID: %d).', $original_post->post_title, $original_attachment_id, $webp_attachment_id));
return true;
}
public function replace_images_with_webp_in_content($content) {
if (is_admin() || (defined('DOING_AJAX') && DOING_AJAX)) {
return $content;
}
preg_match_all('/<img[^>]+>/i', $content, $matches);
if (empty($matches[0])) {
return $content;
}
foreach ($matches[0] as $img_tag) {
$original_id = 0;
if (preg_match('/wp-image-([0-9]+)/i', $img_tag, $class_id_match)) {
$original_id = intval($class_id_match[1]);
} else if (preg_match('/src="([^"]+)"/i', $img_tag, $src_match)) {
$original_id = attachment_url_to_postid($src_match[1]);
}
if (!$original_id) {
continue;
}
$webp_id = get_post_meta($original_id, '_webp_converted_to', true);
if (!empty($webp_id) && get_post($webp_id)) {
preg_match('/class="([^"]+)"/', $img_tag, $class_match);
$classes = isset($class_match[1]) ? $class_match[1] : '';
$size = 'full';
if (preg_match('/size-([a-zA-Z0-9_-]+)/', $classes, $size_match)) {
$size = $size_match[1];
}
$webp_url = wp_get_attachment_image_url($webp_id, $size);
$alt_text = get_post_meta($original_id, '_wp_attachment_image_alt', true);
$new_tag = sprintf(
'<picture><source type="image/webp" srcset="%s"><img src="%s" alt="%s" class="%s"></picture>',
esc_url($webp_url),
esc_url(wp_get_attachment_image_url($original_id, $size)),
esc_attr($alt_text),
esc_attr($classes)
);
if ($new_tag) {
$content = str_replace($img_tag, $new_tag, $content);
}
}
}
return $content;
}
}
WebP_Free::instance();
To purchase the WebP Pro plugin, fill out the form below
Free Method to Display Last Updated Date in Elementor
If you only want to display the update date on an Elementor or Elementor Pro page, you can do this for free using the Post Info widget. Simply select the widget, choose the “Custom” option in the relevant section, and place it in the appropriate area of your page.

