PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
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 → 24.1.0 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 );