Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ A WordPress plugin that allows multiple authors to edit a single post via shared
- **Co-author management** -- Add or remove co-authors from any post via the block editor sidebar panel.
- **Shared invite links** -- Generate a shareable URL that lets any registered user join as a co-author. Links expire after 24 hours and are automatically revoked when the post is published; existing co-authors keep their access.
- **Capability-aware** -- Co-authors can edit and read their assigned posts without gaining broader site permissions.
- **Post-publish edit gate** -- Co-authors lose edit access once the post is published. Site editors and admins can opt in per-post to keep co-author access after publish.
- **Multisite support** -- Invited users are automatically added to the site with a subscriber role so they can access the editor.
- **Author preservation** -- When a post's author is reassigned, the previous author is automatically kept as a co-author.

Expand All @@ -24,6 +25,11 @@ The only things they **cannot** do:

- Reassign post authorship (requires `edit_others_posts`, enforced by WordPress core)
- Delete the post (the `map_meta_cap` filter explicitly excludes `delete_post`)
- Change the per-post "Allow co-authors to edit after publish" setting (requires `edit_others_posts`)

## Editing after publish

By default, publishing a post revokes co-authors' edit access — only the post author and site editors/admins can continue editing. Site editors and administrators can flip this per-post via the **"Allow co-authors to edit after publish"** toggle in the Co-Authors sidebar panel. Co-authors and Author-role post authors do not see this toggle.

## Requirements

Expand Down
2 changes: 1 addition & 1 deletion build/index.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-core-data', 'wp-data', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => 'fa1237d525aae3cf7d2d');
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-core-data', 'wp-data', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => '6ebc5c489779f8ecfec0');
2 changes: 1 addition & 1 deletion build/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 21 additions & 6 deletions includes/class-map-capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,28 @@ public static function handle_co_author_caps( array $caps, string $cap, int $use
return $caps;
}

if ( Co_Authors::is_co_author( $post_id, $user_id ) ) {
// Returning an empty array grants the capability unconditionally,
// which is intentional here: co-authors are explicitly trusted for
// this specific post regardless of their site role.
return array();
if ( ! Co_Authors::is_co_author( $post_id, $user_id ) ) {
return $caps;
}

// Edit access is revoked on publish unless an editor/admin has opted in
// for this specific post. read_post stays unconditional — published
// posts are public anyway, and co-authors of unpublished posts retain
// the read access they were granted.
if ( 'edit_post' === $cap ) {
$post = get_post( $post_id );
if (
$post
&& 'publish' === $post->post_status
&& ! Co_Authors::allows_post_publish_access( $post_id )
) {
return $caps;
}
}

return $caps;
// Returning an empty array grants the capability unconditionally,
// which is intentional here: co-authors are explicitly trusted for
// this specific post regardless of their site role.
return array();
}
}
62 changes: 61 additions & 1 deletion includes/class-map-co-authors.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
*/
class Co_Authors {

const META_KEY = '_map_co_author';
const META_KEY = '_map_co_author';
const POST_PUBLISH_ACCESS_META_KEY = '_map_co_author_post_publish_access';

/**
* Register post meta on init.
Expand Down Expand Up @@ -42,6 +43,65 @@ public static function register_meta(): void {
},
)
);

register_post_meta(
'',
self::POST_PUBLISH_ACCESS_META_KEY,
array(
'type' => 'boolean',
'description' => 'Whether co-authors retain edit access after the post is published.',
'single' => true,
'show_in_rest' => false,
'auth_callback' => function ( $allowed, $meta_key, $post_id ) {
return self::current_user_can_manage_settings( (int) $post_id );
},
)
);
}

/**
* Whether co-authors retain edit access on a post once it is published.
*
* Defaults to false: publishing strips co-author edit access unless an
* editor/admin opts in for the specific post.
*
* @param int $post_id Post ID.
*/
public static function allows_post_publish_access( int $post_id ): bool {
return (bool) get_post_meta( $post_id, self::POST_PUBLISH_ACCESS_META_KEY, true );
}

/**
* Set the post-publish co-author access flag.
*
* @param int $post_id Post ID.
* @param bool $allowed Whether co-authors retain edit access after publish.
*/
public static function set_post_publish_access( int $post_id, bool $allowed ): void {
if ( $allowed ) {
update_post_meta( $post_id, self::POST_PUBLISH_ACCESS_META_KEY, '1' );
} else {
delete_post_meta( $post_id, self::POST_PUBLISH_ACCESS_META_KEY );
}
}

/**
* Whether the current user can change co-author settings for a post.
*
* Gated on the post-type-specific edit_others_posts capability so that
* co-authors and lone post authors (e.g. an Author role) cannot toggle
* settings — only real site editors and admins.
*
* @param int $post_id Post ID.
*/
public static function current_user_can_manage_settings( int $post_id ): bool {
$post = get_post( $post_id );
if ( ! $post ) {
return false;
}
$pto = get_post_type_object( $post->post_type );
$cap = $pto ? $pto->cap->edit_others_posts : 'edit_others_posts';
return current_user_can( $cap );
}

/**
Expand Down
Loading
Loading