Skip to content

Instantly share code, notes, and snippets.

@doiftrue
Created January 15, 2024 21:05
Show Gist options
  • Save doiftrue/3b2f31c422fb0c16bbc2ce6799d4d453 to your computer and use it in GitHub Desktop.
Save doiftrue/3b2f31c422fb0c16bbc2ce6799d4d453 to your computer and use it in GitHub Desktop.
[wpkama embed] https://wp-kama.ru/9227 Ability to upload and modify post thumbnails from the posts list table in the admin panel.
<?php
if( ! is_admin() ){
return;
}
add_action( 'admin_init', [ Kama_Post_List_Table_Thumb::class, 'init' ] );
/**
* Ability to upload and modify post thumbnails from the posts list table in the admin panel.
*
* @author Kama (wp-kama.ru)
*
* @require PHP 7.4
*
* @version 1.2.3
*/
class Kama_Post_List_Table_Thumb {
use Kama_Post_List_Table_Thumb__ajax_handlers;
/** @var array For which posts to include the code. By default, for all public ones. */
private static array $post_types = [ 'post', 'handbook', 'article' ];
/** @var array Post types for which setting a thumbnail re-attaches the attachment file. */
private static array $re_set_attach_for_post_types = [];
/** @var string Meta key name. */
private static string $meta_key = '_thumbnail_id';
/** @var string Empty image Url. */
private static string $add_img_url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkAQM' .
'AAABKLAcXAAAABlBMVEUAAAC7u7s37rVJAAAAAXRSTlMAQObYZgAAACJJREFUOMtjGA' .
'V0BvL/G0YMr/4/CDwY0rzBFJ704o0CWgMAvyaRh+c6m54AAAAASUVORK5CYII=';
public static function init( $args = [] ) {
$post_type = self::$post_types ?: get_post_types( [ 'public' => true ], 'names' );
foreach( $post_type as $ptype ){
add_filter( "manage_{$ptype}_posts_columns", [ __CLASS__, 'add_image_column' ] );
add_filter( "manage_{$ptype}_posts_custom_column", [ __CLASS__, 'fill_image_column' ], 10, 2 );
}
// general access cap check for all ajax requests
add_filter( 'ajaxs_allow_process', static function( $allow, $action ) {
if(
str_contains( $action, Kama_Post_List_Table_Thumb::class )
&& ! current_user_can( 'edit_others_posts' )
){
return false;
}
return $allow;
}, 10, 2 );
}
public static function add_image_column( $columns ) {
add_action( 'admin_notices', [ __CLASS__, '_js_css_html' ] );
// untitled column.
return array_slice( $columns, 0, 1 ) + [ 'image' => '' ] + $columns;
}
public static function fill_image_column( $colname, $post_id ){
if( $colname === 'image' && current_user_can( 'edit_post', $post_id ) ){
$attach_id = get_post_meta( $post_id, self::$meta_key, true );
$img_src = $attach_id ? wp_get_attachment_image_url( $attach_id, 'thumbnail' ) : self::$add_img_url;
?>
<a class="pthumb thickbox" data-post_id="<?= (int) $post_id ?>" data-attach_id="<?= (int) $attach_id ?>"
href="/?TB_inline&inlineId=up-set-post-thumb&width=700&height=500"
title="Setting post thumbnail"
>
<img src="<?= esc_attr( $img_src ) ?>" alt=""/>
</a>
<?php
}
}
# modal window html, css and js
public static function _js_css_html(){
// modal window scripts
add_thickbox();
add_action( 'admin_print_footer_scripts', [ __CLASS__, '_js' ], 99 );
self::_css();
?>
<!-- modal window -->
<div id="up-set-post-thumb" style="display:none;">
<div class="pthumb-thickbox">
<script type="text/template" id="imagebox-tpl-js">
<div class="imagebox" data-attach_id="{attach_id}" data-full_url="{full_url}">
<img src="{attach_url}" alt="" />
</div>
</script>
<div class="filters">
<button class="button button-small active / post-images-js">Post Images</button>
<button class="button button-small / last-images-js">Last from Library</button>
</div>
<div class="full-view" style="display:none;">
<div class="close"><span class="dashicons dashicons-no-alt"></span></div>
<img src="" alt="">
</div>
<div class="images"></div>
<div class="ajax-res" style="display:none;"></div>
<div class="footer">
<button class="button-primary / set-thumb-js">Set Thumbnail</button>
<button class="button / file-btn-js"><span class="dashicons dashicons-format-image" style=""></span> Upload Images</button>
<input type="file" name="pthumb_files[]" multiple accept="image/*" style="display:none;" />
<button class="button / del-post-thumb-js"><span class="dashicons dashicons-no-alt"></span>Unset Thumbnail</button>
<button class="button / del-attach-js"><span class="dashicons dashicons-trash"></span>Delete File</button>
</div>
</div>
</div>
<?php
}
public static function _css(){
?>
<style>
.column-image{ width:50px; text-align:center; }
.pthumb{ display:block; }
.pthumb img{ width:35px; background:#eee; border-radius:3px; cursor:pointer; }
.pthumb:hover img{ opacity:.7; }
.pthumb-thickbox{ box-sizing:border-box; }
.pthumb-thickbox *{ box-sizing:inherit; }
.pthumb-thickbox{ display:flex; flex-direction:column; height:calc(100% + 17px); margin:-2px -15px -15px; }
.pthumb-thickbox > *{ padding:15px; }
.pthumb-thickbox .images{ flex-grow:1; height:1px; overflow-y:scroll; }
.pthumb-thickbox .filters{ background:#eee; padding-top:5px;padding-bottom:5px; }
.pthumb-thickbox .filters .active{ font-weight:700; }
.pthumb-thickbox .imagebox{ float:left; padding:10px; margin:0 1em 1em 0; background:#eee; border:1px solid #ddd; cursor:pointer; }
/*.pthumb-thickbox .imagebox:nth-child(5n){ margin-right:0; }*/
.pthumb-thickbox .imagebox:hover{ outline:5px solid #9ca4ac; }
.pthumb-thickbox .imagebox.selected{ outline:5px solid #2d3238; }
.pthumb-thickbox .imagebox img{ width:100px; height:100px; display:block; }
.pthumb-thickbox .footer{ display:flex; justify-content:space-between; height:60px; border-top:1px solid #ddd; background:#eee; box-shadow:0 0 1em #b5b9bb; z-index:1; }
.pthumb-thickbox .footer .dashicons{ margin-top:6px; font-size:15px; }
.pthumb-thickbox .ajax-res{ position:absolute; width:100%; bottom:60px; padding-right:60px; }
.pthumb-thickbox .ajax-res{ background:#ddeacb; color:#3d6703; }
.pthumb-thickbox .ajax-res.err{ background:#ffebeb; color:#951212; }
.pthumb-thickbox .full-view{ position:relative; flex-grow:1; max-height:100%; text-align:center; background:#7d7d7d; }
.pthumb-thickbox .full-view img{ max-width:100%; max-height:380px; }
.pthumb-thickbox .close{ position:absolute; top:0; right:0; padding:15px; background:rgba(0,0,0,.2); color:#fff; cursor:pointer; }
</style>
<?php
}
public static function _js(){
?>
<script>
document.addEventListener( 'DOMContentLoaded', function(){
let $thickbox = jQuery( '.pthumb-thickbox' )
let $file = $thickbox.find( '[type="file"]' )
let $images = $thickbox.find( '.images' )
let $fullView = $thickbox.find( '.full-view' )
let $filters = $thickbox.find( '.filters' )
let imageboxTpl = $thickbox.find( '#imagebox-tpl-js' ).html()
let $cur_pthumb = null
const addImgSrc = '<?= self::$add_img_url ?>'
let showMessage = function( message, type ){
let $res = $thickbox.find( '.ajax-res' ).removeClass( 'err' )
$res.html( message + '<div class="close" onclick="jQuery(this).parent().slideUp(100)"><span class="dashicons dashicons-no-alt"></span></div>' )
.addClass( type === 'error' ? 'err' : '' ).slideDown( 100 )
setTimeout( function(){
$res.slideUp( 100 )
}, 25000 )
},
hideMessage = function(){
$thickbox.find( '.ajax-res' ).slideUp( 100 )
}
let _fillImagesBox = function( attachs ){
$images.empty()
if( typeof attachs === 'string' ){
$images.html( attachs )
return
}
// go through the array of objects and add elements.
for( let id in attachs ){
let attach = attachs[id]
$images.append(
imageboxTpl.replace( '{attach_url}', attach.thumb_url )
.replace( '{attach_id}', attach.ID )
.replace( '{full_url}', attach.guid )
)
// set the current thumbnail
if( $cur_pthumb.data( 'attach_id' ) ){
$images.find( '[data-attach_id="' + $cur_pthumb.data( 'attach_id' ) + '"]' ).addClass( 'selected' )
}
else{
$images.find( '.imagebox:first' ).addClass( 'selected' )
}
}
}
let _loadAttachments = function( filter_name ){
$images.empty() // clear images
$filters.find( 'button' ).removeClass( 'active' ) // clear filters
let data = {
post_id : $cur_pthumb.data( 'post_id' ),
cur_attach_id: $cur_pthumb.data( 'attach_id' )
}
if( filter_name === 'last_media' ){
delete data.post_id
$filters.find( '.last-images-js' ).addClass( 'active' )
}
else {
$filters.find( '.post-images-js' ).addClass( 'active' )
}
// AJAX request - load all images of the post
showMessage( 'Loading...' )
ajaxs( 'Kama_Post_List_Table_Thumb::ajaxs_get_images', data, function( resp ){
hideMessage()
if( resp.toString() === '' )
_loadAttachments( 'last_media' )
else
_fillImagesBox( resp )
} )
}
window.resetPthumbImage = function( args ){
// args: post_id, attach_id, src, clear, $pthumb
if( ! args.$pthumb ){
if( args.post_id ) args.$pthumb = jQuery( '.pthumb[data-post_id="' + args.post_id + '"]' )
else if( args.attach_id ) args.$pthumb = jQuery( '.pthumb[data-attach_id="' + args.attach_id + '"]' )
}
var new_attach_id = args.clear ? '' : args.attach_id
args.$pthumb.data( 'attach_id', new_attach_id ).attr( 'data-attach_id', new_attach_id )
args.$pthumb.find( 'img' ).attr( 'src', args.clear ? addImgSrc : args.src )
}
// Click on "Upload any images" when the post has no images
$filters.on( 'click', 'button', function( resp ){
var $btn = jQuery( this )
if( $btn.hasClass( 'post-images-js' ) ){
_loadAttachments()
}
if( $btn.hasClass( 'last-images-js' ) ){
_loadAttachments( 'last_media' )
}
} )
// Click on the thumbnail in the post table
jQuery( '.wp-list-table' ).on( 'click', '.pthumb', function( ev ){
$cur_pthumb = jQuery( this )
_loadAttachments()
} )
// Click on the thumbnail in the modal window - image selection
$images.on( 'click', '.imagebox', function(){
let $box = jQuery( this )
$images.find( '.imagebox' ).removeClass( 'selected' )
$box.addClass( 'selected' )
} )
// Click on the "Set Thumbnail" button
$thickbox.find( '.set-thumb-js' ).on( 'click', function(){
let $setbtn = jQuery( this )
let $selected_imagebox = $images.find( '.selected' )
let data = {
attach_id: $selected_imagebox.data( 'attach_id' ),
post_id : $cur_pthumb.data( 'post_id' )
}
// AJAX
showMessage( 'Wait...' )
ajaxs( 'Kama_Post_List_Table_Thumb::ajaxs_set_post_thumbnail', data, function( resp ){
hideMessage()
// Set, close the modal
if( resp.success ){
jQuery( '#TB_closeWindowButton' ).trigger( 'click' )
resetPthumbImage( {
$pthumb : $cur_pthumb,
attach_id: data.attach_id,
src : $selected_imagebox.find( 'img' ).attr( 'src' )
} )
}
else
showMessage( resp.data, 'error' )
} )
} )
// Click on the "Remove Thumbnail" button
$thickbox.find( '.del-post-thumb-js' ).on( 'click', function(){
if( ! $cur_pthumb.data( 'attach_id' ) ){
showMessage( 'ОШИБКА: У текущей записи миниатюра не установлена...', 'error' )
return
}
// AJAX
showMessage( 'Unsetting...' )
ajaxs( 'Kama_Post_List_Table_Thumb::ajaxs_delete_post_thumbnail',
{ post_id: $cur_pthumb.data( 'post_id' ) },
function( resp ){
$images.find( '.imagebox' ).removeClass( 'selected' )
showMessage( 'Thumbnail cancelled!' )
resetPthumbImage( { clear: 1, $pthumb: $cur_pthumb } )
}
)
} )
// Click on the "Upload Files" button - trigger input file
$thickbox.find( '.file-btn-js' ).on( 'click', function(){
$file.trigger( 'click' )
} )
// Upload file
$file.change( function(){
// AJAX upload files
showMessage( 'Loading...' )
ajaxs( 'Kama_Post_List_Table_Thumb::ajaxs_upload_files',
{
post_id: $cur_pthumb.data( 'post_id' ),
foo : $thickbox
},
function( resp ){
showMessage( resp.data, (resp.success ? 'success' : 'error') )
if( resp.success ){
_loadAttachments()
}
}
)
} )
// Click on "Delete File"
$thickbox.find( '.del-attach-js' ).on( 'click', function(){
let $selected = $images.find( '.selected' )
if( ! $selected.length ){
return showMessage( 'ERROR: Nothing has been selected....', 'error' )
}
if( !confirm( 'Are you sure to delete? The file will be permanently deleted!' ) ){
return
}
// AJAX
ajaxs( 'Kama_Post_List_Table_Thumb::ajaxs_delete_attach',
{ attach_id: $selected.data( 'attach_id' ) },
function( resp ){
if( resp.success ){
$selected.remove()
}
else{
showMessage( resp.data )
}
}
)
} )
// Double click on the image in the modal window - view the image in full size
$images.on( 'dblclick', '.imagebox', function(){
$fullView.show().find( 'img' ).attr( 'src', this.dataset['full_url'] )
$thickbox.find( '.images' ).hide()
} )
$fullView.find( '.close' ).on( 'click', function(){
$fullView.hide()
$images.show()
} )
} )
</script>
<?php
}
}
trait Kama_Post_List_Table_Thumb__ajax_handlers {
public static function ajaxs_get_images( AJAX_Simply_Core $jx ) {
$args = [
'post_type' => 'attachment',
'post_mime_type' => 'image',
'order_by' => 'post_date',
'order' => 'DESC',
'numberposts' => $jx->limit ?: 100,
];
if( $jx->post_id ){
$args['post_parent'] = $jx->post_id;
}
if( $jx->cur_attach_id ){
$args['exclude'] = $jx->cur_attach_id;
}
$attachs = get_posts( $args );
// add the current thumbnail
if( $jx->cur_attach_id && $attach = get_post( $jx->cur_attach_id ) ){
array_unshift( $attachs, $attach );
}
// add a link to the thumbnail
foreach( $attachs as $attach ){
$attach->thumb_url = wp_get_attachment_image_url( $attach->ID, 'thumbnail' );
}
return $attachs;
}
public static function ajaxs_set_post_thumbnail( AJAX_Simply_Core $jx ) {
$atatch = $jx->attach_id ? get_post( $jx->attach_id ) : 0;
$post = $jx->post_id ? get_post( $jx->post_id ) : 0;
if( ! $atatch || ! $post ){
$jx->error( 'ERROR: no attachment or post...' ); // die
}
$attach_post = $atatch->post_parent ? get_post( $atatch->post_parent ) : 0;
set_post_thumbnail( $post->ID, $atatch->ID );
// the attachment is not attached anywhere, let's attach it to the current post
if( ! $attach_post ){
wp_update_post( [ 'ID' => $atatch->ID, 'post_parent' => $post->ID ] );
}
// the attachment is attached to a special post type and the current post type is the same as the attachment post type
// detach the attachment and attach it to the current post
elseif(
in_array( $attach_post->post_type, self::$re_set_attach_for_post_types, true )
&& $attach_post->post_type === $post->post_type
){
wp_update_post( [ 'ID' => $atatch->ID, 'post_parent' => $post->ID ] );
// remove the thumbnail from the previous post if the same thumbnail is set for it
if( $attach_post->ID !== $post->ID && (int) get_post_thumbnail_id( $attach_post->ID ) === (int) $atatch->ID ){
delete_post_thumbnail( $attach_post->ID );
$jx->call( 'window.resetPthumbImage', [ 'clear' => 1,
'post_id' => $attach_post->ID,
] ); // clear the thumbnail of the previous post
}
}
$jx->success();
}
public static function ajaxs_delete_post_thumbnail( AJAX_Simply_Core $jx ) {
return delete_post_thumbnail( $jx->post_id );
}
public static function ajaxs_upload_files( AJAX_Simply_Core $jx ) {
if( ! $jx->pthumb_files ){
$jx->error( 'ERROR: No files...' );
}
if( ! $jx->post_id ){
$jx->error( 'ERROR: Post ID is not specified...' );
}
// filter for allowed file types - allow only images
add_filter( 'upload_mimes', static function( $mimes ) {
return [
'jpg|jpeg|jpe' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'webp' => 'image/webp',
];
} );
// uploading files
$results = [];
foreach( $jx->pthumb_files['compact'] as $filedata ){
// so that if the name is in Cyrillic, it remains in Cyrillic...
$name = sanitize_text_field( preg_replace( '/\.(?:jpg|jpeg|png|gif|webp)$/', '', $filedata['name'] ) );
$id = media_handle_sideload( $filedata, $jx->post_id, $name );
if( is_wp_error( $id ) ){
@ unlink( $filedata['tmp_name'] );
$results[] = 'ERROR: ' . $id->get_error_message() . ' File: ' . esc_html( $filedata['name'] );
}
else{
$results[] = 'OK: ' . esc_html( $filedata['name'] );
}
}
$jx->success( implode( '<br>', $results ) );
}
public static function ajaxs_delete_attach( AJAX_Simply_Core $jx ) {
if( ! $jx->attach_id ){
$jx->error( 'ERROR: Attachment ID not specified...' );
}
$res = wp_delete_attachment( $jx->attach_id, $force_delete = true );
if( false === $res ){
$jx->error( 'ERROR: Failed to delete...' );
}
// clean the thumbnail in the posts table
$jx->call( 'window.resetPthumbImage', [ 'clear' => 1, 'attach_id' => $jx->attach_id ] );
$jx->success( 'Deleted!' );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment