Skip to content

Commit e11c078

Browse files
authored
RTC: Allow disabling collaboration by post type (#78984)
* RTC: Allow disabling collaboration by post type * Rename post type collaboration opt-out filter * Add Core backport changelog entry
1 parent d83ec0f commit e11c078

6 files changed

Lines changed: 247 additions & 2 deletions

File tree

‎lib/compat/wordpress-7.1/collaboration.php‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,32 @@ function wp_is_collaboration_allowed() {
208208
}
209209
}
210210

211+
if ( ! function_exists( 'wp_is_post_type_collaboration_disabled' ) ) {
212+
/**
213+
* Determines whether real-time collaboration is disabled for a post type.
214+
*
215+
* @since 7.1.0
216+
*
217+
* @param string $post_type Post type name.
218+
* @return bool Whether real-time collaboration is disabled for the post type.
219+
*/
220+
function wp_is_post_type_collaboration_disabled( $post_type ) {
221+
if ( ! post_type_exists( $post_type ) ) {
222+
return true;
223+
}
224+
225+
/**
226+
* Filters whether real-time collaboration is disabled for a post type.
227+
*
228+
* @since 7.1.0
229+
*
230+
* @param bool $disabled Whether real-time collaboration is disabled for the post type.
231+
* @param string $post_type Post type name.
232+
*/
233+
return (bool) apply_filters( 'wp_is_post_type_collaboration_disabled', false, $post_type );
234+
}
235+
}
236+
211237
/**
212238
* Injects the real-time collaboration setting into a global variable.
213239
*
@@ -229,9 +255,17 @@ function gutenberg_inject_real_time_collaboration_setting() {
229255
$enabled = false;
230256
}
231257

258+
$disabled_post_types = array_values(
259+
array_filter(
260+
get_post_types( array( 'show_in_rest' => true ) ),
261+
'wp_is_post_type_collaboration_disabled'
262+
)
263+
);
264+
232265
wp_add_inline_script(
233266
'wp-core-data',
234-
'window._wpCollaborationEnabled = ' . wp_json_encode( $enabled ) . ';',
267+
'window._wpCollaborationEnabled = ' . wp_json_encode( $enabled ) . ';' .
268+
'window._wpCollaborationDisabledPostTypes = ' . wp_json_encode( $disabled_post_types ) . ';',
235269
'after'
236270
);
237271
}
@@ -402,6 +436,10 @@ function gutenberg_post_list_collaboration_row_actions( $actions, $post ) {
402436
return $actions;
403437
}
404438

439+
if ( wp_is_post_type_collaboration_disabled( $post->post_type ) ) {
440+
return $actions;
441+
}
442+
405443
$title = _draft_or_post_title( $post->ID );
406444

407445
/*

‎packages/core-data/src/entities.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,11 @@ async function loadPostTypeEntities() {
464464
null
465465
);
466466
},
467+
shouldSync: () =>
468+
! (
469+
Array.isArray( window._wpCollaborationDisabledPostTypes ) &&
470+
window._wpCollaborationDisabledPostTypes.includes( name )
471+
),
467472
};
468473

469474
return entity;

‎packages/core-data/src/test/entities.js‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,20 @@ describe( 'prePersistPostType', () => {
136136

137137
describe( 'loadPostTypeEntities', () => {
138138
let originalCollaborationEnabled;
139+
let originalCollaborationDisabledPostTypes;
139140

140141
beforeEach( () => {
141142
apiFetch.mockReset();
142143
applyPostChangesToCRDTDoc.mockReset();
143144
originalCollaborationEnabled = window._wpCollaborationEnabled;
145+
originalCollaborationDisabledPostTypes =
146+
window._wpCollaborationDisabledPostTypes;
144147
} );
145148

146149
afterEach( () => {
147150
window._wpCollaborationEnabled = originalCollaborationEnabled;
151+
window._wpCollaborationDisabledPostTypes =
152+
originalCollaborationDisabledPostTypes;
148153
} );
149154

150155
it( 'should include custom taxonomy rest_bases in synced properties when collaboration is enabled', async () => {
@@ -224,6 +229,52 @@ describe( 'loadPostTypeEntities', () => {
224229
expect( syncedProperties ).not.toContain( 'tags' );
225230
} );
226231

232+
it( 'should sync post type entities by default', async () => {
233+
window._wpCollaborationEnabled = false;
234+
window._wpCollaborationDisabledPostTypes = undefined;
235+
236+
const mockPostTypes = {
237+
post: {
238+
name: 'Posts',
239+
rest_base: 'posts',
240+
rest_namespace: 'wp/v2',
241+
},
242+
};
243+
244+
apiFetch.mockResolvedValueOnce( mockPostTypes );
245+
246+
const postTypeLoader = additionalEntityConfigLoaders.find(
247+
( loader ) => loader.kind === 'postType'
248+
);
249+
const entities = await postTypeLoader.loadEntities();
250+
const postEntity = entities.find( ( e ) => e.name === 'post' );
251+
252+
expect( postEntity.syncConfig.shouldSync() ).toBe( true );
253+
} );
254+
255+
it( 'should not sync post type entities disabled for collaboration', async () => {
256+
window._wpCollaborationEnabled = false;
257+
window._wpCollaborationDisabledPostTypes = [ 'book' ];
258+
259+
const mockPostTypes = {
260+
book: {
261+
name: 'Books',
262+
rest_base: 'books',
263+
rest_namespace: 'wp/v2',
264+
},
265+
};
266+
267+
apiFetch.mockResolvedValueOnce( mockPostTypes );
268+
269+
const postTypeLoader = additionalEntityConfigLoaders.find(
270+
( loader ) => loader.kind === 'postType'
271+
);
272+
const entities = await postTypeLoader.loadEntities();
273+
const bookEntity = entities.find( ( e ) => e.name === 'book' );
274+
275+
expect( bookEntity.syncConfig.shouldSync() ).toBe( false );
276+
} );
277+
227278
it( 'should skip taxonomy rest_base when taxonomy is not found in fetched taxonomies', async () => {
228279
window._wpCollaborationEnabled = true;
229280

‎packages/editor/src/store/private-selectors.js‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
getRenderingMode,
2626
getCurrentPost,
2727
getCurrentPostType,
28+
getCurrentPostId,
2829
getEditorSettings,
2930
getCurrentPostRevisionsCount,
3031
} from './selectors';
@@ -598,13 +599,22 @@ export const isCollaborationEnabledForCurrentPost = createRegistrySelector(
598599
}
599600

600601
const currentPostType = getCurrentPostType( state );
602+
const currentPostId = getCurrentPostId( state );
601603
const entityConfig = select( coreStore ).getEntityConfig(
602604
'postType',
603605
currentPostType
604606
);
607+
const syncConfig = entityConfig?.syncConfig;
605608

606609
return Boolean(
607-
entityConfig?.syncConfig && window._wpCollaborationEnabled
610+
syncConfig &&
611+
syncConfig.supportsPersistence &&
612+
window._wpCollaborationEnabled &&
613+
false !==
614+
syncConfig.shouldSync?.(
615+
`postType/${ currentPostType }`,
616+
currentPostId
617+
)
608618
);
609619
}
610620
);

‎packages/editor/src/store/test/private-selectors.js‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import { store as preferencesStore } from '@wordpress/preferences';
1010
import {
1111
getDefaultRenderingMode,
1212
getPostBlocksByName,
13+
isCollaborationEnabledForCurrentPost,
1314
} from '../private-selectors';
15+
import { lock } from '../../lock-unlock';
1416

1517
describe( 'getPostBlocksByName', () => {
1618
const state = {
@@ -86,6 +88,73 @@ describe( 'getPostBlocksByName', () => {
8688
} );
8789
} );
8890

91+
describe( 'isCollaborationEnabledForCurrentPost', () => {
92+
let originalCollaborationEnabled;
93+
94+
beforeEach( () => {
95+
originalCollaborationEnabled = window._wpCollaborationEnabled;
96+
window._wpCollaborationEnabled = true;
97+
} );
98+
99+
afterEach( () => {
100+
window._wpCollaborationEnabled = originalCollaborationEnabled;
101+
} );
102+
103+
function setupRegistry( {
104+
collaborationSupported = true,
105+
syncConfig = {},
106+
} = {} ) {
107+
isCollaborationEnabledForCurrentPost.registry = {
108+
select: ( store ) => {
109+
if ( store === coreStore ) {
110+
const selectors = {
111+
getEntityConfig: () => ( { syncConfig } ),
112+
};
113+
lock( selectors, {
114+
isCollaborationSupported: () => collaborationSupported,
115+
} );
116+
return selectors;
117+
}
118+
},
119+
};
120+
}
121+
122+
it( 'returns true when the current post type sync config should sync', () => {
123+
const shouldSync = jest.fn( () => true );
124+
setupRegistry( {
125+
syncConfig: { shouldSync, supportsPersistence: true },
126+
} );
127+
128+
const state = { postType: 'book', postId: 123 };
129+
130+
expect( isCollaborationEnabledForCurrentPost( state ) ).toBe( true );
131+
expect( shouldSync ).toHaveBeenCalledWith( 'postType/book', 123 );
132+
} );
133+
134+
it( 'returns false when the current post type sync config does not support persistence', () => {
135+
const shouldSync = jest.fn( () => true );
136+
setupRegistry( { syncConfig: { shouldSync } } );
137+
138+
const state = { postType: 'book', postId: 123 };
139+
140+
expect( isCollaborationEnabledForCurrentPost( state ) ).toBe( false );
141+
expect( shouldSync ).not.toHaveBeenCalled();
142+
} );
143+
144+
it( 'returns false when the current post type sync config should not sync', () => {
145+
setupRegistry( {
146+
syncConfig: {
147+
shouldSync: () => false,
148+
supportsPersistence: true,
149+
},
150+
} );
151+
152+
const state = { postType: 'book', postId: 123 };
153+
154+
expect( isCollaborationEnabledForCurrentPost( state ) ).toBe( false );
155+
} );
156+
} );
157+
89158
describe( 'getDefaultRenderingMode', () => {
90159
function setupRegistry( {
91160
supportsEditor = true,
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
/**
3+
* Tests for collaborative editing post list row actions.
4+
*
5+
* @package gutenberg
6+
* @subpackage Collaboration
7+
*
8+
* @group collaboration
9+
*/
10+
class Tests_Collaboration_PostListCollaborationRowActions extends WP_UnitTestCase {
11+
12+
private static int $post_id;
13+
private static int $admin_id;
14+
15+
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
16+
self::$admin_id = $factory->user->create( array( 'role' => 'administrator' ) );
17+
self::$post_id = $factory->post->create(
18+
array(
19+
'post_title' => 'Collaboration row action post',
20+
'post_type' => 'post',
21+
)
22+
);
23+
}
24+
25+
public static function wpTearDownAfterClass() {
26+
self::delete_user( self::$admin_id );
27+
wp_delete_post( self::$post_id, true );
28+
}
29+
30+
public function set_up() {
31+
parent::set_up();
32+
33+
wp_set_current_user( self::$admin_id );
34+
}
35+
36+
public function tear_down() {
37+
remove_filter( 'wp_is_post_type_collaboration_disabled', array( $this, 'disable_post_collaboration' ) );
38+
39+
parent::tear_down();
40+
}
41+
42+
public function test_post_list_collaboration_row_actions_include_join_action() {
43+
$post = get_post( self::$post_id );
44+
$actions = array(
45+
'edit' => '<a href="#">Edit</a>',
46+
);
47+
48+
$filtered_actions = gutenberg_post_list_collaboration_row_actions( $actions, $post );
49+
50+
$this->assertStringContainsString( 'edit-action-text', $filtered_actions['edit'] );
51+
$this->assertStringContainsString( 'join-action-text', $filtered_actions['edit'] );
52+
$this->assertStringContainsString( '>Join</a>', $filtered_actions['edit'] );
53+
}
54+
55+
public function test_post_list_collaboration_row_actions_are_unchanged_for_disabled_post_type() {
56+
add_filter( 'wp_is_post_type_collaboration_disabled', array( $this, 'disable_post_collaboration' ), 10, 2 );
57+
58+
$post = get_post( self::$post_id );
59+
$actions = array(
60+
'edit' => '<a href="#">Edit</a>',
61+
);
62+
63+
$this->assertSame(
64+
$actions,
65+
gutenberg_post_list_collaboration_row_actions( $actions, $post )
66+
);
67+
}
68+
69+
public function disable_post_collaboration( $disabled, $post_type ) {
70+
return 'post' === $post_type ? true : $disabled;
71+
}
72+
}

0 commit comments

Comments
 (0)