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

Proof-of-concept: allow partial permissions for FSE theme templates #27632

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 144 additions & 18 deletions lib/full-site-editing/templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,31 +109,157 @@ function gutenberg_register_wp_theme_taxonomy() {
}
add_action( 'init', 'gutenberg_register_wp_theme_taxonomy' );

/**
* Automatically set the theme meta for templates.
*
* @param array $post_id Template ID.
*/
function gutenberg_set_template_and_template_part_post_theme( $post_id ) {
$themes = wp_get_post_terms( $post_id, 'wp_theme' );
if ( ! $themes ) {
wp_set_post_terms( $post_id, array( wp_get_theme()->get_stylesheet() ), 'wp_theme', true );
}
}
add_action( 'save_post_wp_template', 'gutenberg_set_template_and_template_part_post_theme', 10, 3 );
add_action( 'save_post_wp_template_part', 'gutenberg_set_template_and_template_part_post_theme', 10, 3 );

/**
* Gets the fallback templates for a given template type.
*
* @param string $type The template type.
* @return array Fallback templates
*/
function gutenberg_get_fallback_templates( $type ) {
$all = array(
'archive' => 'index',
'archive-$posttype' => 'archive',
'attachment' => 'single',
'author' => 'archive',
'author-$id' => 'author',
'author-$nicename' => 'author-$id',
'category' => 'archive',
'category-$id' => 'category',
'category-$slug' => 'category-$id',
'date' => 'archive',
'home' => 'index',
'$mimetype' => 'attachment',
'$mimetype-$subtype' => '$subtype',
'page' => 'singular',
'page-$id' => 'page',
'page-$slug' => 'page-$id',
'search' => 'index',
'single' => 'singular',
'singular' => 'index',
'single-post' => 'single',
'single-$posttype' => 'single',
'single-$posttype-$slug' => 'single-$posttype',
'$subtype' => '$mimetype',
'tag' => 'archive',
'tag-$id' => 'tag',
'tag-$slug' => 'tag-$id',
'taxonomy' => 'archive',
'taxonomy-$taxonomy' => 'taxonomy',
'taxonomy-$taxonomy-$term' => 'taxonomy-$taxonomy',
'404' => 'index'
);

$hierarchy = array();

do {
$hierarchy[] = $type;
}
while ( isset( $all[ $type ] ) && ( $type = $all[ $type ] ) );

return $hierarchy;
}

/**
* Filters the capabilities of a user to conditionally grant them capabilities for managing 'wp_template' posts.
*
* Any user who can 'edit_theme_options' will have access.
*
* @param array $allcaps A user's capabilities.
* @return array Filtered $allcaps.
* @param array $caps The capabilities being mapped to.
* @param string $cap The capability to be mapped.
* @param int|WP_User $user The user being queried.
* @param array $args Additional arguments.
* @return array Filtered $caps.
*/
function gutenberg_grant_template_caps( $caps, $cap, $user, $args ) {
foreach ( $caps as &$capability ) {
if ( in_array( $capability, array(
'edit_templates',
'edit_others_templates',
'edit_published_templates',
'edit_private_templates',
'delete_templates',
'delete_others_templates',
'delete_published_templates',
'delete_private_templates',
'publish_templates',
'read_private_templates'
) ) ) {
$type = isset( $args[0] ) && ( $post = get_post( $args[0] ) ) && !empty( $post->post_name ) ? $post->post_name : 'index';
$capability = str_replace( 'templates', $type . '_templates', $capability );
}

$pattern = '/^(?>(edit(_(others|published|private))?|delete(_(others|published|private))?|publish|read_private)_)(.+)_templates$/';
if ( ( $type = preg_filter( $pattern, '$6', $capability ) ) ) {

$hierarchy = array_reverse( array_slice( gutenberg_get_fallback_templates( $type ), 1 ) );

if ( empty( $hierarchy ) ) {
if ( user_can( $user, 'edit_theme_options' ) ) {
$capability = 'exist';
} else {
$capability = preg_replace( $pattern, '$1_templates', $capability );
}
} else {
foreach ( $hierarchy as $next ) {
if ( user_can( $user, str_replace( $type, $next, $capability ) ) ) {
$capability = 'exist';
break;
}
}
}
}
}

return $caps;
}
// Hook to priority 9 to get ahead of plugins.
add_filter( 'map_meta_cap', 'gutenberg_grant_template_caps', 9, 4 );

/**
* Prevents users losing edit access to templates when changing the slug
*
* @param array $data The post data to be saved.
* @return array Filtered $data.
*/
function gutenberg_grant_template_caps( array $allcaps ) {
if ( isset( $allcaps['edit_theme_options'] ) ) {
$allcaps['edit_templates'] = $allcaps['edit_theme_options'];
$allcaps['edit_others_templates'] = $allcaps['edit_theme_options'];
$allcaps['edit_published_templates'] = $allcaps['edit_theme_options'];
$allcaps['edit_private_templates'] = $allcaps['edit_theme_options'];
$allcaps['delete_templates'] = $allcaps['edit_theme_options'];
$allcaps['delete_others_templates'] = $allcaps['edit_theme_options'];
$allcaps['delete_published_templates'] = $allcaps['edit_theme_options'];
$allcaps['delete_private_templates'] = $allcaps['edit_theme_options'];
$allcaps['publish_templates'] = $allcaps['edit_theme_options'];
$allcaps['read_private_templates'] = $allcaps['edit_theme_options'];
}

return $allcaps;
function gutenberg_prevent_unauthorized_edits_to_templates( $data ) {
if ( $data['post_type'] != 'wp_template' ) {
return $data;
}

if ( $data['post_author'] != get_current_user_id() && !current_user_can( 'edit_others_' . $data['post_name'] . '_templates' ) ) {
wp_die( 'You can\'t edit templates of this type.' );
}

if ( $data['post_status'] == 'publish' && !current_user_can( 'edit_published_' . $data['post_name'] . '_templates' ) ) {
wp_die( 'You can\'t edit templates of this type.' );
}

if ( $data['post_status'] == 'private' && !current_user_can( 'edit_private_' . $data['post_name'] . '_templates' ) ) {
wp_die( 'You can\'t edit templates of this type.' );
}

if ( !current_user_can( 'edit_' . $data['post_name'] . '_templates' ) ) {
wp_die( 'You can\'t edit templates of this type.' );
}

return $data;
}
add_filter( 'user_has_cap', 'gutenberg_grant_template_caps' );
// Hook to priority 9 to get ahead of plugins.
add_filter( 'wp_insert_post_data', 'gutenberg_prevent_unauthorized_edits_to_templates' );

/**
* Fixes the label of the 'wp_template' admin menu entry.
Expand Down