Skip to content

Commit 43d999b

Browse files
authored
Merge branch 'trunk' into 65020-update-is-active-check
2 parents e15d155 + 00ac218 commit 43d999b

7 files changed

Lines changed: 273 additions & 64 deletions

File tree

src/wp-admin/includes/image-edit.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function wp_image_editor( $post_id, $msg = false ) {
6161
<div class="imgedit-panel-content imgedit-panel-tools wp-clearfix">
6262
<div class="imgedit-menu wp-clearfix">
6363
<button type="button" onclick="imageEdit.toggleCropTool( <?php echo "$post_id, '$nonce'"; ?>, this );" aria-expanded="false" aria-controls="imgedit-crop" class="imgedit-crop button disabled" disabled><?php esc_html_e( 'Crop' ); ?></button>
64-
<button type="button" class="imgedit-scale button" onclick="imageEdit.toggleControls(this);" aria-expanded="false" aria-controls="imgedit-scale"><?php esc_html_e( 'Scale' ); ?></button>
64+
<button type="button" class="imgedit-scale button" onclick="imageEdit.toggleControls(this);" aria-expanded="false" aria-controls="imgedit-scale"><?php echo esc_html_x( 'Scale', 'verb' ); ?></button>
6565
<div class="imgedit-rotate-menu-container">
6666
<button type="button" aria-controls="imgedit-rotate-menu" class="imgedit-rotate button" aria-expanded="false" onclick="imageEdit.togglePopup(this)" onblur="imageEdit.monitorPopup()"><?php esc_html_e( 'Image Rotation' ); ?></button>
6767
<div id="imgedit-rotate-menu" class="imgedit-popup-menu">
@@ -158,7 +158,7 @@ function wp_image_editor( $post_id, $msg = false ) {
158158
<span class="imgedit-separator" aria-hidden="true">&times;</span>
159159
<label for="imgedit-scale-height-<?php echo $post_id; ?>" class="screen-reader-text"><?php _e( 'scale height' ); ?></label>
160160
<input type="number" step="1" min="0" max="<?php echo $meta['height'] ?? ''; ?>" aria-describedby="imgedit-scale-warn-<?php echo $post_id; ?>" id="imgedit-scale-height-<?php echo $post_id; ?>" onkeyup="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0, this)" onblur="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0, this)" value="<?php echo $meta['height'] ?? 0; ?>" />
161-
<button id="imgedit-scale-button" type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'scale')" class="button button-primary"><?php esc_html_e( 'Scale' ); ?></button>
161+
<button id="imgedit-scale-button" type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'scale')" class="button button-primary"><?php echo esc_html_x( 'Scale', 'verb' ); ?></button>
162162
</div>
163163
<span class="imgedit-scale-warn" id="imgedit-scale-warn-<?php echo $post_id; ?>"><span class="dashicons dashicons-warning" aria-hidden="true"></span><?php esc_html_e( 'Images cannot be scaled to a size larger than the original.' ); ?></span>
164164
</fieldset>

src/wp-includes/connectors.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,28 @@ function _wp_connectors_init(): void {
214214
_wp_connectors_register_default_ai_providers( $registry );
215215
}
216216

