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; + } +}