Restricting users to view only media library items they have uploaded?

I want users to be able to upload photos using add_cap('upload_files') but in their profile page, the Media Library shows every image that’s been uploaded. How can I filter that so that they can only view the images they uploaded?

Here’s my solution for the moment… I’m doing a simple WP query, then a loop on the user’s “Profile” page

$querystr = " SELECT wposts.post_date,wposts.post_content,wposts.post_title, guid 
FROM $wpdb->posts wposts
WHERE wposts.post_author = $author 
AND wposts.post_type = 'attachment' 
ORDER BY wposts.post_date DESC";

$pageposts = $wpdb->get_results($querystr, OBJECT);

Related posts

Leave a Reply

9 comments

  1. You could always filter the media list using a pre_get_posts filter that first determines the page, and the capabilities of the user, and sets the author parameter when certain conditions are met..

    Example

    add_action('pre_get_posts','users_own_attachments');
    function users_own_attachments( $wp_query_obj ) {
    
        global $current_user, $pagenow;
    
        $is_attachment_request = ($wp_query_obj->get('post_type')=='attachment');
    
        if( !$is_attachment_request )
            return;
    
        if( !is_a( $current_user, 'WP_User') )
            return;
    
        if( !in_array( $pagenow, array( 'upload.php', 'admin-ajax.php' ) ) )
            return;
    
        if( !current_user_can('delete_pages') )
            $wp_query_obj->set('author', $current_user->ID );
    
        return;
    }
    

    I used the delete pages cap as a condition so Admins and Editors still see the full media listing.

    There is one small side effect, which i can’t see any hooks for, and that’s with the attachment counts shown above the media list(which will still show the total count of media items, not that of the given user – i’d consider this a minor issue though).

    Thought i’d post it up all the same, might be useful.. 😉

  2. As of WP 3.7 there is a much better way via the ajax_query_attachments_args filter, as provided in the documentation:

    add_filter( 'ajax_query_attachments_args', 'show_current_user_attachments' );
    
    function show_current_user_attachments( $query ) {
        $user_id = get_current_user_id();
        if ( $user_id ) {
            $query['author'] = $user_id;
        }
        return $query;
    }
    
  3. Here’s a complete solution for both posts and media (this code is specifically for authors, but you can change it for any user role). This also fixes the post/media count without hacking the core files.

    // Show only posts and media related to logged in author
    add_action('pre_get_posts', 'query_set_only_author' );
    function query_set_only_author( $wp_query ) {
        global $current_user;
        if( is_admin() && !current_user_can('edit_others_posts') ) {
            $wp_query->set( 'author', $current_user->ID );
            add_filter('views_edit-post', 'fix_post_counts');
            add_filter('views_upload', 'fix_media_counts');
        }
    }
    
    // Fix post counts
    function fix_post_counts($views) {
        global $current_user, $wp_query;
        unset($views['mine']);
        $types = array(
            array( 'status' =>  NULL ),
            array( 'status' => 'publish' ),
            array( 'status' => 'draft' ),
            array( 'status' => 'pending' ),
            array( 'status' => 'trash' )
        );
        foreach( $types as $type ) {
            $query = array(
                'author'      => $current_user->ID,
                'post_type'   => 'post',
                'post_status' => $type['status']
            );
            $result = new WP_Query($query);
            if( $type['status'] == NULL ):
                $class = ($wp_query->query_vars['post_status'] == NULL) ? ' class="current"' : '';
                $views['all'] = sprintf(
                '<a href="%1$s"%2$s>%4$s <span class="count">(%3$d)</span></a>',
                admin_url('edit.php?post_type=post'),
                $class,
                $result->found_posts,
                __('All')
            );
            elseif( $type['status'] == 'publish' ):
                $class = ($wp_query->query_vars['post_status'] == 'publish') ? ' class="current"' : '';
                $views['publish'] = sprintf(
                '<a href="%1$s"%2$s>%4$s <span class="count">(%3$d)</span></a>',
                admin_url('edit.php?post_type=post'),
                $class,
                $result->found_posts,
                __('Publish')
            );
            elseif( $type['status'] == 'draft' ):
                $class = ($wp_query->query_vars['post_status'] == 'draft') ? ' class="current"' : '';
                $views['draft'] = sprintf(
                '<a href="%1$s"%2$s>%4$s <span class="count">(%3$d)</span></a>',
                admin_url('edit.php?post_type=post'),
                $class,
                $result->found_posts,
                __('Draft')
            );
            elseif( $type['status'] == 'pending' ):
                $class = ($wp_query->query_vars['post_status'] == 'pending') ? ' class="current"' : '';
                $views['pending'] = sprintf(
                '<a href="%1$s"%2$s>%4$s <span class="count">(%3$d)</span></a>',
                admin_url('edit.php?post_type=post'),
                $class,
                $result->found_posts,
                __('Pending')
            );
            elseif( $type['status'] == 'trash' ):
                $class = ($wp_query->query_vars['post_status'] == 'trash') ? ' class="current"' : '';
                $views['trash'] = sprintf(
                '<a href="%1$s"%2$s>%4$s <span class="count">(%3$d)</span></a>',
                admin_url('edit.php?post_type=post'),
                $class,
                $result->found_posts,
                __('Trash')
            );
            endif;
        }
        return $views;
    }
    
    // Fix media counts
    function fix_media_counts($views) {
        global $wpdb, $current_user, $post_mime_types, $avail_post_mime_types;
        $views = array();
        $count = $wpdb->get_results( "
            SELECT post_mime_type, COUNT( * ) AS num_posts 
            FROM $wpdb->posts 
            WHERE post_type = 'attachment' 
            AND post_author = $current_user->ID 
            AND post_status != 'trash' 
            GROUP BY post_mime_type
        ", ARRAY_A );
        foreach( $count as $row )
            $_num_posts[$row['post_mime_type']] = $row['num_posts'];
        $_total_posts = array_sum($_num_posts);
        $detached = isset( $_REQUEST['detached'] ) || isset( $_REQUEST['find_detached'] );
        if ( !isset( $total_orphans ) )
            $total_orphans = $wpdb->get_var("
                SELECT COUNT( * ) 
                FROM $wpdb->posts 
                WHERE post_type = 'attachment'
                AND post_author = $current_user->ID 
                AND post_status != 'trash' 
                AND post_parent < 1
            ");
        $matches = wp_match_mime_types(array_keys($post_mime_types), array_keys($_num_posts));
        foreach ( $matches as $type => $reals )
            foreach ( $reals as $real )
                $num_posts[$type] = ( isset( $num_posts[$type] ) ) ? $num_posts[$type] + $_num_posts[$real] : $_num_posts[$real];
        $class = ( empty($_GET['post_mime_type']) && !$detached && !isset($_GET['status']) ) ? ' class="current"' : '';
        $views['all'] = "<a href='upload.php'$class>" . sprintf( __('All <span class="count">(%s)</span>', 'uploaded files' ), number_format_i18n( $_total_posts )) . '</a>';
        foreach ( $post_mime_types as $mime_type => $label ) {
            $class = '';
            if ( !wp_match_mime_types($mime_type, $avail_post_mime_types) )
                continue;
            if ( !empty($_GET['post_mime_type']) && wp_match_mime_types($mime_type, $_GET['post_mime_type']) )
                $class = ' class="current"';
            if ( !empty( $num_posts[$mime_type] ) )
                $views[$mime_type] = "<a href='upload.php?post_mime_type=$mime_type'$class>" . sprintf( translate_nooped_plural( $label[2], $num_posts[$mime_type] ), $num_posts[$mime_type] ) . '</a>';
        }
        $views['detached'] = '<a href="upload.php?detached=1"' . ( $detached ? ' class="current"' : '' ) . '>' . sprintf( __( 'Unattached <span class="count">(%s)</span>', 'detached files' ), $total_orphans ) . '</a>';
        return $views;
    }
    
  4. This is a modified version of the accepted answer. Since the accepted answer only targets the Media menu item on the left, users could still see the whole media library within the modal box when uploading a photo to a post. This slightly modified code fixes that situation. The targeted users will only see their own media items from the Media Library tab of the modal box that pops up within a post.

    This is the code from the accepted answer with a comment marking the line to edit…

    add_action('pre_get_posts','users_own_attachments');
    function users_own_attachments( $wp_query_obj ) {
    
        global $current_user, $pagenow;
    
        if( !is_a( $current_user, 'WP_User') )
            return;
    
        if( 'upload.php' != $pagenow ) // <-- let's work on this line
            return;
    
        if( !current_user_can('delete_pages') )
            $wp_query_obj->set('author', $current_user->id );
    
        return;
    }
    

    For users to only view their own media from Media menu AND Media Library tab of the upload modal, replace the indicated line with this…

    if( (   'upload.php' != $pagenow ) &&
        ( ( 'admin-ajax.php' != $pagenow ) || ( $_REQUEST['action'] != 'query-attachments' ) ) )
    

    (line-breaks & spacing only inserted for readability here)

    The following is the same as above but also restricts them to seeing their own posts from the Posts menu item.

    if( (   'edit.php' != $pagenow ) &&
        (   'upload.php' != $pagenow ) &&
        ( ( 'admin-ajax.php' != $pagenow ) || ( $_REQUEST['action'] != 'query-attachments' ) ) )
    

    (line-breaks & spacing only inserted for readability here)

    Notes: as in the accepted answer, the posts and media counters will be wrong. However, there are solutions for this in some other answers on this page. I did not incorporate those simply because I had not tested them.

  5. Complete working code.. Only issue is, getting wrong count of images in media library on Add Post page.

    function my_files_only( $wp_query ) {
    if ( strpos( $_SERVER[ 'REQUEST_URI' ], '/wp-admin/upload.php' ) !== false ) {
        if ( !current_user_can( 'level_5' ) ) {
            global $current_user;
            $wp_query->set( 'author', $current_user->id );
        }
    }
    else if ( strpos( $_SERVER[ 'REQUEST_URI' ], '/wp-admin/media-upload.php' ) !== false ) {
        if ( !current_user_can( 'level_5' ) ) {
            global $current_user;
            $wp_query->set( 'author', $current_user->id );
        }
    }
    }
    add_filter('parse_query', 'my_files_only' );
    
  6. t31os has a great solution up there. The only thing is that the number of all the posts still shows up.

    I figured out a way to keep the number count from showing up using jQuery.

    Just add this to your function file.

        function jquery_remove_counts()
    {
        ?>
        <script type="text/javascript">
        jQuery(function(){
            jQuery("ul.subsubsub").find("span.count").remove();
        });
        </script>
        <?php
    }
    add_action('admin_head', 'jquery_remove_counts');
    

    It’s working for me!

  7. I solved my problem with a pretty rough, but workable solution.

    1) I installed the WP Hide Dashboard plugin, so the User would only see a link to their profile edit form.

    2) In author.php template file, I inserted the code I used above.

    3) Then, for logged in users, I displayed a direct link to the Upload page “wp-admin/media-new.php”

    4) The next issue I noticed, was after they uploaded the photo, it would redirect them to upload.php… and they could see all the other pictures. I haven’t found a hook into the media-new.php page, so I ended up hacking into the core “media-upload.php” and redirecting them to their profile page:

        global $current_user;
        get_currentuserinfo();
        $userredirect =  get_bloginfo('home') . "/author/" .$current_user->user_nicename;
    

    Then replaced wp_redirect( admin_url($location) ); with wp_redirect($userredirect);

    A couple of issues, though. First, the logged in user can still go to “upload.php”, if they know it exists. They can’t do anything except LOOK at the files, and 99% of people won’t even know about it, but it’s still not optimal. Second, it also redirect the Admin to the profile page after uploading. These can have a fairly simple fix by checking user roles, and only redirecting Subscribers.

    If anyone has ideas about hooking into the Media page without going into the core files, I’d appreciate it. Thanks!

  8. <?php
    /*
    Plugin Name: Manage Your Media Only
    Version: 0.1
    */
    
    //Manage Your Media Only
    function mymo_parse_query_useronly( $wp_query ) {
        if ( strpos( $_SERVER[ 'REQUEST_URI' ], '/wp-admin/upload.php' ) !== false ) {
            if ( !current_user_can( 'level_5' ) ) {
                global $current_user;
                $wp_query->set( 'author', $current_user->id );
            }
        }
    }
    
    add_filter('parse_query', 'mymo_parse_query_useronly' );
    ?>
    

    Save code above as manage_your_media_only.php, zip it, upload as plugin to your WP and activate it, that’s all.

  9. One way of doing this is to use the Role Scoper plugin, it is great for managing very specific roles and capabilities too. You can actually lock access to images in the Media Library to only those uploaded by each user. I’ve been using it for a project that I’m working on at the moment and it works well.