217+
// Non-AI default connectors.
218+
$registry->register(
219+
'akismet',
220+
array(
221+
'name' => __( 'Akismet Anti-spam' ),
222+
'description' => __( 'Protect your site from spam.' ),
223+
'type' => 'spam_filtering',
224+
'plugin' => array(
225+
'file' => 'akismet/akismet.php',
226+
'is_active' => static function () {
227+
return defined( 'AKISMET_VERSION' );
228+
},
229+
),
230+
'authentication' => array(
231+
'method' => 'api_key',
232+
'credentials_url' => 'https://akismet.com/get/',
233+
'setting_name' => 'wordpress_api_key',
234+
'constant_name' => 'WPCOM_API_KEY',
235+
),
236+
)
237+
);
238+
217239
/**
218240
* Fires when the connector registry is ready for plugins to register connectors.
219241
*

src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -229,41 +229,45 @@ public function create_item( $request ) {
229229
require_once ABSPATH . 'wp-admin/includes/post.php';
230230
}
231231

232-
$post_lock = wp_check_post_lock( $post->ID );
233-
$is_draft = 'draft' === $post->post_status || 'auto-draft' === $post->post_status;
232+
$post_lock_is_active = wp_check_post_lock( $post->ID );
233+
$is_auto_draft = 'auto-draft' === $post->post_status;
234+
$is_draft = 'draft' === $post->post_status || $is_auto_draft;
235+
$is_collaboration_enabled = wp_is_collaboration_enabled();
234236

235237
/*
236-
* In the context of real-time collaboration, all peers are effectively
237-
* authors and we don't want to vary behavior based on whether they are the
238-
* original author. Always target an autosave revision.
239-
*
240-
* This avoids the following issue when real-time collaboration is enabled:
241-
*
242-
* - Autosaves from the original author (if they have the post lock) will
243-
* target the saved post.
238+
* When a post is still in draft form, updates from the author can directly update the post.
239+
* Other autosaves must be stored as per-user autosave revisions.
244240
*
245-
* - Autosaves from other users are applied to a post revision.
241+
* When RTC is active, however, regular draft autosaves must not update the parent post directly.
242+
* Since all peers are sharing a persisted editing state (a shared CRDT), it’s important that
243+
* they all store updates in a revision. If edits were applied to the post, then upon the next
244+
* editor reload, it would appear as though the post had been updated externally, and those same
245+
* changes would be re-applied to the CRDT, duplicating the edits.
246246
*
247-
* - If any user reloads a post, they load changes from the author's autosave.
248-
*
249-
* - The saved post has now diverged from the persisted CRDT document. The
250-
* content (and/or title or excerpt) are now "ahead" of the persisted CRDT
251-
* document.
252-
*
253-
* - When the persisted CRDT document is loaded, a diff is computed against
254-
* the saved post. This diff is then applied to the in-memory CRDT
255-
* document, which can lead to duplicate inserts or deletions.
247+
* The one caveat for RTC is that the first peer to store an edit must promote an auto-draft
248+
* into a real draft post. If this doesn’t happen then the peers may continue to make edits
249+
* but the draft will be lost, as auto-drafts are not listed in post views.
256250
*/
257-
$is_collaboration_enabled = wp_is_collaboration_enabled();
251+
$can_update_author_draft_post = (
252+
$is_draft &&
253+
(int) $post->post_author === $user_id &&
254+
! $is_collaboration_enabled
255+
);
256+
257+
$can_promote_auto_draft_post = (
258+
$is_auto_draft &&
259+
$is_collaboration_enabled &&
260+
current_user_can( 'edit_post', $post->ID )
261+
);
262+
263+
$should_update_parent_draft_post = (
264+
$can_promote_auto_draft_post ||
265+
( ! $post_lock_is_active && $can_update_author_draft_post )
266+
);
258267

259-
if ( $is_draft && (int) $post->post_author === $user_id && ! $post_lock && ! $is_collaboration_enabled ) {
260-
/*
261-
* Draft posts for the same author: autosaving updates the post and does not create a revision.
262-
* Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
263-
*/
268+
if ( $should_update_parent_draft_post ) {
264269
$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
265270
} else {
266-
// Non-draft posts: create or update the post autosave. Pass the meta data.
267271
$autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) );
268272
}
269273

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/**
4+
* @group admin
5+
*
6+
* @covers ::update_option_new_admin_email
7+
*/
8+
class Admin_Includes_Misc_UpdateOptionNewAdminEmail_Test extends WP_UnitTestCase {
9+
10+
/**
11+
* @ticket 59520
12+
*/
13+
public function test_new_admin_email_subject_filter() {
14+
// Default value.
15+
$mailer = tests_retrieve_phpmailer_instance();
16+
update_option_new_admin_email( 'old@example.com', 'new@example.com' );
17+
$this->assertSame( '[Test Blog] New Admin Email Address', $mailer->get_sent()->subject );
18+
19+
// Filtered value.
20+
add_filter(
21+
'new_admin_email_subject',
22+
function () {
23+
return 'Filtered Admin Email Address';
24+
},
25+
10,
26+
1
27+
);
28+
29+
$mailer->mock_sent = array();
30+
31+
$mailer = tests_retrieve_phpmailer_instance();
32+
update_option_new_admin_email( 'old@example.com', 'new@example.com' );
33+
$this->assertSame( 'Filtered Admin Email Address', $mailer->get_sent()->subject );
34+
}
35+
}

