-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'add/outbox-collection' of https://github.com/Automattic…
…/wordpress-activitypub into add/outbox-collection
- Loading branch information
Showing
7 changed files
with
837 additions
and
186 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,306 @@ | ||
<?php | ||
/** | ||
* Outbox Controller file. | ||
* | ||
* @package Activitypub | ||
*/ | ||
|
||
namespace Activitypub\Rest; | ||
|
||
use Activitypub\Activity\Activity; | ||
use Activitypub\Collection\Actors; | ||
use Activitypub\Collection\Outbox; | ||
use Activitypub\Transformer\Factory; | ||
use function ActivityPub\get_rest_url_by_path; | ||
|
||
/** | ||
* ActivityPub Outbox Controller. | ||
* | ||
* @author Matthias Pfefferle | ||
* | ||
* @see https://www.w3.org/TR/activitypub/#outbox | ||
*/ | ||
class Outbox_Controller extends \WP_REST_Controller { | ||
/** | ||
* The namespace of this controller's route. | ||
* | ||
* @var string | ||
*/ | ||
protected $namespace = ACTIVITYPUB_REST_NAMESPACE; | ||
|
||
/** | ||
* The base of this controller's route. | ||
* | ||
* @var string | ||
*/ | ||
protected $rest_base = '(?:users|actors)/(?P<user_id>[\w\-\.]+)/outbox'; | ||
|
||
/** | ||
* Register routes. | ||
*/ | ||
public function register_routes() { | ||
\register_rest_route( | ||
$this->namespace, | ||
'/' . $this->rest_base, | ||
array( | ||
'args' => array( | ||
'user_id' => array( | ||
'description' => 'The ID of the user or actor.', | ||
'type' => 'string', | ||
'validate_callback' => array( $this, 'validate_user_id' ), | ||
), | ||
), | ||
array( | ||
'methods' => \WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_items' ), | ||
'permission_callback' => array( 'Activitypub\Rest\Server', 'verify_signature' ), | ||
'args' => array( | ||
'page' => array( | ||
'description' => 'Current page of the collection.', | ||
'type' => 'integer', | ||
'default' => 1, | ||
'minimum' => 1, | ||
), | ||
'per_page' => array( | ||
'description' => 'Maximum number of items to be returned in result set.', | ||
'type' => 'integer', | ||
'default' => 10, | ||
'minimum' => 1, | ||
'maximum' => 100, | ||
), | ||
), | ||
), | ||
'schema' => array( $this, 'get_collection_schema' ), | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* Validates the user_id parameter. | ||
* | ||
* @param mixed $user_id The user_id parameter. | ||
* @return bool|\WP_Error True if the user_id is valid, WP_Error otherwise. | ||
*/ | ||
public function validate_user_id( $user_id ) { | ||
$user = Actors::get_by_various( $user_id ); | ||
if ( \is_wp_error( $user ) ) { | ||
return $user; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Retrieves a collection of outbox items. | ||
* | ||
* @param \WP_REST_Request $request Full details about the request. | ||
* @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure. | ||
*/ | ||
public function get_items( $request ) { | ||
$user_id = $request->get_param( 'user_id' ); | ||
$page = $request->get_param( 'page' ); | ||
$user = Actors::get_by_various( $user_id ); | ||
|
||
/** | ||
* Action triggered prior to the ActivityPub profile being created and sent to the client. | ||
* | ||
* @param \WP_REST_Request $request The request object. | ||
*/ | ||
\do_action( 'activitypub_rest_outbox_pre', $request ); | ||
|
||
/** | ||
* Filters the list of activity types to include in the outbox. | ||
* | ||
* @param string[] $activity_types The list of activity types. | ||
*/ | ||
$activity_types = apply_filters( 'rest_activitypub_outbox_activity_types', array( 'Announce', 'Create', 'Like', 'Update' ) ); | ||
|
||
$args = array( | ||
'posts_per_page' => $request->get_param( 'per_page' ), | ||
'author' => $user_id > 0 ? $user_id : null, | ||
'paged' => $page, | ||
'post_type' => Outbox::POST_TYPE, | ||
'post_status' => 'draft', | ||
|
||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query | ||
'meta_query' => array( | ||
array( | ||
'key' => '_activitypub_activity_type', | ||
'value' => $activity_types, | ||
'compare' => 'IN', | ||
), | ||
array( | ||
'relation' => 'OR', | ||
array( | ||
'key' => 'activitypub_content_visibility', | ||
'compare' => 'NOT EXISTS', | ||
), | ||
array( | ||
'key' => 'activitypub_content_visibility', | ||
'value' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, | ||
), | ||
), | ||
), | ||
); | ||
|
||
if ( current_user_can( 'activitypub' ) ) { | ||
unset( $args['meta_query'] ); | ||
} | ||
|
||
/** | ||
* Filters WP_Query arguments when querying Outbox items via the REST API. | ||
* | ||
* Enables adding extra arguments or setting defaults for an outbox collection request. | ||
* | ||
* @param array $args Array of arguments for WP_Query. | ||
* @param \WP_REST_Request $request The REST API request. | ||
*/ | ||
$args = apply_filters( 'rest_activitypub_outbox_query', $args, $request ); | ||
|
||
$outbox_query = new \WP_Query(); | ||
$query_result = $outbox_query->query( $args ); | ||
|
||
$response = array( | ||
'@context' => array( 'https://www.w3.org/ns/activitystreams' ), | ||
'id' => get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) ), | ||
'generator' => 'https://wordpress.org/?v=' . \get_bloginfo( 'version' ), | ||
'actor' => $user->get_id(), | ||
'type' => 'OrderedCollectionPage', | ||
'partOf' => get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) ), | ||
'totalItems' => $outbox_query->found_posts, | ||
'orderedItems' => array(), | ||
); | ||
|
||
update_postmeta_cache( \wp_list_pluck( $query_result, 'ID' ) ); | ||
foreach ( $query_result as $outbox_item ) { | ||
$response['orderedItems'][] = $this->prepare_item_for_response( $outbox_item, $request ); | ||
} | ||
|
||
$max_pages = \ceil( $response['totalItems'] / $request->get_param( 'per_page' ) ); | ||
$response['first'] = \add_query_arg( 'page', 1, $response['partOf'] ); | ||
$response['last'] = \add_query_arg( 'page', $max_pages, $response['partOf'] ); | ||
|
||
if ( $max_pages > $page ) { | ||
$response['next'] = \add_query_arg( 'page', $page + 1, $response['partOf'] ); | ||
} | ||
|
||
if ( $page > 1 ) { | ||
$response['prev'] = \add_query_arg( 'page', $page - 1, $response['partOf'] ); | ||
} | ||
|
||
/** | ||
* Filter the ActivityPub outbox array. | ||
* | ||
* @param array $response The ActivityPub outbox array. | ||
* @param \WP_REST_Request $request The request object. | ||
*/ | ||
$response = \apply_filters( 'activitypub_rest_outbox_array', $response, $request ); | ||
|
||
/** | ||
* Action triggered after the ActivityPub profile has been created and sent to the client. | ||
* | ||
* @param \WP_REST_Request $request The request object. | ||
*/ | ||
\do_action( 'activitypub_outbox_post', $request ); | ||
|
||
$response = \rest_ensure_response( $response ); | ||
$response->header( 'Content-Type', 'application/activity+json; charset=' . \get_option( 'blog_charset' ) ); | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Prepares the item for the REST response. | ||
* | ||
* @param mixed $item WordPress representation of the item. | ||
* @param \WP_REST_Request $request Request object. | ||
* @return array Response object on success, or WP_Error object on failure. | ||
*/ | ||
public function prepare_item_for_response( $item, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable | ||
$type = \get_post_meta( $item->ID, '_activitypub_activity_type', true ); | ||
$transformer = Factory::get_transformer( $item->post_content ); | ||
$activity = $transformer->to_activity( $type ); | ||
|
||
return $activity->to_array( false ); | ||
} | ||
|
||
/** | ||
* Retrieves the outbox schema, conforming to JSON Schema. | ||
* | ||
* @return array Collection schema data. | ||
*/ | ||
public function get_collection_schema() { | ||
if ( $this->schema ) { | ||
return $this->add_additional_fields_schema( $this->schema ); | ||
} | ||
|
||
$schema = array( | ||
'$schema' => 'http://json-schema.org/draft-04/schema#', | ||
'title' => 'outbox', | ||
'type' => 'object', | ||
'properties' => array( | ||
'@context' => array( | ||
'description' => 'The JSON-LD context for the collection.', | ||
'type' => array( 'string', 'array', 'object' ), | ||
'required' => true, | ||
), | ||
'id' => array( | ||
'description' => 'The unique identifier for the collection.', | ||
'type' => 'string', | ||
'format' => 'uri', | ||
'required' => true, | ||
), | ||
'type' => array( | ||
'description' => 'The type of the collection.', | ||
'type' => 'string', | ||
'enum' => array( 'OrderedCollection', 'OrderedCollectionPage' ), | ||
'required' => true, | ||
), | ||
'actor' => array( | ||
'description' => 'The actor who owns this outbox.', | ||
'type' => 'string', | ||
'format' => 'uri', | ||
'required' => true, | ||
), | ||
'totalItems' => array( | ||
'description' => 'The total number of items in the collection.', | ||
'type' => 'integer', | ||
'minimum' => 0, | ||
'required' => true, | ||
), | ||
'orderedItems' => array( | ||
'description' => 'The items in the collection.', | ||
'type' => 'array', | ||
'items' => array( | ||
'type' => 'object', | ||
), | ||
'required' => true, | ||
), | ||
'first' => array( | ||
'description' => 'The first page of the collection.', | ||
'type' => 'string', | ||
'format' => 'uri', | ||
), | ||
'last' => array( | ||
'description' => 'The last page of the collection.', | ||
'type' => 'string', | ||
'format' => 'uri', | ||
), | ||
'next' => array( | ||
'description' => 'The next page of the collection.', | ||
'type' => 'string', | ||
'format' => 'uri', | ||
), | ||
'prev' => array( | ||
'description' => 'The previous page of the collection.', | ||
'type' => 'string', | ||
'format' => 'uri', | ||
), | ||
), | ||
); | ||
|
||
$this->schema = $schema; | ||
|
||
return $this->add_additional_fields_schema( $this->schema ); | ||
} | ||
} |
Oops, something went wrong.