Skip to content

Instantly share code, notes, and snippets.

@abidkakkur11
Last active November 14, 2025 04:08
Show Gist options
  • Select an option

  • Save abidkakkur11/def35516d3764ad8f53ce8b839b455cb to your computer and use it in GitHub Desktop.

Select an option

Save abidkakkur11/def35516d3764ad8f53ce8b839b455cb to your computer and use it in GitHub Desktop.
This snippet adds an "Update SEO Details button" in all media modals, which update SEO alt and description from title - remove special characters and numbers
<?php
/**
* Media SEO Updater — Inline JS + AJAX save (single snippet)
* Paste into Code Snippets or theme/plugin. Runs in admin.
*/
add_action( 'admin_enqueue_scripts', function() {
// Ensure media scripts are available
if ( function_exists( 'wp_enqueue_media' ) ) {
wp_enqueue_media();
}
// Print inline JS in footer of admin pages
add_action( 'admin_print_footer_scripts', function() {
$ajax_url = admin_url( 'admin-ajax.php' );
$nonce = wp_create_nonce( 'msu-save-nonce' );
?>
<script>
(function($){
'use strict';
if (!window.wp || !wp.media || !wp.media.view) return;
// helper: show admin notice style toast (simple)
function msuNotify(msg){
// fallback to alert if not desired
try {
// small unobtrusive message
var $n = $('<div>').text(msg).css({
position: 'fixed',
right: '20px',
bottom: '20px',
'z-index': 999999,
padding: '8px 12px',
'background-color': '#0073aa',
color: '#fff',
'border-radius': '4px',
'box-shadow': '0 2px 6px rgba(0,0,0,0.2)'
}).appendTo('body').delay(2200).fadeOut(400, function(){ $(this).remove(); });
} catch(e){
alert(msg);
}
}
function injectButton(view){
if (!view || !view.$el || view.$el.find('.msu-update-seo-btn').length) return;
var $btn = $('<button type="button" class="button button-primary msu-update-seo-btn" style="margin-top:10px; margin-left:8px;">Update SEO Details</button>');
$btn.on('click', function(e){
e.preventDefault();
var model = view.model;
if (!model || !model.get) {
msuNotify('No attachment data found.');
return;
}
var attachmentId = model.get('id') || model.get('ID') || 0;
if (!attachmentId) {
msuNotify('Attachment ID not found.');
return;
}
// Read current title from model (prefer model data)
var title = String( model.get('title') || '' );
// Clean title: remove numbers & special chars, keep letters & spaces
var clean = title.replace(/[^a-zA-Z\s]/g,' ').replace(/\s+/g,' ').trim();
// Update model (so modal fields update)
model.set({
alt: clean,
caption: clean,
description: clean
});
// Prepare payload for AJAX save
var data = {
action: 'msu_save_attachment',
_ajax_nonce: '<?php echo $nonce; ?>',
attachment_id: attachmentId,
title: title, // keep original title for post_title
alt: clean,
caption: clean,
description: clean
};
// disable button while saving
$btn.prop('disabled', true).text('Saving...');
$.post('<?php echo $ajax_url; ?>', data)
.done(function(res){
$btn.prop('disabled', false).text('Update SEO Details');
if (res && res.success) {
msuNotify('Saved ✓');
// sync returned values into model (in case WP made changes)
if (res.data) {
model.set({
title: res.data.title || model.get('title'),
alt: res.data.alt || model.get('alt'),
caption: res.data.caption || model.get('caption'),
description: res.data.description || model.get('description')
});
}
} else {
var msg = (res && res.data && res.data.message) ? res.data.message : 'Save failed';
msuNotify(msg);
console.error('MSU save error', res);
}
})
.fail(function(xhr, status, err){
$btn.prop('disabled', false).text('Update SEO Details');
msuNotify('Request failed: ' + (xhr.status || status));
console.error('MSU AJAX failure', xhr, status, err);
});
});
// Append to sensible locations in modal
var $target = view.$el.find('.details, .attachment-info, .attachment-actions');
if ($target.length) {
$target.first().append($btn);
} else {
view.$el.append($btn);
}
}
// Attach to multiple modal view variants (robust)
var targets = [
wp.media.view.Attachment.Details,
(wp.media.view.Attachment.Details && wp.media.view.Attachment.Details.TwoColumn) ? wp.media.view.Attachment.Details.TwoColumn : null,
wp.media.view.Attachment.Details.Sidebar
];
targets.forEach(function(ViewType){
if (!ViewType || !ViewType.prototype) return;
var orig = ViewType.prototype.render;
ViewType.prototype.render = function(){
var result = orig.apply(this, arguments);
injectButton(this);
return result;
};
});
})(jQuery);
</script>
<?php
}, 20 ); // footer scripts
});
/**
* AJAX handler: persist attachment fields to DB
*/
add_action( 'wp_ajax_msu_save_attachment', function() {
// nonce check
if ( ! isset( $_POST['_ajax_nonce'] ) || ! wp_verify_nonce( $_POST['_ajax_nonce'], 'msu-save-nonce' ) ) {
wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 );
}
$attachment_id = isset( $_POST['attachment_id'] ) ? intval( $_POST['attachment_id'] ) : 0;
if ( ! $attachment_id ) {
wp_send_json_error( array( 'message' => 'No attachment ID' ), 400 );
}
// capability check: current user must be able to edit the attachment
if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ), 403 );
}
// Sanitize inputs
$title = isset( $_POST['title'] ) ? wp_kses_post( wp_unslash( $_POST['title'] ) ) : '';
$alt = isset( $_POST['alt'] ) ? sanitize_text_field( wp_unslash( $_POST['alt'] ) ) : '';
$caption = isset( $_POST['caption'] ) ? wp_kses_post( wp_unslash( $_POST['caption'] ) ) : '';
$description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
// Update post fields: post_title, post_excerpt (caption), post_content (description)
$update = array(
'ID' => $attachment_id,
'post_title' => $title,
'post_excerpt' => $caption,
'post_content' => $description,
);
$result = wp_update_post( $update, true );
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => 'Could not update attachment' ), 500 );
}
// Update alt meta
update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt );
// Return saved values (sanitized)
$saved = array(
'title' => get_the_title( $attachment_id ),
'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
'caption' => wp_get_attachment_caption( $attachment_id ),
'description' => get_post_field( 'post_content', $attachment_id ),
);
wp_send_json_success( $saved );
} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment