How to make custom bulk actions work on the media/upload page?

I’m adapting a script I found online to add custom bulk actions to the screen with the list of posts. It has this line:

add_action('load-edit.php', 'custom_bulk_action');

I’m trying to adapt it for the media library. I see that in place of edit.php I should use upload.php, which leads me to believe I need to find the media analog for load-edit.php. Sounds easy, but I can’t even find load-edit.php in my WP install to see if by chance it might be what I’m looking for itself. I have found a few references online to load-*.php (e.g., Custom bulk_action), but nothing that tells me what values * can take.

Read More

(I’ve tried load-upload.php but it’s not working–though it could always be something else in my code that’s gumming up the works.)

So my questions are two:

  1. What is the media analog of load-edit.php?
  2. Where is load-edit.php (and the other load-*.php files), or what code handles these file requests?

The first is my real question, but the second has gotten under my skin.

Can any of you experts out there give me some guidance? I would very much appreciate it.

EDIT

By “not working” I meant not that it crashes, but that it wasn’t doing as it was supposed (changing a media attribute).

The code I’m adapting can be downloaded at the bottom of the post “Add a WordPress Custom Bulk Action” by Justin Stern of Fox Run Software. Going back to verify each step of the code, I got the adapted version to work, but only if I comment out the conditional and the security check (both asterisked below). What are the media analogs I should use to replace these?

 add_action('load-upload.php', array(&$this, 'custom_bulk_action'));


function custom_bulk_action() {

//  ***if($post_type == 'attachment') {  REPLACE WITH:
    if ( !isset( $_REQUEST['detached'] ) ) {

    // get the action
    $wp_list_table = _get_list_table('WP_Media_List_Table');  
    $action = $wp_list_table->current_action();

    echo "naction = $actionn</pre>";

    $allowed_actions = array("export");
    if(!in_array($action, $allowed_actions)) return;

    // security check
//  ***check_admin_referer('bulk-posts'); REPLACE WITH:
    check_admin_referer('bulk-media'); 

    // make sure ids are submitted.  depending on the resource type, this may be 'media' or 'ids'
    if(isset($_REQUEST['media'])) {
      $post_ids = array_map('intval', $_REQUEST['media']);
    }

    if(empty($post_ids)) return;

    // this is based on wp-admin/edit.php
    $sendback = remove_query_arg( array('exported', 'untrashed', 'deleted', 'ids'), wp_get_referer() );
    if ( ! $sendback )
      $sendback = admin_url( "upload.php?post_type=$post_type" );

    $pagenum = $wp_list_table->get_pagenum();
    $sendback = add_query_arg( 'paged', $pagenum, $sendback );

    switch($action) {
      case 'export':

        // if we set up user permissions/capabilities, the code might look like:
        //if ( !current_user_can($post_type_object->cap->export_post, $post_id) )
        //  wp_die( __('You are not allowed to export this post.') );

        $exported = 0;
        foreach( $post_ids as $post_id ) {

          if ( !$this->perform_export($post_id) )
            wp_die( __('Error exporting post.') );
          $exported++;
        }

        $sendback = add_query_arg( array('exported' => $exported, 'ids' => join(',', $post_ids) ), $sendback );
      break;

      default: return;
    }

    $sendback = remove_query_arg( array('action', 'action2', 'tags_input', 'post_author', 'comment_status', 'ping_status', '_status',  'post', 'bulk_edit', 'post_view'), $sendback );

    wp_redirect($sendback);
    exit();
  }
}

I appreciate your help.

EDIT 2

I modified the code above to reflect information from the accepted answer. Many thanks to Ralf912!

Related posts

Leave a Reply

2 comments

  1. If you want to use your code, try this:

    If you want to check if the medias are attachments, you can try to use $_REQUEST['detached']

    add_action( 'load-upload.php', 'export_media_test' );
    
    function export_media_test() {
    
        if ( ! isset( $_REQUEST['action'] ) )
            return;
    
        echo 'Export Media';
    
        if ( isset( $_REQUEST['detached'] ) ) {
            die( 'No attachments' );
        } else {
            die( 'Attachments' );
        }
    
        exit();
    }
    

    You can not check an nonce that wasn’t set. The nonce bulk-posts is set in edit.php, and this is the the posts list. In upload.php is the bulk-media nonce set. So use check_admin_referer('bulk-media');

  2. WordPress is missing some apply_filters and do_action in upload.php. So you have to do some nasty tricks.

    At first, we have to add an export action to the bulk actions. WP_Media_List_Table is an object and this job is very easy. We can simply extend the class and override/extend the needed methods:

    require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
    require_once( ABSPATH . 'wp-admin/includes/class-wp-media-list-table.php' );
    
    class Extended_Media_List_Table extends WP_Media_List_Table
    {
    
        /**
         * Add the export bulk action
         * @return array
         */
        public function get_bulk_actions() {
    
            // get the original bulk actions    
            $actions = parent::get_bulk_actions();
    
            // add our own action(s)
            $actions['export'] = __( 'Export' );
    
            // return the actions    
            return $actions;
        }
    
        /**
         * Returns the current action
         * @return string
         */
        public function current_action() {
    
            // check if our action(s) are set and handle them
            if ( isset( $_REQUEST['action'] ) && 'export' === $_REQUEST['action'] ) {
                return 'export_media';
            }
    
            // let the parent class handle all other actions
            parent::current_action();
    
        }
    
    }
    

    The first method simply add the export action. The second method returns export_media if the export action is choosen.

    Now it become nasty. There is no apply_filter in upload.php and we can not change the class upload.php use to display the medias. The second point is, there is no add_action to hook into to add another action if a bulk action is selected.

    Copy upload.php and rename it (e.g. extended_upload.php). Edit your new file and remove require_once( './admin.php' );. Later we hook into load-upload.php, this hook is called in admin.php. Leaving this line, will end in an endless lopp.

    The next step is to insert a handler for your export action. As you can see in the extended class above, ::current_action() returns a string that will be copied into $doaction. In extended_upload.php you will find a switch statement which handles the action. Add a case to handle the export action:

    case 'export_media':
        // handle only attachments
        // $wp_list_table->detached = false ==> attachments
        // $wp_list_table->detached = true ==> not attached files
        if ( true == $wp_list_table->detached )
            return;
    
        // $_REQUEST['media'] contains an array with the post_ids
        if ( ! empty( $_REQUEST['media'] ) ) {
            foreach ( $_REQUEST['media'] as $postID ) {
                export_image( $postID );
            }
        }
        break;
    

    Remember! All actions will be handled inside an ajax-request. So no output (echo, print, printf, …) will be displayed!!

    The last step to do is, to hook into load-upload.php and say WordPress to use our new extended_upload.php instead of the original upload.php:

    add_action( 'load-upload.php', 'add_bulk_action_export_to_list_media' );
    function add_bulk_action_export_to_list_media() {
        require_once 'extended_upload.php';
        // DO NOT RETURN HERE OR THE ORIGINAL upload.php WILL BE LOADED TOO!
    }
    

    This is a very nasty and tricky solution. Every WordPress update can destroy it and you have to track every changes to upload.php. But this is the only way to retrieve some values (e.g. $wp_list_table->detached which will tell you if it is an attachment or not).

    Maybe it is not a bad idea to write a ticket to filter $wp_list_table and and add an action to the switch statement.

    $wp_list_table = apply_filters( 'upload_list_table', _get_list_table('WP_Media_List_Table') );
    
    switch ( $doaction ) {
    [...]
    
    default:
      global $doaction;
      do_action( 'upload_list_table_actions' );
      break;
    }
    

    This two changes would make it very easy to add bulk actions and handle them.