multisite-global-media icon indicating copy to clipboard operation
multisite-global-media copied to clipboard

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

Open jockebq opened this issue 5 years ago • 29 comments

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?

jockebq avatar Oct 30 '18 14:10 jockebq

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 )?

virgo79 avatar Nov 02 '18 18:11 virgo79

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.

bueltge avatar Nov 05 '18 20:11 bueltge

@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.

jockebq avatar Nov 06 '18 14:11 jockebq

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

virgo79 avatar Nov 06 '18 16:11 virgo79

@virgo79 which version of this plugin do you use?

jockebq avatar Nov 06 '18 17:11 jockebq

@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.

virgo79 avatar Nov 06 '18 18:11 virgo79

@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.

jockebq avatar Nov 06 '18 18:11 jockebq

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 );

bueltge avatar Nov 07 '18 10:11 bueltge

@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 avatar Nov 07 '18 11:11 virgo79

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 Id and prefix.

virgo79 avatar Nov 07 '18 11:11 virgo79

@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.

jockebq avatar Nov 07 '18 11:11 jockebq

@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.

bueltge avatar Nov 07 '18 12:11 bueltge

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.

virgo79 avatar Nov 07 '18 13:11 virgo79

@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?

widoz avatar Nov 07 '18 20:11 widoz

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?

virgo79 avatar Nov 08 '18 12:11 virgo79

@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.

jockebq avatar Nov 08 '18 14:11 jockebq

@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.

holzhannes avatar Nov 09 '18 10:11 holzhannes

@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 avatar Nov 10 '18 20:11 jockebq

@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?

jockebq avatar Nov 15 '18 14:11 jockebq

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

virgo79 avatar Nov 20 '18 17:11 virgo79

@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.

widoz avatar Nov 20 '18 17:11 widoz

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

virgo79 avatar Nov 20 '18 18:11 virgo79

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!

jockebq avatar Nov 20 '18 19:11 jockebq

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:

  • idPrefixIncludedInAttachmentId Check if the attachmentId is in the form of 1000015 where the first value is the id of the Media Site followed by a sequences of zero that are the Prefix pad value and the attachment id (see multisite-global-media/src/Site.php ).
  • stripSiteIdPrefixFromAttachmentId If you have that attachmentId like value, you have to use stripSiteIdPrefixFromAttachmentId to retrieve the correct attachmentId ( the integer value ) then switch the blog using \MultisiteGlobalMedia\SingleSwitcher.

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

widoz avatar Nov 20 '18 22:11 widoz

@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'] );
}

jockebq avatar Nov 21 '18 07:11 jockebq

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

holzhannes avatar Nov 21 '18 08:11 holzhannes

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

widoz avatar Nov 21 '18 22:11 widoz

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

axraph avatar Nov 03 '19 05:11 axraph

Thanks for your feedback and testing.

bueltge avatar Nov 03 '19 09:11 bueltge