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' ); + } +}