Skip to content

Commit 0ee2226

Browse files
authored
Notes: do not prepend post titles to reply posts (#2419)
1 parent 865bb9e commit 0ee2226

File tree

3 files changed

+149
-99
lines changed

3 files changed

+149
-99
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: changed
3+
4+
Reply posts: do not display post title before @mentions in posts that are replies to somebody else

includes/transformer/class-post.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -894,11 +894,20 @@ protected function get_post_content_template() {
894894
$template = $content ?: ACTIVITYPUB_CUSTOM_POST_CONTENT; // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
895895

896896
$post_format_setting = \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE );
897+
$type = $this->get_type();
897898

898899
if ( 'wordpress-post-format' === $post_format_setting ) {
899900
$template = '';
900901

901-
if ( 'Note' === $this->get_type() ) {
902+
/*
903+
* If the post is a note, not a reply, and does not have mentions
904+
* force the inclusion of the post title.
905+
*/
906+
if (
907+
'Note' === $type
908+
&& empty( $this->get_in_reply_to() )
909+
&& empty( $this->get_mentions() )
910+
) {
902911
$template .= "[ap_title type=\"html\"]\n\n";
903912
}
904913

@@ -913,10 +922,13 @@ protected function get_post_content_template() {
913922
* shortcodes like [ap_title] and [ap_content] that are processed during content
914923
* generation.
915924
*
925+
* @since 7.6.0 Added the $type parameter.
926+
*
916927
* @param string $template The template string containing shortcodes.
917928
* @param \WP_Post $item The WordPress post object being transformed.
929+
* @param string $type ActivityStreams 2.0 Object-Type for the post.
918930
*/
919-
return apply_filters( 'activitypub_object_content_template', $template, $this->item );
931+
return apply_filters( 'activitypub_object_content_template', $template, $this->item, $type );
920932
}
921933

922934
/**

tests/phpunit/tests/includes/transformer/class-test-post.php

Lines changed: 131 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,43 +1034,47 @@ public function test_get_interaction_policy_invalid_value_returns_null() {
10341034
}
10351035

10361036
/**
1037-
* Test that get_post_content_template falls back to constant when option is empty.
1037+
* Test get_post_content_template with various post types and reply scenarios.
10381038
*
1039-
* @covers ::get_post_content_template
1040-
*/
1041-
public function test_get_post_content_template_fallback_with_empty_option() {
1042-
$post = $this->create_test_post();
1043-
1044-
// Set object type to something other than wordpress-post-format.
1045-
\update_option( 'activitypub_object_type', 'Article' );
1046-
1047-
// Test with empty string option - should fall back to constant.
1048-
\update_option( 'activitypub_custom_post_content', '' );
1049-
1050-
$transformer = new Post( $post );
1051-
$reflection = new \ReflectionClass( Post::class );
1052-
$method = $reflection->getMethod( 'get_post_content_template' );
1053-
$method->setAccessible( true );
1054-
1055-
$template = $method->invoke( $transformer );
1056-
1057-
$this->assertSame( ACTIVITYPUB_CUSTOM_POST_CONTENT, $template, 'Empty option should fall back to ACTIVITYPUB_CUSTOM_POST_CONTENT constant.' );
1058-
}
1059-
1060-
/**
1061-
* Test that get_post_content_template uses option value when set.
1039+
* Tests how the template is generated for different post types (Article, Note)
1040+
* and reply configurations, as well as option fallback scenarios.
10621041
*
1042+
* @dataProvider wordpress_post_format_template_provider
10631043
* @covers ::get_post_content_template
1044+
*
1045+
* @param array $post_data The post data to create.
1046+
* @param string $expected_template The expected template string.
1047+
* @param string $object_type The activitypub_object_type option value.
1048+
* @param string|null $custom_post_content The activitypub_custom_post_content option value (null to delete).
1049+
* @param string $description Description of the test case.
10641050
*/
1065-
public function test_get_post_content_template_uses_option_when_set() {
1066-
$post = $this->create_test_post();
1051+
public function test_get_post_content_template_with_scenarios( $post_data, $expected_template, $object_type, $custom_post_content, $description ) {
1052+
// Set object type.
1053+
\update_option( 'activitypub_object_type', $object_type );
10671054

1068-
// Set object type to something other than wordpress-post-format.
1069-
\update_option( 'activitypub_object_type', 'Article' );
1055+
// Set or delete custom post content option.
1056+
if ( null === $custom_post_content ) {
1057+
\delete_option( 'activitypub_custom_post_content' );
1058+
} else {
1059+
\update_option( 'activitypub_custom_post_content', $custom_post_content );
1060+
}
1061+
1062+
// Mock mentions extraction if the post content contains mention patterns.
1063+
$content = $post_data['post_content'] ?? '';
1064+
$mentions_filter = null;
1065+
if ( \preg_match( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $content ) ) {
1066+
$mentions_filter = function ( $mentions, $post_content ) {
1067+
// Extract all mention patterns from content.
1068+
\preg_match_all( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $post_content, $all_matches );
1069+
foreach ( $all_matches[0] as $match ) {
1070+
$mentions[ $match ] = 'https://example.com/' . \ltrim( $match, '@' );
1071+
}
1072+
return $mentions;
1073+
};
1074+
\add_filter( 'activitypub_extract_mentions', $mentions_filter, 10, 2 );
1075+
}
10701076

1071-
// Test with custom template option.
1072-
$custom_template = '[ap_title]\n\n[ap_content]\n\n[ap_hashtags]';
1073-
\update_option( 'activitypub_custom_post_content', $custom_template );
1077+
$post = self::factory()->post->create_and_get( $post_data );
10741078

10751079
$transformer = new Post( $post );
10761080
$reflection = new \ReflectionClass( Post::class );
@@ -1079,79 +1083,109 @@ public function test_get_post_content_template_uses_option_when_set() {
10791083

10801084
$template = $method->invoke( $transformer );
10811085

1082-
$this->assertSame( $custom_template, $template, 'Should use custom template option when set.' );
1083-
}
1084-
1085-
/**
1086-
* Test that get_post_content_template falls back with null option.
1087-
*
1088-
* @covers ::get_post_content_template
1089-
*/
1090-
public function test_get_post_content_template_fallback_with_false_option() {
1091-
$post = $this->create_test_post();
1092-
1093-
// Set object type to something other than wordpress-post-format.
1094-
\update_option( 'activitypub_object_type', 'Article' );
1095-
1096-
// Test with false option (not set) - should fall back to constant.
1097-
\delete_option( 'activitypub_custom_post_content' );
1098-
1099-
$transformer = new Post( $post );
1100-
$reflection = new \ReflectionClass( Post::class );
1101-
$method = $reflection->getMethod( 'get_post_content_template' );
1102-
$method->setAccessible( true );
1086+
// Clean up mentions filter if it was added.
1087+
if ( $mentions_filter ) {
1088+
\remove_filter( 'activitypub_extract_mentions', $mentions_filter, 10 );
1089+
}
11031090

1104-
$template = $method->invoke( $transformer );
1091+
// All wordpress-post-format templates should contain [ap_content].
1092+
if ( 'wordpress-post-format' === $object_type ) {
1093+
$this->assertStringContainsString( '[ap_content]', $template, $description . ' - should contain [ap_content]' );
1094+
}
11051095

1106-
$this->assertSame( ACTIVITYPUB_CUSTOM_POST_CONTENT, $template, 'False option should fall back to ACTIVITYPUB_CUSTOM_POST_CONTENT constant.' );
1096+
$this->assertSame( $expected_template, $template, $description );
11071097
}
11081098

11091099
/**
1110-
* Test that get_post_content_template respects wordpress-post-format setting.
1100+
* Data provider for get_post_content_template tests with various scenarios.
11111101
*
1112-
* @covers ::get_post_content_template
1102+
* @return array Each test case contains:
1103+
* - post_data: The post data to create
1104+
* - expected_template: The expected template string
1105+
* - object_type: The activitypub_object_type option value
1106+
* - custom_post_content: The activitypub_custom_post_content option value (null to delete)
1107+
* - description: Description of the test case
11131108
*/
1114-
public function test_get_post_content_template_wordpress_post_format() {
1115-
// Create a post with long content and title (will be Article type).
1116-
$article_post = self::factory()->post->create_and_get(
1117-
array(
1118-
'post_title' => 'Test Article',
1119-
'post_content' => str_repeat( 'Long content. ', 100 ),
1120-
'post_status' => 'publish',
1121-
)
1122-
);
1123-
1124-
// Set custom content template.
1125-
\update_option( 'activitypub_custom_post_content', '[ap_title]\n\n[ap_content]' );
1126-
1127-
// Set post format setting to wordpress-post-format.
1128-
\update_option( 'activitypub_object_type', 'wordpress-post-format' );
1129-
1130-
$transformer = new Post( $article_post );
1131-
$reflection = new \ReflectionClass( Post::class );
1132-
$method = $reflection->getMethod( 'get_post_content_template' );
1133-
$method->setAccessible( true );
1134-
1135-
$template = $method->invoke( $transformer );
1136-
1137-
// When wordpress-post-format is set, template should be generated based on post type.
1138-
// For an Article (long content with title), it should just be [ap_content].
1139-
$this->assertSame( '[ap_content]', $template, 'wordpress-post-format should override custom template for Article type.' );
1140-
1141-
// Test with a Note type (no title or short content).
1142-
$note_post = self::factory()->post->create_and_get(
1143-
array(
1144-
'post_title' => '',
1145-
'post_content' => 'Short note',
1146-
'post_status' => 'publish',
1147-
)
1109+
public function wordpress_post_format_template_provider() {
1110+
return array(
1111+
'Article type' => array(
1112+
array(
1113+
'post_title' => 'Test Article',
1114+
'post_content' => str_repeat( 'Long content. ', 100 ),
1115+
'post_status' => 'publish',
1116+
),
1117+
'[ap_content]',
1118+
'wordpress-post-format',
1119+
'[ap_title]\n\n[ap_content]',
1120+
'wordpress-post-format should override custom template for Article type.',
1121+
),
1122+
'Note type without reply' => array(
1123+
array(
1124+
'post_title' => '',
1125+
'post_content' => 'Short note',
1126+
'post_status' => 'publish',
1127+
),
1128+
"[ap_title type=\"html\"]\n\n[ap_content]",
1129+
'wordpress-post-format',
1130+
'[ap_title]\n\n[ap_content]',
1131+
'wordpress-post-format should add title for Note type without reply.',
1132+
),
1133+
'Note type with reply block' => array(
1134+
array(
1135+
'post_title' => '',
1136+
'post_content' => '<!-- wp:activitypub/reply {"url":"https://example.com/posts/123"} /-->' . PHP_EOL .
1137+
'<!-- wp:paragraph --><p>This is a reply note.</p><!-- /wp:paragraph -->',
1138+
'post_status' => 'publish',
1139+
),
1140+
'[ap_content]',
1141+
'wordpress-post-format',
1142+
'[ap_title]\n\n[ap_content]',
1143+
'wordpress-post-format should not add title for Note type when it is a reply.',
1144+
),
1145+
'Note type with mentions' => array(
1146+
array(
1147+
'post_title' => '',
1148+
'post_content' => 'Short note mentioning @[email protected]',
1149+
'post_status' => 'publish',
1150+
),
1151+
'[ap_content]',
1152+
'wordpress-post-format',
1153+
null,
1154+
'wordpress-post-format should not add title for Note type when it has mentions.',
1155+
),
1156+
'fallback_with_false_option' => array(
1157+
array(
1158+
'post_title' => 'Interaction Policy Test',
1159+
'post_content' => 'Content',
1160+
'post_status' => 'publish',
1161+
),
1162+
ACTIVITYPUB_CUSTOM_POST_CONTENT,
1163+
'Article',
1164+
null,
1165+
'False option should fall back to ACTIVITYPUB_CUSTOM_POST_CONTENT constant.',
1166+
),
1167+
'uses_custom_option_when_set' => array(
1168+
array(
1169+
'post_title' => 'Interaction Policy Test',
1170+
'post_content' => 'Content',
1171+
'post_status' => 'publish',
1172+
),
1173+
'[ap_title]\n\n[ap_content]\n\n[ap_hashtags]',
1174+
'Article',
1175+
'[ap_title]\n\n[ap_content]\n\n[ap_hashtags]',
1176+
'Should use custom template option when set.',
1177+
),
1178+
'fallback_with_empty_option' => array(
1179+
array(
1180+
'post_title' => 'Interaction Policy Test',
1181+
'post_content' => 'Content',
1182+
'post_status' => 'publish',
1183+
),
1184+
ACTIVITYPUB_CUSTOM_POST_CONTENT,
1185+
'Article',
1186+
'',
1187+
'Empty activitypub_custom_post_content option should fall back to ACTIVITYPUB_CUSTOM_POST_CONTENT constant.',
1188+
),
11481189
);
1149-
1150-
$note_transformer = new Post( $note_post );
1151-
$note_template = $method->invoke( $note_transformer );
1152-
1153-
// For a Note, the template should include the title.
1154-
$this->assertStringContainsString( '[ap_title type="html"]', $note_template, 'wordpress-post-format should add title for Note type.' );
1155-
$this->assertStringContainsString( '[ap_content]', $note_template, 'wordpress-post-format should include content for Note type.' );
11561190
}
11571191
}

0 commit comments

Comments
 (0)