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(); }