Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use CPT caps for field groups #211

Open
justintadlock opened this issue Aug 7, 2019 · 4 comments

Comments

@justintadlock
Copy link

commented Aug 7, 2019

I'm creating a plugin that bridges the Members role editor and ACF. One of the issues I've run into is that ACF hardcodes acf_get_setting( 'capability' ) any time it needs to check a capability.

Instead, it should use get_post_type_object( 'acf-field-group' )->cap->{$capname} when checking directly if a user can do something. acf_get_setting( 'capability' ) should be set at the register_post_type() level. That way, it trickles down.

The biggest place this is an issue is in the save_post callback for field groups: https://github.com/AdvancedCustomFields/acf/blob/master/includes/admin/admin-field-group.php#L461-L464

Instead of checking the ACF cap, it should check current_user_can( 'edit_post', $post_id ) and let WP map this meta capability back to the CPT's primitive caps.

I've managed to code a workaround by filtering the capability setting and faking it during this callback and removing the filter afterward. However, that's not future proof and could fall apart whenever there's an update to the underlying ACF code to do the same cap check elsewhere.

Longer term

Ideally, any action that a user could take when editing or deleting either a field group or a field itself would be tied directly into the CPT caps for these two post types. By default, ACF could set all of its CPT caps to acf_get_setting( 'capability' ).

Doing this wouldn't change how ACF works internally. However, it'd make it flexible enough for third-party developers to do some really cool stuff with caps.

I'd be happy to look over any work with this if you decide to make the changes.

Quick example of doing this during the CPT registration process:

$cap = acf_get_setting( 'capability' );

register_post_type( 'acf-field-group', [

	'map_meta_cap' => true,

	'capabilities' => [
		// meta caps (don't assign these to roles)
		'edit_post'              => 'edit_acf_field_group',
		'read_post'              => 'read_acf_field_group',
		'delete_post'            => 'delete_acf_field_group',

		// primitive caps
		'create_posts'           => $cap,
		'edit_posts'             => $cap,
		'edit_others_posts'      => $cap,
		'publish_posts'          => $cap,
		'read_private_posts'     => $cap,
		'read'                   => 'read',
		'delete_posts'           => $cap,
		'delete_private_posts'   => $cap,
		'delete_published_posts' => $cap,
		'delete_others_posts'    => $cap,
		'edit_private_posts'     => $cap,
		'edit_published_posts'   => $cap
	]
] );

Extra

The "active" status for a field group should only be available to users who can $type->cap->publish_posts. Otherwise, it should fall back to "inactive". Sort of like how draft posts work.

@justintadlock

This comment has been minimized.

Copy link
Author

commented Aug 7, 2019

One other extra note:

This is also an issue with the admin menus added by the plugin:

  • Custom Fields (top-level)
  • Field Groups (sub-menu)
  • Add New (sub-menu)

The only way to change the capability for them individually is to unregister the menus and re-add them. By using the CPT caps for $type->cap->edit_posts (custom fields and field groups) and $type->cap->create_posts (add new), third-party devs could alter this more easily and not risk breaking other add-ons.

@elliotcondon

This comment has been minimized.

Copy link
Contributor

commented Aug 8, 2019

Hi @justintadlock

Thanks for the topic and code examples.
I'm on board with this, and agree that using capabilities will allow for finer control without much effort.

I'll add this to my to-do list, which is unfortunately a little bloated at the moment, but I'll try to prioritize it.

I'll also need to freshen up on capabilities too. Can you elaborate a little more about the 'map_meta_cap' => true setting, and also the ''read', edit_post', 'read_post', delete_post' caps (thy they are different to $cap)?

Thanks
Elliot

@justintadlock

This comment has been minimized.

Copy link
Author

commented Aug 11, 2019

Can you elaborate a little more about the 'map_meta_cap' => true setting, and also the ''read', edit_post', 'read_post', delete_post' caps (thy they are different to $cap)?

Sure thing. I'm going to just run through a quick tutorial of the cap system in WordPress, which really isn't well documented.

There are two types of caps in WordPress:

  1. Meta Caps: (ex: edit_post) These are caps that are used when checking if a user has permission to "do something" to an object (e.g., post, term, user, etc.). Note that the standard practice is to use the "singular" form of the word -- _post instead of _posts -- for these. Meta caps aren't actually assigned to roles and can't do anything on their own. They're sort of like placeholders that get mapped to primitive caps.

  2. Primitive Caps: (ex: edit_posts) These are caps that you assign to roles. They determine whether a user has permission to perform an action.

So the edit_post, delete_post, and read_post meta caps are there merely for mapping purposes. They don't "do" anything on their own. When you register a CPT, these should generally be unique to the CPT.

The reason we want to set 'map_meta_cap' => true is so that WordPress automatically maps these meta caps to your primitive caps (e.g., edit_posts, edit_others_posts, etc.).


An example of how this works:

When checking if a user can edit a post, WordPress checks current_user_can( 'edit_post', $post_id ). This calls up the core map_meta_cap() function.

At that point, WP runs through a series of conditionals that look sort of like:

  1. If the user is the post author, add the required cap of edit_posts.
  2. If the post is published, add the required cap of edit_published_posts.
  3. If the post is private, add the required cap of edit_private_posts.
  4. If the user is not the post author, add the required cap of edit_others_posts.

Depending on the scenario, the user may need 1-4 different primitive caps.

Of course, in your case, all of this could just map back to manage_options, which is what ACF uses by default now.

@elliotcondon

This comment has been minimized.

Copy link
Contributor

commented Aug 11, 2019

Thanks @justintadlock

You explained this very elegantly :)

I'm happy to add this into the next version. Leave this with me for now, and I'll be in touch shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.