tests/phpunit/tests/admin/includesMisc.php renamed to tests/phpunit/tests/admin/Admin_Includes_Misc_UrlShorten_Test.php

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22

33
/**
44
* @group admin
5+
*
6+
* @covers ::url_shorten
57
*/
6-
class Tests_Admin_IncludesMisc extends WP_UnitTestCase {
8+
class Admin_Includes_Misc_UrlShorten_Test extends WP_UnitTestCase {
79

8-
/**
9-
* @covers ::url_shorten
10-
*/
11-
public function test_shorten_url() {
10+
public function test_url_shorten() {
1211
$tests = array(
1312
'wordpress\.org/about/philosophy'
1413
=> 'wordpress\.org/about/philosophy', // No longer strips slashes.
@@ -27,30 +26,4 @@ public function test_shorten_url() {
2726
$this->assertSame( $v, url_shorten( $k ) );
2827
}
2928
}
30-
31-
/**
32-
* @ticket 59520
33-
*/
34-
public function test_new_admin_email_subject_filter() {
35-
// Default value.
36-
$mailer = tests_retrieve_phpmailer_instance();
37-
update_option_new_admin_email( 'old@example.com', 'new@example.com' );
38-
$this->assertSame( '[Test Blog] New Admin Email Address', $mailer->get_sent()->subject );
39-
40-
// Filtered value.
41-
add_filter(
42-
'new_admin_email_subject',
43-
function () {
44-
return 'Filtered Admin Email Address';
45-
},
46-
10,
47-
1
48-
);
49-
50-
$mailer->mock_sent = array();
51-
52-
$mailer = tests_retrieve_phpmailer_instance();
53-
update_option_new_admin_email( 'old@example.com', 'new@example.com' );
54-
$this->assertSame( 'Filtered Admin Email Address', $mailer->get_sent()->subject );
55-
}
5629
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
/**
3+
* Tests for the collaboration autosaves REST controller override.
4+
*
5+
* @package WordPress
6+
* @subpackage Collaboration
7+
*
8+
* @group collaboration
9+
* @group restapi
10+
*/
11+
class Tests_Collaboration_RestAutosavesController extends WP_UnitTestCase {
12+
13+
protected static int $author_id;
14+
protected static int $editor_id;
15+
16+
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
17+
self::$author_id = $factory->user->create( array( 'role' => 'author' ) );
18+
self::$editor_id = $factory->user->create( array( 'role' => 'editor' ) );
19+
}
20+
21+
public static function wpTearDownAfterClass() {
22+
self::delete_user( self::$author_id );
23+
self::delete_user( self::$editor_id );
24+
delete_option( 'wp_collaboration_enabled' );
25+
}
26+
27+
public function set_up() {
28+
parent::set_up();
29+
wp_set_current_user( self::$author_id );
30+
}
31+
32+
/**
33+
* Creates an empty auto-draft post.
34+
*
35+
* @return int Post ID.
36+
*/
37+
private function create_auto_draft(): int {
38+
return self::factory()->post->create(
39+
array(
40+
'post_author' => self::$author_id,
41+
'post_content' => '',
42+
'post_status' => 'auto-draft',
43+
'post_title' => 'Auto Draft',
44+
'post_type' => 'post',
45+
)
46+
);
47+
}
48+
49+
/**
50+
* Creates a draft post.
51+
*
52+
* @param string $title Post title.
53+
* @param string $content Post content.
54+
* @return int Post ID.
55+
*/
56+
private function create_draft( string $title, string $content ): int {
57+
return self::factory()->post->create(
58+
array(
59+
'post_author' => self::$author_id,
60+
'post_content' => $content,
61+
'post_status' => 'draft',
62+
'post_title' => $title,
63+
'post_type' => 'post',
64+
)
65+
);
66+
}
67+
68+
/**
69+
* Dispatches an autosave request for a post.
70+
*
71+
* @param int $post_id Post ID.
72+
* @param string $title Autosaved post title.
73+
* @param string $content Autosaved post content.
74+
* @return WP_REST_Response Autosave response.
75+
*/
76+
private function dispatch_autosave( int $post_id, string $title, string $content ): WP_REST_Response {
77+
$request = new WP_REST_Request( 'POST', "/wp/v2/posts/{$post_id}/autosaves" );
78+
$request->set_param( 'title', $title );
79+
$request->set_param( 'content', $content );
80+
$request->set_param( 'status', 'draft' );
81+
82+
return rest_get_server()->dispatch( $request );
83+
}
84+
85+
/**
86+
* @ticket 65138
87+
*/
88+
public function test_auto_draft_autosave_promotes_parent_post_when_collaboration_is_disabled() {
89+
update_option( 'wp_collaboration_enabled', 0 );
90+
91+
$post_id = $this->create_auto_draft();
92+
$title = 'No RTC autosaved title';
93+
$content = '<!-- wp:paragraph --><p>No RTC autosaved content</p><!-- /wp:paragraph -->';
94+
95+
$response = $this->dispatch_autosave( $post_id, $title, $content );
96+
97+
$this->assertSame( 200, $response->get_status() );
98+
$post = get_post( $post_id );
99+
$this->assertSame( 'draft', $post->post_status );
100+
$this->assertSame( $title, $post->post_title );
101+
$this->assertSame( $content, $post->post_content );
102+
}
103+
104+
/**
105+
* @ticket 65138
106+
*/
107+
public function test_auto_draft_autosave_promotes_parent_post_when_collaboration_is_enabled() {
108+
update_option( 'wp_collaboration_enabled', 1 );
109+
110+
$post_id = $this->create_auto_draft();
111+
$title = 'RTC autosaved title';
112+
$content = '<!-- wp:paragraph --><p>RTC autosaved content</p><!-- /wp:paragraph -->';
113+
114+
$response = $this->dispatch_autosave( $post_id, $title, $content );
115+
116+
$this->assertSame( 200, $response->get_status() );
117+
$post = get_post( $post_id );
118+
$this->assertSame( 'draft', $post->post_status );
119+
$this->assertSame( $title, $post->post_title );
120+
$this->assertSame( $content, $post->post_content );
121+
}
122+
123+
/**
124+
* @ticket 65138
125+
*/
126+
public function test_collaborator_auto_draft_autosave_promotes_parent_post_when_collaboration_is_enabled() {
127+
update_option( 'wp_collaboration_enabled', 1 );
128+
129+
$post_id = $this->create_auto_draft();
130+
$title = 'RTC collaborator autosaved title';
131+
$content = '<!-- wp:paragraph --><p>RTC collaborator autosaved content</p><!-- /wp:paragraph -->';
132+
133+
wp_set_current_user( self::$editor_id );
134+
$response = $this->dispatch_autosave( $post_id, $title, $content );
135+
136+
$this->assertSame( 200, $response->get_status() );
137+
$post = get_post( $post_id );
138+
$this->assertSame( 'draft', $post->post_status );
139+
$this->assertSame( $title, $post->post_title );
140+
$this->assertSame( $content, $post->post_content );
141+
}
142+
143+
/**
144+
* @ticket 65138
145+
*/
146+
public function test_draft_autosave_creates_revision_when_collaboration_is_enabled() {
147+
update_option( 'wp_collaboration_enabled', 1 );
148+
149+
$original_title = 'Original RTC draft title';
150+
$original_content = '<!-- wp:paragraph --><p>Original RTC draft content</p><!-- /wp:paragraph -->';
151+
$post_id = $this->create_draft( $original_title, $original_content );
152+
$title = 'RTC draft autosaved title';
153+
$content = '<!-- wp:paragraph --><p>RTC draft autosaved content</p><!-- /wp:paragraph -->';
154+
155+
$response = $this->dispatch_autosave( $post_id, $title, $content );
156+
157+
$this->assertSame( 200, $response->get_status() );
158+
$post = get_post( $post_id );
159+
$this->assertSame( 'draft', $post->post_status );
160+
$this->assertSame( $original_title, $post->post_title );
161+
$this->assertSame( $original_content, $post->post_content );
162+
163+
$autosave = wp_get_post_autosave( $post_id, self::$author_id );
164+
$this->assertInstanceOf( WP_Post::class, $autosave );
165+
$this->assertSame( $title, $autosave->post_title );
166+
$this->assertSame( $content, $autosave->post_content );
167+
}
168+
}

0 commit comments

Comments
 (0)