From 253cffff12291ae2ae40538ae16fefc44b69e9a3 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 12 Dec 2024 22:20:38 +0100 Subject: [PATCH] Improved comment-type handling (#1072) --- CHANGELOG.md | 1 + includes/class-admin.php | 2 +- includes/class-comment.php | 108 ++++++++++++------- includes/collection/class-interactions.php | 9 +- readme.txt | 1 + tests/includes/class-test-comment.php | 120 +++++++++++++++++++++ 6 files changed, 197 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a1700de9..b554024c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Email templates for Likes and Reposts * Improve Interactions moderation * Compatibility with Akismet +* Comment type mapping for `Like` and `Announce` ### Fixed diff --git a/includes/class-admin.php b/includes/class-admin.php index ac30413e9..bda5b5736 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -600,7 +600,7 @@ public static function comment_row_actions( $actions, $comment ) { unset( $actions['quickedit'] ); } - if ( in_array( get_comment_type( $comment ), Comment::get_comment_type_names(), true ) ) { + if ( in_array( get_comment_type( $comment ), Comment::get_comment_type_slugs(), true ) ) { unset( $actions['reply'] ); } diff --git a/includes/class-comment.php b/includes/class-comment.php index c81e6d1ed..b6496795c 100644 --- a/includes/class-comment.php +++ b/includes/class-comment.php @@ -506,6 +506,27 @@ public static function enqueue_scripts() { } } + /** + * Get the comment type by activity type. + * + * @param string $activity_type The activity type. + * + * @return array|null The comment type. + */ + public static function get_comment_type_by_activity_type( $activity_type ) { + $activity_type = \strtolower( $activity_type ); + $activity_type = \sanitize_key( $activity_type ); + $comment_types = self::get_comment_types(); + + foreach ( $comment_types as $comment_type ) { + if ( in_array( $activity_type, $comment_type['activity_types'], true ) ) { + return $comment_type; + } + } + + return null; + } + /** * Return the registered custom comment types. * @@ -520,23 +541,39 @@ public static function get_comment_types() { /** * Is this a registered comment type. * - * @param string $slug The name of the type. + * @param string $slug The slug of the type. + * * @return boolean True if registered. */ public static function is_registered_comment_type( $slug ) { - $slug = strtolower( $slug ); - $slug = sanitize_key( $slug ); + $slug = \strtolower( $slug ); + $slug = \sanitize_key( $slug ); - return in_array( $slug, array_keys( self::get_comment_types() ), true ); + $comment_types = self::get_comment_types(); + + return isset( $comment_types[ $slug ] ); + } + + /** + * Return the registered custom comment type slugs. + * + * @return array The registered custom comment type slugs. + */ + public static function get_comment_type_slugs() { + return array_keys( self::get_comment_types() ); } /** - * Return the registered custom comment types names. + * Return the registered custom comment type slugs. + * + * @deprecated 4.5.0 Use get_comment_type_slugs instead. * - * @return array The registered custom comment type names. + * @return array The registered custom comment type slugs. */ public static function get_comment_type_names() { - return array_values( wp_list_pluck( self::get_comment_types(), 'type' ) ); + _deprecated_function( __METHOD__, '4.5.0', 'get_comment_type_slugs' ); + + return self::get_comment_type_slugs(); } /** @@ -552,22 +589,15 @@ public static function get_comment_type_names() { * @return array The comment type. */ public static function get_comment_type( $type ) { - $type = strtolower( $type ); - $type = sanitize_key( $type ); - $types = self::get_comment_types(); + $type = strtolower( $type ); + $type = sanitize_key( $type ); - $type_array = array(); + $comment_types = self::get_comment_types(); + $type_array = array(); // Check array keys. - if ( in_array( $type, array_keys( $types ), true ) ) { - $type_array = $types[ $type ]; - } else { // Fall back to type attribute. - foreach ( $types as $item ) { - if ( $type === $item['type'] ) { - $type_array = $item; - break; - } - } + if ( in_array( $type, array_keys( $comment_types ), true ) ) { + $type_array = $comment_types[ $type ]; } /** @@ -609,28 +639,30 @@ public static function get_comment_type_attr( $type, $attr ) { */ public static function register_comment_types() { register_comment_type( - 'announce', + 'repost', array( - 'label' => __( 'Reposts', 'activitypub' ), - 'singular' => __( 'Repost', 'activitypub' ), - 'description' => __( 'A repost on the indieweb is a post that is purely a 100% re-publication of another (typically someone else\'s) post.', 'activitypub' ), - 'icon' => '♻️', - 'class' => 'p-repost', - 'type' => 'repost', - 'excerpt' => __( '… reposted this!', 'activitypub' ), + 'label' => __( 'Reposts', 'activitypub' ), + 'singular' => __( 'Repost', 'activitypub' ), + 'description' => __( 'A repost on the indieweb is a post that is purely a 100% re-publication of another (typically someone else\'s) post.', 'activitypub' ), + 'icon' => '♻️', + 'class' => 'p-repost', + 'type' => 'repost', + 'activity_types' => array( 'announce' ), + 'excerpt' => __( '… reposted this!', 'activitypub' ), ) ); register_comment_type( 'like', array( - 'label' => __( 'Likes', 'activitypub' ), - 'singular' => __( 'Like', 'activitypub' ), - 'description' => __( 'A like is a popular webaction button and in some cases post type on various silos such as Facebook and Instagram.', 'activitypub' ), - 'icon' => '👍', - 'class' => 'p-like', - 'type' => 'like', - 'excerpt' => __( '… liked this!', 'activitypub' ), + 'label' => __( 'Likes', 'activitypub' ), + 'singular' => __( 'Like', 'activitypub' ), + 'description' => __( 'A like is a popular webaction button and in some cases post type on various silos such as Facebook and Instagram.', 'activitypub' ), + 'icon' => '👍', + 'class' => 'p-like', + 'type' => 'like', + 'activity_types' => array( 'like' ), + 'excerpt' => __( '… liked this!', 'activitypub' ), ) ); } @@ -643,7 +675,7 @@ public static function register_comment_types() { * @return array show avatars on Activities */ public static function get_avatar_comment_types( $types ) { - $comment_types = self::get_comment_type_names(); + $comment_types = self::get_comment_type_slugs(); $types = array_merge( $types, $comment_types ); return array_unique( $types ); @@ -672,7 +704,7 @@ public static function comment_query( $query ) { } // Exclude likes and reposts by the ActivityPub plugin. - $query->query_vars['type__not_in'] = self::get_comment_type_names(); + $query->query_vars['type__not_in'] = self::get_comment_type_slugs(); } /** @@ -726,7 +758,7 @@ public static function pre_wp_update_comment_count_now( $new_count, $old_count, if ( null === $new_count ) { global $wpdb; - $excluded_types = self::get_comment_type_names(); + $excluded_types = self::get_comment_type_slugs(); // phpcs:ignore WordPress.DB $new_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type NOT IN ('" . implode( "','", $excluded_types ) . "')", $post_id ) ); diff --git a/includes/collection/class-interactions.php b/includes/collection/class-interactions.php index f115b52e5..3733df08a 100644 --- a/includes/collection/class-interactions.php +++ b/includes/collection/class-interactions.php @@ -98,11 +98,11 @@ public static function add_reaction( $activity ) { } $url = object_to_uri( $activity['object'] ); - $comment_post_id = url_to_postid( $url ); + $comment_post_id = \url_to_postid( $url ); $parent_comment_id = url_to_commentid( $url ); if ( ! $comment_post_id && $parent_comment_id ) { - $parent_comment = get_comment( $parent_comment_id ); + $parent_comment = \get_comment( $parent_comment_id ); $comment_post_id = $parent_comment->comment_post_ID; } @@ -111,14 +111,13 @@ public static function add_reaction( $activity ) { return false; } - $type = $activity['type']; + $comment_type = Comment::get_comment_type_by_activity_type( $activity['type'] ); - if ( ! Comment::is_registered_comment_type( $type ) ) { + if ( ! $comment_type ) { // Not a valid comment type. return false; } - $comment_type = Comment::get_comment_type( $type ); $comment_content = $comment_type['excerpt']; $commentdata['comment_post_ID'] = $comment_post_id; diff --git a/readme.txt b/readme.txt index d132fb67a..c4a6e8587 100644 --- a/readme.txt +++ b/readme.txt @@ -141,6 +141,7 @@ For reasons of data protection, it is not possible to see the followers of other * Improved: Email templates for Likes and Reposts * Improved: Interactions moderation * Improved: Compatibility with Akismet +* Improved: Comment type mapping for `Like` and `Announce` * Fixed: Empty `url` attributes in the Reply block no longer cause PHP warnings = 4.4.0 = diff --git a/tests/includes/class-test-comment.php b/tests/includes/class-test-comment.php index 9fc7cc992..1ef7b33c5 100644 --- a/tests/includes/class-test-comment.php +++ b/tests/includes/class-test-comment.php @@ -455,4 +455,124 @@ public function ability_to_federate_threaded_comment() { ), ); } + + /** + * Test get_comment_type_by_activity_type method. + * + * @covers ::get_comment_type_by_activity_type + */ + public function test_get_comment_type_by_activity_type() { + // Test Like activity type. + $comment_type = Comment::get_comment_type_by_activity_type( 'Like' ); + $this->assertIsArray( $comment_type ); + $this->assertEquals( 'like', $comment_type['type'] ); + $this->assertEquals( 'Like', $comment_type['singular'] ); + $this->assertEquals( 'Likes', $comment_type['label'] ); + $this->assertContains( 'like', $comment_type['activity_types'] ); + + // Test Announce activity type. + $comment_type = Comment::get_comment_type_by_activity_type( 'Announce' ); + $this->assertIsArray( $comment_type ); + $this->assertEquals( 'repost', $comment_type['type'] ); + $this->assertEquals( 'Repost', $comment_type['singular'] ); + $this->assertEquals( 'Reposts', $comment_type['label'] ); + $this->assertContains( 'announce', $comment_type['activity_types'] ); + + // Test case insensitivity. + $comment_type = Comment::get_comment_type_by_activity_type( 'like' ); + $this->assertIsArray( $comment_type ); + $this->assertEquals( 'like', $comment_type['type'] ); + + $comment_type = Comment::get_comment_type_by_activity_type( 'ANNOUNCE' ); + $this->assertIsArray( $comment_type ); + $this->assertEquals( 'repost', $comment_type['type'] ); + + // Test invalid activity type. + $comment_type = Comment::get_comment_type_by_activity_type( 'InvalidType' ); + $this->assertNull( $comment_type ); + + // Test empty activity type. + $comment_type = Comment::get_comment_type_by_activity_type( '' ); + $this->assertNull( $comment_type ); + } + + /** + * Test is_registered_comment_type. + * + * @covers ::is_registered_comment_type + */ + public function test_is_registered_comment_type() { + // Test registered types (these are registered in Comment::register_comment_types()). + $this->assertTrue( Comment::is_registered_comment_type( 'repost' ) ); + $this->assertTrue( Comment::is_registered_comment_type( 'like' ) ); + + // Test case insensitivity. + $this->assertTrue( Comment::is_registered_comment_type( 'REPOST' ) ); + $this->assertTrue( Comment::is_registered_comment_type( 'Like' ) ); + + // Test with spaces and special characters (sanitize_key removes these). + $this->assertTrue( Comment::is_registered_comment_type( ' repost ' ) ); + $this->assertTrue( Comment::is_registered_comment_type( 'like!' ) ); + + // Test unregistered types. + $this->assertFalse( Comment::is_registered_comment_type( 'nonexistent' ) ); + $this->assertFalse( Comment::is_registered_comment_type( '' ) ); + $this->assertFalse( Comment::is_registered_comment_type( 'comment' ) ); + } + + /** + * Test get_comment_type_slugs. + * + * @covers ::get_comment_type_slugs + */ + public function test_get_comment_type_slugs() { + // Get the registered slugs. + $slugs = Comment::get_comment_type_slugs(); + + // Test that we get an array. + $this->assertIsArray( $slugs ); + + // Test that the array is not empty. + $this->assertNotEmpty( $slugs ); + + // Test that it contains the expected default types. + $this->assertContains( 'repost', $slugs ); + $this->assertContains( 'like', $slugs ); + + // Test that the array only contains strings. + foreach ( $slugs as $slug ) { + $this->assertIsString( $slug ); + } + + // Test that there are no duplicate slugs. + $this->assertEquals( count( $slugs ), count( array_unique( $slugs ) ) ); + } + + /** + * Test get_comment_type_names to maintain backwards compatibility. + * + * @covers ::get_comment_type_names + */ + public function test_get_comment_type_names() { + $this->setExpectedDeprecated( 'Activitypub\Comment::get_comment_type_names' ); + + // Get both types of results. + $names = Comment::get_comment_type_names(); + $slugs = Comment::get_comment_type_slugs(); + + // Test that we get an array. + $this->assertIsArray( $names ); + + // Test that the array is not empty. + $this->assertNotEmpty( $names ); + + // Test that it returns exactly the same as get_comment_type_slugs(). + $this->assertEquals( $slugs, $names ); + + // Verify it returns slugs and not singular names. + $this->assertContains( 'repost', $names ); + $this->assertContains( 'like', $names ); + $this->assertNotContains( 'Repost', $names ); + $this->assertNotContains( 'Like', $names ); + } }