From f1f3c3165d8b35e56b48c7b1d73366fc51dff0f3 Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Wed, 28 Dec 2022 14:44:45 +0100 Subject: [PATCH 01/11] support threaded comments from ActivityPub --- includes/rest/class-inbox.php | 53 ++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 5c21f43..8891853 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -431,19 +431,47 @@ class Inbox { * @param int $user_id The id of the local blog-user */ public static function handle_create( $object, $user_id ) { - $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); - - if ( ! isset( $object['object']['inReplyTo'] ) ) { - return; - } - // check if Activity is public or not if ( ! self::is_activity_public( $object ) ) { // @todo maybe send email - return; + return false; } - $comment_post_id = \url_to_postid( $object['object']['inReplyTo'] ); + $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); + + $comment_post_id = 0; + // TODO: search for an existing comment with the source url and abandon if it's already there + + if ( isset( $object['object']['context'] ) ) { + $comment_post_id = \url_to_postid( $object['object']['context'] ); + } + + if ( ! $comment_post_id && isset( $object['object']['inReplyTo'] ) ) { + $comment_post_id = \url_to_postid( $object['object']['inReplyTo'] ); + } + + $comment_parent_id = 0; + $comment_meta = array( + 'source_id' => \esc_url_raw( $object['object']['id'] ), + 'source_url' => \esc_url_raw( $object['object']['url'] ), + 'avatar_url' => \esc_url_raw( $meta['icon']['url'] ), + 'protocol' => 'activitypub', + ); + + if ( isset( $object['object']['inReplyTo'] ) ) { + $comment_meta['parent_url'] = $object['object']['inReplyTo']; + $comment_query = new \WP_Comment_Query( array( 'meta_key' => 'source_id', 'meta_value' => $object['object']['inReplyTo'] ) ); + if ( $comment_query->comments ) { + foreach ( $comment_query->comments as $comment ) { + if ( ! $comment_parent_id ) { + $comment_parent_id = $comment->comment_ID; + } + if ( ! $comment_post_id ) { + $comment_post_id = $comment->comment_post_ID; + } + } + } + } // save only replys and reactions if ( ! $comment_post_id ) { @@ -457,12 +485,8 @@ class Inbox { 'comment_content' => \wp_filter_kses( $object['object']['content'] ), 'comment_type' => '', 'comment_author_email' => '', - 'comment_parent' => 0, - 'comment_meta' => array( - 'source_url' => \esc_url_raw( $object['object']['url'] ), - 'avatar_url' => \esc_url_raw( $meta['icon']['url'] ), - 'protocol' => 'activitypub', - ), + 'comment_parent' => $comment_parent_id, + 'comment_meta' => $comment_meta, ); // disable flood control @@ -472,6 +496,7 @@ class Inbox { \add_filter( 'pre_option_require_name_email', '__return_false' ); $state = \wp_new_comment( $commentdata, true ); + // TODO: search for comments with that source url as their parent url and update their parent \remove_filter( 'pre_option_require_name_email', '__return_false' ); From 6a906e5fe2f1441bdf05782314d2ee3eadda252d Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Tue, 3 Jan 2023 21:42:43 +0100 Subject: [PATCH 02/11] refactor support for threaded comments from ActivityPub --- includes/functions.php | 37 +++++++++++++++ includes/rest/class-inbox.php | 86 +++++++++++++++++++---------------- 2 files changed, 84 insertions(+), 39 deletions(-) diff --git a/includes/functions.php b/includes/functions.php index 1abb9d1..3e79e5e 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -311,3 +311,40 @@ function url_to_authorid( $url ) { return 0; } + +/** + * Examine a comment ID and look up an existing comment it represents. + * + * @param string $id ActivityPub object ID (usually a URL) to check. + * + * @return WP_Comment, or undef if no comment could be found. + */ +function object_id_to_comment( $id ) { + $comment_query = new \WP_Comment_Query( array( 'meta_key' => 'source_id', 'meta_value' => $id ) ); + if ( !$comment_query->comments ) { + return; + } + if ( count( $comment_query->comments ) > 1 ){ + \error_log( "More than one comment under {$id}" ); + return; + } + return $comment_query->comments[0]; +} + +/** + * Examine an activity object and find the post that the specified URL field refers to. + * + * @param string $field_name The name of the URL field in the object to query. + * + * @return int Post ID, or null on failure. + */ +function object_to_post_id_by_field_name( $object, $field_name ) { + if ( ! isset( $object['object'][$field_name] ) ) { + return; + } + $result = \url_to_postid( $object['object'][$field_name] ); + \error_log( "@@@ found result for " . $field_name . ": " . $result ); + if ( $result > 0 ) { + return $result; + } +} diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 8891853..38d1d50 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -425,12 +425,13 @@ class Inbox { } /** - * Handles "Create" requests + * Converts a new ActivityPub object to comment data suitable for creating a comment * - * @param array $object The activity-object - * @param int $user_id The id of the local blog-user + * @param array $object The activity-object. + * + * @return array Comment data suitable for creating a comment. */ - public static function handle_create( $object, $user_id ) { + private static function convert_object_to_comment_data( $object ) { // check if Activity is public or not if ( ! self::is_activity_public( $object ) ) { // @todo maybe send email @@ -439,55 +440,62 @@ class Inbox { $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); - $comment_post_id = 0; - // TODO: search for an existing comment with the source url and abandon if it's already there + // Objects must have IDs + if ( ! isset( $object['object']['id'] ) ) { + \error_log( "Comment provided without ID" ); + return; + } + $id = $object['object']['id']; - if ( isset( $object['object']['context'] ) ) { - $comment_post_id = \url_to_postid( $object['object']['context'] ); + // Only handle replies + if ( ! isset( $object['object']['inReplyTo'] ) ) { + return; + } + $in_reply_to = $object['object']['inReplyTo']; + + // Comment already exists + if ( \Activitypub\object_id_to_comment( $id ) ) { + return; } - if ( ! $comment_post_id && isset( $object['object']['inReplyTo'] ) ) { - $comment_post_id = \url_to_postid( $object['object']['inReplyTo'] ); - } + $parent_comment = \Activitypub\object_id_to_comment( $in_reply_to ); - $comment_parent_id = 0; - $comment_meta = array( - 'source_id' => \esc_url_raw( $object['object']['id'] ), - 'source_url' => \esc_url_raw( $object['object']['url'] ), - 'avatar_url' => \esc_url_raw( $meta['icon']['url'] ), - 'protocol' => 'activitypub', - ); - - if ( isset( $object['object']['inReplyTo'] ) ) { - $comment_meta['parent_url'] = $object['object']['inReplyTo']; - $comment_query = new \WP_Comment_Query( array( 'meta_key' => 'source_id', 'meta_value' => $object['object']['inReplyTo'] ) ); - if ( $comment_query->comments ) { - foreach ( $comment_query->comments as $comment ) { - if ( ! $comment_parent_id ) { - $comment_parent_id = $comment->comment_ID; - } - if ( ! $comment_post_id ) { - $comment_post_id = $comment->comment_post_ID; - } - } - } - } - - // save only replys and reactions + // save only replies and reactions + $comment_post_id = \Activitypub\object_to_post_id_by_field_name( $object, 'context' ) ?? + \Activitypub\object_to_post_id_by_field_name( $object, 'inReplyTo' ) ?? + ( $parent_comment ? $parent_comment->comment_post_ID : 0 ); if ( ! $comment_post_id ) { - return false; + return; } - $commentdata = array( + return array( 'comment_post_ID' => $comment_post_id, 'comment_author' => \esc_attr( $meta['name'] ), 'comment_author_url' => \esc_url_raw( $object['actor'] ), 'comment_content' => \wp_filter_kses( $object['object']['content'] ), 'comment_type' => '', 'comment_author_email' => '', - 'comment_parent' => $comment_parent_id, - 'comment_meta' => $comment_meta, + 'comment_parent' => $parent_comment ? $parent_comment->comment_ID : 0, + 'comment_meta' => array( + 'source_id' => \esc_url_raw( $id ), + 'source_url' => \esc_url_raw( $object['object']['url'] ), + 'avatar_url' => \esc_url_raw( $meta['icon']['url'] ), + 'protocol' => 'activitypub', + ), ); + } + + /** + * Handles "Create" requests + * + * @param array $object The activity-object + * @param int $user_id The id of the local blog-user + */ + public static function handle_create( $object, $user_id ) { + $commentdata = self::convert_object_to_comment_data( $object ); + if ( !$commentdata ) { + return false; + } // disable flood control \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 ); From 8db9be5c2ea12aaac6faf98ddaf3a08b932e2a84 Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Tue, 24 Jan 2023 11:10:01 +0800 Subject: [PATCH 03/11] remove debugging log line --- includes/functions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/functions.php b/includes/functions.php index 3e79e5e..caae289 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -343,7 +343,6 @@ function object_to_post_id_by_field_name( $object, $field_name ) { return; } $result = \url_to_postid( $object['object'][$field_name] ); - \error_log( "@@@ found result for " . $field_name . ": " . $result ); if ( $result > 0 ) { return $result; } From 7fa58cf26c38b4052b502598b8d2a2fdaf4793cf Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Tue, 24 Jan 2023 13:23:23 +0800 Subject: [PATCH 04/11] add first unit tests for class inbox --- includes/rest/class-inbox.php | 6 ++- tests/test-class-inbox.php | 69 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/test-class-inbox.php diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 38d1d50..8dc8ae4 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -431,7 +431,11 @@ class Inbox { * * @return array Comment data suitable for creating a comment. */ - private static function convert_object_to_comment_data( $object ) { + public static function convert_object_to_comment_data( $object ) { + if ( ! isset( $object['object'] ) ) { + return false; + } + // check if Activity is public or not if ( ! self::is_activity_public( $object ) ) { // @todo maybe send email diff --git a/tests/test-class-inbox.php b/tests/test-class-inbox.php new file mode 100644 index 0000000..4b13e1d --- /dev/null +++ b/tests/test-class-inbox.php @@ -0,0 +1,69 @@ +user_url = $authordata->user_url; + + $post = \wp_insert_post( + array( + 'post_author' => 1, + 'post_content' => 'test', + ) + ); + $this->post_permalink = \get_permalink( $post ); + + \add_filter( 'pre_get_remote_metadata_by_actor', array( '\Test_Inbox', 'get_remote_metadata_by_actor' ), 10, 2); + } + + public function get_remote_metadata_by_actor( $value, $actor ) { + return array( + "name" => "Example User", + "icon" => array( + "url" => "https://example.com/icon", + ), + ); + } + + public function test_convert_object_to_comment_data_basic() { + $inbox = new \Activitypub\Rest\Inbox(); + $object = array( + "actor" => $this->user_url, + "to" => [ $this->user_url ], + "cc" => [ "https://www.w3.org/ns/activitystreams#Public" ], + "object" => array( + "id" => "123", + "url" => "https://example.com/example", + "inReplyTo" => $this->post_permalink, + "content" => "example", + ), + ); + $converted = $inbox->convert_object_to_comment_data($object); + + $this->assertGreaterThan(1, $converted["comment_post_ID"]); + $this->assertEquals($converted["comment_author"], "Example User"); + $this->assertEquals($converted["comment_author_url"], "http://example.org"); + $this->assertEquals($converted["comment_content"], "example"); + $this->assertEquals($converted["comment_type"], ""); + $this->assertEquals($converted["comment_author_email"], ""); + $this->assertEquals($converted["comment_parent"], 0); + $this->assertArrayHasKey("comment_meta", $converted); + $this->assertEquals($converted["comment_meta"]["source_id"], "http://123"); + $this->assertEquals($converted["comment_meta"]["source_url"], "https://example.com/example"); + $this->assertEquals($converted["comment_meta"]["avatar_url"], "https://example.com/icon"); + $this->assertEquals($converted["comment_meta"]["protocol"], "activitypub"); + } + + public function test_convert_object_to_comment_data_non_public_rejected() { + $inbox = new \Activitypub\Rest\Inbox(); + $object = array( + "to" => ["https://example.com/profile/test"], + "cc" => [], + ); + $converted = $inbox->convert_object_to_comment_data($object); + $this->assertFalse($converted); + } +} From 85ca37aa1d991a4f5be68fe35de96d9c9110bd96 Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Tue, 24 Jan 2023 13:32:29 +0800 Subject: [PATCH 05/11] fix code smells --- includes/functions.php | 15 ++++++++++----- includes/rest/class-inbox.php | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/includes/functions.php b/includes/functions.php index caae289..8872e84 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -320,11 +320,16 @@ function url_to_authorid( $url ) { * @return WP_Comment, or undef if no comment could be found. */ function object_id_to_comment( $id ) { - $comment_query = new \WP_Comment_Query( array( 'meta_key' => 'source_id', 'meta_value' => $id ) ); - if ( !$comment_query->comments ) { + $comment_query = new \WP_Comment_Query( + array( + 'meta_key' => 'source_id', + 'meta_value' => $id, + ) + ); + if ( ! $comment_query->comments ) { return; } - if ( count( $comment_query->comments ) > 1 ){ + if ( count( $comment_query->comments ) > 1 ) { \error_log( "More than one comment under {$id}" ); return; } @@ -339,10 +344,10 @@ function object_id_to_comment( $id ) { * @return int Post ID, or null on failure. */ function object_to_post_id_by_field_name( $object, $field_name ) { - if ( ! isset( $object['object'][$field_name] ) ) { + if ( ! isset( $object['object'][ $field_name ] ) ) { return; } - $result = \url_to_postid( $object['object'][$field_name] ); + $result = \url_to_postid( $object['object'][ $field_name ] ); if ( $result > 0 ) { return $result; } diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 8dc8ae4..5c3cd17 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -432,10 +432,10 @@ class Inbox { * @return array Comment data suitable for creating a comment. */ public static function convert_object_to_comment_data( $object ) { - if ( ! isset( $object['object'] ) ) { + if ( ! isset( $object['object'] ) ) { return false; - } - + } + // check if Activity is public or not if ( ! self::is_activity_public( $object ) ) { // @todo maybe send email @@ -446,7 +446,7 @@ class Inbox { // Objects must have IDs if ( ! isset( $object['object']['id'] ) ) { - \error_log( "Comment provided without ID" ); + \error_log( 'Comment provided without ID' ); return; } $id = $object['object']['id']; @@ -465,9 +465,13 @@ class Inbox { $parent_comment = \Activitypub\object_id_to_comment( $in_reply_to ); // save only replies and reactions - $comment_post_id = \Activitypub\object_to_post_id_by_field_name( $object, 'context' ) ?? - \Activitypub\object_to_post_id_by_field_name( $object, 'inReplyTo' ) ?? - ( $parent_comment ? $parent_comment->comment_post_ID : 0 ); + $comment_post_id = \Activitypub\object_to_post_id_by_field_name( $object, 'context' ); + if ( ! $comment_post_id ) { + $comment_post_id = \Activitypub\object_to_post_id_by_field_name( $object, 'inReplyTo' ); + } + if ( ! $comment_post_id ) { + $comment_post_id = $parent_comment->comment_post_ID; + } if ( ! $comment_post_id ) { return; } @@ -497,7 +501,7 @@ class Inbox { */ public static function handle_create( $object, $user_id ) { $commentdata = self::convert_object_to_comment_data( $object ); - if ( !$commentdata ) { + if ( ! $commentdata ) { return false; } From a9a5b112b0f10b535b7eb3386b1ef616ba98fab2 Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Tue, 24 Jan 2023 13:34:15 +0800 Subject: [PATCH 06/11] make filter function static --- tests/test-class-inbox.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-class-inbox.php b/tests/test-class-inbox.php index 4b13e1d..f5b29b1 100644 --- a/tests/test-class-inbox.php +++ b/tests/test-class-inbox.php @@ -19,7 +19,7 @@ class Test_Inbox extends WP_UnitTestCase { \add_filter( 'pre_get_remote_metadata_by_actor', array( '\Test_Inbox', 'get_remote_metadata_by_actor' ), 10, 2); } - public function get_remote_metadata_by_actor( $value, $actor ) { + public static function get_remote_metadata_by_actor( $value, $actor ) { return array( "name" => "Example User", "icon" => array( From 22e0ddc134cd57e0cfaa195b89fb52cd830dfab7 Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Tue, 24 Jan 2023 13:55:00 +0800 Subject: [PATCH 07/11] attempt to resolve backwards compatibility issues --- tests/test-class-inbox.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test-class-inbox.php b/tests/test-class-inbox.php index f5b29b1..38f978e 100644 --- a/tests/test-class-inbox.php +++ b/tests/test-class-inbox.php @@ -1,10 +1,11 @@ user_url = $authordata->user_url; From cbfe6ea43199ac1ac679bee83b5ae647c17915a5 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 27 Jan 2023 16:50:04 +0100 Subject: [PATCH 08/11] Protect code HTML --- includes/class-hashtag.php | 12 +++++++++++- includes/class-mention.php | 12 +++++++++++- tests/test-class-activitypub-hashtag.php | 13 +++++++++++++ tests/test-class-activitypub-mention.php | 6 ++++-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/includes/class-hashtag.php b/includes/class-hashtag.php index 342320d..41ed483 100644 --- a/includes/class-hashtag.php +++ b/includes/class-hashtag.php @@ -20,7 +20,7 @@ class Hashtag { /** * Filter to save #tags as real WordPress tags * - * @param int $id the rev-id + * @param int $id the rev-id * @param WP_Post $post the post * * @return @@ -44,6 +44,16 @@ class Hashtag { */ public static function the_content( $the_content ) { $protected_tags = array(); + $the_content = preg_replace_callback( + '#<(code|textarea|style)\b[^>]*>.*?]*>#i', + function( $m ) use ( &$protected_tags ) { + $c = count( $protected_tags ); + $protect = '!#!#PROTECT' . $c . '#!#!'; + $protected_tags[ $protect ] = $m[0]; + return $protect; + }, + $the_content + ); $the_content = preg_replace_callback( '#<[^>]+>#i', function( $m ) use ( &$protected_tags ) { diff --git a/includes/class-mention.php b/includes/class-mention.php index 0227d8a..23c0c59 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -24,6 +24,16 @@ class Mention { */ public static function the_content( $the_content ) { $protected_tags = array(); + $the_content = preg_replace_callback( + '#<(code|textarea|style)\b[^>]*>.*?]*>#i', + function( $m ) use ( &$protected_tags ) { + $c = count( $protected_tags ); + $protect = '!#!#PROTECT' . $c . '#!#!'; + $protected_tags[ $protect ] = $m[0]; + return $protect; + }, + $the_content + ); $the_content = preg_replace_callback( '#]+>.*?#i', function( $m ) use ( &$protected_tags ) { @@ -68,7 +78,7 @@ class Mention { /** * Extract the mentions from the post_content. * - * @param array $mentions The already found mentions. + * @param array $mentions The already found mentions. * @param string $post_content The post content. * @return mixed The discovered mentions. */ diff --git a/tests/test-class-activitypub-hashtag.php b/tests/test-class-activitypub-hashtag.php index 5c207bd..0c7c6b0 100644 --- a/tests/test-class-activitypub-hashtag.php +++ b/tests/test-class-activitypub-hashtag.php @@ -5,6 +5,7 @@ class Test_Activitypub_Hashtag extends WP_UnitTestCase { */ public function test_the_content( $content, $content_with_hashtag ) { \wp_create_term( 'object', 'post_tag' ); + \wp_create_term( 'ccc', 'post_tag' ); $object = \get_term_by( 'name', 'object', 'post_tag' ); $link = \get_term_link( $object, 'post_tag' ); @@ -14,6 +15,15 @@ class Test_Activitypub_Hashtag extends WP_UnitTestCase { } public function the_content_provider() { + $code = 'text with some #object and tag inside'; + $style = << + + +ENDSTYLE; + $textarea = ''; return array( array( 'test', 'test' ), array( '#test', '#test' ), @@ -27,6 +37,9 @@ class Test_Activitypub_Hashtag extends WP_UnitTestCase { array( '
#object
', '
#object
' ), array( '
#object', '#object' ), array( '
object', '
object' ), + array( $code, $code ), + array( $style, $style ), + array( $textarea, $textarea ), ); } } diff --git a/tests/test-class-activitypub-mention.php b/tests/test-class-activitypub-mention.php index e777dff..e4054d0 100644 --- a/tests/test-class-activitypub-mention.php +++ b/tests/test-class-activitypub-mention.php @@ -2,8 +2,8 @@ class Test_Activitypub_Mention extends ActivityPub_TestCase_Cache_HTTP { public static $users = array( 'username@example.org' => array( - 'url' => 'https://example.org/users/username', - 'name' => 'username', + 'url' => 'https://example.org/users/username', + 'name' => 'username', ), ); /** @@ -18,12 +18,14 @@ class Test_Activitypub_Mention extends ActivityPub_TestCase_Cache_HTTP { } public function the_content_provider() { + $code = 'hallo @username@example.org test'; return array( array( 'hallo @username@example.org test', 'hallo @username test' ), array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle test' ), array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ), array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ), array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ), + array( $code, $code ), ); } From e7894f4c4addf4a9b20d76ca9158f0ec3ac78e62 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 27 Jan 2023 16:55:52 +0100 Subject: [PATCH 09/11] Also protect

---
 includes/class-hashtag.php               | 2 +-
 includes/class-mention.php               | 2 +-
 tests/test-class-activitypub-hashtag.php | 8 ++++++++
 tests/test-class-activitypub-mention.php | 7 +++++++
 4 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/includes/class-hashtag.php b/includes/class-hashtag.php
index 41ed483..cbdb7df 100644
--- a/includes/class-hashtag.php
+++ b/includes/class-hashtag.php
@@ -45,7 +45,7 @@ class Hashtag {
 	public static function the_content( $the_content ) {
 		$protected_tags = array();
 		$the_content = preg_replace_callback(
-			'#<(code|textarea|style)\b[^>]*>.*?]*>#i',
+			'#<(pre|code|textarea|style)\b[^>]*>.*?]*>#is',
 			function( $m ) use ( &$protected_tags ) {
 				$c = count( $protected_tags );
 				$protect = '!#!#PROTECT' . $c . '#!#!';
diff --git a/includes/class-mention.php b/includes/class-mention.php
index 23c0c59..1912dfa 100644
--- a/includes/class-mention.php
+++ b/includes/class-mention.php
@@ -25,7 +25,7 @@ class Mention {
 	public static function the_content( $the_content ) {
 		$protected_tags = array();
 		$the_content = preg_replace_callback(
-			'#<(code|textarea|style)\b[^>]*>.*?]*>#i',
+			'#<(pre|code|textarea|style)\b[^>]*>.*?]*>#is',
 			function( $m ) use ( &$protected_tags ) {
 				$c = count( $protected_tags );
 				$protect = '!#!#PROTECT' . $c . '#!#!';
diff --git a/tests/test-class-activitypub-hashtag.php b/tests/test-class-activitypub-hashtag.php
index 0c7c6b0..319b47d 100644
--- a/tests/test-class-activitypub-hashtag.php
+++ b/tests/test-class-activitypub-hashtag.php
@@ -5,6 +5,7 @@ class Test_Activitypub_Hashtag extends WP_UnitTestCase {
 	 */
 	public function test_the_content( $content, $content_with_hashtag ) {
 		\wp_create_term( 'object', 'post_tag' );
+		\wp_create_term( 'touch', 'post_tag' );
 		\wp_create_term( 'ccc', 'post_tag' );
 		$object = \get_term_by( 'name', 'object', 'post_tag' );
 		$link = \get_term_link( $object, 'post_tag' );
@@ -23,6 +24,12 @@ color: #ccc;
 ]]>
 
 ENDSTYLE;
+		$pre = <<
+Please don't #touch
+  this.
+
+ENDPRE; $textarea = ''; return array( array( 'test', 'test' ), @@ -40,6 +47,7 @@ ENDSTYLE; array( $code, $code ), array( $style, $style ), array( $textarea, $textarea ), + array( $pre, $pre ), ); } } diff --git a/tests/test-class-activitypub-mention.php b/tests/test-class-activitypub-mention.php index e4054d0..6f6b9ff 100644 --- a/tests/test-class-activitypub-mention.php +++ b/tests/test-class-activitypub-mention.php @@ -19,6 +19,12 @@ class Test_Activitypub_Mention extends ActivityPub_TestCase_Cache_HTTP { public function the_content_provider() { $code = 'hallo @username@example.org test'; + $pre = << +Please don't mention @username@example.org + here. + +ENDPRE; return array( array( 'hallo @username@example.org test', 'hallo @username test' ), array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle test' ), @@ -26,6 +32,7 @@ class Test_Activitypub_Mention extends ActivityPub_TestCase_Cache_HTTP { array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ), array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ), array( $code, $code ), + array( $pre, $pre ), ); } From 6ea46c5024db00d16b7789159f6fcb05cba09663 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 27 Jan 2023 16:59:15 +0100 Subject: [PATCH 10/11] Protect cdata --- includes/class-hashtag.php | 25 ++++++++++++------------ includes/class-mention.php | 25 ++++++++++++------------ tests/test-class-activitypub-hashtag.php | 2 +- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/includes/class-hashtag.php b/includes/class-hashtag.php index cbdb7df..9ce8b34 100644 --- a/includes/class-hashtag.php +++ b/includes/class-hashtag.php @@ -44,24 +44,25 @@ class Hashtag { */ public static function the_content( $the_content ) { $protected_tags = array(); + $protect = function( $m ) use ( &$protected_tags ) { + $c = count( $protected_tags ); + $protect = '!#!#PROTECT' . $c . '#!#!'; + $protected_tags[ $protect ] = $m[0]; + return $protect; + }; + $the_content = preg_replace_callback( + '##is', + $protect, + $the_content + ); $the_content = preg_replace_callback( '#<(pre|code|textarea|style)\b[^>]*>.*?]*>#is', - function( $m ) use ( &$protected_tags ) { - $c = count( $protected_tags ); - $protect = '!#!#PROTECT' . $c . '#!#!'; - $protected_tags[ $protect ] = $m[0]; - return $protect; - }, + $protect, $the_content ); $the_content = preg_replace_callback( '#<[^>]+>#i', - function( $m ) use ( &$protected_tags ) { - $c = count( $protected_tags ); - $protect = '!#!#PROTECT' . $c . '#!#!'; - $protected_tags[ $protect ] = $m[0]; - return $protect; - }, + $protect, $the_content ); diff --git a/includes/class-mention.php b/includes/class-mention.php index 1912dfa..7012e40 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -24,24 +24,25 @@ class Mention { */ public static function the_content( $the_content ) { $protected_tags = array(); + $protect = function( $m ) use ( &$protected_tags ) { + $c = count( $protected_tags ); + $protect = '!#!#PROTECT' . $c . '#!#!'; + $protected_tags[ $protect ] = $m[0]; + return $protect; + }; + $the_content = preg_replace_callback( + '##is', + $protect, + $the_content + ); $the_content = preg_replace_callback( '#<(pre|code|textarea|style)\b[^>]*>.*?]*>#is', - function( $m ) use ( &$protected_tags ) { - $c = count( $protected_tags ); - $protect = '!#!#PROTECT' . $c . '#!#!'; - $protected_tags[ $protect ] = $m[0]; - return $protect; - }, + $protect, $the_content ); $the_content = preg_replace_callback( '#]+>.*?#i', - function( $m ) use ( &$protected_tags ) { - $c = count( $protected_tags ); - $protect = '!#!#PROTECT' . $c . '#!#!'; - $protected_tags[ $protect ] = $m[0]; - return $protect; - }, + $protect, $the_content ); diff --git a/tests/test-class-activitypub-hashtag.php b/tests/test-class-activitypub-hashtag.php index 319b47d..51a532c 100644 --- a/tests/test-class-activitypub-hashtag.php +++ b/tests/test-class-activitypub-hashtag.php @@ -19,7 +19,7 @@ class Test_Activitypub_Hashtag extends WP_UnitTestCase { $code = 'text with some #object and tag inside'; $style = << - From 7e3a5f4e6818a9488ecfff4de6f9726647d3c733 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 27 Jan 2023 17:23:25 +0100 Subject: [PATCH 11/11] Handle double protect --- includes/class-hashtag.php | 4 ++-- includes/class-mention.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/class-hashtag.php b/includes/class-hashtag.php index 9ce8b34..b14a8fa 100644 --- a/includes/class-hashtag.php +++ b/includes/class-hashtag.php @@ -51,7 +51,7 @@ class Hashtag { return $protect; }; $the_content = preg_replace_callback( - '##is', + '##is', $protect, $the_content ); @@ -68,7 +68,7 @@ class Hashtag { $the_content = \preg_replace_callback( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', array( '\Activitypub\Hashtag', 'replace_with_links' ), $the_content ); - $the_content = str_replace( array_keys( $protected_tags ), array_values( $protected_tags ), $the_content ); + $the_content = str_replace( array_reverse( array_keys( $protected_tags ) ), array_reverse( array_values( $protected_tags ) ), $the_content ); return $the_content; } diff --git a/includes/class-mention.php b/includes/class-mention.php index 7012e40..7c8672a 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -31,7 +31,7 @@ class Mention { return $protect; }; $the_content = preg_replace_callback( - '##is', + '##is', $protect, $the_content ); @@ -48,7 +48,7 @@ class Mention { $the_content = \preg_replace_callback( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/', array( '\Activitypub\Mention', 'replace_with_links' ), $the_content ); - $the_content = str_replace( array_keys( $protected_tags ), array_values( $protected_tags ), $the_content ); + $the_content = str_replace( array_reverse( array_keys( $protected_tags ) ), array_reverse( array_values( $protected_tags ) ), $the_content ); return $the_content; }