diff --git a/activitypub.php b/activitypub.php index 57cb9f3..e121a5c 100644 --- a/activitypub.php +++ b/activitypub.php @@ -26,6 +26,7 @@ function init() { require_once \dirname( __FILE__ ) . '/includes/table/followers-list.php'; + require_once \dirname( __FILE__ ) . '/includes/table/migrate-list.php'; require_once \dirname( __FILE__ ) . '/includes/class-signature.php'; require_once \dirname( __FILE__ ) . '/includes/peer/class-followers.php'; require_once \dirname( __FILE__ ) . '/includes/functions.php'; @@ -131,7 +132,7 @@ function flush_rewrite_rules() { * Activate plugin; */ function activate_plugin() { - if( ! function_exists('get_plugin_data') ){ + if ( ! function_exists( 'get_plugin_data' ) ) { require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); } $plugin_data = \get_plugin_data( __FILE__ ); diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index d698020..d8bed79 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -14,8 +14,10 @@ class Activity_Dispatcher { */ public static function init() { \add_action( 'activitypub_send_post_activity', array( '\Activitypub\Activity_Dispatcher', 'send_post_activity' ) ); + \add_action( 'activitypub_send_announce_activity', array( '\Activitypub\Activity_Dispatcher', 'send_announce_activity' ) ); \add_action( 'activitypub_send_update_activity', array( '\Activitypub\Activity_Dispatcher', 'send_update_activity' ) ); - \add_action( 'activitypub_send_delete_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_activity', 2 ) ); + \add_action( 'activitypub_send_delete_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_activity' ) ); + \add_action( 'activitypub_send_delete_url_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_url_activity' ), 10, 2 ); \add_action( 'activitypub_send_comment_activity', array( '\Activitypub\Activity_Dispatcher', 'send_comment_activity' ) ); \add_action( 'activitypub_send_update_comment_activity', array( '\Activitypub\Activity_Dispatcher', 'send_update_comment_activity' ) ); @@ -43,6 +45,27 @@ class Activity_Dispatcher { } } + /** + * Send "announce" activities. + * + * @param \Activitypub\Model\Post $activitypub_post + */ + public static function send_announce_activity( $activitypub_post ) { + // get latest version of post + $user_id = $activitypub_post->get_post_author(); + $activitypub_announce = new \Activitypub\Model\Activity( 'Announce', \Activitypub\Model\Activity::TYPE_FULL ); + $activitypub_announce->set_actor( \get_author_posts_url( $user_id ) ); + $activitypub_create = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_NONE ); + $activitypub_create->from_post( $activitypub_post->to_array() ); + $activitypub_announce->from_post( $activitypub_create->to_array() ); + + foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { + $activitypub_announce->set_to( $to ); + $activity = $activitypub_announce->to_json(); // phpcs:ignore + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); + } + } + /** * Send "update" activities. * @@ -67,9 +90,10 @@ class Activity_Dispatcher { /** * Send "delete" activities. * - * @param \Activitypub\Model\Post $activitypub_post + * @param str $activitypub_url + * @param int $user_id */ - public static function send_delete_activity( $activitypub_post, $permalink = null ) { + public static function send_delete_activity( $activitypub_url, $user_id ) { // get latest version of post $user_id = $activitypub_post->get_post_author(); $deleted = \current_time( 'Y-m-d\TH:i:s\Z', true ); @@ -90,6 +114,30 @@ class Activity_Dispatcher { } } + /** + * Send "delete" activities. + * + * @param str $activitypub_url + * @param int $user_id + */ + public static function send_delete_url_activity( $activitypub_url, $user_id ) { + // get latest version of post + $actor = \get_author_posts_url( $user_id ); + $deleted = \current_time( 'Y-m-d\TH:i:s\Z', true ); + + $activitypub_activity = new \Activitypub\Model\Activity( 'Delete', \Activitypub\Model\Activity::TYPE_SIMPLE ); + $activitypub_activity->set_id( $activitypub_url . '#delete' ); + $activitypub_activity->set_actor( $actor ); + $activitypub_activity->set_object( array( "id" => $activitypub_url, "type" => "Tombstone" ) ); + $activitypub_activity->set_deleted( $deleted ); + + foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { + $activitypub_activity->set_to( $to ); + $activity = $activitypub_activity->to_json(); // phpcs:ignore + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); + } + } + /** * Send "create" activities for comments * diff --git a/includes/class-admin.php b/includes/class-admin.php index 7c3968b..ff6d9b2 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -37,6 +37,8 @@ class Admin { $followers_list_page = \add_users_page( \__( 'Followers', 'activitypub' ), \__( 'Followers (Fediverse)', 'activitypub' ), 'read', 'activitypub-followers-list', array( '\Activitypub\Admin', 'followers_list_page' ) ); \add_action( 'load-' . $followers_list_page, array( '\Activitypub\Admin', 'add_followers_list_help_tab' ) ); + + \add_management_page( \__( 'ActivityPub Management', 'activitypub' ), \__( 'ActivityPub Management', 'activitypub' ), 'manage_options', 'activitypub_tools', array( '\Activitypub\Admin', 'migrate_tools_page' ) ); } /** @@ -53,6 +55,13 @@ class Admin { \load_template( \dirname( __FILE__ ) . '/../templates/followers-list.php' ); } + /** + * Load ActivityPub Tools page + */ + public static function migrate_tools_page() { + \load_template( \dirname( __FILE__ ) . '/../templates/migrate-page.php' ); + } + /** * Register ActivityPub settings */ @@ -129,10 +138,10 @@ class Admin { * Update ActivityPub plugin */ public static function version_check() { - if( ! function_exists('get_plugin_data') ){ + if ( ! function_exists( 'get_plugin_data' ) ) { require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); } - $plugin_data = \get_plugin_data( ACTIVITYPUB_PLUGIN ); + $plugin_data = \get_plugin_data( ACTIVITYPUB_PLUGIN ); $activitypub_db_version = \get_option( 'activitypub_version' ); // Needs update @@ -140,11 +149,11 @@ class Admin { // Check for specific migrations if ( '0.13.5' > $activitypub_db_version ) { - // This updates post_meta with _activitypub_permalink_compat. + // This updates post_meta with _activitypub_permalink_compat. // Posts that have this meta will be backwards compatible with their permalink based ActivityPub ID (URI) - // This will create false positives, where the permalink has changed (slug, permalink structure) since federation, - // for those cases a delete_url will allow for federating a delete based on the federated object ID + // This may create false positives, where the permalink has changed (slug, permalink structure) since federation, + // for those cases a delete_url will allow for federating a delete based on the federated object ID (the old permalink) \Activitypub\Migrate\Posts::backcompat_posts(); } @@ -218,5 +227,4 @@ class Admin { true ); } - } diff --git a/includes/class-migrate.php b/includes/class-migrate.php index 6c6bf5c..e634f0f 100644 --- a/includes/class-migrate.php +++ b/includes/class-migrate.php @@ -37,29 +37,6 @@ class Posts { return $posts_to_migrate; } - // public static function get_posts_with_comments( $args = null ) { - // $page = ( \get_query_var( 'page' ) ? \get_query_var( 'page' ) : 1 ); - // if ( is_null( $args ) ) { - // $args = array( - // 'number' => '10', - // 'offset' => $page, - // 'type' => 'activitypub', - // 'order' => 'ASC', - // ); - // } - // $comments = \get_comments( $args ); - // $compat = array(); - // foreach ( $comments as $comment ) { - // $post_id = $comment->comment_post_ID; - // if ( \get_post_meta( $post_id, '_activitypub_permalink_compat', true ) ) { - // // if has url needs migration - // $compat[] = $post_id; - // } - // } - // $posts_migrate = \get_posts( \array_unique( $compat ) ); - // return $posts_migrate; - // } - public static function count_posts() { $posts = self::get_posts(); return \count( $posts ); @@ -67,31 +44,37 @@ class Posts { /** * migrate_post - * first send Delete (obj) - * then send Announce (obj) + * Federate Delete + * Federate Announce * - * @param int $post_id - * @param str $url + * @param str post_url (_activitypub_permalink_compat) + * @param str post_author (user_id) */ - // public static function migrate_post( $post_id, $url ) { - // self::delete_url( $post_id, $url ); - // $post = \get_post( $post_id ); - // $activitypub_post = new \Activitypub\Model\Post( $post ); - // \wp_schedule_single_event( \time() + 1, 'send_announce_activity', array( $activitypub_post ) ); - // //return \count( $posts ); - // } + public static function migrate_post( $post_url, $post_author ) { + self::delete_url( $post_url, $post_author ); + self::announce_url( $post_url ); + } + + /** + * announce_url + * send Announce (obj) + * + * @param str post_url (post_id) + */ + public static function announce_url( $post_url ) { + $post_id = \url_to_postid( $post_url ); + $activitypub_post = new \Activitypub\Model\Post( \get_post( $post_id ) ); + \wp_schedule_single_event( \time() + 1, 'activitypub_send_announce_activity', array( $activitypub_post ) ); + } /** * delete_url * Send a Delete activity to the Fediverse * - * @param str $activitypub_permalink_compat + * @param str post_url (_activitypub_permalink_compat) + * @param str post_author (user_id) */ - // public static function delete_url( $post_id, $url ) { - // $post = \get_post( $post_id ); - // $activitypub_post = new \Activitypub\Model\Post( $post ); - // \wp_schedule_single_event( \time(), 'activitypub_send_delete_activity', array( $activitypub_post, $url ) ); - // \wp_schedule_single_event( \time() + 1, 'delete_post_meta', array( $post_id, '_activitypub_permalink_compat' ) ); - // // return admin_notice?; - // } + public static function delete_url( $post_url, $post_author ) { + \wp_schedule_single_event( \time(), 'activitypub_send_delete_url_activity', array( $post_url, $post_author ) ); + } } diff --git a/includes/table/migrate-list.php b/includes/table/migrate-list.php new file mode 100644 index 0000000..4a0562c --- /dev/null +++ b/includes/table/migrate-list.php @@ -0,0 +1,123 @@ + \__( 'Post Author (user_id)', 'activitypub' ), + 'title' => \__( 'Post', 'activitypub' ), + 'date' => \__( 'Publication Date', 'activitypub' ), + 'comments' => \__( 'Comments', 'activitypub' ), + 'migrate' => \__( 'Migrate', 'activitypub' ), + ); + } + + public function get_sortable_columns() { + return array(); + } + + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array( 'post_author', 'migrate' ); + $sortable = $this->get_sortable_columns(); + + $this->process_action(); + $this->_column_headers = array( $columns, $hidden, $sortable ); + + foreach ( \Activitypub\Migrate\Posts::get_posts() as $post ) { + $this->items[] = array( + 'post_author' => $post->post_author, + 'title' => \sprintf( + '%2s', + \get_permalink( $post->ID ), + $post->post_title + ), + 'date' => $post->post_date, + 'comments' => $post->comment_count, + 'migrate' => \get_post_meta( $post->ID, '_activitypub_permalink_compat', true ), + ); + } + + /* pagination */ + $per_page = $this->get_items_per_page('elements_per_page', 10); + $current_page = $this->get_pagenum(); + $total_items = count($this->items); + $table_data = array_slice( $this->items, ( ( $current_page - 1 ) * $per_page ), $per_page ); + $this->set_pagination_args( + array( + 'total_items' => $total_items, + 'per_page' => $per_page, + 'total_pages' => ceil( $total_items / $per_page ) + ) + ); + + /* actions */ + if ( isset( $_REQUEST['_wpnonce'] ) ) { + $nonce = \esc_attr( $_REQUEST['_wpnonce'] ); + } + // delete + if (isset($_REQUEST['action']) && $_REQUEST['page'] == "activitypub_tools" && $_REQUEST['action'] == "delete") { + if ( wp_verify_nonce( $nonce, 'activitypub_delete_post' ) ) { + \Activitypub\Migrate\Posts::delete_url( rawurldecode( $_REQUEST['post_url'] ), absint( $_REQUEST['post_author'] ) ); + \delete_post_meta( \url_to_postid( $_REQUEST['post_url'] ), '_activitypub_permalink_compat' ); + } + } + // delete and announce + if (isset($_REQUEST['action']) && $_REQUEST['page'] == "activitypub_tools" && $_REQUEST['action'] == "delete_and_boost") { + if ( wp_verify_nonce( $nonce, 'activitypub_delete_announce_post' ) ) { + \Activitypub\Migrate\Posts::migrate_post( rawurldecode( $_REQUEST['post_url'] ), absint( $_REQUEST['post_author'] ) ); + \delete_post_meta( \url_to_postid( $_REQUEST['post_url'] ), '_activitypub_permalink_compat' ); + } + } + } + + /** + * Render a column when no column specific method exists. + * + * @param array $item + * @param string $column_name + * + * @return mixed + */ + public function column_default( $item, $column_name ) { + switch ( $column_name ) { + case 'post_author': + case 'title': + case 'date': + case 'comments': + case 'migrate': + return $item[ $column_name ]; + default: + return print_r( $item, true ); //Show the whole array for troubleshooting purposes + } + } + + function column_title( $item ) { + $delete_announce_nonce = wp_create_nonce( 'activitypub_delete_announce_post' ); + $delete_nonce = wp_create_nonce( 'activitypub_delete_post' ); + + $actions = array( + 'delete_boost' => sprintf('%s', + esc_attr($_REQUEST['page']), + 'delete_and_boost', + $item['post_author'], + \rawurlencode( $item['migrate'] ), + $delete_announce_nonce, + __( 'Delete & Announce', 'activitypub' ) + ), + 'delete' => sprintf('%s', + esc_attr($_REQUEST['page']), + 'delete', + $item['post_author'], + \rawurlencode( $item['migrate'] ), + $delete_nonce, + __( 'Delete', 'activitypub' ) + ), + ); + return sprintf('%1$s %2$s', $item['title'], $this->row_actions($actions, true) ); + } +} diff --git a/templates/migrate-page.php b/templates/migrate-page.php new file mode 100644 index 0000000..6bb32d1 --- /dev/null +++ b/templates/migrate-page.php @@ -0,0 +1,51 @@ + +
+

+ +

+ + + +
+ prepare_items(); + $token_table->display(); + ?> +
+
+
+
+

+ + + +

+
+
+
+
+
+

+ + + +

+
+
+