updated plugin ActivityPub version 8.3.0

This commit is contained in:
2026-06-03 21:28:46 +00:00
committed by Gitium
parent a4b78ec277
commit 6fe182458a
340 changed files with 43232 additions and 7568 deletions

View File

@ -0,0 +1,397 @@
<?php
/**
* User functions.
*
* Functions for working with users and actors in ActivityPub context.
*
* @package Activitypub
*/
namespace Activitypub;
use Activitypub\Collection\Actors;
use Activitypub\Collection\Followers;
/**
* Returns a users WebFinger "resource".
*
* @deprecated 7.1.0 Use {@see \Activitypub\Webfinger::get_user_resource} instead.
*
* @param int $user_id The user ID.
*
* @return string The User resource.
*/
function get_webfinger_resource( $user_id ) {
\_deprecated_function( __FUNCTION__, '7.1.0', 'Activitypub\Webfinger::get_user_resource' );
return Webfinger::get_user_resource( $user_id );
}
/**
* Returns the followers of a given user.
*
* @param int $user_id The user ID.
*
* @return array The followers.
*/
function get_followers( $user_id ) {
return Followers::get_many( $user_id );
}
/**
* Count the number of followers for a given user.
*
* @param int $user_id The user ID.
*
* @return int The number of followers.
*/
function count_followers( $user_id ) {
return Followers::count( $user_id );
}
/**
* Examine a url and try to determine the author ID it represents.
*
* Checks are supposedly from the hosted site blog.
*
* @param string $url Permalink to check.
*
* @return int|null User ID, or null on failure.
*/
function url_to_authorid( $url ) {
global $wp_rewrite;
// Check if url has the same host.
$request_host = \wp_parse_url( $url, \PHP_URL_HOST );
if ( \wp_parse_url( \home_url(), \PHP_URL_HOST ) !== $request_host && get_option( 'activitypub_old_host' ) !== $request_host ) {
return null;
}
// First, check to see if there is an 'author=N' to match against.
if ( \preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) {
return \absint( $values[1] );
}
// Check to see if we are using rewrite rules.
$rewrite = $wp_rewrite->wp_rewrite_rules();
// Not using rewrite rules, and 'author=N' method failed, so we're out of options.
if ( empty( $rewrite ) ) {
return null;
}
// Generate rewrite rule for the author url.
$author_rewrite = $wp_rewrite->get_author_permastruct();
$author_regexp = \str_replace( '%author%', '', $author_rewrite );
// Match the rewrite rule with the passed url.
if ( \preg_match( '/https?:\/\/(.+)' . \preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) {
$user = \get_user_by( 'slug', $match[2] );
if ( $user ) {
return $user->ID;
}
}
return null;
}
/**
* This function checks if a user is enabled for ActivityPub.
*
* @param int|string $user_id The user ID.
*
* @return boolean True if the user is enabled, false otherwise.
*/
function user_can_activitypub( $user_id ) {
if ( ! is_numeric( $user_id ) ) {
return false;
}
switch ( $user_id ) {
case Actors::APPLICATION_USER_ID:
$enabled = true; // Application user is always enabled.
break;
case Actors::BLOG_USER_ID:
$enabled = ! is_user_type_disabled( 'blog' );
break;
default:
if ( ! \get_user_by( 'id', $user_id ) ) {
$enabled = false;
break;
}
if ( is_user_type_disabled( 'user' ) ) {
$enabled = false;
break;
}
$enabled = \user_can( $user_id, 'activitypub' );
}
/**
* Allow plugins to enable/disable users for ActivityPub.
*
* @param boolean $enabled True if the user is enabled, false otherwise.
* @param int $user_id The user ID.
*/
return apply_filters( 'activitypub_user_can_activitypub', $enabled, $user_id );
}
/**
* Whether the current user is allowed to act on behalf of the blog actor.
*
* The blog actor is virtual (no `wp_users` row), so ownership and authoring
* checks against `BLOG_USER_ID = 0` cannot rely on identity equality. This
* helper centralizes the "can the current user post / read as the blog?"
* decision: administrators by default, filterable for integrations.
*
* @since 8.3.0
*
* @return bool True if the current user can act as the blog actor.
*/
function user_can_act_as_blog() {
/**
* Filters whether the current user is allowed to act as the blog actor.
*
* Defaults to true for users with the `manage_options` capability (administrators).
* Filter to broaden the allow-list, for example to editors on multi-author sites.
*
* Security note: returning a static `true` (e.g. via `__return_true`) grants
* EVERY authenticated user the right to post as, read private outbox items of,
* and view stats for the blog actor. Always inspect the current user inside
* the callback (`current_user_can()`, role, allowlist) before returning `true`.
*
* @since 8.3.0
*
* @param bool $can_act_as_blog Whether the current user can act as the blog actor.
*/
return (bool) \apply_filters( 'activitypub_user_can_act_as_blog', \current_user_can( 'manage_options' ) );
}
/**
* Checks if a User-Type is disabled for ActivityPub.
*
* This function is used to check if the 'blog' or 'user'
* type is disabled for ActivityPub.
*
* @param string $type User type. 'blog' or 'user'.
*
* @return boolean True if the user type is disabled, false otherwise.
*/
function is_user_type_disabled( $type ) {
switch ( $type ) {
case 'blog':
if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) ) {
if ( ACTIVITYPUB_SINGLE_USER_MODE ) {
$disabled = false;
break;
}
}
if ( \defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) ) {
$disabled = ACTIVITYPUB_DISABLE_BLOG_USER;
break;
}
if ( ACTIVITYPUB_ACTOR_MODE === \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) {
$disabled = true;
break;
}
$disabled = false;
break;
case 'user':
if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) ) {
if ( ACTIVITYPUB_SINGLE_USER_MODE ) {
$disabled = true;
break;
}
}
if ( \defined( 'ACTIVITYPUB_DISABLE_USER' ) ) {
$disabled = ACTIVITYPUB_DISABLE_USER;
break;
}
if ( ACTIVITYPUB_BLOG_MODE === \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) {
$disabled = true;
break;
}
$disabled = false;
break;
default:
// Treat unknown user types as disabled to ensure a consistent boolean return value.
$disabled = true;
break;
}
/**
* Allow plugins to disable user types for ActivityPub.
*
* @param boolean $disabled True if the user type is disabled, false otherwise.
* @param string $type The User-Type.
*/
return apply_filters( 'activitypub_is_user_type_disabled', $disabled, $type );
}
/**
* Check if the blog is in single-user mode.
*
* @return boolean True if the blog is in single-user mode, false otherwise.
*/
function is_single_user() {
if (
false === is_user_type_disabled( 'blog' ) &&
true === is_user_type_disabled( 'user' )
) {
return true;
}
return false;
}
/**
* Get active users based on a given duration.
*
* Counts users who published posts (of any ActivityPub-enabled post type)
* or approved comments within the given time period.
*
* @param int $duration Optional. The duration to check in month(s). Default 1.
*
* @return int The number of active users.
*/
function get_active_users( $duration = 1 ) {
$duration = \intval( $duration );
$transient_key = \sprintf( 'monthly_active_users_%d', $duration );
$count = \get_transient( $transient_key );
if ( false === $count ) {
global $wpdb;
$post_types = \get_post_types_by_support( 'activitypub' );
$post_authors = array();
if ( ! empty( $post_types ) ) {
$placeholders = \implode( ', ', \array_fill( 0, \count( $post_types ), '%s' ) );
// Get distinct user IDs who published posts of AP-enabled post types.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$post_authors = $wpdb->get_col(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT DISTINCT post_author FROM {$wpdb->posts} WHERE post_type IN ( {$placeholders} ) AND post_status = 'publish' AND post_date >= DATE_SUB( NOW(), INTERVAL %d MONTH )",
\array_merge( $post_types, array( $duration ) )
)
);
}
// Get distinct user IDs who made approved comments.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$comment_authors = $wpdb->get_col(
$wpdb->prepare(
"SELECT DISTINCT user_id FROM {$wpdb->comments} WHERE comment_approved = '1' AND user_id != 0 AND comment_date >= DATE_SUB( NOW(), INTERVAL %d MONTH )",
$duration
)
);
// Deduplicate and filter out anonymous (0) entries.
$active_ids = \array_unique( \array_filter( \array_map( 'absint', \array_merge( $post_authors, $comment_authors ) ) ) );
if ( empty( $active_ids ) ) {
$count = 0;
} else {
// Count only users who have the activitypub capability.
$user_query = new \WP_User_Query(
array(
'capability__in' => array( 'activitypub' ),
'include' => $active_ids,
'number' => 1, // Minimize memory; get_total() still returns full count.
)
);
$count = $user_query->get_total();
}
\set_transient( $transient_key, $count, DAY_IN_SECONDS );
}
// If 0 authors were active.
if ( 0 === (int) $count ) {
return 0;
}
// If single user mode.
if ( is_single_user() ) {
return 1;
}
// If blog user is disabled.
if ( ! user_can_activitypub( Actors::BLOG_USER_ID ) ) {
$active = (int) $count;
} else {
// Also count blog user.
$active = (int) $count + 1;
}
// Ensure active users doesn't exceed total users.
return \min( $active, get_total_users() );
}
/**
* Get the total number of users.
*
* @return int The total number of users.
*/
function get_total_users() {
// If single user mode.
if ( is_single_user() ) {
return 1;
}
$user_query = new \WP_User_Query(
array(
'capability__in' => array( 'activitypub' ),
'number' => 1,
)
);
$users = $user_query->get_total();
// If blog user is disabled.
if ( ! user_can_activitypub( Actors::BLOG_USER_ID ) ) {
return (int) $users;
}
return (int) $users + 1;
}
/**
* Get the ActivityPub ID of a User by the WordPress User ID.
*
* Fall back to blog user if in blog mode or if user is not found.
*
* @param int $id The WordPress User ID.
*
* @return string|false The ActivityPub ID (a URL) of the User or false if not found.
*/
function get_user_id( $id ) {
$mode = \get_option( 'activitypub_actor_mode', 'default' );
if ( ACTIVITYPUB_BLOG_MODE === $mode ) {
$user = Actors::get_by_id( Actors::BLOG_USER_ID );
} else {
$user = Actors::get_by_id( $id );
if ( \is_wp_error( $user ) ) {
$user = Actors::get_by_id( Actors::BLOG_USER_ID );
}
}
if ( \is_wp_error( $user ) ) {
return false;
}
return $user->get_id();
}