PluginProbe
ActivityPub / 4.1.1
ActivityPub v4.1.1
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-migration.php

class-migration.php in ActivityPub 4.1.1, at includes/class-migration.php

458 lines 12.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Migration class file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
10 use Activitypub\Collection\Users;
11 use Activitypub\Collection\Followers;
12
13 /**
14 * ActivityPub Migration Class
15 *
16 * @author Matthias Pfefferle
17 */
18 class Migration {
19 /**
20 * Initialize the class, registering WordPress hooks.
21 */
22 public static function init() {
23 \add_action( 'activitypub_migrate', array( self::class, 'async_migration' ) );
24
25 self::maybe_migrate();
26 }
27
28 /**
29 * Get the target version.
30 *
31 * This is the version that the database structure will be updated to.
32 * It is the same as the plugin version.
33 *
34 * @return string The target version.
35 */
36 public static function get_target_version() {
37 return get_plugin_version();
38 }
39
40 /**
41 * The current version of the database structure.
42 *
43 * @return string The current version.
44 */
45 public static function get_version() {
46 return get_option( 'activitypub_db_version', 0 );
47 }
48
49 /**
50 * Locks the database migration process to prevent simultaneous migrations.
51 */
52 public static function lock() {
53 \update_option( 'activitypub_migration_lock', \time() );
54 }
55
56 /**
57 * Unlocks the database migration process.
58 */
59 public static function unlock() {
60 \delete_option( 'activitypub_migration_lock' );
61 }
62
63 /**
64 * Whether the database migration process is locked.
65 *
66 * @return boolean
67 */
68 public static function is_locked() {
69 $lock = \get_option( 'activitypub_migration_lock' );
70
71 if ( ! $lock ) {
72 return false;
73 }
74
75 $lock = (int) $lock;
76
77 if ( $lock < \time() - 1800 ) {
78 self::unlock();
79 return false;
80 }
81
82 return true;
83 }
84
85 /**
86 * Whether the database structure is up to date.
87 *
88 * @return bool True if the database structure is up to date, false otherwise.
89 */
90 public static function is_latest_version() {
91 return (bool) \version_compare(
92 self::get_version(),
93 self::get_target_version(),
94 '=='
95 );
96 }
97
98 /**
99 * Updates the database structure if necessary.
100 */
101 public static function maybe_migrate() {
102 if ( self::is_latest_version() ) {
103 return;
104 }
105
106 if ( self::is_locked() ) {
107 return;
108 }
109
110 self::lock();
111
112 $version_from_db = self::get_version();
113
114 // Check for inital migration.
115 if ( ! $version_from_db ) {
116 self::add_default_settings();
117 $version_from_db = self::get_target_version();
118 }
119
120 // Schedule the async migration.
121 if ( ! \wp_next_scheduled( 'activitypub_migrate', $version_from_db ) ) {
122 \wp_schedule_single_event( \time(), 'activitypub_migrate', array( $version_from_db ) );
123 }
124 if ( \version_compare( $version_from_db, '0.17.0', '<' ) ) {
125 self::migrate_from_0_16();
126 }
127 if ( \version_compare( $version_from_db, '1.3.0', '<' ) ) {
128 self::migrate_from_1_2_0();
129 }
130 if ( \version_compare( $version_from_db, '2.1.0', '<' ) ) {
131 self::migrate_from_2_0_0();
132 }
133 if ( \version_compare( $version_from_db, '2.3.0', '<' ) ) {
134 self::migrate_from_2_2_0();
135 }
136 if ( \version_compare( $version_from_db, '3.0.0', '<' ) ) {
137 self::migrate_from_2_6_0();
138 }
139 if ( \version_compare( $version_from_db, '4.0.0', '<' ) ) {
140 self::migrate_to_4_0_0();
141 }
142 if ( \version_compare( $version_from_db, '4.1.0', '<' ) ) {
143 self::migrate_to_4_1_0();
144 }
145
146 /**
147 * Fires when the system has to be migrated.
148 *
149 * @param string $version_from_db The version from which to migrate.
150 * @param string $target_version The target version to migrate to.
151 */
152 \do_action( 'activitypub_migrate', $version_from_db, self::get_target_version() );
153
154 \update_option( 'activitypub_db_version', self::get_target_version() );
155
156 self::unlock();
157 }
158
159 /**
160 * Asynchronously migrates the database structure.
161 *
162 * @param string $version_from_db The version from which to migrate.
163 */
164 public static function async_migration( $version_from_db ) {
165 if ( \version_compare( $version_from_db, '1.0.0', '<' ) ) {
166 self::migrate_from_0_17();
167 }
168 }
169
170 /**
171 * Updates the custom template to use shortcodes instead of the deprecated templates.
172 */
173 private static function migrate_from_0_16() {
174 // Get the custom template.
175 $old_content = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
176
177 /*
178 * If the old content exists but is a blank string, we're going to need a flag to updated it even
179 * after setting it to the default contents.
180 */
181 $need_update = false;
182
183 // If the old contents is blank, use the defaults.
184 if ( '' === $old_content ) {
185 $old_content = ACTIVITYPUB_CUSTOM_POST_CONTENT;
186 $need_update = true;
187 }
188
189 // Set the new content to be the old content.
190 $content = $old_content;
191
192 // Convert old templates to shortcodes.
193 $content = \str_replace( '%title%', '[ap_title]', $content );
194 $content = \str_replace( '%excerpt%', '[ap_excerpt]', $content );
195 $content = \str_replace( '%content%', '[ap_content]', $content );
196 $content = \str_replace( '%permalink%', '[ap_permalink type="html"]', $content );
197 $content = \str_replace( '%shortlink%', '[ap_shortlink type="html"]', $content );
198 $content = \str_replace( '%hashtags%', '[ap_hashtags]', $content );
199 $content = \str_replace( '%tags%', '[ap_hashtags]', $content );
200
201 // Store the new template if required.
202 if ( $content !== $old_content || $need_update ) {
203 \update_option( 'activitypub_custom_post_content', $content );
204 }
205 }
206
207 /**
208 * Updates the DB-schema of the followers-list.
209 */
210 public static function migrate_from_0_17() {
211 // Migrate followers.
212 foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
213 $followers = get_user_meta( $user_id, 'activitypub_followers', true );
214
215 if ( $followers ) {
216 foreach ( $followers as $actor ) {
217 Followers::add_follower( $user_id, $actor );
218 }
219 }
220 }
221
222 Activitypub::flush_rewrite_rules();
223 }
224
225 /**
226 * Clear the cache after updating to 1.3.0.
227 */
228 private static function migrate_from_1_2_0() {
229 $user_ids = \get_users(
230 array(
231 'fields' => 'ID',
232 'capability__in' => array( 'publish_posts' ),
233 )
234 );
235
236 foreach ( $user_ids as $user_id ) {
237 wp_cache_delete( sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
238 }
239 }
240
241 /**
242 * Unschedule Hooks after updating to 2.0.0.
243 */
244 private static function migrate_from_2_0_0() {
245 wp_clear_scheduled_hook( 'activitypub_send_post_activity' );
246 wp_clear_scheduled_hook( 'activitypub_send_update_activity' );
247 wp_clear_scheduled_hook( 'activitypub_send_delete_activity' );
248
249 wp_unschedule_hook( 'activitypub_send_post_activity' );
250 wp_unschedule_hook( 'activitypub_send_update_activity' );
251 wp_unschedule_hook( 'activitypub_send_delete_activity' );
252
253 $object_type = \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE );
254 if ( 'article' === $object_type ) {
255 \update_option( 'activitypub_object_type', 'wordpress-post-format' );
256 }
257 }
258
259 /**
260 * Add the ActivityPub capability to all users that can publish posts
261 * Delete old meta to store followers.
262 */
263 private static function migrate_from_2_2_0() {
264 // Add the ActivityPub capability to all users that can publish posts.
265 self::add_activitypub_capability();
266 }
267
268 /**
269 * Rename DB fields.
270 */
271 private static function migrate_from_2_6_0() {
272 wp_cache_flush();
273
274 self::update_usermeta_key( 'activitypub_user_description', 'activitypub_description' );
275
276 self::update_options_key( 'activitypub_blog_user_description', 'activitypub_blog_description' );
277 self::update_options_key( 'activitypub_blog_user_identifier', 'activitypub_blog_identifier' );
278 }
279
280 /**
281 * * Update actor-mode settings.
282 * * Get the ID of the latest blog post and save it to the options table.
283 */
284 private static function migrate_to_4_0_0() {
285 $latest_post_id = 0;
286
287 // Get the ID of the latest blog post and save it to the options table.
288 $latest_post = get_posts(
289 array(
290 'numberposts' => 1,
291 'orderby' => 'ID',
292 'order' => 'DESC',
293 'post_type' => 'any',
294 'post_status' => 'publish',
295 )
296 );
297
298 if ( $latest_post ) {
299 $latest_post_id = $latest_post[0]->ID;
300 }
301
302 \update_option( 'activitypub_last_post_with_permalink_as_id', $latest_post_id );
303
304 $users = \get_users(
305 array(
306 'capability__in' => array( 'activitypub' ),
307 )
308 );
309
310 foreach ( $users as $user ) {
311 $followers = Followers::get_followers( $user->ID );
312
313 if ( $followers ) {
314 \update_user_option( $user->ID, 'activitypub_use_permalink_as_id', '1' );
315 }
316 }
317
318 $followers = Followers::get_followers( Users::BLOG_USER_ID );
319
320 if ( $followers ) {
321 \update_option( 'activitypub_use_permalink_as_id_for_blog', '1' );
322 }
323
324 self::migrate_actor_mode();
325 }
326
327 /**
328 * Upate to 4.1.0
329 *
330 * * Migrate the `activitypub_post_content_type` to only use `activitypub_custom_post_content`.
331 */
332 public static function migrate_to_4_1_0() {
333 $content_type = \get_option( 'activitypub_post_content_type' );
334
335 switch ( $content_type ) {
336 case 'excerpt':
337 $template = "[ap_excerpt]\n\n[ap_permalink type=\"html\"]";
338 break;
339 case 'title':
340 $template = "[ap_title type=\"html\"]\n\n[ap_permalink type=\"html\"]";
341 break;
342 case 'content':
343 $template = "[ap_content]\n\n[ap_permalink type=\"html\"]\n\n[ap_hashtags]";
344 break;
345 case 'custom':
346 $template = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
347 break;
348 default:
349 $template = ACTIVITYPUB_CUSTOM_POST_CONTENT;
350 break;
351 }
352
353 \update_option( 'activitypub_custom_post_content', $template );
354
355 \delete_option( 'activitypub_post_content_type' );
356
357 $object_type = \get_option( 'activitypub_object_type', false );
358 if ( ! $object_type ) {
359 \update_option( 'activitypub_object_type', 'note' );
360 }
361
362 // Clean up empty visibility meta.
363 global $wpdb;
364 $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
365 "DELETE FROM $wpdb->postmeta
366 WHERE meta_key = 'activitypub_content_visibility'
367 AND (meta_value IS NULL OR meta_value = '')"
368 );
369 }
370
371 /**
372 * Set the defaults needed for the plugin to work.
373 *
374 * Add the ActivityPub capability to all users that can publish posts.
375 */
376 public static function add_default_settings() {
377 self::add_activitypub_capability();
378 }
379
380 /**
381 * Add the ActivityPub capability to all users that can publish posts.
382 */
383 private static function add_activitypub_capability() {
384 // Get all WP_User objects that can publish posts.
385 $users = \get_users(
386 array(
387 'capability__in' => array( 'publish_posts' ),
388 )
389 );
390
391 // Add ActivityPub capability to all users that can publish posts.
392 foreach ( $users as $user ) {
393 $user->add_cap( 'activitypub' );
394 }
395 }
396
397 /**
398 * Rename meta keys.
399 *
400 * @param string $old_key The old comment meta key.
401 * @param string $new_key The new comment meta key.
402 */
403 private static function update_usermeta_key( $old_key, $new_key ) {
404 global $wpdb;
405
406 $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
407 $wpdb->usermeta,
408 array( 'meta_key' => $new_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
409 array( 'meta_key' => $old_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
410 array( '%s' ),
411 array( '%s' )
412 );
413 }
414
415 /**
416 * Rename option keys.
417 *
418 * @param string $old_key The old option key.
419 * @param string $new_key The new option key.
420 */
421 private static function update_options_key( $old_key, $new_key ) {
422 global $wpdb;
423
424 $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
425 $wpdb->options,
426 array( 'option_name' => $new_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
427 array( 'option_name' => $old_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
428 array( '%s' ),
429 array( '%s' )
430 );
431 }
432
433 /**
434 * Migrate the actor mode settings.
435 */
436 public static function migrate_actor_mode() {
437 $blog_profile = \get_option( 'activitypub_enable_blog_user', '0' );
438 $author_profiles = \get_option( 'activitypub_enable_users', '1' );
439
440 if (
441 '1' === $blog_profile &&
442 '1' === $author_profiles
443 ) {
444 \update_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_AND_BLOG_MODE );
445 } elseif (
446 '1' === $blog_profile &&
447 '1' !== $author_profiles
448 ) {
449 \update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
450 } elseif (
451 '1' !== $blog_profile &&
452 '1' === $author_profiles
453 ) {
454 \update_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE );
455 }
456 }
457 }
458