Enhanced Media Library

Enhance the WordPress media library

Display estimated delivery date
PHP
/**
 * Snippet Name:     Enhanced Media Library
 * Snippet Author:   coding-bunny.com
 * Description:      Enhance the WordPress media library.
 * Version:          1.0.2
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

add_action('admin_head', 'cbaml_styles');
add_action('admin_footer', 'cbaml_scripts');
add_action('wp_ajax_cbaml_save_rating', 'cbaml_save_rating');
add_action('wp_ajax_cbaml_save_label', 'cbaml_save_label');
add_action('wp_ajax_cbaml_save_thumbsize', 'cbaml_save_thumbsize');
add_action('wp_ajax_cbaml_save_theme', 'cbaml_save_theme');
add_action('wp_ajax_cbaml_get_media', 'cbaml_get_media');
add_action('wp_ajax_cbaml_delete_media', 'cbaml_delete_media');

function cbaml_styles() {
    if (!current_user_can('upload_files')) return;
    $screen = get_current_screen();
    if ($screen && $screen->id === 'upload') {
        ?>
        <style>
        :root {
            --bg1: #f7f8fa;
            --bg2: #fff;
            --bg3: #eceef0;
            --text1: #23272f;
            --text2: #7a7a7a;
            --text3: #b0b0b0;
            --border: #d7dadf;
            --accent: #3858e9;
            --danger: #fa5252;
            --warning: #ffd43b;
            --shadow1: rgba(0,0,0,0.04);
            --shadow2: rgba(0,0,0,0.13);
        }
        [data-theme="dark"] {
            --bg1: #16191c;
            --bg2: #23272f;
            --bg3: #262a33;
            --text1: #fff;
            --text2: #b0b0b0;
            --text3: #888;
            --border: #23272f;
            --accent: #4a9eff;
            --danger: #ff6b6b;
            --warning: #ffe066;
            --shadow1: rgba(0,0,0,0.13);
            --shadow2: rgba(0,0,0,0.24);
        }
        .upload-php .wp-list-table.media,
        .upload-php .tablenav,
        .upload-php .wp-filter,
        .upload-php .view-switch,
        .upload-php .media-toolbar,
        .upload-php .attachments-browser,
        .upload-php .media-frame,
        .upload-php .media-toolbar-secondary,
        .upload-php .media-toolbar-primary,
        .upload-php .media-frame-content,
        .upload-php .media-sidebar {display:none!important;}
        .cbaml-container {background:var(--bg1);border-radius:3px;padding:20px;margin:20px 0;box-shadow:0 2px 10px var(--shadow1);}
        .cbaml-controls {background:var(--bg2);padding:15px 15px 10px 15px;border-radius:3px;margin-bottom:18px;border:1px solid var(--border);}
        .cbaml-row {display:flex;flex-wrap:wrap;gap:14px;align-items:center;margin-bottom:12px;}
        .cbaml-row:last-child {margin-bottom:0;}
        .cbaml-group {display:flex;align-items:center;gap:8px;}
        .cbaml-label {font-weight:600;color:var(--text1);font-size:14px;min-width:70px;}
        .cbaml-theme {background:var(--bg3);border:none;color:var(--text1);padding:6px 14px;border-radius:3px;cursor:pointer;font-size:13px;font-weight:600;transition:background .2s;display:flex;align-items:center;gap:5px;}
        .cbaml-theme:hover {background:var(--accent);color:#fff;}
        .cbaml-search {flex:1;min-width:180px;padding:9px 16px;border:1px solid var(--border);border-radius:3px;font-size:15px;background:var(--bg3);color:var(--text1);transition:border .2s;}
        .cbaml-search:focus {outline:none;border-color:var(--accent);background:var(--bg2);}
        .cbaml-slider {width:120px;margin:0 7px;height:5px;background:var(--border);border-radius:3px;-webkit-appearance:none;}
        .cbaml-slider::-webkit-slider-thumb,.cbaml-slider::-moz-range-thumb {width:16px;height:16px;border-radius:2px;background:var(--accent);cursor:pointer;border:none;}
        .cbaml-slidervalue {font-size:13px;color:var(--text2);min-width:45px;font-weight:600;background:var(--bg3);color:var(--text1);padding:2px 7px;border-radius:3px;}
        .cbaml-filters {display:flex;gap:7px;flex-wrap:wrap;}
        .cbaml-filter {padding:6px 13px;background:var(--bg3);border:1px solid var(--border);border-radius:3px;cursor:pointer;font-size:13px;font-weight:600;color:var(--text1);transition:background .2s,color .2s,border .2s;}
        .cbaml-filter.active,.cbaml-filter:focus {background:var(--accent);color:#fff;border-color:var(--accent);}
        .cbaml-filter:hover:not(.active) {background:var(--border);}
        .cbaml-labels {display:flex;gap:5px;flex-wrap:wrap;align-items:center;}
        .cbaml-labelcolor {width:22px;height:22px;border-radius:3px;cursor:pointer;border:2px solid transparent;transition:border .2s,box-shadow .2s,background .2s;position:relative;flex-shrink:0;}
        .cbaml-labelcolor.active,.cbaml-labelcolor:hover {border-color:var(--accent);box-shadow:0 1px 3px var(--shadow2);}
        .cbaml-labelcolor.red {background:#e74c3c;}
        .cbaml-labelcolor.blue {background:#3498db;}
        .cbaml-labelcolor.green {background:#2ecc71;}
        .cbaml-labelcolor.yellow {background:#f1c40f;}
        .cbaml-labelcolor.purple {background:#9b59b6;}
        .cbaml-labelcolor.orange {background:#e67e22;}
        .cbaml-labelcolor.pink {background:#ff69b4;}
        .cbaml-labelcolor.teal {background:#1abc9c;}
        .cbaml-labelcolor.none {background:var(--bg2);border-color:var(--border);}
        .cbaml-labelcolor.none::before {content:'×';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--text3);font-weight:bold;font-size:15px;}
        .cbaml-gallery {display:grid;gap:14px;margin-top:16px;transition:all 0.2s;}
        .cbaml-list-view {display:block;}
        .cbaml-list-table {width:100%;border-collapse:collapse;}
        .cbaml-list-table th,.cbaml-list-table td {padding:8px 6px;font-size:13px;}
        .cbaml-list-table th {background:var(--bg3);color:var(--text1);font-weight:600;text-align: left;}
        .cbaml-list-table tr {border-bottom:1px solid var(--border);}
        .cbaml-list-table td.cbaml-thumbcell img {height:40px;width:auto;}
        .cbaml-list-table td.cbaml-list-actions {white-space:nowrap;}
        .cbaml-view-toggle {margin-left:16px;border:none;background:var(--bg3);color:var(--text1);font-size:15px;padding:6px 14px;border-radius:3px;font-weight:600;cursor:pointer;transition:background .2s;}
        .cbaml-view-toggle.active,.cbaml-view-toggle:focus {background:var(--accent);color:#fff;}
        .cbaml-item {background:var(--bg2);border-radius:3px;padding:10px;box-shadow:0 2px 8px var(--shadow1);position:relative;cursor:pointer;border:1.5px solid transparent;transition:border .2s,box-shadow .2s,transform .18s;}
        .cbaml-item:hover {border-color:var(--accent);box-shadow:0 4px 12px var(--shadow2);transform:translateY(-2px);}
        .cbaml-item.selected {border-color:var(--accent);box-shadow:0 0 0 2px var(--accent);}
        .cbaml-itemlabel {position:absolute;top:8px;right:8px;width:20px;height:20px;border-radius:2px;border:1.5px solid #fff;z-index:3;}
        .cbaml-itemlabel.red {background:#e74c3c;}
        .cbaml-itemlabel.blue {background:#3498db;}
        .cbaml-itemlabel.green {background:#2ecc71;}
        .cbaml-itemlabel.yellow {background:#f1c40f;}
        .cbaml-itemlabel.purple {background:#9b59b6;}
        .cbaml-itemlabel.orange {background:#e67e22;}
        .cbaml-itemlabel.pink {background:#ff69b4;}
        .cbaml-itemlabel.teal {background:#1abc9c;}
        .cbaml-thumb {width:100%;object-fit:contain;border-radius:2px;margin-bottom:7px;background:var(--bg3);transition:transform .18s;box-shadow:none;}
        .cbaml-item:hover .cbaml-thumb {transform:scale(1.01);}
        .cbaml-filename {font-size:12px;font-weight:600;color:var(--text1);margin-bottom:6px;word-break:break-word;line-height:1.35;max-height:2.5em;overflow:hidden;}
        .cbaml-meta {font-size:12px;color:var(--text2);margin-bottom:6px;}
        .cbaml-ratingwrap {display:flex;align-items:center;justify-content:space-between;margin-bottom:7px;padding:3px 3px 3px 5px;background:var(--bg3);border-radius:2px;}
        .cbaml-stars {display:flex;gap:1px;align-items:center;}
        .cbaml-star {cursor:pointer;color:var(--border);font-size:16px;transition:color .13s,transform .13s;user-select:none;}
        .cbaml-star.filled,.cbaml-star.hover {color:var(--warning);}
        .cbaml-star.remove-hover {color:var(--danger);}
        .cbaml-star:hover,.cbaml-star.hover {transform:scale(1.12);}
        .cbaml-star.remove-hover {transform:scale(1.16);}
        .cbaml-remove {cursor:pointer;color:var(--danger);font-size:15px;font-weight:bold;margin-left:4px;padding:0 4px;border-radius:2px;background:transparent;border:none;transition:background .15s;display:flex;align-items:center;justify-content:center;}
        .cbaml-remove:hover {background:var(--danger);color:#fff;}
        .cbaml-ratingcount {font-size:11px;color:var(--text3);font-weight:600;padding:0 4px;border-radius:2px;}
        .cbaml-labelselect {display:none;gap:3px;justify-content:center;margin-bottom:7px;}
        .cbaml-item.selected .cbaml-labelselect,.cbaml-item:hover .cbaml-labelselect {display:flex;}
        .cbaml-dot {width:14px;height:14px;border-radius:2px;cursor:pointer;border:1.5px solid transparent;transition:border .15s,box-shadow .15s;flex-shrink:0;position:relative;}
        .cbaml-dot.active,.cbaml-dot:hover {border-color:var(--accent);box-shadow:0 1px 2px var(--shadow2);}
        .cbaml-dot.red {background:#e74c3c;}
        .cbaml-dot.blue {background:#3498db;}
        .cbaml-dot.green {background:#2ecc71;}
        .cbaml-dot.yellow {background:#f1c40f;}
        .cbaml-dot.purple {background:#9b59b6;}
        .cbaml-dot.orange {background:#e67e22;}
        .cbaml-dot.pink {background:#ff69b4;}
        .cbaml-dot.teal {background:#1abc9c;}
        .cbaml-dot.none {background:var(--bg2);border-color:var(--border);}
        .cbaml-dot.none::before {content:'×';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--text3);font-weight:bold;font-size:11px;}
        .cbaml-actions {display:flex;gap:5px;opacity:0;transition:opacity .13s;}
        .cbaml-item:hover .cbaml-actions {opacity:1;}
        .cbaml-action {flex:1;padding:4px 8px;font-size:12px;border:none;border-radius:2px;cursor:pointer;font-weight:600;background:var(--accent);color:#fff;transition:background .13s;}
        .cbaml-action.delete {background:var(--danger);}
        .cbaml-action:hover {filter:brightness(1.05);}
        .cbaml-bulk {background:var(--accent);color:#fff;padding:13px 16px;border-radius:3px;margin-bottom:16px;display:none;align-items:center;justify-content:space-between;}
        .cbaml-bulk.show {display:flex;}
        .cbaml-bulkcontrols {display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
        .cbaml-bulkbtn {background:rgba(255,255,255,0.17);border:none;color:#fff;padding:5px 10px;border-radius:3px;cursor:pointer;font-size:13px;font-weight:600;transition:background .13s;}
        .cbaml-bulkbtn:hover {background:rgba(255,255,255,0.24);}
        .cbaml-stats {background:var(--bg2);padding:9px 15px;border-radius:3px;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center;font-size:13px;color:var(--text2);border:1px solid var(--border);}
        .cbaml-statsleft {font-weight:600;color:var(--text1);}
        .cbaml-statsright {color:var(--accent);font-weight:500;}
        .cbaml-spinner {display:inline-block;width:32px;height:32px;border:4px solid var(--border);border-top:4px solid var(--accent);border-radius:50%;animation:spin 1s linear infinite;margin:40px auto;}
        @keyframes spin {0%{transform:rotate(0deg);}100%{transform:rotate(360deg);}}
        .cbaml-empty {text-align:center;color:var(--text2);padding:42px 12px;font-size:17px;}
        .cbaml-emptyicon {font-size:42px;margin-bottom:12px;opacity:0.6;}
        @media (max-width:1200px) {
            .cbaml-row {flex-direction:column;align-items:stretch;}
            .cbaml-search {min-width:auto;}
        }
        @media (max-width:768px) {
            .cbaml-container,.cbaml-controls {padding:10px;}
            .cbaml-filters,.cbaml-labels {justify-content:center;}
            .cbaml-bulk {flex-direction:column;gap:10px;text-align:center;}
            .cbaml-bulkcontrols {justify-content:center;}
        }
        </style>
        <?php
    }
}

function cbaml_scripts() {
    if (!current_user_can('upload_files')) return;
    $screen = get_current_screen();
    if ($screen && $screen->id === 'upload') {
        $user_id = get_current_user_id();
        $saved_size = get_user_meta($user_id, 'cbaml_thumbsize', true);
        $saved_theme = get_user_meta($user_id, 'cbaml_theme', true);
        $thumbsize = $saved_size ? intval($saved_size) : 150;
        $theme = ($saved_theme && intval($saved_theme) === 1) ? 'true' : 'false';
        $ajaxurl = esc_url_raw(admin_url('admin-ajax.php'));
        $nonce_media = wp_create_nonce('cbaml_media');
        $nonce_size = wp_create_nonce('cbaml_size');
        $nonce_theme = wp_create_nonce('cbaml_theme');
        $nonce_rating = wp_create_nonce('cbaml_rating');
        $nonce_label = wp_create_nonce('cbaml_label');
        ?>
        <script>
        jQuery(document).ready(function($) {
            let selected = new Set();
            let fRating = 'all', fLabel = 'all', fType = 'all', fDate = 'all', fSort = 'date_desc';
            let items = [], filtered = [];
            let thumbsize = <?php echo $thumbsize; ?>;
            let theme = <?php echo $theme; ?>;
            let page = 1, perPage = 30, more = false;
            let view = 'grid';
            window.cbamlItems = items;

            function applyTheme() {
                if(theme){
                    $('body').attr('data-theme','dark');
                    $('.cbaml-themeicon').text('☀️');
                    $('.cbaml-themetext').text('Light');
                }else{
                    $('body').removeAttr('data-theme');
                    $('.cbaml-themeicon').text('🌙');
                    $('.cbaml-themetext').text('Dark');
                }
            }
            function sanitize(str) {
                return $('<div/>').text(str).html();
            }

            function getType(fn, mime) {
                const ext = fn.split('.').pop().toLowerCase();
                if (/^image\//.test(mime) || ['jpg','jpeg','png','gif','svg','webp','bmp'].includes(ext)) return 'image';
                if (/^video\//.test(mime) || ['mp4','mov','avi','webm','mkv'].includes(ext)) return 'video';
                if (/^audio\//.test(mime) || ['mp3','wav','ogg','aac'].includes(ext)) return 'audio';
                if (ext === 'pdf') return 'pdf';
                if (['doc','docx','odt','rtf'].includes(ext)) return 'doc';
                return 'other';
            }

            function render() {
                filtered = items.filter(item=>{
                    if(fRating!=='all'&&(parseInt(item.rating)||0)!==parseInt(fRating)) return false;
                    if(fLabel!=='all'){
                        const label = item.label||'';
                        if(fLabel==='none'&&label!=='')return false;
                        if(fLabel!=='none'&&label!==fLabel)return false;
                    }
                    if(fType!=='all'&&getType(item.filename,item.mime)!==fType)return false;
                    if(fDate!=='all'){
                        const d = new Date(item.raw_date), now = new Date();
                        if(fDate==='today'&&d.toDateString()!==now.toDateString())return false;
                        if(fDate==='7days'&&(now-d)/864e5>7)return false;
                        if(fDate==='30days'&&(now-d)/864e5>30)return false;
                        if(fDate==='year'&&(now-d)/864e5>365)return false;
                    }
                    const s = $('.cbaml-search').val().toLowerCase();
                    if(s&&!item.filename.toLowerCase().includes(s))return false;
                    return true;
                });
                if(fSort==='az')filtered.sort((a,b)=>a.filename.localeCompare(b.filename));
                else if(fSort==='za')filtered.sort((a,b)=>b.filename.localeCompare(a.filename));
                else if(fSort==='date_asc')filtered.sort((a,b)=>new Date(a.raw_date)-new Date(b.raw_date));
                else filtered.sort((a,b)=>new Date(b.raw_date)-new Date(a.raw_date));
                renderPage();
                stats();
            }

            function renderPage() {
                const g = $('.cbaml-gallery');
                let start=0,end=page*perPage;
                let show=filtered.slice(0,end);more=end<filtered.length;
                if(!show.length){g.html('<div class="cbaml-empty"><div class="cbaml-emptyicon">🔍</div>No items found.</div>');$('.cbaml-loadmore').hide();return;}
                if(view==='list'){ renderList(show); }
                else { renderGrid(show); }
                if(more) $('.cbaml-loadmore').show();else $('.cbaml-loadmore').hide();
            }

            function renderGrid(show) {
                let html='';
                show.forEach(item=>{
                    const rating=parseInt(item.rating)||0,label=item.label||'',stars=starHtml(rating),dots=dotHtml(label);
                    const sel=selected.has(item.id)?'selected':'';
                    html+=`
                    <div class="cbaml-item ${sel}" data-id="${sanitize(item.id)}" data-rating="${sanitize(rating)}" data-label="${sanitize(label)}">
                        ${label?`<div class="cbaml-itemlabel ${sanitize(label)}"></div>`:''}
                        <img src="${sanitize(item.thumbnail)}" alt="${sanitize(item.filename)}" class="cbaml-thumb" loading="lazy" style="height:${thumbsize}px;">
                        <div class="cbaml-filename" title="${sanitize(item.filename)}">${sanitize(item.filename)}</div>
                        <div class="cbaml-meta">${sanitize(item.dimensions)} • ${sanitize(item.filesize)} • ${sanitize(item.date)}</div>
                        <div class="cbaml-ratingwrap">
                            <div class="cbaml-stars" data-id="${sanitize(item.id)}">
                                ${stars}
                                <button type="button" class="cbaml-remove" data-id="${sanitize(item.id)}" title="Remove rating">×</button>
                            </div>
                            <span class="cbaml-ratingcount">${sanitize(rating)}/5</span>
                        </div>
                        <div class="cbaml-labelselect" data-id="${sanitize(item.id)}">${dots}</div>
                        <div class="cbaml-actions">
                            <button class="cbaml-action edit" data-id="${sanitize(item.id)}">Edit</button>
                            <button class="cbaml-action delete" data-id="${sanitize(item.id)}">Delete</button>
                        </div>
                    </div>`;
                });
                $('.cbaml-gallery').removeClass('cbaml-list-view').html(html);
            }

            function renderList(show) {
                let html = `<table class="cbaml-list-table"><thead>
                    <tr>
                        <th>Thumb</th>
                        <th>Filename</th>
                        <th>Type</th>
                        <th>Dimensions</th>
                        <th>Size</th>
                        <th>Date</th>
                        <th>Rating</th>
                        <th>Label</th>
                        <th>Actions</th>
                    </tr></thead><tbody>`;
                show.forEach(item=>{
                    const rating=parseInt(item.rating)||0,label=item.label||'';
                    let stars = '';
                    for(let i=1;i<=5;i++)stars+=`<span class="cbaml-star${i<=rating?' filled':''}" data-rating="${i}" style="font-size:14px;">★</span>`;
                    let dot = label ? `<span class="cbaml-dot ${sanitize(label)} active"></span>` : `<span class="cbaml-dot none active"></span>`;
                    html+=`
                    <tr class="cbaml-list-row" data-id="${sanitize(item.id)}" data-rating="${sanitize(rating)}" data-label="${sanitize(label)}">
                        <td class="cbaml-thumbcell"><img src="${sanitize(item.thumbnail)}" alt="${sanitize(item.filename)}" style="height:40px;"></td>
                        <td>${sanitize(item.filename)}</td>
                        <td>${sanitize(getType(item.filename,item.mime))}</td>
                        <td>${sanitize(item.dimensions)}</td>
                        <td>${sanitize(item.filesize)}</td>
                        <td>${sanitize(item.date)}</td>
                        <td>
                            <span class="cbaml-stars" data-id="${sanitize(item.id)}">${stars}</span>
                            <button type="button" class="cbaml-remove" data-id="${sanitize(item.id)}" title="Remove rating" style="margin-left:5px;">×</button>
                        </td>
                        <td>
                            <div class="cbaml-labelselect" data-id="${sanitize(item.id)}" style="display:inline-flex;gap:2px;vertical-align:middle;">
                                ${dotHtml(label)}
                            </div>
                        </td>
                        <td class="cbaml-list-actions">
                            <button class="cbaml-action edit" data-id="${sanitize(item.id)}">Edit</button>
                            <button class="cbaml-action delete" data-id="${sanitize(item.id)}">Delete</button>
                        </td>
                    </tr>`;
                });
                html+='</tbody></table>';
                $('.cbaml-gallery').addClass('cbaml-list-view').html(html);
            }

            function starHtml(rating) {
                let s='';for(let i=1;i<=5;i++){const f=i<=rating?'filled':'';s+=`<span class="cbaml-star ${f}" data-rating="${i}">★</span>`;}return s;
            }
            function dotHtml(active) {
                const colors=['red','blue','green','yellow','purple','orange','pink','teal'];
                let d = `<div class="cbaml-dot none${active===''?' active':''}" data-label="" title="No label"></div>`;
                colors.forEach(c=>{d+=`<div class="cbaml-dot ${c}${active===c?' active':''}" data-label="${c}" title="${c.charAt(0).toUpperCase()+c.slice(1)}"></div>`;});
                return d;
            }
            function stats() {
                const total=items.length,visible=$('.cbaml-item,.cbaml-list-row').length,sel=selected.size;
                $('.cbaml-total').text(`${visible} of ${total} items`);
                $('.cbaml-statsright').text(sel>0?`${sel} selected`:'');
            }
            function grid() {
                const min=Math.max(thumbsize,120);
                $('.cbaml-gallery').css('grid-template-columns',`repeat(auto-fill,minmax(${min}px,1fr))`);
            }

            function load() {
                $.ajax({
                    url: '<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_get_media',nonce:'<?php echo $nonce_media; ?>'},
                    success: function(r){
                        if(r.success){items=r.data;window.cbamlItems=items;page=1;render();}
                    },
                    error:function(){$('.cbaml-gallery').html('<div class="cbaml-empty"><div class="cbaml-emptyicon">😞</div>Error loading media.</div>');}
                });
            }

            function saveThumb() {
                $.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_save_thumbsize',size:thumbsize,nonce:'<?php echo $nonce_size; ?>'}});
            }
            function saveTheme() {
                $.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_save_theme',theme: theme ? 'true' : 'false',nonce:'<?php echo $nonce_theme; ?>'}});
            }
            function saveRating(id,r) {
                $.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_save_rating',item_id:id,rating:r,nonce:'<?php echo $nonce_rating; ?>'}});
            }
            function saveLabel(id,l) {
                $.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_save_label',item_id:id,label:l,nonce:'<?php echo $nonce_label; ?>'}});
            }

            function bind() {
                $('.cbaml-theme').on('click',function(){
                    theme=!theme;
                    applyTheme();
                    saveTheme();
                });
                $('.cbaml-slider').on('input',function(){
                    thumbsize=parseInt($(this).val());
                    $('.cbaml-slidervalue').text(thumbsize+'px');
                    $('.cbaml-thumb').css('height',thumbsize+'px');
                    grid();
                }).on('change',saveThumb);

                $(document).on('click','.cbaml-remove',function(e){
                    e.stopPropagation();const id=$(this).data('id');setRating(id,0);
                });
                $(document).on('click','.cbaml-star',function(e){
                    e.stopPropagation();
                    const $s=$(this),r=$s.data('rating'),id=$s.closest('.cbaml-stars').data('id');
                    const $item=view==='list'?$('.cbaml-list-row[data-id="'+id+'"]'):$(`.cbaml-item[data-id="${id}"]`);
                    const curr=parseInt($item.data('rating'))||0;
                    setRating(id,curr===r?0:r);
                });
                $(document).on('mouseenter','.cbaml-star',function(){
                    const $t=$(this),r=$t.data('rating'),$s=$t.closest('.cbaml-stars').find('.cbaml-star'),id=$t.closest('.cbaml-stars').data('id');
                    const $item=view==='list'?$('.cbaml-list-row[data-id="'+id+'"]'):$(`.cbaml-item[data-id="${id}"]`);
                    const curr=parseInt($item.data('rating'))||0;
                    $s.removeClass('hover remove-hover');
                    if(curr===r){$t.addClass('remove-hover');}else{$s.slice(0,r).addClass('hover');}
                });
                $(document).on('mouseleave','.cbaml-stars',function(){$(this).find('.cbaml-star').removeClass('hover remove-hover');});
                $(document).on('click','.cbaml-dot',function(e){
                    e.stopPropagation();
                    const $d=$(this),label=$d.data('label'),id=$d.closest('.cbaml-labelselect').data('id');
                    setLabel(id,label);
                });
                $(document).on('click','.cbaml-item',function(e){
                    if($(e.target).is('.cbaml-star,.cbaml-action,.cbaml-dot,.cbaml-remove'))return;
                    const id=parseInt($(this).data('id'));
                    if(e.ctrlKey||e.metaKey){toggle(id);}
                    else{clearSel();select(id);}
                });

                $('.cbaml-search').on('input',debounce(function(){render();},300));
                $('.cbaml-sort').on('change',function(){fSort=$(this).val();render();});
                $('.cbaml-type').on('change',function(){fType=$(this).val();render();});
                $('.cbaml-date').on('change',function(){fDate=$(this).val();render();});
                $(document).on('click','.cbaml-filter[data-filter]',function(){
                    $('.cbaml-filter[data-filter]').removeClass('active');$(this).addClass('active');
                    fRating=$(this).data('filter');render();
                });
                $(document).on('click','[data-label-filter]',function(){
                    $('[data-label-filter]').removeClass('active');$(this).addClass('active');
                    fLabel=$(this).data('label-filter');render();
                });
                $(document).on('click','.cbaml-bulkbtn.rate',function(){
                    const r=$(this).data('rating');selected.forEach(id=>setRating(id,r));
                });
                $(document).on('click','.cbaml-bulklabel',function(){
                    const l=$(this).data('label');selected.forEach(id=>setLabel(id,l));
                });
                $(document).on('click','.cbaml-bulkbtn.deselect',function(){clearSel();});
                $(document).on('keydown',function(e){
                    if(e.ctrlKey&&e.key==='a'){e.preventDefault();selectAll();}
                    if(e.key>='1'&&e.key<='5'&&selected.size>0)selected.forEach(id=>setRating(id,parseInt(e.key)));
                    if(e.key==='0'&&selected.size>0)selected.forEach(id=>setRating(id,0));
                });
                $(document).on('click','.cbaml-action.edit',function(e){
                    e.stopPropagation();
                    const id=$(this).data('id');
                    window.open('<?php echo esc_url(admin_url('post.php')); ?>?post='+encodeURIComponent(id)+'&action=edit','_blank');
                });
                $(document).on('click','.cbaml-action.delete',function(e){
                    e.stopPropagation();const id=$(this).data('id');
                    if(confirm('Are you sure?')){
                        $.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_delete_media',nonce:'<?php echo $nonce_media; ?>',item_id:id},
                            success:function(r){
                                if(r.success){items=items.filter(i=>i.id!=id);selected.delete(id);render();}
                                else alert(r.data||'Delete error');
                            }
                        });
                    }
                });
                $('.cbaml-loadmore').on('click',function(){page+=1;renderPage();});
                $('.cbaml-view-toggle').on('click',function(){
                    $('.cbaml-view-toggle').removeClass('active');
                    $(this).addClass('active');
                    view=$(this).data('view');
                    page=1;
                    render();
                });
            }

            function setRating(id,r){
                const $item=view==='list'?$('.cbaml-list-row[data-id="'+id+'"]'):$(`.cbaml-item[data-id="${id}"]`),$s=$item.find('.cbaml-star');
                $s.removeClass('filled');if(r>0)$s.slice(0,r).addClass('filled');
                $item.find('.cbaml-ratingcount').text(`${r}/5`);
                $item.attr('data-rating',r);
                const it=items.find(i=>i.id==id);if(it)it.rating=r;
                saveRating(id,r);
            }
            function setLabel(id,l){
                const $item=view==='list'?$('.cbaml-list-row[data-id="'+id+'"]'):$(`.cbaml-item[data-id="${id}"]`),$d=$item.find('.cbaml-dot');
                $d.removeClass('active');$d.filter(`[data-label="${l}"]`).addClass('active');
                $item.attr('data-label',l);
                $item.find('.cbaml-itemlabel').remove();
                if(l&&!view==='list')$item.prepend(`<div class="cbaml-itemlabel ${l}"></div>`);
                const it=items.find(i=>i.id==id);if(it)it.label=l;
                saveLabel(id,l);
            }
            function toggle(id){if(selected.has(id)){selected.delete(id);$(`.cbaml-item[data-id="${id}"]`).removeClass('selected');}else{selected.add(id);$(`.cbaml-item[data-id="${id}"]`).addClass('selected');}bulk();}
            function select(id){selected.add(id);$(`.cbaml-item[data-id="${id}"]`).addClass('selected');bulk();}
            function clearSel(){selected.clear();$('.cbaml-item').removeClass('selected');bulk();}
            function selectAll(){$('.cbaml-item:visible').each(function(){const id=parseInt($(this).data('id'));selected.add(id);$(this).addClass('selected');});bulk();}
            function bulk(){const c=selected.size;if(c>0){$('.cbaml-bulk').addClass('show');$('.cbaml-bulkcount').text(`${c} selected`);}else{$('.cbaml-bulk').removeClass('show');}stats();}
            function debounce(f,w){let t;return function(...a){const l=()=>{clearTimeout(t);f(...a);};clearTimeout(t);t=setTimeout(l,w);};}
            function layout() {
                const html=`
                <div class="cbaml-container">
                    <div style="text-align:right;margin-bottom:8px;">
                        <button class="cbaml-view-toggle active" data-view="grid" title="Grid view">▥</button>
                        <button class="cbaml-view-toggle" data-view="list" title="List view">☰</button>
                    </div>
                    <div class="cbaml-stats">
                        <div class="cbaml-statsleft"><span class="cbaml-total">Loading...</span></div>
                        <div class="cbaml-statsright"></div>
                    </div>
                    <div class="cbaml-bulk">
                        <span class="cbaml-bulkcount">0 selected</span>
                        <div class="cbaml-bulkcontrols">
                            <button class="cbaml-bulkbtn rate" data-rating="1">★</button>
                            <button class="cbaml-bulkbtn rate" data-rating="2">★★</button>
                            <button class="cbaml-bulkbtn rate" data-rating="3">★★★</button>
                            <button class="cbaml-bulkbtn rate" data-rating="4">★★★★</button>
                            <button class="cbaml-bulkbtn rate" data-rating="5">★★★★★</button>
                            <button class="cbaml-bulkbtn rate" data-rating="0">Remove Rating</button>
                            <div class="cbaml-labels">
                                <div class="cbaml-labelcolor red cbaml-bulklabel" data-label="red" title="Red"></div>
                                <div class="cbaml-labelcolor blue cbaml-bulklabel" data-label="blue" title="Blue"></div>
                                <div class="cbaml-labelcolor green cbaml-bulklabel" data-label="green" title="Green"></div>
                                <div class="cbaml-labelcolor yellow cbaml-bulklabel" data-label="yellow" title="Yellow"></div>
                                <div class="cbaml-labelcolor purple cbaml-bulklabel" data-label="purple" title="Purple"></div>
                                <div class="cbaml-labelcolor orange cbaml-bulklabel" data-label="orange" title="Orange"></div>
                                <div class="cbaml-labelcolor pink cbaml-bulklabel" data-label="pink" title="Pink"></div>
                                <div class="cbaml-labelcolor teal cbaml-bulklabel" data-label="teal" title="Teal"></div>
                                <div class="cbaml-labelcolor none cbaml-bulklabel" data-label="" title="Remove label"></div>
                            </div>
                            <button class="cbaml-bulkbtn deselect">Deselect</button>
                        </div>
                    </div>
                    <div class="cbaml-controls">
                        <div class="cbaml-row">
                            <input type="text" class="cbaml-search" placeholder="🔍 Search filename...">
                            <div class="cbaml-group"><span class="cbaml-label">Sort:</span>
                                <select class="cbaml-sort">
                                    <option value="date_desc">Newest</option>
                                    <option value="date_asc">Oldest</option>
                                    <option value="az">A → Z</option>
                                    <option value="za">Z → A</option>
                                </select>
                            </div>
                            <div class="cbaml-group"><span class="cbaml-label">Type:</span>
                                <select class="cbaml-type">
                                    <option value="all">All</option>
                                    <option value="image">Images</option>
                                    <option value="video">Videos</option>
                                    <option value="audio">Audio</option>
                                    <option value="pdf">PDF</option>
                                    <option value="doc">Doc</option>
                                    <option value="other">Other</option>
                                </select>
                            </div>
                            <div class="cbaml-group"><span class="cbaml-label">Date:</span>
                                <select class="cbaml-date">
                                    <option value="all">All</option>
                                    <option value="today">Today</option>
                                    <option value="7days">Last 7 days</option>
                                    <option value="30days">Last 30 days</option>
                                    <option value="year">Last year</option>
                                </select>
                            </div>
                            <div class="cbaml-group"><span class="cbaml-label">Size:</span>
                                <input type="range" class="cbaml-slider" min="100" max="300" value="${thumbsize}">
                                <span class="cbaml-slidervalue">${thumbsize}px</span>
                            </div>
                            <button class="cbaml-theme">
                                <span class="cbaml-themeicon">${theme?'☀️':'🌙'}</span>
                                <span class="cbaml-themetext">${theme?'Light':'Dark'}</span>
                            </button>
                        </div>
                        <div class="cbaml-row">
                            <div class="cbaml-group">
                                <span class="cbaml-label">Rating:</span>
                                <div class="cbaml-filters">
                                    <button class="cbaml-filter active" data-filter="all">All</button>
                                    <button class="cbaml-filter" data-filter="5">★★★★★</button>
                                    <button class="cbaml-filter" data-filter="4">★★★★</button>
                                    <button class="cbaml-filter" data-filter="3">★★★</button>
                                    <button class="cbaml-filter" data-filter="2">★★</button>
                                    <button class="cbaml-filter" data-filter="1">★</button>
                                    <button class="cbaml-filter" data-filter="0">No rating</button>
                                </div>
                            </div>
                        </div>
                        <div class="cbaml-row">
                            <div class="cbaml-group">
                                <span class="cbaml-label">Labels:</span>
                                <div class="cbaml-filters">
                                    <button class="cbaml-filter active" data-label-filter="all">All</button>
                                </div>
                                <div class="cbaml-labels">
                                    <div class="cbaml-labelcolor red" data-label-filter="red" title="Red"></div>
                                    <div class="cbaml-labelcolor blue" data-label-filter="blue" title="Blue"></div>
                                    <div class="cbaml-labelcolor green" data-label-filter="green" title="Green"></div>
                                    <div class="cbaml-labelcolor yellow" data-label-filter="yellow" title="Yellow"></div>
                                    <div class="cbaml-labelcolor purple" data-label-filter="purple" title="Purple"></div>
                                    <div class="cbaml-labelcolor orange" data-label-filter="orange" title="Orange"></div>
                                    <div class="cbaml-labelcolor pink" data-label-filter="pink" title="Pink"></div>
                                    <div class="cbaml-labelcolor teal" data-label-filter="teal" title="Teal"></div>
                                    <div class="cbaml-labelcolor none" data-label-filter="none" title="No label"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="cbaml-gallery"><div class="cbaml-spinner"></div></div>
                    <div style="text-align:center;margin:18px 0 0 0;">
                        <button class="cbaml-loadmore" style="display:none;padding:8px 24px;border:none;border-radius:3px;background:var(--accent);color:#fff;font-size:14px;font-weight:400;cursor:pointer;">LOAD MORE...</button>
                    </div>
                </div>`;
                $('.wrap h1').after(html);
            }

            layout();bind();load();grid();applyTheme();
        });
        </script>
        <?php
    }
}

function cbaml_check_permissions() {
    if (!current_user_can('upload_files')) {
        wp_send_json_error('Permission denied', 403);
        exit;
    }
}

function cbaml_get_media() {
    cbaml_check_permissions();
    check_ajax_referer('cbaml_media', 'nonce');
    $args = array(
        'post_type' => 'attachment',
        'post_status' => 'any',
        'posts_per_page' => -1,
        'orderby' => 'date',
        'order' => 'DESC'
    );
    $attachments = get_posts($args);
    $items = array();
    foreach ($attachments as $a) {
        $fp = get_attached_file($a->ID);
        if (!$fp || !file_exists($fp)) continue;
        $m = wp_get_attachment_metadata($a->ID);
        $rating = get_post_meta($a->ID, '_media_rating', true) ?: 0;
        $label = get_post_meta($a->ID, '_media_label', true) ?: '';
        $size = filesize($fp);
        $thumb = wp_get_attachment_image_url($a->ID, 'medium') ?: wp_get_attachment_url($a->ID);
        $orig = wp_get_attachment_url($a->ID);
        $mime = get_post_mime_type($a->ID);
        $items[] = array(
            'id' => (int)$a->ID,
            'filename' => esc_html(basename($fp)),
            'thumbnail' => esc_url_raw($thumb),
            'original_url' => esc_url_raw($orig),
            'dimensions' => isset($m['width']) ? ((int)$m['width'].'×'.(int)$m['height']) : 'N/A',
            'filesize' => size_format($size),
            'date' => esc_html(date_i18n('Y-m-d', strtotime($a->post_date))),
            'raw_date' => esc_html($a->post_date),
            'rating' => (int)$rating,
            'label' => sanitize_text_field($label),
            'mime' => sanitize_mime_type($mime),
        );
    }
    wp_send_json_success($items);
}

function cbaml_save_rating() {
    cbaml_check_permissions();
    check_ajax_referer('cbaml_rating', 'nonce');
    $id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
    $r = isset($_POST['rating']) ? intval($_POST['rating']) : 0;
    if ($id && $r >= 0 && $r <= 5) {
        update_post_meta($id, '_media_rating', $r);
        wp_send_json_success();
    }
    wp_send_json_error('Invalid request');
}

function cbaml_save_label() {
    cbaml_check_permissions();
    check_ajax_referer('cbaml_label', 'nonce');
    $id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
    $l = isset($_POST['label']) ? sanitize_text_field($_POST['label']) : '';
    $ok = ['red','blue','green','yellow','purple','orange','pink','teal',''];
    if ($id && in_array($l, $ok, true)) {
        update_post_meta($id, '_media_label', $l);
        wp_send_json_success();
    }
    wp_send_json_error('Invalid request');
}

function cbaml_save_thumbsize() {
    cbaml_check_permissions();
    check_ajax_referer('cbaml_size', 'nonce');
    $size = isset($_POST['size']) ? intval($_POST['size']) : 0;
    $user = get_current_user_id();
    if ($size >= 100 && $size <= 300 && $user) {
        update_user_meta($user, 'cbaml_thumbsize', $size);
        wp_send_json_success();
    }
    wp_send_json_error('Invalid request');
}

function cbaml_save_theme() {
    cbaml_check_permissions();
    check_ajax_referer('cbaml_theme', 'nonce');
    $theme = (isset($_POST['theme']) && $_POST['theme'] === 'true') ? 1 : 0;
    $user = get_current_user_id();
    if ($user) {
        update_user_meta($user, 'cbaml_theme', $theme);
        wp_send_json_success();
    }
    wp_send_json_error('Invalid request');
}

function cbaml_delete_media() {
    cbaml_check_permissions();
    check_ajax_referer('cbaml_media', 'nonce');
    $id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
    if ($id && current_user_can('delete_post', $id)) {
        $deleted = wp_delete_attachment($id, true);
        if ($deleted) {
            wp_send_json_success();
        }
        wp_send_json_error('Delete failed');
    }
    wp_send_json_error('Permission denied');
}

add_filter('attachment_fields_to_edit', function($fields, $post) {
    if (!current_user_can('upload_files')) return $fields;
    $rating = get_post_meta($post->ID, '_media_rating', true) ?: 0;
    $label = get_post_meta($post->ID, '_media_label', true) ?: '';
    $fields['media_rating'] = array(
        'label' => 'Rating',
        'input' => 'html',
        'html' => '<select name="attachments[' . $post->ID . '][media_rating]">
            <option value="0"' . selected($rating, 0, false) . '>None</option>
            <option value="1"' . selected($rating, 1, false) . '>★ (1/5)</option>
            <option value="2"' . selected($rating, 2, false) . '>★★ (2/5)</option>
            <option value="3"' . selected($rating, 3, false) . '>★★★ (3/5)</option>
            <option value="4"' . selected($rating, 4, false) . '>★★★★ (4/5)</option>
            <option value="5"' . selected($rating, 5, false) . '>★★★★★ (5/5)</option>
        </select>'
    );
    $fields['media_label'] = array(
        'label' => 'Label',
        'input' => 'html',
        'html' => '<select name="attachments[' . $post->ID . '][media_label]">
            <option value=""' . selected($label, '', false) . '>None</option>
            <option value="red"' . selected($label, 'red', false) . '>Red</option>
            <option value="blue"' . selected($label, 'blue', false) . '>Blue</option>
            <option value="green"' . selected($label, 'green', false) . '>Green</option>
            <option value="yellow"' . selected($label, 'yellow', false) . '>Yellow</option>
            <option value="purple"' . selected($label, 'purple', false) . '>Purple</option>
            <option value="orange"' . selected($label, 'orange', false) . '>Orange</option>
            <option value="pink"' . selected($label, 'pink', false) . '>Pink</option>
            <option value="teal"' . selected($label, 'teal', false) . '>Teal</option>
        </select>'
    );
    return $fields;
}, 10, 2);

add_filter('attachment_fields_to_save', function($post, $att) {
    if (!current_user_can('upload_files')) return $post;
    if (isset($att['media_rating'])) update_post_meta($post['ID'], '_media_rating', intval($att['media_rating']));
    if (isset($att['media_label'])) update_post_meta($post['ID'], '_media_label', sanitize_text_field($att['media_label']));
    return $post;
}, 10, 2);
?>

How To Implement This Solution?

Leave a Reply