diff --git a/assets/css/event-bridge-for-activitypub-admin.css b/assets/css/event-bridge-for-activitypub-admin.css index 6c62d22..3b22ddd 100644 --- a/assets/css/event-bridge-for-activitypub-admin.css +++ b/assets/css/event-bridge-for-activitypub-admin.css @@ -185,3 +185,12 @@ code.event-bridge-for-activitypub-settings-example-url { #event_bridge_for_activitypub_summary_type_custom-details > details { padding: 0.5em; } + +.event_bridge_for_activitypub-list { + list-style: disc; + padding-left: 22px; +} + +.event_bridge_for_activitypub-admin-table-container { + padding-left: 22px; +} diff --git a/composer.json b/composer.json index 01cb90e..4f88074 100644 --- a/composer.json +++ b/composer.json @@ -56,11 +56,12 @@ "@test-eventin", "@test-modern-events-calendar-lite", "@test-eventprime", - "@test-event-organiser" + "@test-event-organiser", + "@test-event-bridge-for-activitypub-event-sources" ], "test-debug": [ "@prepare-test", - "@test-event-bridge-for-activitypub-shortcodes" + "@test-event-bridge-for-activitypub-event-sources" ], "test-vs-event-list": "phpunit --filter=vs_event_list", "test-the-events-calendar": "phpunit --filter=the_events_calendar", @@ -72,6 +73,7 @@ "test-eventprime": "phpunit --filter=eventprime", "test-event-organiser": "phpunit --filter=event_organiser", "test-event-bridge-for-activitypub-shortcodes": "phpunit --filter=event_bridge_for_activitypub_shortcodes", + "test-event-bridge-for-activitypub-event-sources": "phpunit --filter=event_bridge_for_activitypub_event_sources", "test-all": "phpunit" } } diff --git a/event-bridge-for-activitypub.php b/event-bridge-for-activitypub.php index 4bb34cf..917124a 100644 --- a/event-bridge-for-activitypub.php +++ b/event-bridge-for-activitypub.php @@ -3,7 +3,7 @@ * Plugin Name: Event Bridge for ActivityPub * Description: Integrating popular event plugins with the ActivityPub plugin. * Plugin URI: https://event-federation.eu/ - * Version: 0.3.1.1 + * Version: 0.3.1.3 * Author: André Menrath * Author URI: https://graz.social/@linos * Text Domain: event-bridge-for-activitypub diff --git a/includes/class-settings.php b/includes/class-settings.php index c74dddc..30d3cf4 100644 --- a/includes/class-settings.php +++ b/includes/class-settings.php @@ -98,14 +98,15 @@ class Settings { 'event-bridge-for-activitypub-event-sources', 'event_bridge_for_activitypub_event_sources_active', array( - 'type' => 'boolean', - 'description' => \__( 'Whether the event sources feature is activated.', 'event-bridge-for-activitypub' ), - 'default' => 1, + 'type' => 'boolean', + 'show_in_rest' => true, + 'description' => \__( 'Whether the event sources feature is activated.', 'event-bridge-for-activitypub' ), + 'default' => 0, ) ); \register_setting( - 'event-bridge-for-activitypub', + 'event-bridge-for-activitypub-event-sources', 'event_bridge_for_activitypub_plugin_used_for_event_source_feature', array( 'type' => 'array', diff --git a/includes/class-setup.php b/includes/class-setup.php index 68f2f49..b381905 100644 --- a/includes/class-setup.php +++ b/includes/class-setup.php @@ -158,6 +158,25 @@ class Setup { return $active_event_plugins; } + /** + * Function that checks which event plugins support the event sources feature. + * + * @return array List of supported event plugins as keys from the SUPPORTED_EVENT_PLUGINS const. + */ + public static function detect_event_plugins_supporting_event_sources(): array { + $plugins_supporting_event_sources = array(); + + foreach ( self::EVENT_PLUGIN_CLASSES as $event_plugin_class ) { + if ( ! class_exists( $event_plugin_class ) || ! method_exists( $event_plugin_class, 'get_plugin_file' ) ) { + continue; + } + if ( call_user_func( array( $event_plugin_class, 'supports_event_sources' ) ) ) { + $plugins_supporting_event_sources[] = new $event_plugin_class(); + } + } + return $plugins_supporting_event_sources; + } + /** * Set up hooks for various purposes. * diff --git a/templates/admin-header.php b/templates/admin-header.php index 7456149..54d7ca2 100644 --- a/templates/admin-header.php +++ b/templates/admin-header.php @@ -3,6 +3,8 @@ * Template for the header and navigation of the admin pages. * * @package Event_Bridge_For_ActivityPub + * @since 1.0.0 + * @license AGPL-3.0-or-later */ // Exit if accessed directly. diff --git a/templates/event-sources.php b/templates/event-sources.php index cdfe441..8a03ffb 100644 --- a/templates/event-sources.php +++ b/templates/event-sources.php @@ -3,7 +3,7 @@ * Event Sources management page for the ActivityPub Event Bridge. * * @package Event_Bridge_For_ActivityPub - * @since 1.0.0 + * @since 1.0.0 * @license AGPL-3.0-or-later */ @@ -26,19 +26,103 @@ if ( ! current_user_can( 'manage_options' ) ) { return; } -$table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources(); +$event_plugins_supporting_event_sources = $args['supports_event_sources']; + +$selected_plugin = \get_option( 'event_bridge_for_activitypub_plugin_used_for_event_source_feature', '' ); +$event_sources_active = \get_option( 'event_bridge_for_activitypub_event_sources_active', false ); ?>
-
-

-

+

+ +
+ + + + + + + + + + + + + + +
+ + + + > +

+
+ + + +

+
+ +
+ +

+

+ '; + foreach ( $plugins_supporting_event_sources as $event_plugin ) { + echo '
  • ' . esc_attr( $event_plugin->get_plugin_name() ) . '
  • '; + } + echo ''; + return; + } + ?> +
    + '; + return; + } + ?> +
    - - - - +
    + +

    + + + + +
    +
    + + + prepare_items(); + $table->search_box( 'Search', 'search' ); + $table->display(); + ?> +
    +
    -
    -
    - - - prepare_items(); - $table->search_box( 'Search', 'search' ); - $table->display(); - ?> -
    -
    diff --git a/templates/settings.php b/templates/settings.php index 7ddfa95..842401d 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -6,6 +6,7 @@ * * @package Event_Bridge_For_ActivityPub * @since 1.0.0 + * @license AGPL-3.0-or-later * * @param array $args An array of arguments for the settings page. */ @@ -41,7 +42,7 @@ $current_category_mapping = \get_option( 'event_bridge_for_activitypub_ev
    - +

    diff --git a/templates/welcome.php b/templates/welcome.php index 503df4f..2dc3977 100644 --- a/templates/welcome.php +++ b/templates/welcome.php @@ -3,6 +3,8 @@ * Status page for the Event Bridge for ActivityPub. * * @package Event_Bridge_For_ActivityPub + * @since 1.0.0 + * @license AGPL-3.0-or-later */ // Exit if accessed directly. diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 643a8aa..e409173 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -96,11 +96,13 @@ function _manually_load_plugin() { if ( $plugin_file ) { _manually_load_event_plugin( $plugin_file ); + } elseif ( 'event_bridge_for_activitypub_event_sources' === $event_bridge_for_activitypub_integration_filter ) { + // For the Event Sources feature we currently only test with GatherPress. + _manually_load_event_plugin( 'gatherpress/gatherpress.php' ); } else { // For all other tests we mainly use the Events Calendar as a reference. _manually_load_event_plugin( 'the-events-calendar/the-events-calendar.php' ); _manually_load_event_plugin( 'very-simple-event-list/vsel.php' ); - } // Hot fix that allows using Events Manager within unit tests, because the em_init() is later not run as admin. diff --git a/tests/test-class-event-bridge-for-activitypub-event-sources.php b/tests/test-class-event-bridge-for-activitypub-event-sources.php new file mode 100644 index 0000000..212d1c4 --- /dev/null +++ b/tests/test-class-event-bridge-for-activitypub-event-sources.php @@ -0,0 +1,356 @@ + 'https://remote.example/@id', + 'type' => 'Follow', + 'actor' => 'https://remote.example/@test', + 'object' => 'https://local.example/@test', + ); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + $response = \rest_do_request( $request ); + + $this->assertEquals( 401, $response->get_status() ); + $this->assertEquals( 'activitypub_signature_verification', $response->get_data()['code'] ); + } + + /** + * Test missing attribute. + */ + public function test_missing_attribute() { + \add_filter( 'activitypub_defer_signature_verification', '__return_true' ); + + $json = array( + 'id' => 'https://remote.example/@id', + 'type' => 'Follow', + 'actor' => 'https://remote.example/@test', + ); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + $response = \rest_do_request( $request ); + + $this->assertEquals( 400, $response->get_status() ); + $this->assertEquals( 'rest_missing_callback_param', $response->get_data()['code'] ); + $this->assertEquals( 'object', $response->get_data()['data']['params'][0] ); + } + + /** + * Test follow request. + */ + public function test_follow_request() { + \add_filter( 'activitypub_defer_signature_verification', '__return_true' ); + + $json = array( + 'id' => 'https://remote.example/@id', + 'type' => 'Follow', + 'actor' => 'https://remote.example/@test', + 'object' => 'https://local.example/@test', + ); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + // Dispatch the request. + $response = \rest_do_request( $request ); + $this->assertEquals( 202, $response->get_status() ); + } + + /** + * Test follow request global inbox. + */ + public function test_follow_request_global_inbox() { + \add_filter( 'activitypub_defer_signature_verification', '__return_true' ); + + $json = array( + 'id' => 'https://remote.example/@id', + 'type' => 'Follow', + 'actor' => 'https://remote.example/@test', + 'object' => 'https://local.example/@test', + ); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + // Dispatch the request. + $response = \rest_do_request( $request ); + $this->assertEquals( 202, $response->get_status() ); + } + + /** + * Test create request with a remote actor. + */ + public function test_create_request() { + \add_filter( 'activitypub_defer_signature_verification', '__return_true' ); + + // Invalid request, because of an invalid object. + $json = array( + 'id' => 'https://remote.example/@id', + 'type' => 'Create', + 'actor' => 'https://remote.example/@test', + 'object' => 'https://local.example/@test', + ); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + // Dispatch the request. + $response = \rest_do_request( $request ); + $this->assertEquals( 400, $response->get_status() ); + $this->assertEquals( 'rest_invalid_param', $response->get_data()['code'] ); + + // Valid request, because of a valid object. + $json['object'] = array( + 'id' => 'https://remote.example/post/test', + 'type' => 'Note', + 'content' => 'Hello, World!', + 'inReplyTo' => 'https://local.example/post/test', + 'published' => '2020-01-01T00:00:00Z', + ); + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + // Dispatch the request. + $response = \rest_do_request( $request ); + $this->assertEquals( 202, $response->get_status() ); + } + + /** + * Test create request global inbox. + */ + public function test_create_request_global_inbox() { + \add_filter( 'activitypub_defer_signature_verification', '__return_true' ); + + // Invalid request, because of an invalid object. + $json = array( + 'id' => 'https://remote.example/@id', + 'type' => 'Create', + 'actor' => 'https://remote.example/@test', + 'object' => 'https://local.example/@test', + ); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + // Dispatch the request. + $response = \rest_do_request( $request ); + $this->assertEquals( 400, $response->get_status() ); + $this->assertEquals( 'rest_invalid_param', $response->get_data()['code'] ); + + // Valid request, because of a valid object. + $json['object'] = array( + 'id' => 'https://remote.example/post/test', + 'type' => 'Note', + 'content' => 'Hello, World!', + 'inReplyTo' => 'https://local.example/post/test', + 'published' => '2020-01-01T00:00:00Z', + ); + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + // Dispatch the request. + $response = \rest_do_request( $request ); + $this->assertEquals( 202, $response->get_status() ); + } + + /** + * Test update request. + */ + public function test_update_request() { + \add_filter( 'activitypub_defer_signature_verification', '__return_true' ); + + $json = array( + 'id' => 'https://remote.example/@id', + 'type' => 'Update', + 'actor' => 'https://remote.example/@test', + 'object' => array( + 'id' => 'https://remote.example/post/test', + 'type' => 'Note', + 'content' => 'Hello, World!', + 'inReplyTo' => 'https://local.example/post/test', + 'published' => '2020-01-01T00:00:00Z', + ), + ); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + // Dispatch the request. + $response = \rest_do_request( $request ); + $this->assertEquals( 202, $response->get_status() ); + } + + /** + * Test like request. + */ + public function test_like_request() { + \add_filter( 'activitypub_defer_signature_verification', '__return_true' ); + + $json = array( + 'id' => 'https://remote.example/@id', + 'type' => 'Like', + 'actor' => 'https://remote.example/@test', + 'object' => 'https://local.example/post/test', + ); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + // Dispatch the request. + $response = \rest_do_request( $request ); + $this->assertEquals( 202, $response->get_status() ); + } + + /** + * Test announce request. + */ + public function test_announce_request() { + \add_filter( 'activitypub_defer_signature_verification', '__return_true' ); + + $json = array( + 'id' => 'https://remote.example/@id', + 'type' => 'Announce', + 'actor' => 'https://remote.example/@test', + 'object' => 'https://local.example/post/test', + ); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' ); + $request->set_header( 'Content-Type', 'application/activity+json' ); + $request->set_body( \wp_json_encode( $json ) ); + + // Dispatch the request. + $response = \rest_do_request( $request ); + $this->assertEquals( 202, $response->get_status() ); + } + + /** + * Test whether an activity is public. + * + * @dataProvider the_data_provider + * + * @param array $data The data. + * @param bool $check The check. + */ + public function test_is_activity_public( $data, $check ) { + $this->assertEquals( $check, Activitypub\is_activity_public( $data ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function the_data_provider() { + return array( + array( + array( + 'cc' => array( + 'https://example.org/@test', + 'https://example.com/@test2', + ), + 'to' => 'https://www.w3.org/ns/activitystreams#Public', + 'object' => array(), + ), + true, + ), + array( + array( + 'cc' => array( + 'https://example.org/@test', + 'https://example.com/@test2', + ), + 'to' => array( + 'https://www.w3.org/ns/activitystreams#Public', + ), + 'object' => array(), + ), + true, + ), + array( + array( + 'cc' => array( + 'https://example.org/@test', + 'https://example.com/@test2', + ), + 'object' => array(), + ), + false, + ), + array( + array( + 'cc' => array( + 'https://example.org/@test', + 'https://example.com/@test2', + ), + 'object' => array( + 'to' => 'https://www.w3.org/ns/activitystreams#Public', + ), + ), + true, + ), + array( + array( + 'cc' => array( + 'https://example.org/@test', + 'https://example.com/@test2', + ), + 'object' => array( + 'to' => array( + 'https://www.w3.org/ns/activitystreams#Public', + ), + ), + ), + true, + ), + ); + } +}