enhance settings page and use upstream test class as boilderplate
All checks were successful
PHP Code Checker / PHP Code Checker (pull_request) Successful in 56s
PHPUnit / PHPUnit – PHP 7.4 (pull_request) Successful in 1m11s
PHPUnit / PHPUnit – PHP 8.0 (pull_request) Successful in 1m8s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m6s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m8s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m7s
PHPUnit / PHPUnit – PHP 8.4 (pull_request) Successful in 1m5s

This commit is contained in:
André Menrath 2024-12-09 18:16:07 +01:00
parent 210bf9cc96
commit 17ca4ff800
11 changed files with 508 additions and 29 deletions

View file

@ -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;
}

View file

@ -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"
}
}

View file

@ -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

View file

@ -99,13 +99,14 @@ class Settings {
'event_bridge_for_activitypub_event_sources_active',
array(
'type' => 'boolean',
'show_in_rest' => true,
'description' => \__( 'Whether the event sources feature is activated.', 'event-bridge-for-activitypub' ),
'default' => 1,
'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',

View file

@ -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.
*

View file

@ -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.

View file

@ -26,17 +26,101 @@ 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 );
?>
<div class="event-bridge-for-activitypub-settings event-bridge-for-activitypub-settings-page hide-if-no-js">
<?php var_dump( $args['supports_event_sources'] ); ?>
<div class="box">
<h2> <?php esc_html_e( 'Federated event sources', 'event-bridge-for-activitypub' ); ?> </h2>
<p> <?php esc_html_e( 'Here you can add any Fediverse Account.', 'event-bridge-for-activitypub' ); ?> </p>
<h3><?php \esc_html_e( 'Configuration of the Event Sources feature', 'activitypub' ); ?></h3>
<?php
if ( count( $event_plugins_supporting_event_sources ) ) {
?>
<form method="post" action="options.php">
<?php
\settings_fields( 'event-bridge-for-activitypub-event-sources' );
?>
<table class="form-table">
<tbody>
<tr>
<th scope="row">
<label for="event_bridge_for_activitypub_event_sources_active"><?php \esc_html_e( 'Enable External Event Sources', 'event-bridge-for-activitypub' ); ?></label>
</th>
<td>
<input
type="checkbox"
name="event_bridge_for_activitypub_event_sources_active"
id="event_bridge_for_activitypub_event_sources_active"
aria-describedby="event-sources-description"
value="1"
<?php echo \checked( $event_sources_active ); ?>
>
<p id="event-sources-description"><?php esc_html_e( 'Activate this feature to allow your WordPress site to fetch events from external sources via ActivityPub. Once enabled, you can add any ActivityPub account as a source of events. These events will be cached on your site and seamlessly integrated into your existing event calendar, creating a unified view of events from both internal and external sources.', 'event-bridge-for-activitypub' ); ?></p>
</td>
</tr>
<?php
if ( $event_sources_active ) {
?>
<tr>
<th scope="row">
<label for="event_bridge_for_activitypub_plugin_used_for_event_source_feature"><?php \esc_html_e( 'Event Plugin', 'event-bridge-for-activitypub' ); ?></label>
</th>
<td>
<select
name="event_bridge_for_activitypub_plugin_used_for_event_source_feature"
id="event_bridge_for_activitypub_plugin_used_for_event_source_feature"
value="gatherpress"
aria-describedby="event-sources-used-plugin-description"
>
<?php
foreach ( $event_plugins_supporting_event_sources as $event_plugin ) {
echo '<option value="' . esc_attr( $event_plugin ) . '" ' . selected( $selected_plugin, $event_plugin, true ) . '>' . esc_attr( $event_plugin ) . '</option>';
}
?>
</select>
<p id="event-sources-used-plugin-description"><?php esc_html_e( 'In case you have multiple event plugins installed you might choose which event plugin is utilized.', 'event-bridge-for-activitypub' ); ?></p>
</td>
<tr>
<?php
}
?>
<tbody>
</table>
<?php
\submit_button();
?>
</form>
<?php
} else {
?>
<p><?php esc_html_e( 'You do not have an Event Plugin installed that supports this feature', 'event-bridge-for-activitypub' ); ?></p>
<p><?php esc_html_e( 'The following Event Plugins are supported:', 'event-bridge-for-activitypub' ); ?></p>
<?php
$plugins_supporting_event_sources = \Event_Bridge_For_ActivityPub\Setup::detect_event_plugins_supporting_event_sources();
echo '<ul class="event_bridge_for_activitypub-list">';
foreach ( $plugins_supporting_event_sources as $event_plugin ) {
echo '<li>' . esc_attr( $event_plugin->get_plugin_name() ) . '</li>';
}
echo '</ul>';
return;
}
?>
</div>
<?php
if ( ! $event_sources_active ) {
echo '</div>';
return;
}
?>
</div>
<div class="wrap event_bridge_for_activitypub-admin-table-container">
<h2> <?php esc_html_e( 'List of Event Sources', 'event-bridge-for-activitypub' ); ?> </h2>
<!-- Button that triggers ThickBox -->
<a href="#TB_inline?width=600&height=400&inlineId=Event_Bridge_For_ActivityPub_add_new_source" class="thickbox button button-primary">
<a href="#TB_inline?width=600&height=400&inlineId=Event_Bridge_For_ActivityPub_add_new_source" class="thickbox page-title-action">
<?php esc_html_e( 'Add Event Source', 'event-bridge-for-activitypub' ); ?>
</a>
@ -50,16 +134,17 @@ $table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources();
<?php \submit_button( __( 'Add Event Source', 'event-bridge-for-activitypub' ) ); ?>
</form>
</div>
</div>
<div class="wrap activitypub-followers-page">
<div class="wrap activitypub-followers-page">
<form method="get">
<input type="hidden" name="page" value="event-bridge-for-activitypub" />
<input type="hidden" name="tab" value="event-sources" />
<?php
$table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources();
$table->prepare_items();
$table->search_box( 'Search', 'search' );
$table->display();
?>
</form>
</div>
</div>

View file

@ -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
<div class="event-bridge-for-activitypub-settings event-bridge-for-activitypub-settings-page hide-if-no-js">
<form method="post" action="options.php">
<?php \settings_fields( 'event-bridge-for-activitypub' ); ?>
<?php \settings_fields( 'event-bridge-for-activitypub-event-sources' ); ?>
<div class="box">
<h2> <?php esc_html_e( 'Event Summary Text', 'event-bridge-for-activitypub' ); ?> </h2>
<p><?php esc_html_e( 'Many Fediverse applications (e.g., Mastodon) don\'t fully support events, instead they will show a summary text along with the events title and the URL to your Website.', 'event-bridge-for-activitypub' ); ?></p>

View file

@ -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.

View file

@ -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.

View file

@ -0,0 +1,356 @@
<?php
/**
* Test file for the Event Sources feature.
*
* @package Event_Bridge_For_ActivityPub
* @since 1.0.0
* @license AGPL-3.0-or-later
*/
/**
* Test class for Activitypub Rest Inbox.
*
* @coversDefaultClass \Activitypub\Rest\Inbox
*/
class Test_Event_Bridge_For_ActivityPub_Event_Sources extends WP_UnitTestCase {
/**
* Set up the test.
*/
public function set_up() {
\add_option( 'permalink_structure', '/%postname%/' );
\Activitypub\Rest\Server::add_hooks();
}
/**
* Tear down the test.
*/
public function tear_down() {
\delete_option( 'permalink_structure' );
\add_filter( 'activitypub_defer_signature_verification', '__return_false' );
}
/**
* Test the inbox signature issue.
*/
public function test_inbox_signature_issue() {
\add_filter( 'activitypub_defer_signature_verification', '__return_false' );
$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 ) );
$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,
),
);
}
}