diff --git a/lib/compat/wordpress-7.1/collaboration.php b/lib/compat/wordpress-7.1/collaboration.php
index c8a30ad82419e9..6f7337b561e3aa 100644
--- a/lib/compat/wordpress-7.1/collaboration.php
+++ b/lib/compat/wordpress-7.1/collaboration.php
@@ -208,6 +208,32 @@ function wp_is_collaboration_allowed() {
}
}
+if ( ! function_exists( 'wp_is_post_type_collaboration_disabled' ) ) {
+ /**
+ * Determines whether real-time collaboration is disabled for a post type.
+ *
+ * @since 7.1.0
+ *
+ * @param string $post_type Post type name.
+ * @return bool Whether real-time collaboration is disabled for the post type.
+ */
+ function wp_is_post_type_collaboration_disabled( $post_type ) {
+ if ( ! post_type_exists( $post_type ) ) {
+ return true;
+ }
+
+ /**
+ * Filters whether real-time collaboration is disabled for a post type.
+ *
+ * @since 7.1.0
+ *
+ * @param bool $disabled Whether real-time collaboration is disabled for the post type.
+ * @param string $post_type Post type name.
+ */
+ return (bool) apply_filters( 'wp_is_post_type_collaboration_disabled', false, $post_type );
+ }
+}
+
/**
* Injects the real-time collaboration setting into a global variable.
*
@@ -229,9 +255,17 @@ function gutenberg_inject_real_time_collaboration_setting() {
$enabled = false;
}
+ $disabled_post_types = array_values(
+ array_filter(
+ get_post_types( array( 'show_in_rest' => true ) ),
+ 'wp_is_post_type_collaboration_disabled'
+ )
+ );
+
wp_add_inline_script(
'wp-core-data',
- 'window._wpCollaborationEnabled = ' . wp_json_encode( $enabled ) . ';',
+ 'window._wpCollaborationEnabled = ' . wp_json_encode( $enabled ) . ';' .
+ 'window._wpCollaborationDisabledPostTypes = ' . wp_json_encode( $disabled_post_types ) . ';',
'after'
);
}
@@ -402,6 +436,10 @@ function gutenberg_post_list_collaboration_row_actions( $actions, $post ) {
return $actions;
}
+ if ( wp_is_post_type_collaboration_disabled( $post->post_type ) ) {
+ return $actions;
+ }
+
$title = _draft_or_post_title( $post->ID );
/*
diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js
index 739dae101c2bed..f32e87ece79e42 100644
--- a/packages/core-data/src/entities.js
+++ b/packages/core-data/src/entities.js
@@ -464,6 +464,11 @@ async function loadPostTypeEntities() {
null
);
},
+ shouldSync: () =>
+ ! (
+ Array.isArray( window._wpCollaborationDisabledPostTypes ) &&
+ window._wpCollaborationDisabledPostTypes.includes( name )
+ ),
};
return entity;
diff --git a/packages/core-data/src/test/entities.js b/packages/core-data/src/test/entities.js
index 5f45462655f5eb..0f59bcabda198c 100644
--- a/packages/core-data/src/test/entities.js
+++ b/packages/core-data/src/test/entities.js
@@ -136,15 +136,20 @@ describe( 'prePersistPostType', () => {
describe( 'loadPostTypeEntities', () => {
let originalCollaborationEnabled;
+ let originalCollaborationDisabledPostTypes;
beforeEach( () => {
apiFetch.mockReset();
applyPostChangesToCRDTDoc.mockReset();
originalCollaborationEnabled = window._wpCollaborationEnabled;
+ originalCollaborationDisabledPostTypes =
+ window._wpCollaborationDisabledPostTypes;
} );
afterEach( () => {
window._wpCollaborationEnabled = originalCollaborationEnabled;
+ window._wpCollaborationDisabledPostTypes =
+ originalCollaborationDisabledPostTypes;
} );
it( 'should include custom taxonomy rest_bases in synced properties when collaboration is enabled', async () => {
@@ -224,6 +229,52 @@ describe( 'loadPostTypeEntities', () => {
expect( syncedProperties ).not.toContain( 'tags' );
} );
+ it( 'should sync post type entities by default', async () => {
+ window._wpCollaborationEnabled = false;
+ window._wpCollaborationDisabledPostTypes = undefined;
+
+ const mockPostTypes = {
+ post: {
+ name: 'Posts',
+ rest_base: 'posts',
+ rest_namespace: 'wp/v2',
+ },
+ };
+
+ apiFetch.mockResolvedValueOnce( mockPostTypes );
+
+ const postTypeLoader = additionalEntityConfigLoaders.find(
+ ( loader ) => loader.kind === 'postType'
+ );
+ const entities = await postTypeLoader.loadEntities();
+ const postEntity = entities.find( ( e ) => e.name === 'post' );
+
+ expect( postEntity.syncConfig.shouldSync() ).toBe( true );
+ } );
+
+ it( 'should not sync post type entities disabled for collaboration', async () => {
+ window._wpCollaborationEnabled = false;
+ window._wpCollaborationDisabledPostTypes = [ 'book' ];
+
+ const mockPostTypes = {
+ book: {
+ name: 'Books',
+ rest_base: 'books',
+ rest_namespace: 'wp/v2',
+ },
+ };
+
+ apiFetch.mockResolvedValueOnce( mockPostTypes );
+
+ const postTypeLoader = additionalEntityConfigLoaders.find(
+ ( loader ) => loader.kind === 'postType'
+ );
+ const entities = await postTypeLoader.loadEntities();
+ const bookEntity = entities.find( ( e ) => e.name === 'book' );
+
+ expect( bookEntity.syncConfig.shouldSync() ).toBe( false );
+ } );
+
it( 'should skip taxonomy rest_base when taxonomy is not found in fetched taxonomies', async () => {
window._wpCollaborationEnabled = true;
diff --git a/packages/editor/src/store/private-selectors.js b/packages/editor/src/store/private-selectors.js
index d5dfa8d2434bdc..0913ea428ade63 100644
--- a/packages/editor/src/store/private-selectors.js
+++ b/packages/editor/src/store/private-selectors.js
@@ -25,6 +25,7 @@ import {
getRenderingMode,
getCurrentPost,
getCurrentPostType,
+ getCurrentPostId,
getEditorSettings,
getCurrentPostRevisionsCount,
} from './selectors';
@@ -598,13 +599,22 @@ export const isCollaborationEnabledForCurrentPost = createRegistrySelector(
}
const currentPostType = getCurrentPostType( state );
+ const currentPostId = getCurrentPostId( state );
const entityConfig = select( coreStore ).getEntityConfig(
'postType',
currentPostType
);
+ const syncConfig = entityConfig?.syncConfig;
return Boolean(
- entityConfig?.syncConfig && window._wpCollaborationEnabled
+ syncConfig &&
+ syncConfig.supportsPersistence &&
+ window._wpCollaborationEnabled &&
+ false !==
+ syncConfig.shouldSync?.(
+ `postType/${ currentPostType }`,
+ currentPostId
+ )
);
}
);
diff --git a/packages/editor/src/store/test/private-selectors.js b/packages/editor/src/store/test/private-selectors.js
index ce216099376d68..59c9560f1c5c1f 100644
--- a/packages/editor/src/store/test/private-selectors.js
+++ b/packages/editor/src/store/test/private-selectors.js
@@ -10,7 +10,9 @@ import { store as preferencesStore } from '@wordpress/preferences';
import {
getDefaultRenderingMode,
getPostBlocksByName,
+ isCollaborationEnabledForCurrentPost,
} from '../private-selectors';
+import { lock } from '../../lock-unlock';
describe( 'getPostBlocksByName', () => {
const state = {
@@ -86,6 +88,73 @@ describe( 'getPostBlocksByName', () => {
} );
} );
+describe( 'isCollaborationEnabledForCurrentPost', () => {
+ let originalCollaborationEnabled;
+
+ beforeEach( () => {
+ originalCollaborationEnabled = window._wpCollaborationEnabled;
+ window._wpCollaborationEnabled = true;
+ } );
+
+ afterEach( () => {
+ window._wpCollaborationEnabled = originalCollaborationEnabled;
+ } );
+
+ function setupRegistry( {
+ collaborationSupported = true,
+ syncConfig = {},
+ } = {} ) {
+ isCollaborationEnabledForCurrentPost.registry = {
+ select: ( store ) => {
+ if ( store === coreStore ) {
+ const selectors = {
+ getEntityConfig: () => ( { syncConfig } ),
+ };
+ lock( selectors, {
+ isCollaborationSupported: () => collaborationSupported,
+ } );
+ return selectors;
+ }
+ },
+ };
+ }
+
+ it( 'returns true when the current post type sync config should sync', () => {
+ const shouldSync = jest.fn( () => true );
+ setupRegistry( {
+ syncConfig: { shouldSync, supportsPersistence: true },
+ } );
+
+ const state = { postType: 'book', postId: 123 };
+
+ expect( isCollaborationEnabledForCurrentPost( state ) ).toBe( true );
+ expect( shouldSync ).toHaveBeenCalledWith( 'postType/book', 123 );
+ } );
+
+ it( 'returns false when the current post type sync config does not support persistence', () => {
+ const shouldSync = jest.fn( () => true );
+ setupRegistry( { syncConfig: { shouldSync } } );
+
+ const state = { postType: 'book', postId: 123 };
+
+ expect( isCollaborationEnabledForCurrentPost( state ) ).toBe( false );
+ expect( shouldSync ).not.toHaveBeenCalled();
+ } );
+
+ it( 'returns false when the current post type sync config should not sync', () => {
+ setupRegistry( {
+ syncConfig: {
+ shouldSync: () => false,
+ supportsPersistence: true,
+ },
+ } );
+
+ const state = { postType: 'book', postId: 123 };
+
+ expect( isCollaborationEnabledForCurrentPost( state ) ).toBe( false );
+ } );
+} );
+
describe( 'getDefaultRenderingMode', () => {
function setupRegistry( {
supportsEditor = true,
diff --git a/phpunit/tests/collaboration/postListCollaborationRowActions.php b/phpunit/tests/collaboration/postListCollaborationRowActions.php
new file mode 100644
index 00000000000000..b5e366a5aa58fd
--- /dev/null
+++ b/phpunit/tests/collaboration/postListCollaborationRowActions.php
@@ -0,0 +1,72 @@
+user->create( array( 'role' => 'administrator' ) );
+ self::$post_id = $factory->post->create(
+ array(
+ 'post_title' => 'Collaboration row action post',
+ 'post_type' => 'post',
+ )
+ );
+ }
+
+ public static function wpTearDownAfterClass() {
+ self::delete_user( self::$admin_id );
+ wp_delete_post( self::$post_id, true );
+ }
+
+ public function set_up() {
+ parent::set_up();
+
+ wp_set_current_user( self::$admin_id );
+ }
+
+ public function tear_down() {
+ remove_filter( 'wp_is_post_type_collaboration_disabled', array( $this, 'disable_post_collaboration' ) );
+
+ parent::tear_down();
+ }
+
+ public function test_post_list_collaboration_row_actions_include_join_action() {
+ $post = get_post( self::$post_id );
+ $actions = array(
+ 'edit' => 'Edit',
+ );
+
+ $filtered_actions = gutenberg_post_list_collaboration_row_actions( $actions, $post );
+
+ $this->assertStringContainsString( 'edit-action-text', $filtered_actions['edit'] );
+ $this->assertStringContainsString( 'join-action-text', $filtered_actions['edit'] );
+ $this->assertStringContainsString( '>Join', $filtered_actions['edit'] );
+ }
+
+ public function test_post_list_collaboration_row_actions_are_unchanged_for_disabled_post_type() {
+ add_filter( 'wp_is_post_type_collaboration_disabled', array( $this, 'disable_post_collaboration' ), 10, 2 );
+
+ $post = get_post( self::$post_id );
+ $actions = array(
+ 'edit' => 'Edit',
+ );
+
+ $this->assertSame(
+ $actions,
+ gutenberg_post_list_collaboration_row_actions( $actions, $post )
+ );
+ }
+
+ public function disable_post_collaboration( $disabled, $post_type ) {
+ return 'post' === $post_type ? true : $disabled;
+ }
+}