From 96c1e921516feb0791222b48afa949a1992e60f2 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 6 Jul 2023 14:42:18 +0200 Subject: [PATCH] optimize and simplify followers --- includes/activity/class-base-object.php | 35 +++- includes/class-scheduler.php | 10 +- includes/collection/class-followers.php | 120 ++++++------ includes/model/class-follower.php | 175 +++++------------- includes/table/class-followers.php | 2 +- templates/user-followers-list.php | 1 - tests/test-class-activitypub-activity.php | 2 +- tests/test-class-db-activitypub-followers.php | 14 +- 8 files changed, 146 insertions(+), 213 deletions(-) diff --git a/includes/activity/class-base-object.php b/includes/activity/class-base-object.php index 151ebf1..510ed23 100644 --- a/includes/activity/class-base-object.php +++ b/includes/activity/class-base-object.php @@ -555,12 +555,12 @@ class Base_Object { * * @return string The JSON string. * - * @return array An Object built from the JSON string. + * @return \Activitypub\Activity\Base_Object An Object built from the JSON string. */ - public static function from_json( $json ) { - $array = wp_json_decode( $json, true ); + public static function init_from_json( $json ) { + $array = \json_decode( $json, true ); - return self::from_array( $array ); + return self::init_from_array( $array ); } /** @@ -568,9 +568,9 @@ class Base_Object { * * @return string The object array. * - * @return array An Object built from the JSON string. + * @return \Activitypub\Activity\Base_Object An Object built from the JSON string. */ - public static function from_array( $array ) { + public static function init_from_array( $array ) { $object = new static(); foreach ( $array as $key => $value ) { @@ -581,6 +581,29 @@ class Base_Object { return $object; } + /** + * Convert JSON input to an array and pre-fill the object. + * + * @param string $json The JSON string. + */ + public function from_json( $json ) { + $array = \json_decode( $json, true ); + + $this->from_array( $array ); + } + + /** + * Convert JSON input to an array and pre-fill the object. + * + * @param array $array The array. + */ + public function from_array( $array ) { + foreach ( $array as $key => $value ) { + $key = camel_to_snake_case( $key ); + $this->set( $key, $value ); + } + } + /** * Convert Object to an array. * diff --git a/includes/class-scheduler.php b/includes/class-scheduler.php index 271c4b6..d77a132 100644 --- a/includes/class-scheduler.php +++ b/includes/class-scheduler.php @@ -111,12 +111,11 @@ class Scheduler { $meta = get_remote_metadata_by_actor( $follower->get_url(), true ); if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) { - $follower->set_error( $meta ); + Followers::add_error( $follower->get__id(), $meta ); } else { - $follower->from_meta( $meta ); + $follower->from_array( $meta ); + $follower->update(); } - - $follower->update(); } } @@ -137,8 +136,7 @@ class Scheduler { if ( 5 <= $follower->count_errors() ) { $follower->delete(); } else { - $follower->set_error( $meta ); - $follower->update(); + Followers::add_error( $follower->get__id(), $meta ); } } else { $follower->reset_errors(); diff --git a/includes/collection/class-followers.php b/includes/collection/class-followers.php index c63a28c..bc35a18 100644 --- a/includes/collection/class-followers.php +++ b/includes/collection/class-followers.php @@ -64,37 +64,7 @@ class Followers { register_post_meta( self::POST_TYPE, - 'preferred_username', - array( - 'type' => 'string', - 'single' => true, - 'sanitize_callback' => function( $value ) { - return sanitize_user( $value, true ); - }, - ) - ); - - register_post_meta( - self::POST_TYPE, - 'icon', - array( - 'single' => true, - ) - ); - - register_post_meta( - self::POST_TYPE, - 'url', - array( - 'type' => 'string', - 'single' => false, - 'sanitize_callback' => array( self::class, 'sanitize_url' ), - ) - ); - - register_post_meta( - self::POST_TYPE, - 'inbox', + 'activitypub_inbox', array( 'type' => 'string', 'single' => true, @@ -104,17 +74,7 @@ class Followers { register_post_meta( self::POST_TYPE, - '_shared_inbox', - array( - 'type' => 'string', - 'single' => true, - 'sanitize_callback' => array( self::class, 'sanitize_url' ), - ) - ); - - register_post_meta( - self::POST_TYPE, - '_errors', + 'activitypub_errors', array( 'type' => 'string', 'single' => false, @@ -130,10 +90,10 @@ class Followers { register_post_meta( self::POST_TYPE, - '_actor', + 'activitypub_user_id', array( 'type' => 'string', - 'single' => true, + 'single' => false, 'sanitize_callback' => function( $value ) { return esc_sql( $value ); }, @@ -197,13 +157,28 @@ class Followers { return $meta; } - $follower = Follower::from_array( $meta ); + $error = null; + + $follower = new Follower(); + + if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) { + $follower->set_id( $actor ); + $follower->set_url( $actor ); + $error = $meta; + } else { + $follower->from_array( $meta ); + } + $follower->upsert(); - $meta = get_post_meta( $follower->get__id(), '_user_id' ); + $meta = get_post_meta( $follower->get__id(), 'activitypub_user_id' ); + + if ( $error ) { + self::add_error( $follower->get__id(), $error ); + } if ( is_array( $meta ) && ! in_array( $user_id, $meta, true ) ) { - add_post_meta( $follower->get__id(), '_user_id', $user_id ); + add_post_meta( $follower->get__id(), 'activitypub_user_id', $user_id ); wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); } @@ -227,7 +202,7 @@ class Followers { return false; } - return delete_post_meta( $follower->get__id(), '_user_id', $user_id ); + return delete_post_meta( $follower->get__id(), 'activitypub_user_id', $user_id ); } /** @@ -243,7 +218,7 @@ class Followers { $post_id = $wpdb->get_var( $wpdb->prepare( - "SELECT DISTINCT p.ID FROM $wpdb->posts p INNER JOIN $wpdb->postmeta pm ON p.ID = pm.post_id WHERE p.post_type = %s AND pm.meta_key = '_user_id' AND pm.meta_value = %d AND p.guid = %s", + "SELECT DISTINCT p.ID FROM $wpdb->posts p INNER JOIN $wpdb->postmeta pm ON p.ID = pm.post_id WHERE p.post_type = %s AND pm.meta_key = 'activitypub_user_id' AND pm.meta_value = %d AND p.guid = %s", array( esc_sql( self::POST_TYPE ), esc_sql( $user_id ), @@ -254,7 +229,7 @@ class Followers { if ( $post_id ) { $post = get_post( $post_id ); - return Follower::from_custom_post_type( $post ); + return Follower::init_from_cpt( $post ); } return null; @@ -310,16 +285,16 @@ class Followers { * * @return array The Term list of Followers, the format depends on $output */ - public static function get_followers( $user_id, $number = null, $offset = null, $args = array() ) { + public static function get_followers( $user_id, $number = null, $page = null, $args = array() ) { $defaults = array( 'post_type' => self::POST_TYPE, 'posts_per_page' => $number, - 'offset' => $offset, + 'paged' => $page, 'orderby' => 'ID', 'order' => 'DESC', 'meta_query' => array( array( - 'key' => '_user_id', + 'key' => 'activitypub_user_id', 'value' => $user_id, ), ), @@ -330,7 +305,7 @@ class Followers { $items = array(); foreach ( $query->get_posts() as $post ) { - $items[] = Follower::from_custom_post_type( $post ); // phpcs:ignore + $items[] = Follower::init_from_cpt( $post ); // phpcs:ignore } return $items; @@ -343,11 +318,11 @@ class Followers { * * @return array The Term list of Followers. */ - public static function get_all_followers( $user_id = null ) { + public static function get_all_followers() { $args = array( 'meta_query' => array(), ); - return self::get_followers( $user_id, null, null, $args ); + return self::get_followers( null, null, null, $args ); } /** @@ -364,7 +339,7 @@ class Followers { 'fields' => 'ids', 'meta_query' => array( array( - 'key' => '_user_id', + 'key' => 'activitypub_user_id', 'value' => $user_id, ), ), @@ -396,11 +371,11 @@ class Followers { 'fields' => 'ids', 'meta_query' => array( array( - 'key' => '_shared_inbox', + 'key' => 'activitypub_inbox', 'compare' => 'EXISTS', ), array( - 'key' => '_user_id', + 'key' => 'activitypub_user_id', 'value' => $user_id, ), ), @@ -418,7 +393,7 @@ class Followers { $wpdb->prepare( "SELECT DISTINCT meta_value FROM {$wpdb->postmeta} WHERE post_id IN (" . implode( ', ', array_fill( 0, count( $posts ), '%d' ) ) . ") - AND meta_key = '_shared_inbox' + AND meta_key = 'activitypub_inbox' AND meta_value IS NOT NULL", $posts ) @@ -458,7 +433,7 @@ class Followers { $items = array(); foreach ( $posts->get_posts() as $follower ) { - $items[] = Follower::from_custom_post_type( $follower ); // phpcs:ignore + $items[] = Follower::init_from_cpt( $follower ); // phpcs:ignore } return $items; @@ -478,7 +453,7 @@ class Followers { 'posts_per_page' => $number, 'meta_query' => array( array( - 'key' => 'errors', + 'key' => 'activitypub_errors', 'compare' => 'EXISTS', ), ), @@ -488,9 +463,28 @@ class Followers { $items = array(); foreach ( $posts->get_posts() as $follower ) { - $items[] = Follower::from_custom_post_type( $follower ); // phpcs:ignore + $items[] = Follower::init_from_cpt( $follower ); // phpcs:ignore } return $items; } + + /** + * Undocumented function + * + * @param [type] $post_id + * @param [type] $error + * @return void + */ + public static function add_error( $post_id, $error ) { + if ( is_string( $error ) ) { + $error_message = $error; + } elseif ( is_wp_error( $error ) ) { + $error_message = $error->get_error_message(); + } else { + $error_message = __( 'Unknown Error or misconfigured Error-Message', 'activitypub' ); + } + + return add_post_meta( $post_id, 'activitypub_errors', $error_message ); + } } diff --git a/includes/model/class-follower.php b/includes/model/class-follower.php index 5dd7e2a..573696b 100644 --- a/includes/model/class-follower.php +++ b/includes/model/class-follower.php @@ -24,60 +24,13 @@ class Follower extends Actor { */ protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore - /** - * The complete Remote-Profile of the Follower - * - * @var array - */ - protected $_shared_inbox; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore - - /** - * The complete Remote-Profile of the Follower - * - * @var array - */ - protected $_actor; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore - - /** - * The latest received error. - * - * This will only temporary and will saved to $this->errors - * - * @var string - */ - protected $_error; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore - - /** - * A list of errors - * - * @var array - */ - protected $_errors; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore - - /** - * Set new Error - * - * @param mixed $error The latest HTTP-Error. - * - * @return void - */ - public function set_error( $error ) { - $this->_errors = array(); - $this->_error = $error; - } - /** * Get the errors. * * @return mixed */ public function get_errors() { - if ( $this->_errors ) { - return $this->_errors; - } - - $this->_errors = get_post_meta( $this->_id, 'errors' ); - return $this->_errors; + return get_post_meta( $this->_id, 'activitypub_errors' ); } /** @@ -86,7 +39,7 @@ class Follower extends Actor { * @return void */ public function reset_errors() { - delete_post_meta( $this->_id, 'errors' ); + delete_post_meta( $this->_id, 'activitypub_errors' ); } /** @@ -95,7 +48,7 @@ class Follower extends Actor { * @return int The number of errors. */ public function count_errors() { - $errors = $this->get__errors(); + $errors = $this->get_errors(); if ( is_array( $errors ) && ! empty( $errors ) ) { return count( $errors ); @@ -104,21 +57,13 @@ class Follower extends Actor { return 0; } - public function get_url() { - if ( ! $this->url ) { - return $this->id; - } - - return $this->url; - } - /** * Return the latest error message. * * @return string The error message. */ public function get_latest_error_message() { - $errors = $this->get__errors(); + $errors = $this->get_errors(); if ( is_array( $errors ) && ! empty( $errors ) ) { return reset( $errors ); @@ -142,16 +87,33 @@ class Follower extends Actor { * @return void */ public function save() { + if ( ! $this->get__id() ) { + global $wpdb; + + $post_id = $wpdb->get_var( + $wpdb->prepare( + "SELECT ID FROM $wpdb->posts WHERE guid=%s", + esc_sql( $this->get_id() ) + ) + ); + + if ( $post_id ) { + $post = get_post( $post_id ); + $this->set__id( $post->ID ); + } + } + $args = array( - 'ID' => $this->get__id(), - 'guid' => $this->get_id(), - 'post_title' => $this->get_name(), - 'post_author' => 0, - 'post_type' => Followers::POST_TYPE, - 'post_name' => $this->get_id(), - 'post_content' => $this->get_summary(), - 'post_status' => 'publish', - 'meta_input' => $this->get_post_meta_input(), + 'ID' => $this->get__id(), + 'guid' => esc_url_raw( $this->get_id() ), + 'post_title' => esc_html( $this->get_name() ), + 'post_author' => 0, + 'post_type' => Followers::POST_TYPE, + 'post_name' => esc_url_raw( $this->get_id() ), + 'post_excerpt' => esc_html( $this->get_summary() ) ? $this->get_summary() : '', + 'post_content' => esc_sql( $this->to_json() ), + 'post_status' => 'publish', + 'meta_input' => $this->get_post_meta_input(), ); $post_id = wp_insert_post( $args ); @@ -164,16 +126,17 @@ class Follower extends Actor { * @return void */ public function upsert() { - if ( $this->_id ) { - $this->update(); - } else { - $this->save(); - } + $this->save(); } /** * Delete the current Follower-Object. * + * Beware that this os deleting a Follower for ALL users!!! + * + * To delete only the User connection (unfollow) + * @see \Activitypub\Rest\Followers::remove_follower() + * * @return void */ public function delete() { @@ -186,27 +149,8 @@ class Follower extends Actor { * @return void */ protected function get_post_meta_input() { - $attributes = array( 'inbox', '_shared_inbox', 'icon', 'preferred_username', '_actor', 'url' ); - $meta_input = array(); - - foreach ( $attributes as $attribute ) { - if ( $this->get( $attribute ) ) { - $meta_input[ $attribute ] = $this->get( $attribute ); - } - } - - if ( $this->_error ) { - if ( is_string( $this->_error ) ) { - $_error = $this->_error; - } elseif ( is_wp_error( $this->_error ) ) { - $_error = $this->_error->get_error_message(); - } else { - $_error = __( 'Unknown Error or misconfigured Error-Message', 'activitypub' ); - } - - $meta_input['_errors'] = $_error; - } + $meta_input['activitypub_inbox'] = esc_url_raw( $this->get_shared_inbox() ); return $meta_input; } @@ -231,37 +175,18 @@ class Follower extends Actor { } /** - * Converts an ActivityPub Array to an Follower Object. + * Get the shared inbox, with a fallback to the inbox. * - * @param array $array The ActivityPub Array. - * - * @return Activitypub\Model\Follower The Follower Object. + * @return string|null The URL to the shared inbox, the inbox or null. */ - public static function from_array( $array ) { - $object = parent::from_array( $array ); - $object->set__actor( $array ); - - global $wpdb; - - $post_id = $wpdb->get_var( - $wpdb->prepare( - "SELECT ID FROM $wpdb->posts WHERE guid=%s", - esc_sql( $object->get_id() ) - ) - ); - - if ( $post_id ) { - $post = get_post( $post_id ); - $object->set__id( $post->ID ); + public function get_shared_inbox() { + if ( ! empty( $this->get_endpoints()['sharedInbox'] ) ) { + return $this->get_endpoints()['sharedInbox']; + } elseif ( ! empty( $this->get_inbox() ) ) { + return $this->get_inbox(); } - if ( ! empty( $object->get_endpoints()['sharedInbox'] ) ) { - $object->_shared_inbox = $object->get_endpoints()['sharedInbox']; - } elseif ( ! empty( $object->get_inbox() ) ) { - $object->_shared_inbox = $object->get_inbox(); - } - - return $object; + return null; } /** @@ -271,16 +196,10 @@ class Follower extends Actor { * * @return array Activitypub\Model\Follower */ - public static function from_custom_post_type( $post ) { - $object = new static(); - + public static function init_from_cpt( $post ) { + $object = self::init_from_json( $post->post_content ); $object->set__id( $post->ID ); $object->set_id( $post->guid ); - $object->set_name( $post->post_title ); - $object->set_summary( $post->post_content ); - $object->set_url( get_post_meta( $post->ID, 'url', true ) ); - $object->set_icon( get_post_meta( $post->ID, 'icon', true ) ); - $object->set_preferred_username( get_post_meta( $post->ID, 'preferred_username', true ) ); $object->set_published( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_published ) ) ); $object->set_updated( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) ) ); diff --git a/includes/table/class-followers.php b/includes/table/class-followers.php index 93d9456..b815df6 100644 --- a/includes/table/class-followers.php +++ b/includes/table/class-followers.php @@ -55,7 +55,7 @@ class Followers extends WP_List_Table { $page_num = $this->get_pagenum(); $per_page = 20; - $followers = FollowerCollection::get_followers( $this->user_id, $per_page, ( $page_num - 1 ) * $per_page ); + $followers = FollowerCollection::get_followers( $this->user_id, $per_page, $page_num ); $counter = FollowerCollection::count_followers( $this->user_id ); $this->items = array(); diff --git a/templates/user-followers-list.php b/templates/user-followers-list.php index 7476192..576b1cf 100644 --- a/templates/user-followers-list.php +++ b/templates/user-followers-list.php @@ -1,6 +1,5 @@

-

diff --git a/tests/test-class-activitypub-activity.php b/tests/test-class-activitypub-activity.php index b25545c..ba9f5a2 100644 --- a/tests/test-class-activitypub-activity.php +++ b/tests/test-class-activitypub-activity.php @@ -37,7 +37,7 @@ class Test_Activitypub_Activity extends WP_UnitTestCase { 'content' => 'Hello world!', ); - $object = \Activitypub\Activity\Base_Object::from_array( $test_array ); + $object = \Activitypub\Activity\Base_Object::init_from_array( $test_array ); $this->assertEquals( 'Hello world!', $object->get_content() ); $this->assertEquals( $test_array, $object->to_array() ); diff --git a/tests/test-class-db-activitypub-followers.php b/tests/test-class-db-activitypub-followers.php index 3634713..559bd06 100644 --- a/tests/test-class-db-activitypub-followers.php +++ b/tests/test-class-db-activitypub-followers.php @@ -6,42 +6,42 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase { 'url' => 'https://example.org/users/username', 'inbox' => 'https://example.org/users/username/inbox', 'name' => 'username', - 'prefferedUsername' => 'username', + 'preferredUsername' => 'username', ), 'jon@example.com' => array( 'id' => 'https://example.com/author/jon', 'url' => 'https://example.com/author/jon', 'inbox' => 'https://example.com/author/jon/inbox', 'name' => 'jon', - 'prefferedUsername' => 'jon', + 'preferredUsername' => 'jon', ), 'doe@example.org' => array( 'id' => 'https://example.org/author/doe', 'url' => 'https://example.org/author/doe', 'inbox' => 'https://example.org/author/doe/inbox', 'name' => 'doe', - 'prefferedUsername' => 'doe', + 'preferredUsername' => 'doe', ), 'sally@example.org' => array( 'id' => 'http://sally.example.org', 'url' => 'http://sally.example.org', 'inbox' => 'http://sally.example.org/inbox', 'name' => 'jon', - 'prefferedUsername' => 'jon', + 'preferredUsername' => 'jon', ), '12345@example.com' => array( 'id' => 'https://12345.example.com', 'url' => 'https://12345.example.com', 'inbox' => 'https://12345.example.com/inbox', 'name' => '12345', - 'prefferedUsername' => '12345', + 'preferredUsername' => '12345', ), 'user2@example.com' => array( 'id' => 'https://user2.example.com', 'url' => 'https://user2.example.com', 'inbox' => 'https://user2.example.com/inbox', 'name' => 'user2', - 'prefferedUsername' => 'user2', + 'preferredUsername' => 'user2', ), ); @@ -223,7 +223,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase { $follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' ); for ( $i = 1; $i <= 15; $i++ ) { - add_post_meta( $follower->get__id(), 'errors', 'error ' . $i ); + add_post_meta( $follower->get__id(), 'activitypub_errors', 'error ' . $i ); } $follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' );