Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

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);
share|improve this question
    
That's a nice question. –  hakre Sep 7 '10 at 21:40
1  
If you found an answer to your own problem, you better add it as an answer below, not in the question itself. This is better in line with the system, and we can up-vote your answer, which will improve your reputation on this site. –  Jan Fabry Sep 9 '10 at 18:18
    
@Jan Thanks. Will do. –  TerryMatula Sep 10 '10 at 14:39
1  
    
I really have to second the 'View Own Posts Media Only' plugin, it worked perfectly for me after looking everywhere for a jquery or php/html/css solution. –  waffl Mar 4 at 13:56

9 Answers 9

up vote 19 down vote accepted

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;

    if( !is_a( $current_user, 'WP_User') )
        return;

    if( 'upload.php' != $pagenow )
        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.. ;)

share|improve this answer
    
I have allowed file upload to subscriber level users. tried to use your code but not working. –  Sisir May 6 '12 at 17:52
1  
"Not working" isn't much to go on. –  t31os Aug 8 '13 at 22:03
    
I can confirm the same observation. For me "not working" means that the "contributor" role can still see all media items when he goes to upload a jpg. However, when he goes to media library from the menu, it's empty. (My "contributor" role already has the extra capability to upload files and that's working.) –  Sparky Feb 3 at 19:27
    
So your code just needs to be tweaked for whatever page fills the "Media Library" tab of the upload window. I'm researching this now. –  Sparky Feb 3 at 19:30
    
If i recall correctly(and mistakes do happen), there was not any appropriate hooks in place at the time of writing this answer, similarly to how no hooks were in place to fix the media count. There's been a good 3 new versions of WordPress since the time of writing however, so solutions may now be possible. –  t31os Feb 3 at 19:33

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.

share|improve this answer

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;
}
share|improve this answer
    
great snippet but if no items are in the media library, it spits out errors, Warning: array_sum() expects parameter 1 to be array, null given, and Warning: array_keys() expects parameter 1 to be array, null given –  chrismccoy Feb 25 '12 at 0:40
    
You just have to define $_num_posts as an array in the fix_media_counts() function. $_num_posts = array(); –  Paul Sep 8 '12 at 23:37
    
The code in this answer works but it also removes any custom fields created by the Advanced Custom Fields plugin. –  Sparky Feb 3 at 20:26
1  
loved your elegant solution :) –  Fresher Oct 16 at 10:18

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!

share|improve this answer

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' );
share|improve this answer
    
You should not use user levels, they are in WordPress still primarily for backwards compatibility(prior to WP 2.0), they are not reliable for determining user capabilities in modern day WordPress(as they will likely disappear from core when that compatibility is no longer required). Use an actual capability to determine user rights. –  t31os Jan 28 at 13:56
    
Despite containing media-upload.php, your code is not working from the upload modal generated by the Post Edit page. Can still see all library items. –  Sparky Feb 3 at 19:58

I used Philip's plugin and made some modifications to plugin and WP files to solve the issue with totals of items. See details at http://junvo.com/blog/2011/restrict-users-to-view-only-their-own-media-library-items-in-wordpress.html

share|improve this answer
    
You are better off posting relevant code here if possible than just posting a link to what looks like your site. –  s_ha_dum Nov 24 '12 at 6:16

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.

share|improve this answer
<?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.

share|improve this answer

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!

share|improve this answer
2  
There is an admin_init hook that runs on every admin request. In case a user requests upload.php and you want to prevent that you could either block that request (e.g. wp_die('Access Denied')) or redirect to some valid place per hook. –  hakre Sep 11 '10 at 0:49

protected by toscho Jul 2 '12 at 21:48

Thank you for your interest in this question. Because it has attracted low-quality answers, posting an answer now requires 10 reputation on this site.

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.