complex restriction of items in media library

I have a need to limit items in the media library for users without a specific capability. There are a lot of examples out there for how to limit them to only items that a given user has uploaded, but my need is more complex than that.

I need to limit media items for users without a specific capability to those attachments that are NOT:

Read More
  1. header images (i.e., set in Appearance > Headers)
  2. page thumbnails (i.e., featured images)

Below is the solution I’ve come up.

/*
 * restrict media items available to users without cap=mycap to those that are NOT:
 *
 * 1. theme headers (i.e., set in Appearance > Headers)
 * 2. page thumbnails (i.e., featured images)
 */
add_action ('pre_get_posts', 'restrict_media') ;
function
restrict_media ($query)
{
    if (!is_admin () || $query->get ('post_type') != 'attachment' ||
            current_user_can ('mycap')) {
        return ;
        }

    // get our theme's headers (i.e., set in Appearance > Headers)
    $args = array (
        'post_type' => 'attachment',
        'post_status' => array ('inherit', 'private'),
        'posts_per_page' => -1,
        'meta_query' => array (
            'relation' => 'AND',
            array (
                'key' => '_wp_attachment_context',
                'value' => 'custom-header',
                ),
            array (
                'key' => '_wp_attachment_is_custom_header',
                'value' => 'antelopevalley',
                ),
            ),
        ) ;
    // remove ourself from pre_get_posts to avoid infinite regression
    remove_action ('pre_get_posts', array ($this, 'restrict_media')) ;
    $headers = new WP_Query ($args) ;
    // get the IDs of the headers
    $header_ids = array_map (function ($p) { return ($p->ID) ; }, $headers->posts) ;

    // add ourself back to pre_get_posts for subsequent queries
    add_action ('pre_get_posts', array ($this, 'restrict_media')) ;

    // get posts with thumbnails
    $args = array (
        'post_type' => 'page',
        'post_status' => array ('publish', 'pending', 'draft', 'private'),
        'posts_per_page' => -1,
        'meta_query' => array (
            array (
                'key' => '_thumbnail_id',
                'compare' => 'EXISTS',
                ),
            ),
        ) ;
    $with_thumbnail = new WP_Query ($args) ;
    // get the IDs of the thumbnails
    $thumbnail_ids = array_map (function ($p) { return (get_post_meta ($p->ID, '_thumbnail_id', true)) ; },
        $with_thumbnail->posts) ;

    // exclude all theme headers and thumbnails
    $query->set ('post__not_in', array_merge ($header_ids, $thumbnail_ids)) ;

    return ;
}

The above solution works; however, it seems overly complex to me!

I’m wondering if anyone can suggest a simpler solution.

Note: I’ve tried the post_{where,join} filters before for other needs and have found them too complex to maintain so I didn’t even try to figure out a solution to this problem using them, but I’m more than open to one that uses them

Related posts

1 comment

  1. Good code. I think you could simplify by making direct SQL queries instead:

    -- Featured Images
    SELECT `meta_value` FROM `wp_postmeta` WHERE `meta_key` = '_thumbnail_id';
    --> $thumbnail_ids
    
    -- Header Images
    SELECT `post_id` FROM `wp_postmeta` WHERE `meta_key` = '_wp_attachment_context' and `meta_value` = 'custom-header';
    --> $header_ids
    

    Also, with your method, I think that if you call WP_Query with suppress_filters => true (or use get_posts() instead; it suppresses filters by default) you can avoid having to remove your own filter.

Comments are closed.