Support "read more" for Activity-Summary (#856)

* Support "read more" for Activity-Summary

* add support for excerpt

* use the same function also for the shortcode

* add mentions, hashtags and links support to `the_excerpt`

* do not make unnecessary extra db calls

* fall back to default if options is empty

* remove unused hook

* some fixes

and improvements based on the feedback of @MatzeKitt

* add phpdoc

* also extract hashtags from excerpt

* use filter, so that it is possible to also support mentions

and maybe tags in the future

* Add PHPDoc

* simplify code

* fix test
This commit is contained in:
Matthias Pfefferle 2024-08-16 12:53:14 +02:00 committed by GitHub
parent caecaf3c47
commit 7806285d88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 108 additions and 126 deletions

View file

@ -28,29 +28,14 @@ class Hashtag {
* @return array the activity object array
*/
public static function filter_activity_object( $object_array ) {
if ( empty( $object_array['summary'] ) ) {
return $object_array;
if ( ! empty( $object_array['summary'] ) ) {
$object_array['summary'] = self::the_content( $object_array['summary'] );
}
\preg_match_all( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/', $object_array['summary'], $matches );
foreach ( $matches[0] as $match_id => $match ) {
$tag_object = \get_term_by( 'name', $matches[1][ $match_id ], 'post_tag' );
if ( ! $tag_object ) {
$tag_object = \get_term_by( 'name', $matches[1][ $match_id ], 'category' );
}
if ( $tag_object ) {
$link = \get_term_link( $tag_object, 'post_tag' );
$object_array['tag'][] = [
'type' => 'Hashtag',
'href' => $link,
'name' => $match,
];
}
if ( ! empty( $object_array['content'] ) ) {
$object_array['content'] = self::the_content( $object_array['content'] );
}
$object_array['summary'] = self::the_content( $object_array['summary'] );
return $object_array;
}
@ -63,12 +48,20 @@ class Hashtag {
* @return
*/
public static function insert_post( $id, $post ) {
if ( \preg_match_all( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', $post->post_content, $match ) ) {
$tags = \implode( ', ', $match[1] );
$tags = array();
\wp_add_post_tags( $post->post_parent, $tags );
if ( \preg_match_all( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', $post->post_content, $match ) ) {
$tags = array_merge( $tags, $match[1] );
}
if ( \preg_match_all( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', $post->post_excerpt, $match ) ) {
$tags = array_merge( $tags, $match[1] );
}
$tags = \implode( ', ', $tags );
\wp_add_post_tags( $post->ID, $tags );
return $id;
}

View file

@ -12,6 +12,7 @@ class Link {
* Initialize the class, registering WordPress hooks
*/
public static function init() {
\add_filter( 'activitypub_extra_field_content', array( self::class, 'the_content' ), 10, 1 );
\add_filter( 'activitypub_activity_object_array', array( self::class, 'filter_activity_object' ), 99 );
}
@ -23,11 +24,13 @@ class Link {
* @return array the activity object array
*/
public static function filter_activity_object( $object_array ) {
if ( empty( $object_array['summary'] ) ) {
return $object_array;
if ( ! empty( $object_array['summary'] ) ) {
$object_array['summary'] = self::the_content( $object_array['summary'] );
}
$object_array['summary'] = self::the_content( $object_array['summary'] );
if ( ! empty( $object_array['content'] ) ) {
$object_array['content'] = self::the_content( $object_array['content'] );
}
return $object_array;
}

View file

@ -19,6 +19,7 @@ class Mention {
public static function init() {
\add_filter( 'the_content', array( self::class, 'the_content' ), 99, 1 );
\add_filter( 'comment_text', array( self::class, 'the_content' ), 10, 1 );
\add_filter( 'activitypub_extra_field_content', array( self::class, 'the_content' ), 10, 1 );
\add_filter( 'activitypub_extract_mentions', array( self::class, 'extract_mentions' ), 99, 2 );
\add_filter( 'activitypub_activity_object_array', array( self::class, 'filter_activity_object' ), 99 );
}
@ -32,11 +33,13 @@ class Mention {
* @return array the activity object array
*/
public static function filter_activity_object( $object_array ) {
if ( empty( $object_array['summary'] ) ) {
return $object_array;
if ( ! empty( $object_array['summary'] ) ) {
$object_array['summary'] = self::the_content( $object_array['summary'] );
}
$object_array['summary'] = self::the_content( $object_array['summary'] );
if ( ! empty( $object_array['content'] ) ) {
$object_array['content'] = self::the_content( $object_array['content'] );
}
return $object_array;
}
@ -145,6 +148,6 @@ class Mention {
$mentions[ $match ] = $link;
}
}
return $mentions;
return \array_unique( $mentions );
}
}

View file

@ -2,6 +2,7 @@
namespace Activitypub;
use function Activitypub\esc_hashtag;
use function Activitypub\generate_post_summary;
class Shortcodes {
/**
@ -108,81 +109,7 @@ class Shortcodes {
$excerpt_length = ACTIVITYPUB_EXCERPT_LENGTH;
}
$excerpt = \get_post_field( 'post_excerpt', $item );
if ( 'attachment' === $item->post_type ) {
// get title of attachment with fallback to alt text.
$content = wp_get_attachment_caption( $item->ID );
if ( empty( $content ) ) {
$content = get_post_meta( $item->ID, '_wp_attachment_image_alt', true );
}
} elseif ( '' === $excerpt ) {
$content = \get_post_field( 'post_content', $item );
// An empty string will make wp_trim_excerpt do stuff we do not want.
if ( '' !== $content ) {
$excerpt = \strip_shortcodes( $content );
/** This filter is documented in wp-includes/post-template.php */
$excerpt = \apply_filters( 'the_content', $excerpt );
$excerpt = \str_replace( ']]>', ']]>', $excerpt );
}
}
// Strip out any remaining tags.
$excerpt = \wp_strip_all_tags( $excerpt );
$excerpt_more = \apply_filters( 'activitypub_excerpt_more', ' […]' );
$excerpt_more_len = strlen( $excerpt_more );
// We now have a excerpt, but we need to check it's length, it may be longer than we want for two reasons:
//
// * The user has entered a manual excerpt which is longer that what we want.
// * No manual excerpt exists so we've used the content which might be longer than we want.
//
// Either way, let's trim it up if we need too. Also, don't forget to take into account the more indicator
// as part of the total length.
//
// Setup a variable to hold the current excerpts length.
$current_excerpt_length = strlen( $excerpt );
// Setup a variable to keep track of our target length.
$target_excerpt_length = $excerpt_length - $excerpt_more_len;
// Setup a variable to keep track of the current max length.
$current_excerpt_max = $target_excerpt_length;
// This is a loop since we can't calculate word break the string after 'the_excpert' filter has run (we would break
// all kinds of html tags), so we have to cut the excerpt down a bit at a time until we hit our target length.
while ( $current_excerpt_length > $target_excerpt_length && $current_excerpt_max > 0 ) {
// Trim the excerpt based on wordwrap() positioning.
// Note: we're using <br> as the linebreak just in case there are any newlines existing in the excerpt from the user.
// There won't be any <br> left after we've run wp_strip_all_tags() in the code above, so they're
// safe to use here. It won't be included in the final excerpt as the substr() will trim it off.
$excerpt = substr( $excerpt, 0, strpos( wordwrap( $excerpt, $current_excerpt_max, '<br>' ), '<br>' ) );
// If something went wrong, or we're in a language that wordwrap() doesn't understand,
// just chop it off and don't worry about breaking in the middle of a word.
if ( strlen( $excerpt ) > $excerpt_length - $excerpt_more_len ) {
$excerpt = substr( $excerpt, 0, $current_excerpt_max );
}
// Add in the more indicator.
$excerpt = $excerpt . $excerpt_more;
// Run it through the excerpt filter which will add some html tags back in.
$excerpt_filtered = apply_filters( 'the_excerpt', $excerpt );
// Now set the current excerpt length to this new filtered length.
$current_excerpt_length = strlen( $excerpt_filtered );
// Check to see if we're over the target length.
if ( $current_excerpt_length > $target_excerpt_length ) {
// If so, remove 20 characters from the current max and run the loop again.
$current_excerpt_max = $current_excerpt_max - 20;
}
}
$excerpt = generate_post_summary( $item, $excerpt_length );
return \apply_filters( 'the_excerpt', $excerpt );
}

View file

@ -37,6 +37,13 @@ class Extra_Fields {
return apply_filters( 'activitypub_get_actor_extra_fields', $fields, $user_id );
}
/**
* Transforms the Extra Fields (Cutom Post Types) to ActivityPub Actor-Attachments.
*
* @param \WP_Post[] $fields The extra fields.
*
* @return array ActivityPub attachments.
*/
public static function fields_to_attachments( $fields ) {
$attachments = array();
\add_filter(
@ -50,7 +57,6 @@ class Extra_Fields {
foreach ( $fields as $post ) {
$content = \get_the_content( null, false, $post );
$content = Link::the_content( $content, true );
$content = \do_blocks( $content );
$content = \wptexturize( $content );
$content = \wp_filter_content_tags( $content );
@ -58,6 +64,7 @@ class Extra_Fields {
$content = \preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $content );
$content = \strip_shortcodes( $content );
$content = \trim( \preg_replace( '/[\n\r\t]/', '', $content ) );
$content = \apply_filters( 'activitypub_extra_field_content', $content, $post );
$attachments[] = array(
'type' => 'PropertyValue',

View file

@ -1116,3 +1116,62 @@ function enrich_content_data( $content, $regex, $regex_callback ) {
return $content_with_links;
}
/**
* Generate a summary of a post.
*
* This function generates a summary of a post by extracting:
*
* 1. The post excerpt if it exists.
* 2. The first part of the post content if it contains the <!--more--> tag.
* 3. An excerpt of the post content if it is longer than the specified length.
*
* @param int|WP_Post $post The post ID or post object.
* @param integer $length The maximum length of the summary.
* Default is 500. It will ne ignored if the post excerpt
* and the content above the <!--more--> tag.
*
* @return string The generated post summary.
*/
function generate_post_summary( $post, $length = 500 ) {
$post = get_post( $post );
if ( ! $post ) {
return '';
}
$content = \sanitize_post_field( 'post_excerpt', $post->post_excerpt, $post->ID );
if ( $content ) {
return \apply_filters( 'the_excerpt', $content );
}
$content = \sanitize_post_field( 'post_content', $post->post_content, $post->ID );
$content_parts = \get_extended( $content );
$excerpt_more = \apply_filters( 'activitypub_excerpt_more', '[…]' );
$length = $length - strlen( $excerpt_more );
// Check for the <!--more--> tag.
if (
! empty( $content_parts['extended'] ) &&
! empty( $content_parts['main'] )
) {
$content = $content_parts['main'] . ' ' . $excerpt_more;
$length = null;
}
$content = \html_entity_decode( $content );
$content = \wp_strip_all_tags( $content );
$content = \trim( $content );
$content = \preg_replace( '/\R+/m', "\n\n", $content );
$content = \preg_replace( '/[\r\t]/', '', $content );
if ( $length && \strlen( $content ) > $length ) {
$content = \wordwrap( $content, $length, '</activitypub-summary>' );
$content = \explode( '</activitypub-summary>', $content, 2 );
$content = $content[0] . ' ' . $excerpt_more;
}
return \apply_filters( 'the_excerpt', $content );
}

View file

@ -10,6 +10,7 @@ use Activitypub\Collection\Users;
use function Activitypub\esc_hashtag;
use function Activitypub\is_single_user;
use function Activitypub\get_enclosures;
use function Activitypub\generate_post_summary;
use function Activitypub\get_rest_url_by_path;
use function Activitypub\site_supports_blocks;
@ -733,24 +734,7 @@ class Post extends Base {
return \__( '(This post is being modified)', 'activitypub' );
}
$content = \get_post_field( 'post_content', $this->wp_object->ID );
$content = \html_entity_decode( $content );
$content = \wp_strip_all_tags( $content );
$content = \trim( $content );
$content = \preg_replace( '/\R+/m', "\n\n", $content );
$content = \preg_replace( '/[\r\t]/', '', $content );
$excerpt_more = \apply_filters( 'activitypub_excerpt_more', '[...]' );
$length = 500;
$length = $length - strlen( $excerpt_more );
if ( \strlen( $content ) > $length ) {
$content = \wordwrap( $content, $length, '</activitypub-summary>' );
$content = \explode( '</activitypub-summary>', $content, 2 );
$content = $content[0];
}
return $content . ' ' . $excerpt_more;
return generate_post_summary( $this->wp_object );
}
/**
@ -849,7 +833,8 @@ class Post extends Base {
$template = "[ap_content]\n\n[ap_permalink type=\"html\"]\n\n[ap_hashtags]";
break;
default:
$template = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
// phpcs:ignore Universal.Operators.DisallowShortTernary.Found
$template = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT ) ?: ACTIVITYPUB_CUSTOM_POST_CONTENT;
break;
}
@ -868,7 +853,12 @@ class Post extends Base {
* @return array The list of @-Mentions.
*/
protected function get_mentions() {
return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_object->post_content, $this->wp_object );
return apply_filters(
'activitypub_extract_mentions',
array(),
$this->wp_object->post_content . ' ' . $this->wp_object->post_excerpt,
$this->wp_object
);
}
/**

View file

@ -89,7 +89,7 @@ class Test_Activitypub_Shortcodes extends WP_UnitTestCase {
$content = do_shortcode( $content );
wp_reset_postdata();
$this->assertEquals( "<p>Lorem ipsum [&hellip;]</p>\n", $content );
$this->assertEquals( "<p>Lorem ipsum dolor […]</p>\n", $content );
Shortcodes::unregister();
}
}