Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interaction: Use Rest Controller structure #1149

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion activitypub.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ function rest_init() {
Rest\Comment::init();
Rest\Server::init();
Rest\Collection::init();
Rest\Interaction::init();
Rest\Post::init();
( new Rest\Interaction_Controller() )->register_routes();
( new Rest\Webfinger_Controller() )->register_routes();

// Load NodeInfo endpoints only if blog is public.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,50 @@
<?php
/**
* ActivityPub Interaction REST-Class file.
* ActivityPub Interaction Controller file.
*
* @package Activitypub
*/

namespace Activitypub\Rest;

use WP_REST_Response;
use Activitypub\Http;

/**
* Interaction class.
* Interaction Controller.
*/
class Interaction {
class Interaction_Controller extends \WP_REST_Controller {
/**
* Initialize the class, registering WordPress hooks.
* The namespace of this controller's route.
*
* @var string
*/
public static function init() {
self::register_routes();
}
protected $namespace = ACTIVITYPUB_REST_NAMESPACE;

/**
* The base of this controller's route.
*
* @var string
*/
protected $rest_base = 'interactions';

/**
* Register routes
* Register routes.
*/
public static function register_routes() {
public function register_routes() {
\register_rest_route(
ACTIVITYPUB_REST_NAMESPACE,
'/interactions',
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( self::class, 'get' ),
'callback' => array( $this, 'get_item' ),
'permission_callback' => '__return_true',
'args' => array(
'uri' => array(
'type' => 'string',
'required' => true,
'sanitize_callback' => 'esc_url',
'description' => 'The URI of the object to interact with.',
'type' => 'string',
'format' => 'uri',
'required' => true,
),
),
),
Expand All @@ -46,28 +53,19 @@ public static function register_routes() {
}

/**
* Handle GET request.
* Retrieves the interaction URL for a given URI.
*
* @param \WP_REST_Request $request The request object.
*
* @return WP_REST_Response Redirect to the editor or die.
* @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
*/
public static function get( $request ) {
public function get_item( $request ) {
$uri = $request->get_param( 'uri' );
$redirect_url = null;
$object = Http::get_remote_object( $uri );

if (
\is_wp_error( $object ) ||
! isset( $object['type'] )
) {
\wp_die(
\esc_html__(
'The URL is not supported!',
'activitypub'
),
400
);
if ( \is_wp_error( $object ) || ! isset( $object['type'] ) ) {
return new \WP_Error( 'activitypub_invalid_object', \esc_html__( 'The URL is not supported!', 'activitypub' ), array( 'status' => 400 ) );
}

if ( ! empty( $object['url'] ) ) {
Expand Down Expand Up @@ -104,31 +102,22 @@ public static function get( $request ) {
}

/**
* Filter the redirect URL.
* Filters the redirect URL.
*
* This filter runs after the type-specific filters and allows for final modifications
* to the interaction URL regardless of the object type.
*
* @param string $redirect_url The URL to redirect to.
* @param string $uri The URI of the object.
* @param array $object The object.
* @param array $object The object being interacted with.
*/
$redirect_url = \apply_filters( 'activitypub_interactions_url', $redirect_url, $uri, $object );

// Check if hook is implemented.
if ( ! $redirect_url ) {
\wp_die(
esc_html__(
'This Interaction type is not supported yet!',
'activitypub'
),
400
);
return new \WP_Error( 'activitypub_interaction_not_supported', \esc_html__( 'This Interaction type is not supported yet!', 'activitypub' ), array( 'status' => 400 ) );
}

return new WP_REST_Response(
null,
302,
array(
'Location' => \esc_url( $redirect_url ),
)
);
return new \WP_REST_Response( null, 302, array( 'Location' => \esc_url( $redirect_url ) ) );
}
}
214 changes: 214 additions & 0 deletions tests/includes/rest/class-test-interaction-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<?php
/**
* Interaction REST API endpoint test file.
*
* @package Activitypub
*/

namespace Activitypub\Tests\Rest;

/**
* Tests for Interaction REST API endpoint.
*
* @coversDefaultClass \Activitypub\Rest\Interaction_Controller
*/
class Test_Interaction_Controller extends \Activitypub\Tests\Test_REST_Controller_Testcase {

/**
* Tear down.
*/
public function tear_down() {
\remove_all_filters( 'activitypub_interactions_follow_url' );
\remove_all_filters( 'activitypub_interactions_reply_url' );

parent::tear_down();
}

/**
* Test route registration.
*
* @covers ::register_routes
*/
public function test_register_routes() {
$routes = rest_get_server()->get_routes();
$this->assertArrayHasKey( '/' . ACTIVITYPUB_REST_NAMESPACE . '/interactions', $routes );
}

/**
* Test get_item with invalid URI.
*
* @covers ::get_item
*/
public function test_get_item_invalid_uri() {
$request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/interactions' );
$request->set_param( 'uri', 'invalid-uri' );
$response = rest_get_server()->dispatch( $request );

$this->assertEquals( 400, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'activitypub_invalid_object', $data['code'] );
}

/**
* Test get_item with Note object type.
*
* @covers ::get_item
*/
public function test_get_item() {
\add_filter(
'pre_http_request',
function () {
return array(
'response' => array( 'code' => 200 ),
'body' => wp_json_encode(
array(
'type' => 'Note',
'url' => 'https://example.org/note',
)
),
);
}
);

$request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/interactions' );
$request->set_param( 'uri', 'https://example.org/note' );
$response = rest_get_server()->dispatch( $request );

$this->assertEquals( 302, $response->get_status() );
$this->assertArrayHasKey( 'Location', $response->get_headers() );
$this->assertStringContainsString( 'post-new.php?in_reply_to=', $response->get_headers()['Location'] );
}

/**
* Test get_item with custom follow URL filter.
*
* @covers ::get_item
*/
public function test_get_item_custom_follow_url() {
\add_filter(
'pre_http_request',
function () {
return array(
'response' => array( 'code' => 200 ),
'body' => wp_json_encode(
array(
'type' => 'Person',
'url' => 'https://example.org/person',
)
),
);
}
);

\add_filter( 'activitypub_interactions_follow_url', array( $this, 'follow_or_reply_url' ) );

$request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/interactions' );
$request->set_param( 'uri', 'https://example.org/person' );
$response = rest_get_server()->dispatch( $request );

$this->assertEquals( 302, $response->get_status() );
$this->assertArrayHasKey( 'Location', $response->get_headers() );
$this->assertEquals( 'https://custom-follow-or-reply-url.com', $response->get_headers()['Location'] );
}

/**
* Test get_item with custom reply URL filter.
*
* @covers ::get_item
*/
public function test_get_item_custom_reply_url() {
\add_filter(
'pre_http_request',
function () {
return array(
'response' => array( 'code' => 200 ),
'body' => wp_json_encode(
array(
'type' => 'Note',
'url' => 'https://example.org/note',
)
),
);
}
);

\add_filter( 'activitypub_interactions_reply_url', array( $this, 'follow_or_reply_url' ) );

$request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/interactions' );
$request->set_param( 'uri', 'https://example.org/note' );
$response = rest_get_server()->dispatch( $request );

$this->assertEquals( 302, $response->get_status() );
$this->assertArrayHasKey( 'Location', $response->get_headers() );
$this->assertEquals( 'https://custom-follow-or-reply-url.com', $response->get_headers()['Location'] );
}

/**
* Test get_item with WP_Error response from get_remote_object.
*
* @covers ::get_item
*/
public function test_get_item_wp_error() {
\add_filter(
'pre_http_request',
function () {
return new \WP_Error( 'http_request_failed', 'Connection failed.' );
}
);

$request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/interactions' );
$request->set_param( 'uri', 'https://example.org/person' );
$response = rest_get_server()->dispatch( $request );

$this->assertEquals( 400, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'activitypub_invalid_object', $data['code'] );
$this->assertEquals( 'The URL is not supported!', $data['message'] );
}

/**
* Test get_item with invalid object without type.
*
* @covers ::get_item
*/
public function test_get_item_invalid_object() {
\add_filter(
'pre_http_request',
function () {
return array(
'response' => array( 'code' => 200 ),
'body' => wp_json_encode(
array(
'url' => 'https://example.org/invalid',
)
),
);
}
);

$request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/interactions' );
$request->set_param( 'uri', 'https://example.org/invalid' );
$response = rest_get_server()->dispatch( $request );

$this->assertEquals( 400, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'activitypub_invalid_object', $data['code'] );
$this->assertEquals( 'The URL is not supported!', $data['message'] );
}

/**
* Test get_item_schema method.
*
* @doesNotPerformAssertions
*/
public function test_get_item_schema() {
// Controller does not implement get_item_schema().
}

/**
* Returns a valid follow URL.
*/
public function follow_or_reply_url() {
return 'https://custom-follow-or-reply-url.com';
}
}
Loading