bueltge / multisite-global-media

Share a media library across the WordPress Multisite network
GNU General Public License v2.0
215 stars 50 forks source link

Avoid need for users to be a member of the Media site #54

Open jockebq opened 5 years ago

jockebq commented 5 years ago

Hi,

I know this has been brought up before. But now I think there is a solution to this. The plugin Network Media Library has solved it: https://github.com/humanmade/network-media-library/issues/6#issuecomment-433379908

Would it be possible to implement this into Multisite Global Media too?

virgo79 commented 5 years ago

Hi @jockebq The following filter should let users view and select global media attachments into their own blog without having a role into the Media Site.

function global_media_user_cap($allcaps, $caps, $args, $this) {

    if( is_admin() && defined( 'DOING_AJAX' ) && DOING_AJAX )
        if( isset($_POST['action']) && $_POST['action'] == 'query-attachments') {

            if( function_exists('MultisiteGlobalMedia\getSideId') ) {

                if( MultisiteGlobalMedia\getSideId() == get_current_blog_id() )
                    $allcaps['upload_files'] = 1;

            }
        }

    return $allcaps;

}
add_filter( 'user_has_cap', 'global_media_user_cap', 10, 4 );

Not sure if this code snippet need more testing, any ideas (@bueltge @johnbillion )?

bueltge commented 5 years ago

The Network Media Library plugin has enhanced a fucntion to solve this, see https://github.com/humanmade/network-media-library/blob/master/network-media-library.php#L381

add_filter( 'map_meta_cap', __NAMESPACE__ . '\allow_media_library_access', 10, 4 );

/**
 * Apply the current site's `upload_files` capability to the network media site.
 *
 * This grants a user access to the network media site's library, if that user has access to
 * the media library of the current site (whichever site the request has been made from).
 *
 * @param string[] $caps    Capabilities for meta capability.
 * @param string   $cap     Capability name.
 * @param int      $user_id The user ID.
 * @param array    $args    Adds the context to the cap. Typically the object ID.
 *
 * @return string[] Updated capabilities.
 */
function allow_media_library_access( array $caps, string $cap, int $user_id, array $args ) : array {
    if ( get_current_blog_id() !== get_site_id() ) {
        return $caps;
    }
    if ( ! in_array( $cap, [ 'edit_post', 'upload_files' ], true ) ) {
        return $caps;
    }
    if ( 'edit_post' === $cap ) {
        $content = get_post( $args[0] );
        if ( 'attachment' !== $content->post_type ) {
            return $caps;
        }
        // Substitute edit_post because the attachment exists only on the network media site.
        $cap = get_post_type_object( $content->post_type )->cap->create_posts;
    }
    /*
     * By the time this function is called, we've already switched context to the network media site.
     * Switch back to the original site -- where the initial request came in from.
     */
    switch_to_blog( (int) $GLOBALS['current_blog']->blog_id );
    remove_filter( 'map_meta_cap', __NAMESPACE__ . '\allow_media_library_access', 10 );
    $user_has_permission = user_can( $user_id, $cap );
    add_filter( 'map_meta_cap', __NAMESPACE__ . '\allow_media_library_access', 10, 4 );
    restore_current_blog();
    return ( $user_has_permission ? [ 'exist' ] : $caps );
}

Currently we haven't a check for access of the user. But I mean we should use a custom cap to check this. However the idea from @virgo79 is simple and should work! Easy to maintain, that's great. Maybe you will create a pull request for this feature so that people can try it? But it would be nice if you can create a solution with the if nesting.

jockebq commented 5 years ago

@virgo79

Hi @jockebq The following filter should let users view and select global media attachments into their own blog without having a role into the Media Site.

Not sure if this code snippet need more testing, any ideas (@bueltge @johnbillion )?

I tested it out. Put your code in the functions.php file, and the site won't load. And tried to put it in the multisite-global-media.php and got the error again.

virgo79 commented 5 years ago

@jockebq I've tested in my multisite installation and does not give any issues. Please, could you provide any debug information?

jockebq commented 5 years ago

@virgo79 which version of this plugin do you use?

virgo79 commented 5 years ago

@jockebq

sorry, I'm using an older version (0.0.7-dev). Many things changed with the latest version. The code need to be updated! I'll post the new code asap.

Thank you very much.

jockebq commented 5 years ago

@virgo79 I tried with 0.0.8 first, but changed to 0.0.7 to try it out. It still doesn't work. Tried both with the code in functions.php and in the plugin file itself. Page doesn't load as soon as I add the function. If I put the function in the plugin file and try to activate the plugin, this is the error:

Fatal error: Cannot use $this as parameter in /wp-content/plugins/multisite-global-media/multisite-global-media.php on line 54

