PluginProbe
ActivityPub / 2.6.0
ActivityPub v2.6.0
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / class-scheduler.php

class-scheduler.php in ActivityPub 2.6.0, at includes/class-scheduler.php

349 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Activitypub;
4
5 use Activitypub\Transformer\Post;
6 use Activitypub\Collection\Users;
7 use Activitypub\Collection\Followers;
8
9 use function Activitypub\was_comment_sent;
10 use function Activitypub\is_user_type_disabled;
11 use function Activitypub\should_comment_be_federated;
12 use function Activitypub\get_remote_metadata_by_actor;
13
14 /**
15 * ActivityPub Scheduler Class
16 *
17 * @author Matthias Pfefferle
18 */
19 class Scheduler {
20
21 /**
22 * Initialize the class, registering WordPress hooks
23 */
24 public static function init() {
25 // Post transitions
26 \add_action( 'transition_post_status', array( self::class, 'schedule_post_activity' ), 33, 3 );
27 \add_action(
28 'edit_attachment',
29 function ( $post_id ) {
30 self::schedule_post_activity( 'publish', 'publish', $post_id );
31 }
32 );
33 \add_action(
34 'add_attachment',
35 function ( $post_id ) {
36 self::schedule_post_activity( 'publish', '', $post_id );
37 }
38 );
39 \add_action(
40 'delete_attachment',
41 function ( $post_id ) {
42 self::schedule_post_activity( 'trash', '', $post_id );
43 }
44 );
45
46 if ( ! ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS ) {
47 // Comment transitions
48 \add_action( 'transition_comment_status', array( self::class, 'schedule_comment_activity' ), 20, 3 );
49 \add_action(
50 'edit_comment',
51 function ( $comment_id ) {
52 self::schedule_comment_activity( 'approved', 'approved', $comment_id );
53 }
54 );
55 \add_action(
56 'wp_insert_comment',
57 function ( $comment_id ) {
58 self::schedule_comment_activity( 'approved', '', $comment_id );
59 }
60 );
61 }
62
63 // Follower Cleanups
64 \add_action( 'activitypub_update_followers', array( self::class, 'update_followers' ) );
65 \add_action( 'activitypub_cleanup_followers', array( self::class, 'cleanup_followers' ) );
66
67 // profile updates for blog options
68 if ( ! is_user_type_disabled( 'blog' ) ) {
69 \add_action( 'update_option_site_icon', array( self::class, 'blog_user_update' ) );
70 \add_action( 'update_option_blogdescription', array( self::class, 'blog_user_update' ) );
71 \add_action( 'update_option_blogname', array( self::class, 'blog_user_update' ) );
72 \add_filter( 'pre_set_theme_mod_custom_logo', array( self::class, 'blog_user_update' ) );
73 \add_filter( 'pre_set_theme_mod_header_image', array( self::class, 'blog_user_update' ) );
74 }
75
76 // profile updates for user options
77 if ( ! is_user_type_disabled( 'user' ) ) {
78 \add_action( 'wp_update_user', array( self::class, 'user_update' ) );
79 \add_action( 'updated_user_meta', array( self::class, 'user_meta_update' ), 10, 3 );
80 // @todo figure out a feasible way of updating the header image since it's not unique to any user.
81 }
82 }
83
84 /**
85 * Schedule all ActivityPub schedules.
86 *
87 * @return void
88 */
89 public static function register_schedules() {
90 if ( ! \wp_next_scheduled( 'activitypub_update_followers' ) ) {
91 \wp_schedule_event( time(), 'hourly', 'activitypub_update_followers' );
92 }
93
94 if ( ! \wp_next_scheduled( 'activitypub_cleanup_followers' ) ) {
95 \wp_schedule_event( time(), 'daily', 'activitypub_cleanup_followers' );
96 }
97 }
98
99 /**
100 * Unscedule all ActivityPub schedules.
101 *
102 * @return void
103 */
104 public static function deregister_schedules() {
105 wp_unschedule_hook( 'activitypub_update_followers' );
106 wp_unschedule_hook( 'activitypub_cleanup_followers' );
107 }
108
109
110 /**
111 * Schedule Activities.
112 *
113 * @param string $new_status New post status.
114 * @param string $old_status Old post status.
115 * @param WP_Post $post Post object.
116 */
117 public static function schedule_post_activity( $new_status, $old_status, $post ) {
118 $post = get_post( $post );
119
120 if ( 'ap_extrafield' === $post->post_type ) {
121 self::schedule_profile_update( $post->post_author );
122 return;
123 }
124
125 // Do not send activities if post is password protected.
126 if ( \post_password_required( $post ) ) {
127 return;
128 }
129
130 // Check if post-type supports ActivityPub.
131 $post_types = \get_post_types_by_support( 'activitypub' );
132 if ( ! \in_array( $post->post_type, $post_types, true ) ) {
133 return;
134 }
135
136 $type = false;
137
138 if (
139 'publish' === $new_status &&
140 'publish' !== $old_status
141 ) {
142 $type = 'Create';
143 } elseif (
144 'publish' === $new_status ||
145 ( 'draft' === $new_status &&
146 'draft' !== $old_status )
147 ) {
148 $type = 'Update';
149 } elseif ( 'trash' === $new_status ) {
150 $type = 'Delete';
151 }
152
153 if ( empty( $type ) ) {
154 return;
155 }
156
157 $hook = 'activitypub_send_post';
158 $args = array( $post->ID, $type );
159
160 if ( false === wp_next_scheduled( $hook, $args ) ) {
161 set_wp_object_state( $post, 'federate' );
162 \wp_schedule_single_event( \time(), $hook, $args );
163 }
164 }
165
166 /**
167 * Schedule Comment Activities
168 *
169 * transition_comment_status()
170 *
171 * @param string $new_status New comment status.
172 * @param string $old_status Old comment status.
173 * @param WP_Comment $comment Comment object.
174 */
175 public static function schedule_comment_activity( $new_status, $old_status, $comment ) {
176 $comment = get_comment( $comment );
177
178 // federate only comments that are written by a registered user.
179 if ( ! $comment->user_id ) {
180 return;
181 }
182
183 $type = false;
184
185 if (
186 'approved' === $new_status &&
187 'approved' !== $old_status
188 ) {
189 $type = 'Create';
190 } elseif ( 'approved' === $new_status ) {
191 $type = 'Update';
192 \update_comment_meta( $comment->comment_ID, 'activitypub_comment_modified', time(), true );
193 } elseif (
194 'trash' === $new_status ||
195 'spam' === $new_status
196 ) {
197 $type = 'Delete';
198 }
199
200 if ( empty( $type ) ) {
201 return;
202 }
203
204 // check if comment should be federated or not
205 if ( ! should_comment_be_federated( $comment ) ) {
206 return;
207 }
208
209 $hook = 'activitypub_send_comment';
210 $args = array( $comment->comment_ID, $type );
211
212 if ( false === wp_next_scheduled( $hook, $args ) ) {
213 set_wp_object_state( $comment, 'federate' );
214 \wp_schedule_single_event( \time(), $hook, $args );
215 }
216 }
217
218 /**
219 * Update followers
220 *
221 * @return void
222 */
223 public static function update_followers() {
224 $number = 5;
225
226 if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
227 $number = 50;
228 }
229
230 $number = apply_filters( 'activitypub_update_followers_number', $number );
231 $followers = Followers::get_outdated_followers( $number );
232
233 foreach ( $followers as $follower ) {
234 $meta = get_remote_metadata_by_actor( $follower->get_id(), false );
235
236 if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
237 Followers::add_error( $follower->get__id(), $meta );
238 } else {
239 $follower->from_array( $meta );
240 $follower->update();
241 }
242 }
243 }
244
245 /**
246 * Cleanup followers
247 *
248 * @return void
249 */
250 public static function cleanup_followers() {
251 $number = 5;
252
253 if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
254 $number = 50;
255 }
256
257 $number = apply_filters( 'activitypub_update_followers_number', $number );
258 $followers = Followers::get_faulty_followers( $number );
259
260 foreach ( $followers as $follower ) {
261 $meta = get_remote_metadata_by_actor( $follower->get_url(), false );
262
263 if ( is_tombstone( $meta ) ) {
264 $follower->delete();
265 } elseif ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
266 if ( $follower->count_errors() >= 5 ) {
267 $follower->delete();
268 \wp_schedule_single_event(
269 \time(),
270 'activitypub_delete_actor_interactions',
271 array( $follower->get_id() )
272 );
273 } else {
274 Followers::add_error( $follower->get__id(), $meta );
275 }
276 } else {
277 $follower->reset_errors();
278 }
279 }
280 }
281
282 /**
283 * Send a profile update when relevant user meta is updated.
284 *
285 * @param int $meta_id Meta ID being updated.
286 * @param int $user_id User ID being updated.
287 * @param string $meta_key Meta key being updated.
288 *
289 * @return void
290 */
291 public static function user_meta_update( $meta_id, $user_id, $meta_key ) {
292 // don't bother if the user can't publish
293 if ( ! \user_can( $user_id, 'activitypub' ) ) {
294 return;
295 }
296 // the user meta fields that affect a profile.
297 $fields = array(
298 'activitypub_user_description',
299 'description',
300 'user_url',
301 'display_name',
302 );
303 if ( in_array( $meta_key, $fields, true ) ) {
304 self::schedule_profile_update( $user_id );
305 }
306 }
307
308 /**
309 * Send a profile update when a user is updated.
310 *
311 * @param int $user_id User ID being updated.
312 *
313 * @return void
314 */
315 public static function user_update( $user_id ) {
316 // don't bother if the user can't publish
317 if ( ! \user_can( $user_id, 'activitypub' ) ) {
318 return;
319 }
320
321 self::schedule_profile_update( $user_id );
322 }
323
324 /**
325 * Theme mods only have a dynamic filter so we fudge it like this.
326 *
327 * @param mixed $value
328 *
329 * @return mixed
330 */
331 public static function blog_user_update( $value = null ) {
332 self::schedule_profile_update( 0 );
333 return $value;
334 }
335
336 /**
337 * Send a profile update to all followers. Gets hooked into all relevant options/meta etc.
338 *
339 * @param int $user_id The user ID to update (Could be 0 for Blog-User).
340 */
341 public static function schedule_profile_update( $user_id ) {
342 \wp_schedule_single_event(
343 \time(),
344 'activitypub_send_update_profile_activity',
345 array( $user_id )
346 );
347 }
348 }
349