'A-Za-z',
'\p{Latin}' => 'A-Za-z',
'\p{M}' => '',
'\p{N}' => '0-9',
)
) . '/i';
}
/**
* A callback for preg_replace to build the user links.
*
* @param array $result The preg_match results.
*
* @return string The final string.
*/
public static function replace_with_links( $result ) {
$post = Remote_Actors::fetch_by_acct( $result[0] );
if ( \is_wp_error( $post ) ) {
return $result[0];
}
$actor = Remote_Actors::get_actor( $post );
if ( \is_wp_error( $actor ) ) {
return $result[0];
}
$username = $actor->get_preferred_username() ?: $actor->get_name() ?: Sanitize::webfinger( $result[0] );
$url = object_to_uri( $actor->get_url() ?: $actor->get_id() );
return \sprintf( '@%2$s', \esc_url( $url ), \esc_html( $username ) );
}
/**
* Get the Inboxes for the mentioned Actors.
*
* @param array $mentioned The list of Actors that were mentioned.
*
* @return array The list of Inboxes.
*/
public static function get_inboxes( $mentioned ) {
$inboxes = array();
foreach ( $mentioned as $actor ) {
$inbox = self::get_inbox_by_mentioned_actor( $actor );
if ( ! \is_wp_error( $inbox ) && $inbox ) {
$inboxes[] = $inbox;
}
}
return $inboxes;
}
/**
* Get the inbox from the Remote-Profile of a mentioned Actor.
*
* @param string $actor The Actor URL.
*
* @return string|\WP_Error The Inbox-URL or WP_Error if not found.
*/
public static function get_inbox_by_mentioned_actor( $actor ) {
$metadata = get_remote_metadata_by_actor( $actor );
if ( \is_wp_error( $metadata ) ) {
return $metadata;
}
if ( isset( $metadata['endpoints']['sharedInbox'] ) ) {
return $metadata['endpoints']['sharedInbox'];
}
if ( \array_key_exists( 'inbox', $metadata ) ) {
return $metadata['inbox'];
}
return new \WP_Error( 'activitypub_no_inbox', \__( 'No "Inbox" found', 'activitypub' ), $metadata );
}
/**
* Extract the mentions from the post_content.
*
* @param array $mentions The already found mentions.
* @param string $post_content The post content.
*
* @return array The discovered mentions.
*/
public static function extract_mentions( $mentions, $post_content ) {
\preg_match_all( self::get_pattern( $post_content ), $post_content, $matches );
foreach ( $matches[0] ?? array() as $match ) {
$link = Webfinger::resolve( $match );
if ( ! \is_wp_error( $link ) ) {
$mentions[ $match ] = $link;
}
}
/*
* Clients and Mastodon write a mention as a link to the actor with a short label,
* `@user`, so the handle is not in the text.
*/
foreach ( self::extract_mention_links( $post_content ) as $href ) {
if ( \in_array( $href, $mentions, true ) ) {
continue;
}
$acct = Webfinger::uri_to_acct( $href );
if ( ! \is_wp_error( $acct ) ) {
$mentions[ \str_replace( 'acct:', '@', $acct ) ] = $href;
}
}
return \array_unique( $mentions );
}
/**
* The links a piece of content marks as mentions.
*
* A mention link marks itself with the `mention` class, the microformats convention Mastodon
* and the plugin share, or with `rel="mention"`.
*
* @since 9.4.0
*
* @param string $content The content.
*
* @return string[] The hrefs, in order of appearance, without duplicates.
*/
public static function extract_mention_links( $content ) {
$links = array();
$processor = new \WP_HTML_Tag_Processor( $content );
while ( $processor->next_tag( array( 'tag_name' => 'A' ) ) ) {
$rel = (string) $processor->get_attribute( 'rel' );
if ( ! $processor->has_class( 'mention' ) && ! \in_array( 'mention', \preg_split( '/\s+/', $rel ), true ) ) {
continue;
}
$href = $processor->get_attribute( 'href' );
if ( \is_string( $href ) && '' !== $href ) {
$links[] = $href;
}
}
return \array_values( \array_unique( $links ) );
}
}