Skip to content

Commit 5e793dd

Browse files
authored
Prevent draft posts from being federated when deleted (#1041)
* Prevent draft posts from being federated when deleted Fixes #1030. * Fix end-of-file line missing * Remove unneeded annotations * Make sure post was federated before sending Delete Props @pfefferle
1 parent 87d6f15 commit 5e793dd

File tree

4 files changed

+236
-15
lines changed

4 files changed

+236
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717

1818
* Prevent hex color codes in HTML attributes from being added as post tags
1919
* Fixed a typo in the custom post content settings
20+
* Prevent draft posts from being federated when bulk deleted
2021

2122
## [4.3.0] - 2024-12-02
2223

includes/class-scheduler.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -138,24 +138,24 @@ public static function schedule_post_activity( $new_status, $old_status, $post )
138138
return;
139139
}
140140

141-
$type = false;
141+
switch ( $new_status ) {
142+
case 'publish':
143+
$type = ( 'publish' === $old_status ) ? 'Update' : 'Create';
144+
break;
142145

143-
if (
144-
'publish' === $new_status &&
145-
'publish' !== $old_status
146-
) {
147-
$type = 'Create';
148-
} elseif (
149-
'publish' === $new_status ||
150-
// We want to send updates for posts that are published and then moved to draft.
151-
( 'draft' === $new_status &&
152-
'publish' === $old_status )
153-
) {
154-
$type = 'Update';
155-
} elseif ( 'trash' === $new_status ) {
156-
$type = 'Delete';
146+
case 'draft':
147+
$type = ( 'publish' === $old_status ) ? 'Update' : false;
148+
break;
149+
150+
case 'trash':
151+
$type = 'federated' === get_wp_object_state( $post ) ? 'Delete' : false;
152+
break;
153+
154+
default:
155+
$type = false;
157156
}
158157

158+
// No activity to schedule.
159159
if ( empty( $type ) ) {
160160
return;
161161
}

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ For reasons of data protection, it is not possible to see the followers of other
139139
* Improved: Clarified settings page text around which users get Activitypub profiles
140140
* Fixed: Prevent hex color codes in HTML attributes from being added as post tags
141141
* Fixed: A typo in the custom post content settings
142+
* Fixed: Prevent draft posts from being federated when bulk deleted
142143

143144
= 4.3.0 =
144145

tests/class-test-scheduler.php

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
<?php
2+
/**
3+
* Test Scheduler class.
4+
*
5+
* @package ActivityPub
6+
*/
7+
8+
namespace Activitypub;
9+
10+
/**
11+
* Test cases for the Scheduler class.
12+
*
13+
* @coversDefaultClass \Activitypub\Scheduler
14+
*/
15+
class Test_Scheduler extends \WP_UnitTestCase {
16+
/**
17+
* Test post.
18+
*
19+
* @var \WP_Post
20+
*/
21+
protected $post;
22+
23+
/**
24+
* Set up test resources before each test.
25+
*
26+
* Creates a test post in draft status.
27+
*/
28+
public function set_up() {
29+
parent::set_up();
30+
31+
$this->post = self::factory()->post->create_and_get(
32+
array(
33+
'post_title' => 'Test Post',
34+
'post_content' => 'Test Content',
35+
'post_status' => 'draft',
36+
'post_author' => 1,
37+
)
38+
);
39+
}
40+
41+
/**
42+
* Clean up test resources after each test.
43+
*
44+
* Deletes the test post.
45+
*/
46+
public function tear_down() {
47+
wp_delete_post( $this->post->ID, true );
48+
parent::tear_down();
49+
}
50+
51+
/**
52+
* Test that moving a draft post to trash does not schedule federation.
53+
*
54+
* @covers ::schedule_post_activity
55+
*/
56+
public function test_draft_to_trash_should_not_schedule_federation() {
57+
Scheduler::schedule_post_activity( 'trash', 'draft', $this->post );
58+
59+
$this->assertFalse(
60+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Delete' ) ),
61+
'Draft to trash transition should not schedule federation'
62+
);
63+
}
64+
65+
/**
66+
* Test that moving a published post to trash schedules a delete activity only if federated.
67+
*
68+
* @covers ::schedule_post_activity
69+
*/
70+
public function test_publish_to_trash_should_schedule_delete_only_if_federated() {
71+
wp_publish_post( $this->post->ID );
72+
$this->post = get_post( $this->post->ID );
73+
74+
// Test without federation state.
75+
Scheduler::schedule_post_activity( 'trash', 'publish', $this->post );
76+
$this->assertFalse(
77+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Delete' ) ),
78+
'Published to trash transition should not schedule delete activity when not federated'
79+
);
80+
81+
// Test with federation state.
82+
set_wp_object_state( $this->post, 'federated' );
83+
Scheduler::schedule_post_activity( 'trash', 'publish', $this->post );
84+
85+
$this->assertNotFalse(
86+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Delete' ) ),
87+
'Published to trash transition should schedule delete activity when federated'
88+
);
89+
}
90+
91+
/**
92+
* Test that updating a draft post does not schedule federation.
93+
*
94+
* @covers ::schedule_post_activity
95+
*/
96+
public function test_draft_to_draft_should_not_schedule_federation() {
97+
Scheduler::schedule_post_activity( 'draft', 'draft', $this->post );
98+
99+
$this->assertFalse(
100+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Update' ) ),
101+
'Draft to draft transition should not schedule federation'
102+
);
103+
}
104+
105+
/**
106+
* Test that moving a published post to draft schedules an update activity.
107+
*
108+
* @covers ::schedule_post_activity
109+
*/
110+
public function test_publish_to_draft_should_schedule_update() {
111+
wp_publish_post( $this->post->ID );
112+
$this->post = get_post( $this->post->ID );
113+
Scheduler::schedule_post_activity( 'draft', 'publish', $this->post );
114+
115+
$this->assertNotFalse(
116+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Update' ) ),
117+
'Published to draft transition should schedule update activity'
118+
);
119+
}
120+
121+
/**
122+
* Test that publishing a draft post schedules a create activity.
123+
*
124+
* @covers ::schedule_post_activity
125+
*/
126+
public function test_draft_to_publish_should_schedule_create() {
127+
Scheduler::schedule_post_activity( 'publish', 'draft', $this->post );
128+
129+
$this->assertNotFalse(
130+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Create' ) ),
131+
'Draft to publish transition should schedule create activity'
132+
);
133+
}
134+
135+
/**
136+
* Test that updating a published post schedules an update activity.
137+
*
138+
* @covers ::schedule_post_activity
139+
*/
140+
public function test_publish_to_publish_should_schedule_update() {
141+
wp_publish_post( $this->post->ID );
142+
$this->post = get_post( $this->post->ID );
143+
Scheduler::schedule_post_activity( 'publish', 'publish', $this->post );
144+
145+
$this->assertNotFalse(
146+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Update' ) ),
147+
'Published to published transition should schedule update activity'
148+
);
149+
}
150+
151+
/**
152+
* Test that various non-standard status transitions do not schedule federation.
153+
*
154+
* Tests transitions from pending, private, and future statuses.
155+
*
156+
* @covers ::schedule_post_activity
157+
*/
158+
public function test_other_status_transitions_should_not_schedule_federation() {
159+
// Test pending to draft.
160+
Scheduler::schedule_post_activity( 'draft', 'pending', $this->post );
161+
162+
$this->assertFalse(
163+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Update' ) ),
164+
'Pending to draft transition should not schedule federation'
165+
);
166+
167+
// Test private to draft.
168+
Scheduler::schedule_post_activity( 'draft', 'private', $this->post );
169+
170+
$this->assertFalse(
171+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Update' ) ),
172+
'Private to draft transition should not schedule federation'
173+
);
174+
175+
// Test future to draft.
176+
Scheduler::schedule_post_activity( 'draft', 'future', $this->post );
177+
178+
$this->assertFalse(
179+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Update' ) ),
180+
'Future to draft transition should not schedule federation'
181+
);
182+
}
183+
184+
/**
185+
* Test that disabled posts do not schedule federation activities.
186+
*
187+
* @covers ::schedule_post_activity
188+
*/
189+
public function test_disabled_post_should_not_schedule_federation() {
190+
update_post_meta( $this->post->ID, 'activitypub_content_visibility', ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL );
191+
Scheduler::schedule_post_activity( 'publish', 'draft', $this->post );
192+
193+
$this->assertFalse(
194+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Create' ) ),
195+
'Disabled posts should not schedule federation activities'
196+
);
197+
}
198+
199+
/**
200+
* Test that password protected posts do not schedule federation activities.
201+
*
202+
* @covers ::schedule_post_activity
203+
*/
204+
public function test_password_protected_post_should_not_schedule_federation() {
205+
wp_update_post(
206+
array(
207+
'ID' => $this->post->ID,
208+
'post_password' => 'test-password',
209+
)
210+
);
211+
$this->post = get_post( $this->post->ID );
212+
Scheduler::schedule_post_activity( 'publish', 'draft', $this->post );
213+
214+
$this->assertFalse(
215+
wp_next_scheduled( 'activitypub_send_post', array( $this->post->ID, 'Create' ) ),
216+
'Password protected posts should not schedule federation activities'
217+
);
218+
}
219+
}

0 commit comments

Comments
 (0)