PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
← All changes | lib/experimental/collaboration/collaboration.php +121 -119 23.7.0 → trunk View file →
@@ -17,8 +17,12 @@
17 17 /**
18 18 * Registers the custom post type for sync storage.
19 19 */
20 20 function gutenberg_register_sync_storage_post_type() {
21 + if ( ! wp_is_collaboration_enabled() ) {
22 + return;
23 + }
24 +
21 25 register_post_type(
22 26 'wp_sync_storage',
23 27 array(
24 28 'labels' => array(
@@ -57,10 +61,36 @@
57 61 /**
58 62 * Registers REST API routes for collaborative editing.
59 63 */
60 64 function gutenberg_register_collaboration_rest_routes(): void {
61 - $sync_storage = new WP_Sync_Post_Meta_Storage();
62 - $sync_server = new WP_HTTP_Polling_Sync_Server( $sync_storage );
65 + if ( ! wp_is_collaboration_enabled() ) {
66 + return;
67 + }
68 +
69 + /**
70 + * Filters the sync storage implementation for collaborative editing.
71 + *
72 + * Allows plugins to replace the default post meta storage with alternative
73 + * backends. The primary use case is the realtime-collaboration plugin,
74 + * which uses Presence API for awareness and a dedicated wp_collaboration
75 + * table for CRDT updates, eliminating cache side effects.
76 + *
77 + * This filter is unstable and may change as RTC explores fundamental changes
78 + * to how syncing works. The current interface assumes a pure naïve relay,
79 + * which could change.
80 + *
81 + * @since Gutenberg 21.x
82 + *
83 + * @param WP_Sync_Storage $sync_storage Storage implementation. Must implement
84 + * the WP_Sync_Storage interface.
85 + */
86 + $sync_storage = apply_filters( '__unstable_wp_sync_storage', new WP_Sync_Post_Meta_Storage() );
87 +
88 + if ( ! $sync_storage instanceof WP_Sync_Storage ) {
89 + $sync_storage = new WP_Sync_Post_Meta_Storage();
90 + }
91 +
92 + $sync_server = new WP_HTTP_Polling_Sync_Server( $sync_storage );
63 93 $sync_server->register_routes();
64 94
65 95 $sync_save_server = new WP_Sync_Save_Server();
66 96 $sync_save_server->register_routes();
@@ -72,8 +102,12 @@
72 102 /**
73 103 * Registers post meta for persisting CRDT documents.
74 104 */
75 105 function gutenberg_rest_api_crdt_post_meta() {
106 + if ( ! wp_is_collaboration_enabled() ) {
107 + return;
108 + }
109 +
76 110 // This string must match POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE in @wordpress/core-data.
77 111 $persisted_crdt_post_meta_key = '_crdt_document';
78 112
79 113 register_meta(
@@ -107,108 +141,21 @@
107 141 }
108 142 add_action( 'init', 'gutenberg_rest_api_crdt_post_meta' );
109 143 }
110 144
111 -if ( ! function_exists( 'wp_collaboration_inject_setting' ) ) {
112 - /**
113 - * Registers the real-time collaboration setting.
114 - */
115 - function gutenberg_register_real_time_collaboration_setting() {
116 - $option_name = 'wp_collaboration_enabled';
117 -
118 - register_setting(
119 - 'writing',
120 - $option_name,
121 - array(
122 - 'type' => 'boolean',
123 - 'description' => __( 'Enable Real-Time Collaboration', 'gutenberg' ),
124 - 'sanitize_callback' => 'rest_sanitize_boolean',
125 - 'default' => true,
126 - 'show_in_rest' => true,
127 - )
128 - );
129 -
130 - add_settings_field(
131 - $option_name,
132 - __( 'Collaboration', 'gutenberg' ),
133 - function () use ( $option_name ) {
134 - $option_value = get_option( $option_name );
135 -
136 - if ( wp_is_collaboration_allowed() ) :
137 - ?>
138 - <label for="wp_collaboration_enabled">
139 - <input name="wp_collaboration_enabled" type="checkbox" id="wp_collaboration_enabled" value="1" <?php checked( '1', $option_value ); ?>/>
140 - <?php _e( "Enable early access to real-time collaboration. Real-time collaboration may affect your website's performance.", 'gutenberg' ); ?>
141 - </label>
142 - <?php else : ?>
143 - <div class="notice notice-warning inline">
144 - <?php
145 - printf(
146 - /* translators: %s: Prefix "Note:". */
147 - '<p>' . __( '%s Real-time collaboration has been disabled.', 'gutenberg' ) . '</p>',
148 - '<strong>' . __( 'Note:', 'gutenberg' ) . '</strong>'
149 - );
150 - ?>
151 - </div>
152 - <?php
153 - endif;
154 - },
155 - 'writing'
156 - );
157 - }
158 - add_action( 'admin_init', 'gutenberg_register_real_time_collaboration_setting' );
159 -}
160 -
161 145 if ( ! function_exists( 'wp_is_collaboration_enabled' ) ) {
162 146 /**
163 147 * Determines whether real-time collaboration is enabled.
164 148 *
165 - * If the WP_ALLOW_COLLABORATION constant is false,
166 - * collaboration is always disabled regardless of the database option.
167 - * Otherwise, falls back to the 'wp_collaboration_enabled' option.
168 - *
169 149 * @since 7.0.0
170 150 *
171 151 * @return bool Whether real-time collaboration is enabled.
172 152 */
173 153 function wp_is_collaboration_enabled() {
174 - return ( wp_is_collaboration_allowed() && (bool) get_option( 'wp_collaboration_enabled' ) );
154 + return gutenberg_is_experiment_enabled( 'gutenberg-real-time-collaboration' );
175 155 }
176 156 }
177 157
178 -if ( ! function_exists( 'wp_is_collaboration_allowed' ) ) {
179 - /**
180 - * Determines whether real-time collaboration is allowed.
181 - *
182 - * If the WP_ALLOW_COLLABORATION constant is false,
183 - * collaboration is not allowed and cannot be enabled.
184 - * The constant defaults to true, unless the WP_ALLOW_COLLABORATION
185 - * environment variable is set to string "false".
186 - *
187 - * @since 7.0.0
188 - *
189 - * @return bool Whether real-time collaboration is allowed.
190 - */
191 - function wp_is_collaboration_allowed() {
192 - if ( ! defined( 'WP_ALLOW_COLLABORATION' ) ) {
193 - $env_value = getenv( 'WP_ALLOW_COLLABORATION' );
194 - if ( false === $env_value ) {
195 - // Environment variable is not defined, default to allowing collaboration.
196 - define( 'WP_ALLOW_COLLABORATION', true );
197 - } else {
198 - /*
199 - * Environment variable is defined, let's confirm it is actually set to
200 - * "true" as it may still have a string value "false" – the preceding
201 - * `if` branch only tests for the boolean `false`.
202 - */
203 - define( 'WP_ALLOW_COLLABORATION', 'true' === $env_value );
204 - }
205 - }
206 -
207 - return WP_ALLOW_COLLABORATION;
208 - }
209 -}
210 -
211 158 if ( ! function_exists( 'wp_is_post_type_collaboration_disabled' ) ) {
212 159 /**
213 160 * Determines whether real-time collaboration is disabled for a post type.
214 161 *
@@ -233,8 +180,38 @@
233 180 return (bool) apply_filters( 'wp_is_post_type_collaboration_disabled', false, $post_type );
234 181 }
235 182 }
236 183
184 +/**
185 + * Disables real-time collaboration for post types that cannot persist the
186 + * CRDT document.
187 + *
188 + * Collaboration stores its CRDT document in post meta. The REST API only
189 + * exposes post meta for post types that support custom fields, so enabling
190 + * collaboration for other post types can cause stale sync updates to replace
191 + * newer entity content.
192 + *
193 + * @param bool $disabled Whether real-time collaboration is disabled for the post type.
194 + * @param string $post_type Post type name.
195 + * @return bool Whether real-time collaboration is disabled for the post type.
196 + */
197 +function gutenberg_disable_collaboration_for_post_types_without_custom_fields( $disabled, $post_type ) {
198 + if ( $disabled ) {
199 + return $disabled;
200 + }
201 +
202 + /*
203 + * The attachments REST controller always exposes meta, regardless of
204 + * whether the attachment post type supports custom fields.
205 + */
206 + if ( 'attachment' === $post_type ) {
207 + return false;
208 + }
209 +
210 + return ! post_type_supports( $post_type, 'custom-fields' );
211 +}
212 +add_filter( 'wp_is_post_type_collaboration_disabled', 'gutenberg_disable_collaboration_for_post_types_without_custom_fields', 10, 2 );
213 +
237 214 if ( ! function_exists( 'gutenberg_get_active_edit_lock_user' ) ) {
238 215 /**
239 216 * Returns the user ID recorded in a fresh edit lock.
240 217 *
@@ -270,28 +247,15 @@
270 247 }
271 248 }
272 249
273 250 /**
274 - * Injects the real-time collaboration setting into a global variable.
275 - *
276 - * @global string $pagenow The filename of the current screen.
251 + * Injects the post types for which real-time collaboration is disabled.
277 252 */
278 -function gutenberg_inject_real_time_collaboration_setting() {
279 - global $pagenow;
280 -
253 +function gutenberg_inject_collaboration_disabled_post_types() {
281 254 if ( ! wp_is_collaboration_enabled() ) {
282 255 return;
283 256 }
284 257
285 - // Disable real-time collaboration on the site editor.
286 - $enabled = true;
287 - if (
288 - 'site-editor.php' === $pagenow ||
289 - ( 'admin.php' === $pagenow && isset( $_GET['page'] ) && 'site-editor-v2' === $_GET['page'] )
290 - ) {
291 - $enabled = false;
292 - }
293 -
294 258 $disabled_post_types = array_values(
295 259 array_filter(
296 260 get_post_types( array( 'show_in_rest' => true ) ),
297 261 'wp_is_post_type_collaboration_disabled'
@@ -299,28 +263,15 @@
299 263 );
300 264
301 265 wp_add_inline_script(
302 266 'wp-core-data',
303 - 'window._wpCollaborationEnabled = ' . wp_json_encode( $enabled ) . ';' .
304 267 'window._wpCollaborationDisabledPostTypes = ' . wp_json_encode( $disabled_post_types ) . ';',
305 268 'after'
306 269 );
307 270 }
308 -add_action( 'admin_init', 'gutenberg_inject_real_time_collaboration_setting' );
271 +add_action( 'admin_init', 'gutenberg_inject_collaboration_disabled_post_types' );
309 272
310 273 /**
311 - * Core adds an option with the default value, so we need to set the option to
312 - * our intended default when the Gutenberg plugin is activated, provided
313 - * collaboration is allowed.
314 - */
315 -function gutenberg_set_collaboration_option_on_activation() {
316 - if ( wp_is_collaboration_allowed() ) {
317 - update_option( 'wp_collaboration_enabled', '1' );
318 - }
319 -}
320 -add_action( 'activate_' . plugin_basename( dirname( __DIR__, 3 ) . '/gutenberg.php' ), 'gutenberg_set_collaboration_option_on_activation' );
321 -
322 -/**
323 274 * Modifies the post list UI and heartbeat responses for real-time collaboration.
324 275 *
325 276 * When RTC is enabled, hides the lock icon and user avatar, replaces the
326 277 * user-specific lock text with "Currently being edited", changes the "Edit"
@@ -589,4 +540,55 @@
589 540 );
590 541
591 542 return $actions;
592 543 }
544 +
545 +/**
546 + * Adds the autosave's CRDT snapshot to the block editor settings when
547 + * real-time collaboration is enabled.
548 + *
549 + * The snapshot describes the document state the autosave captured. The editor
550 + * verifies its own shared document against it, and suppresses the "there is a
551 + * more recent autosave" notice when the shared document already contains
552 + * everything the autosave holds.
553 + *
554 + * @param array $settings Editor settings.
555 + * @param WP_Block_Editor_Context $block_editor_context The current block editor context.
556 + * @return array Filtered editor settings.
557 + */
558 +function gutenberg_add_autosave_details_to_editor_settings( $settings, $block_editor_context ) {
559 + if ( ! isset( $settings['autosave'] ) || empty( $block_editor_context->post ) ) {
560 + return $settings;
561 + }
562 +
563 + if ( ! wp_is_collaboration_enabled() ) {
564 + return $settings;
565 + }
566 +
567 + $post = $block_editor_context->post;
568 +
569 + if ( wp_is_post_type_collaboration_disabled( $post->post_type ) ) {
570 + return $settings;
571 + }
572 +
573 + $autosave = wp_get_post_autosave( $post->ID );
574 +
575 + if ( ! $autosave ) {
576 + return $settings;
577 + }
578 +
579 + $snapshot = get_post_meta( $autosave->ID, Gutenberg_REST_Autosaves_Controller::CRDT_SNAPSHOT_META_KEY, true );
580 +
581 + /*
582 + * Snapshots can be missing from a pre-collaboration autosave, classic editor autosave,
583 + * and other paths. The worst case is a "more recent autosave" notice when newer CRDT
584 + * content is already present in the shared document.
585 + */
586 + if ( ! is_string( $snapshot ) || '' === $snapshot ) {
587 + return $settings;
588 + }
589 +
590 + $settings['autosave']['crdtSnapshot'] = $snapshot;
591 +
592 + return $settings;
593 +}
594 +add_filter( 'block_editor_settings_all', 'gutenberg_add_autosave_details_to_editor_settings', 10, 2 );