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

Your email address will not be published. Required fields are marked *

My Agile Privacy
This site uses technical and profiling cookies. You can accept, decline or customize cookies by pressing the desired buttons. By closing this policy you will continue without accepting.

Need help?

Choose one of the following options:

Powered by CodingBunny