This is line 54: function global_media_user_cap($allcaps, $caps, $args, $this) {

When function is put in functions.php I get no error, just a blank page.

bueltge commented 5 years ago

The filter user_has_cap has only 3 parameter, so that you should try with this source, a copy from @virgo79 with small changes.

function global_media_user_cap($allcaps, $caps, $args, $user) {

    if ( ! is_admin() ) {
        return $allcaps;
    }

    if ( defined( 'DOING_AJAX' ) && ! DOING_AJAX ) {
        return $allcaps;
    }

    if ( isset($_POST['action']) && $_POST['action'] !== 'query-attachments' ) {
        return $allcaps;
    }

    if ( function_exists('MultisiteGlobalMedia\getSideId') ) {

        if ( MultisiteGlobalMedia\getSideId() === get_current_blog_id() ) {
            $allcaps['upload_files'] = 1;
        }
    }

    return $allcaps;
}
add_filter( 'user_has_cap', 'global_media_user_cap', 10, 4 );
virgo79 commented 5 years ago

@jockebq the $this param was a typo from copy/paste action, could be replaced by $user as it represents a WP_USER object.

@bueltge as of wp 3.7.0 the 4th param has been added, as shown in the following wordpress doc reference: https://developer.wordpress.org/reference/hooks/user_has_cap/ I suppose that codex page is not updated.

virgo79 commented 5 years ago

In the filter I posted before there is another step that has to be fixed.

The MultisiteGlobalMedia\getSideId is no more publicly exposed, as it was in the In the 0.0.7-dev version. Many things has changed with the latests version and now this method is available only within the Site Class Object.

@bueltge how can we access the id and idSitePrefix properties that are currently available only in the Site Class?

These properties should be more accessible, or publicly exposed, to let thirdy part plugins or themes do their customizations.

For example, I'm working on a plugin to let ACF works with Multisite Global Media and to do that it's necessary having direct access to Media Site Idand prefix.

jockebq commented 5 years ago

@virgo79 using 0.0.8 and the code that @bueltge just posted it works great! I have two small issues. If I go to the Global Media Tab and try searching, it will not find anything, and I will be presented with the ability to upload a file. If I do this (from the Global Media tab) it will upload fine, and it will place itself in the Media Library. But if I try to search this file when I am in the Media Library tab it is not found. Something is lost when the file is uploaded via the wrong tab. My solution would be to hide/remove the Filter and Search bar from the Global Media tab. Is this possible? I want it visible in the Media Library tab but removed from Global Media tab. That way this problem wont occur.

EDIT: Realised that it will happen again, because you are able to drag and drop upload to the Global Media Tab.

bueltge commented 5 years ago

@virgo79 good hint, I don't see inside the source, IDE for this hook. SO I have update the code above with a 4th parameter. But it should also work without, because we need this parameter currently.

@widoz is currently the lead on the source, so I would invite him to get the site ID (function in https://github.com/bueltge/multisite-global-media/blob/50e34d3ff30b94ea8f80aa46deb2b864e57eba04/src/Site.php#L32) in a public context and find a solution.

But I think the method is public so you should try \MultisiteGlobalMedia\Site::idSitePrefix.

how can we access the id and idSitePrefix properties that are currently available only in the Site Class?

These properties should be more accessible, or publicly exposed, to let thirdy part plugins or themes do their customizations.

For example, I'm working on a plugin to let ACF works with Multisite Global Media and to do that it's necessary having direct access to Media Site Id and prefix.

virgo79 commented 5 years ago

But if I try to search this file when I am in the Media Library tab it is not found. Something is lost when the file is uploaded via the wrong tab.

@jockebq not able to reproduce this error. I can search and find by name any file uploaded from the Globam Media Tab. Of course, uploading file from the Global Media Tab results in a file correctly uploaded into the Media Library of current blog (not Media Site) and this is fine.

widoz commented 5 years ago

@virgo79 If you need to get the id and the idSitePrefix you can simply create an instance of Site class. $site = new MultisiteGlobalMedia\Site(); then you can call the methods $site->id(); and $site->idSitePrefix(). I don't think we need functions here.

If I go to the Global Media Tab and try searching, it will not find anything, and I will be presented with the ability to upload a file. If I do this (from the Global Media tab) it will upload fine, and it will place itself in the Media Library. But if I try to search this file when I am in the Media Library tab it is not found. Something is lost when the file is uploaded via the wrong tab.

@jockebq Could you please open a separate issue for that?

virgo79 commented 5 years ago

Ok, this is the updated (and hopefully working) version of the filter:

function global_media_user_cap($allcaps, $caps, $args, $user) {

    if( !in_array('upload_files', $args) )
        return $allcaps;

    if( defined('DOING_AJAX') && DOING_AJAX && isset($_POST['action']) && $_POST['action'] == 'query-attachments' ) {

        // version > 0.0.8
        if( class_exists('MultisiteGlobalMedia\Site') ) {

            $site = new MultisiteGlobalMedia\Site();
            if( $site->id() == get_current_blog_id() ) {
                $allcaps['upload_files'] = 1;
            }

        }
        // version <= 0.0.8
        elseif( function_exists('MultisiteGlobalMedia\getSideId') ) {
            if( MultisiteGlobalMedia\getSideId() == get_current_blog_id() ) {
                $allcaps['upload_files'] = 1;
            }
        }

    }

    return $allcaps;

}
add_filter( 'user_has_cap', 'global_media_user_cap', 99, 4 );

The first check it was added because this filter is fired many times during the wordpress life cycle so we should exit early if the $cap request (upload_files) is not in the list.

I've also added compatibility for version older than 0.1.0. So should work also with previous versions.

@bueltge I prefer using a single if statement instead of the if sequence you suggested above. I think that should be more fast, and if not needed should exit early.

@widoz thanks for the clarifications.

@jockebq could you test it with the latest version?

jockebq commented 5 years ago

@virgo79 using your code on version 0.0.8 and it works great! I was able to reproduce the error I described by going to Global Media Tab, and search for something, and when the No Items found appears, press the Select files button. After I uploaded a file I was not able to find it. But I redid the same thing once again, and this time it worked. And I am not sure what causes this to happen.

@widoz Sure, but I am not sure if it is because of Multisite Global Media plugin or the function in this thread.

holzhannes commented 5 years ago

@virgo79 using the code from https://github.com/bueltge/multisite-global-media/issues/54#issuecomment-436980167 on version 0.0.8 it works 😄. Beta version I will test once it is working.

jockebq commented 5 years ago

@virgo79 is there a reason the function you made doesn't work if you put it inside the plugin file itself? I have no need for updates right now, so I tried renaming the plugin and adding this function directly to the plugin's php file. But this breaks the site. Function works great in functions.php though.

jockebq commented 5 years ago

@virgo79 I just found a "bug" which took me a while to figure out. I have a custom post type where you can attach a video to the post. This plugin and your code is amazing because I can use the same videos across the multisite. The issue however is that I use a function to get the length of the video selected. The function looks like this:

$attachment_id = attachment_url_to_postid( $_POST[ 'background-video' ] ); $video_meta = get_post_meta( $attachment_id , '_wp_attachment_metadata', true ); if( isset( $_POST[ 'background-video' ] ) && $video_meta['length'] >= 1 ) { update_post_meta( $post_id, 'slide_duration', $video_meta['length'] ); }

The function gets the post ID via the URL and then get_post_meta length from the attachment and sets this the length in a new custom post meta called slide_duration. This works great for files uploaded to the site, but it doesn't work for the Global Media files. Is there any workaround to this?

virgo79 commented 5 years ago

Hi @jockebq I think the problem is that you should switch to the Media Site before calling the attachment_url_to_postid If you have selected a video from the global Media Site, you need to use the switch_to_blog function to switch to the Media Site before calling any function related to this video, due to the fact that the meta data information about that file are stored in the Media Site Database,.

You should do something like this:

switch_to_blog($media_site);

$attachment_id = attachment_url_to_postid( $_POST[ 'background-video' ] );
$video_meta = get_post_meta( $attachment_id , '_wp_attachment_metadata', true ); 

restore_current_blog();

if( isset( $_POST[ 'background-video' ] ) && $video_meta['length'] >= 1 ) {
    update_post_meta( $post_id, 'slide_duration', $video_meta['length'] );
}

where $media_site is the id of the Media Site in your network

widoz commented 5 years ago

@virgo79 @jockebq there is a filter we could use. I created an issue for that https://github.com/bueltge/multisite-global-media/issues/65 if you like to discuss about that please continue in that issue.

This one is starting to be a bit offtopic.

virgo79 commented 5 years ago

I agree with @widoz. You can also consider to close this ticket for now. There is a working solution to the main question: https://github.com/bueltge/multisite-global-media/issues/54#issuecomment-436980167

jockebq commented 5 years ago

Hi @jockebq I think the problem is that you should switch to the Media Site before calling the attachment_url_to_postid If you have selected a video from the global Media Site, you need to use the switch_to_blog function to switch to the Media Site before calling any function related to this video, due to the fact that the meta data information about that file are stored in the Media Site Database,.

You should do something like this:

switch_to_blog($media_site);

$attachment_id = attachment_url_to_postid( $_POST[ 'background-video' ] );
$video_meta = get_post_meta( $attachment_id , '_wp_attachment_metadata', true ); 

restore_current_blog();

if( isset( $_POST[ 'background-video' ] ) && $video_meta['length'] >= 1 ) {
    update_post_meta( $post_id, 'slide_duration', $video_meta['length'] );
}

where $media_site is the id of the Media Site in your network

You are right. I am so sorry for taking it slightly offtopic. Your solution with switch_to_blog works, but sadly I cannot just input the site id for the Media Site. As this breaks the function if the user chooses a file he uploaded. What I cannot figure out is how to detect if the file selected was from the Media Site, and if it is, switch blog, else stay on the current blog.

I would love to take this outside this thread, but there is no way via Github as far as I am aware.

Thanks for everything!

widoz commented 5 years ago

Hello @jockebq,

If I understand correctly the question:

What I cannot figure out is how to detect if the file selected was from the Media Site, and if it is, switch blog, else stay on the current blog.

That's is simple, every attachment id retrieve from the Media Site contains the siteId plus a sequences of zero numbers and the attachmentId.

You can know that using the Helper Trait located at  \MultisiteGlobalMedia\Helper > multisite-global-media/src/Helper.php

You have to create a new class and use the trait within that.

There are two methods:

Just an example:

$attachmentId = (int)$attachmentId;
$siteId = $this->site->id();
$idPrefix = $this->site->idSitePrefix();

if ($this->idPrefixIncludedInAttachmentId($attachmentId, $idPrefix)) {
    $attachmentId = $this->stripSiteIdPrefixFromAttachmentId($idPrefix, $attachmentId);
    $this->siteSwitcher->switchToBlog($siteId);
    $html = // ... here you call your functions to retrieve the value you need
    $this->siteSwitcher->restoreBlog();
}

return $html; // you return the filtered value

Have a look at \MultisiteGlobalMedia\Thumbnail::postThumbnailHtml (multisite-global-media/src/Thumbnail.php) for more informations.

The $this->siteSwitcher->switchToBlog will switch the blog only if the following condition is meet get_current_blog_id() === $siteId. See multisite-global-media/src/SingleSwitcher.php

jockebq commented 5 years ago

@widoz Thank you for the help, but I cannot manage to make it work. Something basic like this would do what I want, but I don't know how to figure out if media is from current site or media site. Could I count the length of the attachment ID, like you said is longer: 1000015


if "media selected is from Media site" { // Check to see if selected media isn't from current site
switch_to_blog('10'); // Site id 10 is my Media site
$attachment_id = attachment_url_to_postid( $_POST[ 'background-video' ] );
$video_meta = get_post_meta( $attachment_id , '_wp_attachment_metadata', true ); 
restore_current_blog();
}
else {
$attachment_id = attachment_url_to_postid( $_POST[ 'background-video' ] );
$video_meta = get_post_meta( $attachment_id , '_wp_attachment_metadata', true ); 
}

if( isset( $_POST[ 'background-video' ] ) && $video_meta['length'] >= 1 ) {
    update_post_meta( $post_id, 'slide_duration', $video_meta['length'] );
}
holzhannes commented 5 years ago
function global_media_user_cap($allcaps, $caps, $args, $user) {

  if( !in_array('upload_files', $args) )
      return $allcaps;

  if( defined('DOING_AJAX') && DOING_AJAX && isset($_POST['action']) && $_POST['action'] == 'query-attachments' ) {

      // version > 0.0.8
      if( class_exists('MultisiteGlobalMedia\Site') ) {

          $site = new MultisiteGlobalMedia\Site();
          if( $site->id() == get_current_blog_id() ) {
              $allcaps['upload_files'] = 1;
          }

      }
      // version <= 0.0.8
      elseif( function_exists('MultisiteGlobalMedia\getSideId') ) {
          if( MultisiteGlobalMedia\getSideId() == get_current_blog_id() ) {
              $allcaps['upload_files'] = 1;
          }
      }

  }

  return $allcaps;

}
add_filter( 'user_has_cap', 'global_media_user_cap', 99, 4 );

Can confirm it is working in Version https://github.com/bueltge/multisite-global-media/tree/0.1.0-beta

widoz commented 5 years ago

@jockebq Since it is correlated with attachment_url_to_postid let's talk in #65 .

axraph commented 4 years ago

The filter code from https://github.com/bueltge/multisite-global-media/issues/54#issuecomment-436980167 is working great on 0.1.0-beta-4 and WP 5.2.4

I'm still testing it. Will keep this updated if/when I run into any issues.

Thank you guys for the great work <3

bueltge commented 4 years ago

Thanks for your feedback and testing.