From 633a5332d13cdab53a0a97dcaaa1228edd8c4202 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Menrath?=
Date: Mon, 23 Dec 2024 21:23:03 +0100
Subject: [PATCH] Add transmogrifier and tests for vs event list
---
composer.json | 2 +-
.../collection/class-event-sources.php | 1 -
.../activitypub/model/class-event-source.php | 7 +
.../transmogrifier/class-gatherpress.php | 3 +-
.../transmogrifier/class-vs-event-list.php | 196 ++++++++----------
includes/class-event-sources.php | 3 +
includes/class-settings.php | 2 +-
includes/integrations/class-vs-event-list.php | 45 +++-
templates/settings.php | 2 +-
templates/welcome.php | 3 +-
tests/bootstrap.php | 6 +
...-list.php => class-test-vs-event-list.php} | 0
.../transmogrifier/class-test-gatherpress.php | 2 +-
.../class-test-the-events-calendar.php | 4 +-
.../class-test-vs-event-list.php | 127 ++++++++++++
.../integrations/class-test-vs-event-list.php | 171 +++++++++++++++
16 files changed, 455 insertions(+), 119 deletions(-)
rename tests/includes/activitypub/transformer/{class-test-plugin-vs-event-list.php => class-test-vs-event-list.php} (100%)
create mode 100644 tests/includes/activitypub/transmogrifier/class-test-vs-event-list.php
create mode 100644 tests/includes/integrations/class-test-vs-event-list.php
diff --git a/composer.json b/composer.json
index 06122c9..2c96db8 100644
--- a/composer.json
+++ b/composer.json
@@ -61,7 +61,7 @@
],
"test-debug": [
"@prepare-test",
- "@test-gatherpress"
+ "@test-vs-event-list"
],
"test-vs-event-list": "phpunit --filter=vs_event_list",
"test-the-events-calendar": "phpunit --filter=the_events_calendar",
diff --git a/includes/activitypub/collection/class-event-sources.php b/includes/activitypub/collection/class-event-sources.php
index 3dc412e..208fb81 100644
--- a/includes/activitypub/collection/class-event-sources.php
+++ b/includes/activitypub/collection/class-event-sources.php
@@ -53,7 +53,6 @@ class Event_Sources {
*/
public static function init() {
self::register_post_type();
- \add_filter( 'allowed_redirect_hosts', array( self::class, 'add_event_sources_hosts_to_allowed_redirect_hosts' ) );
\add_action( 'event_bridge_for_activitypub_follow', array( self::class, 'activitypub_follow_actor' ), 10, 1 );
\add_action( 'event_bridge_for_activitypub_unfollow', array( self::class, 'activitypub_unfollow_actor' ), 10, 1 );
}
diff --git a/includes/activitypub/model/class-event-source.php b/includes/activitypub/model/class-event-source.php
index df9d882..7344be9 100644
--- a/includes/activitypub/model/class-event-source.php
+++ b/includes/activitypub/model/class-event-source.php
@@ -54,6 +54,13 @@ class Event_Source extends Actor {
return $icon;
}
+ /**
+ * Return the Post-IDs of all events cached by this event source.
+ */
+ public static function get_cached_events(): array {
+ return array();
+ }
+
/**
* Get the WordPress post which stores the Event Source by the ActivityPub actor id of the event source.
*
diff --git a/includes/activitypub/transmogrifier/class-gatherpress.php b/includes/activitypub/transmogrifier/class-gatherpress.php
index 7567373..7da7305 100644
--- a/includes/activitypub/transmogrifier/class-gatherpress.php
+++ b/includes/activitypub/transmogrifier/class-gatherpress.php
@@ -12,6 +12,7 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
use DateTime;
+use Event_Bridge_For_ActivityPub\Integrations\GatherPress as IntegrationsGatherPress;
use function Activitypub\sanitize_url;
@@ -51,7 +52,7 @@ class GatherPress extends Base {
// Add the tags as terms to the post.
if ( ! empty( $tag_names ) ) {
- wp_set_object_terms( $post_id, $tag_names, 'gatherpress_topic', true );
+ wp_set_object_terms( $post_id, $tag_names, IntegrationsGatherPress::get_event_category_taxonomy(), true );
}
return true;
diff --git a/includes/activitypub/transmogrifier/class-vs-event-list.php b/includes/activitypub/transmogrifier/class-vs-event-list.php
index f1731d5..e34c2e1 100644
--- a/includes/activitypub/transmogrifier/class-vs-event-list.php
+++ b/includes/activitypub/transmogrifier/class-vs-event-list.php
@@ -12,6 +12,8 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
+use Event_Bridge_For_ActivityPub\Integrations\VS_Event_List as IntegrationsVS_Event_List;
+
use function Activitypub\sanitize_url;
// Exit if accessed directly.
@@ -27,94 +29,67 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
*/
class VS_Event_List extends Base {
/**
- * Get a list of Post IDs of events that have ended.
+ * Extract location and address as string.
*
- * @param int $cache_retention_period Additional time buffer in seconds.
- * @return ?array
+ * @param ?array $location The ActivitySTreams location as an associative array.
+ * @return string The location and address formatted as a single string.
*/
- public static function get_past_events( $cache_retention_period = 0 ): ?array {
- unset( $cache_retention_period );
+ private function get_location_as_string( $location ): string {
+ $location_string = '';
- $results = array();
+ // Return empty string when location is not an associative array.
+ if ( is_null( $location ) || ! is_array( $location ) ) {
+ return $location_string;
+ }
- return $results;
+ if ( ! isset( $location['type'] ) || 'Place' !== $location['type'] ) {
+ return $location_string;
+ }
+
+ // Add name of the location.
+ if ( isset( $location['name'] ) ) {
+ $location_string .= $location['name'];
+ }
+
+ // Add delimiter between name and address if both are set.
+ if ( isset( $location['name'] ) && isset( $location['address'] ) ) {
+ $location_string .= ' – ';
+ }
+
+ // Add address.
+ if ( isset( $location['address'] ) ) {
+ $location_string .= $this->address_to_string( $location['address'] );
+ }
+ return $location_string;
}
/**
- * Map an ActivityStreams Place to the Events Calendar venue.
+ * Add tags to post.
*
- * @param array $location An ActivityPub location as an associative array.
- * @link https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place
- * @return array
+ * @param int $post_id The post ID.
*/
- private function get_venue_args( $location ) {
- $args = array(
- 'venue' => $location['name'],
- 'status' => 'publish',
- );
+ private function add_tags_to_post( $post_id ) {
+ $tags_array = $this->activitypub_event->get_tag();
- if ( is_array( $location['address'] ) && isset( $location['address']['type'] ) && 'PostalAddress' === $location['address']['type'] ) {
- $mapping = array(
- 'streetAddress' => 'address',
- 'postalCode' => 'zip',
- 'addressLocality' => 'city',
- 'addressState' => 'state',
- 'addressCountry' => 'country',
- 'url' => 'website',
- );
-
- foreach ( $mapping as $postal_address_key => $venue_key ) {
- if ( isset( $location['address'][ $postal_address_key ] ) ) {
- $args[ $venue_key ] = $location['address'][ $postal_address_key ];
- }
- }
- } elseif ( is_string( $location['address'] ) ) {
- // Use the address field for a solely text address.
- $args['address'] = $location['address'];
+ // Ensure the input is valid.
+ if ( empty( $tags_array ) || ! is_array( $tags_array ) || ! $post_id ) {
+ return false;
}
- return $args;
- }
-
- /**
- * Add venue.
- *
- * @return int|bool $post_id The venues post ID.
- */
- private function add_venue() {
- $location = $this->activitypub_event->get_location();
-
- if ( ! $location ) {
- return;
- }
-
- if ( ! isset( $location['name'] ) ) {
- return;
- }
-
- // Fallback for Gancio instances.
- if ( 'online' === $location['name'] ) {
- return;
- }
-
- $post_ids = tribe_events()->search( $location['name'] )->all();
-
- $post_id = false;
-
- if ( count( $post_ids ) ) {
- $post_id = reset( $post_ids );
- }
-
- if ( $post_id && get_post_meta( $post_id, '_event_bridge_for_activitypub_is_remote_cached', true ) ) {
- tribe_venues()->where( 'id', $post_id )->set_args( $this->get_venue_args( $location ) )->save()[0];
- } else {
- $post = tribe_venues()->set_args( $this->get_venue_args( $location ) )->create();
- if ( $post ) {
- $post_id = $post->ID;
+ // Extract and process tag names.
+ $tag_names = array();
+ foreach ( $tags_array as $tag ) {
+ if ( isset( $tag['name'] ) && 'Hashtag' === $tag['type'] ) {
+ $tag_names[] = ltrim( $tag['name'], '#' ); // Remove the '#' from the name.
}
}
- return $post_id;
+ // Add the tags as terms to the post.
+ if ( ! empty( $tag_names ) ) {
+ wp_set_object_terms( $post_id, $tag_names, IntegrationsVS_Event_List::get_event_category_taxonomy(), true );
+ }
+
+ return true;
}
/**
@@ -124,59 +99,62 @@ class VS_Event_List extends Base {
*/
public function save_event() {
// Limit this as a safety measure.
- add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
+ \add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
$post_id = $this->get_post_id_from_activitypub_id();
- $duration = $this->get_duration();
-
- $venue_id = $this->add_venue();
-
$args = array(
- 'title' => sanitize_text_field( $this->activitypub_event->get_name() ),
- 'content' => wp_kses_post( $this->activitypub_event->get_content() ),
- 'start_date' => gmdate( 'Y-m-d H:i:s', strtotime( $this->activitypub_event->get_start_time() ) ),
- 'duration' => $duration,
- 'status' => 'publish',
- 'guid' => sanitize_url( $this->activitypub_event->get_id() ),
+ 'post_title' => \sanitize_text_field( $this->activitypub_event->get_name() ),
+ 'post_type' => \Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::get_post_type(),
+ 'post_content' => \wp_kses_post( $this->activitypub_event->get_content() ?? '' ),
+ 'post_excerpt' => \wp_kses_post( $this->activitypub_event->get_summary() ?? '' ),
+ 'post_status' => 'publish',
+ 'guid' => \sanitize_url( $this->activitypub_event->get_id() ),
+ 'meta_input' => array(
+ 'event-start-date' => \strtotime( $this->activitypub_event->get_start_time() ),
+ 'event-link' => \sanitize_url( $this->activitypub_event->get_url() ),
+ 'event-link-label' => \sanitize_text_field( __( 'Original Website', 'event-bridge-for-activitypub' ) ),
+ 'event-link-target' => 'yes', // Open in new window.
+ 'event-link-title' => 'no', // Whether to redirect event title to original source.
+ 'event-link-image' => 'no', // Whether to redirect events featured image to original source.
+ ),
);
- if ( $venue_id ) {
- $args['venue'] = $venue_id;
- $args['VenueID'] = $venue_id;
+ // Add end time.
+ $end_time = $this->activitypub_event->get_end_time();
+ if ( $end_time ) {
+ $args['meta_input']['event-date'] = \strtotime( $end_time );
}
- $tribe_event = new The_Events_Calendar_Event_Repository();
+ // Maybe add location.
+ $location = $this->get_location_as_string( $this->activitypub_event->get_location() );
+ if ( $location ) {
+ $args['meta_input']['event-location'] = $location;
+ }
if ( $post_id ) {
- $args['post_title'] = $args['title'];
- $args['post_content'] = $args['content'];
- // Update existing GatherPress event post.
- $post = \Tribe__Events__API::updateEvent( $post_id, $args );
+ // Update existing event post.
+ $args['ID'] = $post_id;
+ $post_id = \wp_update_post( $args );
} else {
- $post = $tribe_event->set_args( $args )->create();
+ // Insert new event post.
+ $post_id = \wp_insert_post( $args );
}
- if ( ! $post ) {
+ if ( ! $post_id || \is_wp_error( $post_id ) ) {
return false;
}
+ // Insert featured image.
+ $image = $this->get_featured_image();
+ self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
+
+ // Add hashtags.
+ $this->add_tags_to_post( $post_id );
+
// Limit this as a safety measure.
- remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
+ \remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
- return $post->ID;
- }
-
- /**
- * Get the events duration in seconds.
- *
- * @return int
- */
- private function get_duration() {
- $end_time = $this->activitypub_event->get_end_time();
- if ( ! $end_time ) {
- return 2 * HOUR_IN_SECONDS;
- }
- return abs( strtotime( $end_time ) - strtotime( $this->activitypub_event->get_start_time() ) );
+ return $post_id;
}
}
diff --git a/includes/class-event-sources.php b/includes/class-event-sources.php
index 06e5165..4d16531 100644
--- a/includes/class-event-sources.php
+++ b/includes/class-event-sources.php
@@ -36,6 +36,9 @@ class Event_Sources {
// Register the Event Sources Collection which takes care of managing the event sources.
\add_action( 'init', array( Event_Sources_Collection::class, 'init' ) );
+ // Allow wp_safe_redirect to all followed event sources hosts.
+ \add_filter( 'allowed_redirect_hosts', array( self::class, 'add_event_sources_hosts_to_allowed_redirect_hosts' ) );
+
// Register handlers for incoming activities to the ActivityPub plugin, e.g. incoming `Event` objects.
\add_action( 'activitypub_register_handlers', array( Handler::class, 'register_handlers' ) );
diff --git a/includes/class-settings.php b/includes/class-settings.php
index a9a91f8..224dac8 100644
--- a/includes/class-settings.php
+++ b/includes/class-settings.php
@@ -122,7 +122,7 @@ class Settings {
'event-bridge-for-activitypub',
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
array(
- 'type' => 'array',
+ 'type' => 'string',
'description' => \__( 'Define which plugin/integration is used for the event sources feature', 'event-bridge-for-activitypub' ),
'default' => array(),
'sanitize_callback' => array( self::class, 'sanitize_event_plugin_integration_used_for_event_sources' ),
diff --git a/includes/integrations/class-vs-event-list.php b/includes/integrations/class-vs-event-list.php
index beb56b4..f00b54c 100644
--- a/includes/integrations/class-vs-event-list.php
+++ b/includes/integrations/class-vs-event-list.php
@@ -13,6 +13,8 @@
namespace Event_Bridge_For_ActivityPub\Integrations;
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\VS_Event_List as VS_Event_List_Transformer;
+use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\VS_Event_List as VS_Event_List_Transmogrifier;
+use WP_Query;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
@@ -25,7 +27,7 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
*
* @since 1.0.0
*/
-final class VS_Event_List extends Event_Plugin_Integration {
+final class VS_Event_List extends Event_Plugin_Integration implements Feature_Event_Sources {
/**
* Returns the full plugin file.
*
@@ -71,4 +73,45 @@ final class VS_Event_List extends Event_Plugin_Integration {
public static function get_activitypub_event_transformer( $post ): VS_Event_List_Transformer {
return new VS_Event_List_Transformer( $post, self::get_event_category_taxonomy() );
}
+
+ /**
+ * Returns the Transmogrifier for The_Events_Calendar.
+ */
+ public static function get_transmogrifier(): VS_Event_List_Transmogrifier {
+ return new VS_Event_List_Transmogrifier();
+ }
+
+ /**
+ * Get a list of Post IDs of events that have ended.
+ *
+ * @param int $ends_before_time Filter to only get events that ended before that datetime as unix-time.
+ *
+ * @return array
+ */
+ public static function get_cached_remote_events( $ends_before_time ): array {
+ $args = array(
+ 'post_type' => 'event',
+ 'posts_per_page' => -1,
+ 'fields' => 'ids',
+ 'meta_query' => array(
+ 'relation' => 'AND',
+ array(
+ 'key' => '_event_bridge_for_activitypub_is_remote_cached',
+ 'compare' => 'EXISTS',
+ ),
+ array(
+ 'key' => 'event-date',
+ 'value' => $ends_before_time,
+ 'type' => 'NUMERIC',
+ 'compare' => '<',
+ ),
+ ),
+ );
+
+ $query = new WP_Query( $args );
+
+ $post_ids = $query->posts;
+
+ return $post_ids;
+ }
}
diff --git a/templates/settings.php b/templates/settings.php
index 7d1fff2..efc0a49 100644
--- a/templates/settings.php
+++ b/templates/settings.php
@@ -143,7 +143,7 @@ $current_category_mapping = \get_option( 'event_bridge_for_activitypub_ev
>
$event_plugin_name ) {
- echo '';
+ echo '';
}
?>
diff --git a/templates/welcome.php b/templates/welcome.php
index 2ee205b..61bda38 100644
--- a/templates/welcome.php
+++ b/templates/welcome.php
@@ -48,10 +48,11 @@ WP_Filesystem();
⚠' . \wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ) . '
';
} elseif ( empty( $active_event_plugins ) ) {
$notice = General_Admin_Notices::get_admin_notice_no_supported_event_plugin_active();
+ echo '⚠' . \wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ) . '
';
}
- echo '⚠' . \wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ) . '
';
?>
get_plugin_name() ); ?>:
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index c966f46..1f7780d 100755
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -81,6 +81,12 @@ function _manually_load_plugin() {
break;
case 'vs_event_list':
$plugin_file = 'very-simple-event-list/vsel.php';
+ \update_option( 'event_bridge_for_activitypub_event_sources_active', true );
+ \update_option(
+ 'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
+ \Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::class
+ );
+ \update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
break;
case 'events_manager':
$plugin_file = 'events-manager/events-manager.php';
diff --git a/tests/includes/activitypub/transformer/class-test-plugin-vs-event-list.php b/tests/includes/activitypub/transformer/class-test-vs-event-list.php
similarity index 100%
rename from tests/includes/activitypub/transformer/class-test-plugin-vs-event-list.php
rename to tests/includes/activitypub/transformer/class-test-vs-event-list.php
diff --git a/tests/includes/activitypub/transmogrifier/class-test-gatherpress.php b/tests/includes/activitypub/transmogrifier/class-test-gatherpress.php
index 3ae578c..8c81375 100644
--- a/tests/includes/activitypub/transmogrifier/class-test-gatherpress.php
+++ b/tests/includes/activitypub/transmogrifier/class-test-gatherpress.php
@@ -113,7 +113,7 @@ class Test_GatherPress extends \WP_UnitTestCase {
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
'name' => 'Fediverse Party',
'to' => 'https://www.w3.org/ns/activitystreams#Public',
- 'published' => '2020-01-01T00:00:00Z',
+ 'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
'location' => array(
'type' => 'Place',
'name' => 'Fediverse Concert Hall',
diff --git a/tests/includes/activitypub/transmogrifier/class-test-the-events-calendar.php b/tests/includes/activitypub/transmogrifier/class-test-the-events-calendar.php
index 6f5b977..2bd29b3 100644
--- a/tests/includes/activitypub/transmogrifier/class-test-the-events-calendar.php
+++ b/tests/includes/activitypub/transmogrifier/class-test-the-events-calendar.php
@@ -93,7 +93,7 @@ class Test_The_Events_Calendar extends \WP_UnitTestCase {
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
'name' => 'Fediverse Party for The Events Calendar',
'to' => 'https://www.w3.org/ns/activitystreams#Public',
- 'published' => '2020-01-01T00:00:00Z',
+ 'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
'location' => array(
'type' => 'Place',
'name' => 'Fediverse Concert Hall',
@@ -155,7 +155,7 @@ class Test_The_Events_Calendar extends \WP_UnitTestCase {
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
'name' => 'Fediverse Party for The Events Calendar',
'to' => 'https://www.w3.org/ns/activitystreams#Public',
- 'published' => '2020-01-01T00:00:00Z',
+ 'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
'location' => array(
'type' => 'Place',
'name' => 'Fediverse Concert Hall',
diff --git a/tests/includes/activitypub/transmogrifier/class-test-vs-event-list.php b/tests/includes/activitypub/transmogrifier/class-test-vs-event-list.php
new file mode 100644
index 0000000..5c74172
--- /dev/null
+++ b/tests/includes/activitypub/transmogrifier/class-test-vs-event-list.php
@@ -0,0 +1,127 @@
+ 'https://remote.example/@organizer',
+ 'type' => 'Person',
+ 'inbox' => 'https://remote.example/@organizer/inbox',
+ 'outbox' => 'https://remote.example/@organizer/outbox',
+ 'name' => 'The Organizer',
+ 'summary' => 'Just a random organizer of events in the Fediverse',
+ );
+
+ /**
+ * REST Server.
+ *
+ * @var WP_REST_Server
+ */
+ protected $server;
+
+ /**
+ * Set up the test.
+ */
+ public function set_up() {
+ if ( ! function_exists( 'vsel_custom_post_type' ) ) {
+ self::markTestSkipped( 'VS Event List plugin is not active.' );
+ }
+
+ \add_option( 'permalink_structure', '/%postname%/' );
+
+ global $wp_rest_server;
+ $wp_rest_server = new WP_REST_Server();
+ $this->server = $wp_rest_server;
+
+ do_action( 'rest_api_init' );
+
+ \Activitypub\Rest\Server::add_hooks();
+
+ // Make sure that ActivityPub support is enabled for The Events Calendar.
+ $aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
+ $aec->activate_activitypub_support_for_active_event_plugins();
+
+ // Add event source (ActivityPub follower).
+ _delete_all_posts();
+ \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
+
+ \update_option( 'event_bridge_for_activitypub_event_sources_active', true );
+ \update_option(
+ 'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
+ \Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::class
+ );
+ \update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
+ }
+
+ /**
+ * Tear down the test.
+ */
+ public function tear_down() {
+ \delete_option( 'permalink_structure' );
+ }
+
+ /**
+ * Test receiving event from followed actor.
+ */
+ public function test_incoming_event() {
+ \add_filter( 'activitypub_defer_signature_verification', '__return_true' );
+
+ $json = array(
+ 'id' => 'https://remote.example/@organizer/events/new-year-party#create',
+ 'type' => 'Create',
+ 'actor' => 'https://remote.example/@organizer',
+ 'object' => array(
+ 'id' => 'https://remote.example/@organizer/events/new-year-party',
+ 'type' => 'Event',
+ 'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
+ 'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
+ 'name' => 'Fediverse Party Test Event',
+ 'to' => 'https://www.w3.org/ns/activitystreams#Public',
+ 'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
+ 'location' => array(
+ 'type' => 'Place',
+ 'name' => 'Fediverse Concert Hall',
+ 'address' => 'Fedistreet 13, Feditown 1337',
+ ),
+ ),
+ );
+
+ $request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
+
+ $events = get_posts( array( 'post_type' => IntegrationsVS_Event_List::get_post_type() ) );
+
+ $this->assertCount( 1, $events );
+ $event = $events[0];
+
+ $this->assertEquals( $json['object']['name'], $event->post_title );
+ $this->assertEquals( $json['object']['startTime'], \gmdate( 'Y-m-d\TH:i:s\Z', get_post_meta( $event->ID, 'event-start-date', true ) ) );
+ $this->assertEquals( $json['object']['endTime'], \gmdate( 'Y-m-d\TH:i:s\Z', get_post_meta( $event->ID, 'event-date', true ) ) );
+ $this->assertStringStartsWith( $json['object']['location']['name'], get_post_meta( $event->ID, 'event-location', true ) );
+ \remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
+ }
+}
diff --git a/tests/includes/integrations/class-test-vs-event-list.php b/tests/includes/integrations/class-test-vs-event-list.php
new file mode 100644
index 0000000..aebd1f4
--- /dev/null
+++ b/tests/includes/integrations/class-test-vs-event-list.php
@@ -0,0 +1,171 @@
+ 'https://remote.example/@organizer',
+ 'type' => 'Person',
+ 'inbox' => 'https://remote.example/@organizer/inbox',
+ 'outbox' => 'https://remote.example/@organizer/outbox',
+ 'name' => 'The Organizer',
+ 'summary' => 'Just a random organizer of events in the Fediverse',
+ );
+
+ /**
+ * REST Server.
+ *
+ * @var WP_REST_Server
+ */
+ protected $server;
+
+ /**
+ * Set up the test.
+ */
+ public function set_up() {
+ if ( ! function_exists( 'vsel_custom_post_type' ) ) {
+ self::markTestSkipped( 'VS Event List plugin is not active.' );
+ }
+
+ \add_option( 'permalink_structure', '/%postname%/' );
+
+ global $wp_rest_server;
+ $wp_rest_server = new WP_REST_Server();
+ $this->server = $wp_rest_server;
+
+ do_action( 'rest_api_init' );
+
+ \Activitypub\Rest\Server::add_hooks();
+
+ // Make sure that ActivityPub support is enabled for The Events Calendar.
+ $aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
+ $aec->activate_activitypub_support_for_active_event_plugins();
+
+ // Add event source (ActivityPub follower).
+ _delete_all_posts();
+ \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
+
+ \update_option( 'event_bridge_for_activitypub_event_sources_active', true );
+ \update_option(
+ 'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
+ \Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::class
+ );
+ \update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
+ }
+
+ /**
+ * Tear down the test.
+ */
+ public function tear_down() {
+ \delete_option( 'permalink_structure' );
+ }
+
+ /**
+ * Test receiving event from followed actor.
+ */
+ public function test_getting_past_remote_events() {
+ \add_filter( 'activitypub_defer_signature_verification', '__return_true' );
+
+ // Federated event 1: starts in one week.
+ $event_in_one_week = array(
+ 'id' => 'https://remote.example/@organizer/events/in-one-week#create',
+ 'type' => 'Create',
+ 'actor' => 'https://remote.example/@organizer',
+ 'object' => array(
+ 'id' => 'https://remote.example/@organizer/events/in-one-week',
+ 'type' => 'Event',
+ 'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
+ 'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
+ 'name' => 'Remote Event in One Week',
+ 'to' => 'https://www.w3.org/ns/activitystreams#Public',
+ 'published' => '2020-01-01T00:00:00Z',
+ 'location' => array(
+ 'type' => 'Place',
+ 'name' => 'Fediverse Concert Hall',
+ 'address' => 'Fedistreet 13, Feditown 1337',
+ ),
+ ),
+ );
+
+ // Federated event 1: starts in two months.
+ $event_in_two_months = array(
+ 'id' => 'https://remote.example/@organizer/events/in-two-months#create',
+ 'type' => 'Create',
+ 'actor' => 'https://remote.example/@organizer',
+ 'object' => array(
+ 'id' => 'https://remote.example/@organizer/events/in-two-months',
+ 'type' => 'Event',
+ 'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * MONTH_IN_SECONDS ),
+ 'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * MONTH_IN_SECONDS + HOUR_IN_SECONDS ),
+ 'name' => 'Remote Event in Two Months',
+ 'to' => 'https://www.w3.org/ns/activitystreams#Public',
+ 'published' => '2020-01-01T00:00:00Z',
+ 'location' => array(
+ 'type' => 'Place',
+ 'name' => 'Fediverse Concert Hall',
+ 'address' => 'Fedistreet 13, Feditown 1337',
+ ),
+ ),
+ );
+
+ $request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/0/inbox' );
+ $request->set_header( 'Content-Type', 'application/activity+json' );
+
+ // Receive both events.
+ $request->set_body( \wp_json_encode( $event_in_one_week ) );
+ $response = \rest_do_request( $request );
+ $this->assertEquals( 202, $response->get_status() );
+ $request->set_body( \wp_json_encode( $event_in_two_months ) );
+ $response = \rest_do_request( $request );
+ $this->assertEquals( 202, $response->get_status() );
+
+ // Create a local event in VS Event List.
+ $post_id = \wp_insert_post(
+ array(
+ 'post_title' => 'VSEL Local Test Event',
+ 'post_status' => 'publish',
+ 'post_type' => 'event',
+ 'meta_input' => array(
+ 'event-start-date' => \strtotime( '+10 days 15:00:00' ),
+ 'event-date' => \strtotime( '+10 days 16:00:00' ),
+ 'event-link' => 'https://event-federation.eu/vsel-test-event',
+ 'event-link-label' => 'Website',
+ ),
+ )
+ );
+
+ $this->assertNotEquals( false, $post_id );
+
+ // Only one event should show up in the remote events query.
+ $events = VS_Event_List::get_cached_remote_events( time() + MONTH_IN_SECONDS );
+ $this->assertEquals( 1, count( $events ) );
+ $this->assertEquals( $event_in_one_week['object']['id'], get_post( $events[0] )->guid );
+
+ // Include the even in two months in the time_span.
+ $events = VS_Event_List::get_cached_remote_events( time() + 3 * MONTH_IN_SECONDS );
+ $this->assertEquals( 2, count( $events ) );
+
+ // All events are in the future, so no events should be in past.
+ $events = VS_Event_List::get_cached_remote_events( time() );
+ $this->assertEquals( 0, count( $events ) );
+
+ \remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
+ }
+}