diff --git a/.forgejo/workflows/phpunit.yml b/.forgejo/workflows/phpunit.yml index 3cb7904..a893dce 100644 --- a/.forgejo/workflows/phpunit.yml +++ b/.forgejo/workflows/phpunit.yml @@ -37,7 +37,7 @@ jobs: path: | ${{ env.WP_CORE_DIR }} ${{ env.WP_TESTS_DIR }} - key: cache-wordpress-8 + key: cache-wordpress-9 - name: Cache Composer id: cache-composer-phpunit @@ -101,5 +101,10 @@ jobs: - name: Run Integration tests for Eventin (WP Event Solution) run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=eventin + env: + PHP_VERSION: ${{ matrix.php-version }} + + - name: Run Integration tests for Modern Events Calendar Lite + run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=modern_events_calendar_lite env: PHP_VERSION: ${{ matrix.php-version }} \ No newline at end of file diff --git a/README.md b/README.md index a23638c..892dc58 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ The Event Federation plugin ensures that users from those platforms are provided ### Features for Your WordPress Events and the Fediverse -**ActivityPub-Enabled Event Sharing:** Your WordPress events are now compatible with the Fediverse, using the ActivityStreams format. This means your events can be easily discovered and followed by users on platforms like Mastodon and other ActivityPub-compatible services. +**ActivityPub-Enabled Event Sharing:** Your WordPress events are not part of the Fediverse and **Automatic Event Summaries:** When your event is shared on the Fediverse, platforms like Mastodon that don't fully support events will display a brief HTML summary of key details — such as the event's title, start time, and location. This ensures that even if someone can't view the full event on their platform, they still get the important info at a glance, with a link to your WordPress event page. @@ -63,6 +63,8 @@ This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/ac * [Events Manager](https://de.wordpress.org/plugins/events-manager/) * [WP Event Manager](https://de.wordpress.org/plugins/wp-event-manager/) * [Eventin](https://de.wordpress.org/plugins/wp-event-solution/) +* [Modern Events Calendar Lite](https://webnus.net/modern-events-calendar/) + ## Configuration diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh index 759ad25..f0bf333 100755 --- a/bin/install-wp-tests.sh +++ b/bin/install-wp-tests.sh @@ -226,6 +226,20 @@ install_wp_plugin() { unzip -q -o "$TMPDIR/$PLUGIN_FILE" -d "$WP_CORE_DIR/wp-content/plugins/" } +install_wp_plugin_mec() { + mkdir -p "$WP_CORE_DIR/wp-content/plugins/" + + if [ -d "$WP_CORE_DIR/wp-content/plugins/modern-events-calendar-lite" ]; then + return; + fi + + PLUGIN_VERSION="v7.15.0" + + URL="https://code.event-federation.eu/Event-Federation/modern-events-calendar-lite" + + git clone $URL "$WP_CORE_DIR/wp-content/plugins/modern-events-calendar-lite" +} + install_wp_plugins() { if [ "$SKIP_PLUGINS_INSTALL" = "true" ]; then echo "Skipping WordPress plugin installation." @@ -240,6 +254,8 @@ install_wp_plugins() { install_wp_plugin events-manager install_wp_plugin wp-event-manager install_wp_plugin wp-event-solution + # Mec is not installable via wordpress.org, we use our own mirror. + install_wp_plugin_mec } install_wp diff --git a/composer.json b/composer.json index 940dff0..eaf1ea5 100644 --- a/composer.json +++ b/composer.json @@ -53,11 +53,12 @@ "@test-gatherpress", "@test-events-manager", "@test-wp-event-manager", - "@test-eventin" + "@test-eventin", + "@test-modern-events-calendar-lite" ], "test-debug": [ "@prepare-test", - "@test-eventin" + "@test-modern-events-calendar-lite" ], "test-vs-event-list": "phpunit --filter=vs_event_list", "test-the-events-calendar": "phpunit --filter=the_events_calendar", @@ -65,6 +66,7 @@ "test-events-manager": "phpunit --filter=events_manager", "test-wp-event-manager": "phpunit --filter=wp_event_manager", "test-eventin": "phpunit --filter=eventin", + "test-modern-events-calendar-lite": "phpunit --filter=modern_events_calendar_lite", "test-all": "phpunit" } } diff --git a/improvements.md b/improvements.md new file mode 100644 index 0000000..c095490 --- /dev/null +++ b/improvements.md @@ -0,0 +1,16 @@ +Make use of: + +https://docs.theeventscalendar.com/reference/classes/tribe__events__api/get_event_terms/ + +for getting all tags/terms for an event! + +``` +public static function get_event_terms( $event_id, array $args = array() ) { + $terms = array(); + foreach ( get_post_taxonomies( $event_id ) as $taxonomy ) { + $tax_terms = wp_get_object_terms( $event_id, $taxonomy, $args ); + $terms[ $taxonomy ] = $tax_terms; + } + return $terms; +} +``` diff --git a/includes/activitypub/transformer/class-modern-events-calendar-lite.php b/includes/activitypub/transformer/class-modern-events-calendar-lite.php new file mode 100644 index 0000000..0877abf --- /dev/null +++ b/includes/activitypub/transformer/class-modern-events-calendar-lite.php @@ -0,0 +1,114 @@ +mec_main = MEC::getInstance( 'app.libraries.main' ); + $this->mec_event = new MEC_Event( $wp_object ); + } + + /** + * Get the end time from the event object. + */ + public function get_start_time(): string { + return \gmdate( 'Y-m-d\TH:i:s\Z', $this->mec_event->get_datetime()['start']['timestamp'] ); + } + + /** + * Get the end time from the event object. + */ + public function get_end_time(): ?string { + return \gmdate( 'Y-m-d\TH:i:s\Z', $this->mec_event->get_datetime()['end']['timestamp'] ); + } + + /** + * Get the location. + */ + public function get_location(): ?Place { + $location_id = $this->mec_main->get_master_location_id( $this->mec_event->ID ); + + if ( ! $location_id ) { + return null; + } + + $data = $this->mec_main->get_location_data( $location_id ); + + $location = new Place(); + $location->set_sensitive( null ); + + if ( ! empty( $data['address'] ) ) { + $location->set_address( $data['address'] ); + } + if ( ! empty( $data['name'] ) ) { + $location->set_name( $data['name'] ); + } + if ( ! empty( $data['longitude'] ) ) { + $location->set_longitude( $data['longitude'] ); + } + if ( ! empty( $data['latitude'] ) ) { + $location->set_latitude( $data['latitude'] ); + } + + return $location; + } + + /** + * Get the location. + */ + public function get_timezone(): string { + $timezone = get_post_meta( $this->wp_object->ID, 'mec_timezone', true ); + + if ( 'global' === $timezone ) { + return parent::get_timezone(); + } + + return $timezone; + } +} diff --git a/includes/class-setup.php b/includes/class-setup.php index e03c7d0..a03c988 100644 --- a/includes/class-setup.php +++ b/includes/class-setup.php @@ -130,6 +130,7 @@ class Setup { '\ActivityPub_Event_Bridge\Plugins\VS_Event_List', '\ActivityPub_Event_Bridge\Plugins\WP_Event_Manager', '\ActivityPub_Event_Bridge\Plugins\Eventin', + '\ActivityPub_Event_Bridge\Plugins\Modern_Events_Calendar_Lite', ); /** diff --git a/includes/plugins/class-modern-events-calendar-lite.php b/includes/plugins/class-modern-events-calendar-lite.php new file mode 100644 index 0000000..f8f5552 --- /dev/null +++ b/includes/plugins/class-modern-events-calendar-lite.php @@ -0,0 +1,61 @@ +get_main_post_type(). + return apply_filters( 'mec_post_type_name', 'mec-events' ); // phpcs:ignore + } + + /** + * Returns the ID of the main settings page of the plugin. + * + * @return string The settings page url. + */ + public static function get_settings_page(): string { + return 'mec-event'; + } + + /** + * Returns the taxonomy used for the plugin's event categories. + * + * @return string + */ + public static function get_event_category_taxonomy(): string { + return 'mec_category'; + } +} diff --git a/readme.txt b/readme.txt index 59e8103..149fe68 100644 --- a/readme.txt +++ b/readme.txt @@ -53,6 +53,7 @@ This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/ac * [Events Manager](https://de.wordpress.org/plugins/events-manager/) * [WP Event Manager](https://de.wordpress.org/plugins/wp-event-manager/) * [Eventin](https://de.wordpress.org/plugins/wp-event-solution/) +* [Modern Events Calendar Lite](https://webnus.net/modern-events-calendar/) == Configuration == diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 4bbd83a..df5b41e 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -62,6 +62,9 @@ function _manually_load_plugin() { case 'eventin': $plugin_file = 'wp-event-solution/eventin.php'; break; + case 'modern_events_calendar_lite': + $plugin_file = 'modern-events-calendar-lite/modern-events-calendar-lite.php'; + break; case 'gatherpress': $plugin_file = 'gatherpress/gatherpress.php'; break; @@ -88,6 +91,12 @@ function _manually_load_plugin() { em_create_locations_table(); } + if ( 'modern_events_calendar_lite' === $activitypub_event_extension_integration_filter ) { + require_once $plugin_dir . 'modern-events-calendar-lite/app/libraries/factory.php'; + $mec_factory = new MEC_factory(); + $mec_factory->install(); + } + // At last manually load our WordPress plugin. require dirname( __DIR__ ) . '/activitypub-event-bridge.php'; } diff --git a/tests/test-class-plugin-modern-events-calendar-lite.php b/tests/test-class-plugin-modern-events-calendar-lite.php new file mode 100644 index 0000000..55114d9 --- /dev/null +++ b/tests/test-class-plugin-modern-events-calendar-lite.php @@ -0,0 +1,185 @@ +activate_activitypub_support_for_active_event_plugins(); + + $this->mec_main = \MEC::getInstance( 'app.libraries.main' ); + + // Delete all posts afterwards. + _delete_all_posts(); + } + + /** + * Test that the right transformer gets applied. + */ + public function test_modern_events_calendar_lite_transformer_class() { + // We only test for one event plugin being active at the same time, + // even though we support multiple onces in theory. + // But testing all combinations is beyond scope. + $active_event_plugins = \ActivityPub_Event_Bridge\Setup::get_instance()->get_active_event_plugins(); + $this->assertEquals( 1, count( $active_event_plugins ) ); + + // Enable ActivityPub support for the event plugin. + $this->assertContains( 'mec-events', get_option( 'activitypub_support_post_types' ) ); + + // Insert a new Event. + $event = array( + 'title' => 'MEC Test Event', + 'status' => 'publish', + 'start_time_hour' => '3', + 'start_time_minutes' => '00', + 'start_time_ampm' => 'PM', + 'start' => '2025-01-01', + 'end' => '2025-01-01', + 'end_time_hour' => '4', + 'end_time_minutes' => '00', + 'end_time_ampm' => 'PM', + 'repeat_status' => 0, + 'repeat_type' => 'daily', + 'interval' => 1, + ); + + $post_id = $this->mec_main->save_event( $event ); + + $wp_object = get_post( $post_id ); + + // Call the transformer Factory. + $transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object ); + + // Check that we got the right transformer. + $this->assertInstanceOf( \ActivityPub_Event_Bridge\Activitypub\Transformer\Modern_Events_Calendar_Lite::class, $transformer ); + } + + /** + * Test that the transformation of minimal event. + */ + public function test_modern_events_calendar_lite_minimal_event() { + $start_timestamp = strtotime( '+10 days 15:00:00' ); + $end_timestamp = strtotime( '+10 days 16:00:00' ); + + // Insert a new Event. + $event = array( + 'title' => 'MEC Test Event', + 'status' => 'publish', + 'content' => 'This is the content of the MEC!', + 'start_time_hour' => gmdate( 'h', $start_timestamp ), + 'start_time_minutes' => gmdate( 'i', $start_timestamp ), + 'start_time_ampm' => gmdate( 'A', $start_timestamp ), + 'start' => gmdate( 'Y-m-d', $start_timestamp ), + 'end' => gmdate( 'Y-m-d', $end_timestamp ), + 'end_time_hour' => gmdate( 'h', $end_timestamp ), + 'end_time_minutes' => gmdate( 'i', $end_timestamp ), + 'end_time_ampm' => gmdate( 'A', $end_timestamp ), + 'repeat_status' => 0, + 'repeat_type' => 'daily', + 'interval' => 1, + ); + + $post_id = $this->mec_main->save_event( $event ); + + $wp_object = get_post( $post_id ); + + // Call the transformer to make the ActivityStreams representation of the event. + $event_array = \Activitypub\Transformer\Factory::get_transformer( $wp_object )->to_object()->to_array(); + + $this->assertEquals( 'Event', $event_array['type'] ); + $this->assertEquals( 'MEC Test Event', $event_array['name'] ); + $this->assertEquals( 'This is the content of the MEC!', wp_strip_all_tags( $event_array['content'] ) ); + $this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] ); + $this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] ); + $this->assertTrue( $event_array['commentsEnabled'] ); + $this->assertEquals( 'allow_all', $event_array['repliesModerationOption'] ); + $this->assertEquals( 'external', $event_array['joinMode'] ); + $this->assertEquals( get_permalink( $wp_object ), $event_array['externalParticipationUrl'] ); + $this->assertArrayNotHasKey( 'location', $event_array ); + $this->assertEquals( 'MEETING', $event_array['category'] ); + } + + /** + * Test that the transformation of minimal event. + */ + public function test_modern_events_calendar_lite_event_with_location() { + $start_timestamp = strtotime( '+10 days 15:00:00' ); + $end_timestamp = strtotime( '+10 days 16:00:00' ); + + // Add new location. + $location = array( + 'name' => 'MEC Location', + 'latitude' => '52.356370', + 'longitude' => '4.955760', + 'address' => 'Stichting NLnet, Science Park 400, 1098 XH Amsterdam', + 'url' => 'https://nlnet.nl/', + ); + + $location_id = $this->mec_main->save_location( $location ); + + // Insert a new Event. + $event = array( + 'title' => 'MEC Test Event', + 'status' => 'publish', + 'content' => 'This is the content of the MEC!', + 'start_time_hour' => gmdate( 'h', $start_timestamp ), + 'start_time_minutes' => gmdate( 'i', $start_timestamp ), + 'start_time_ampm' => gmdate( 'A', $start_timestamp ), + 'start' => gmdate( 'Y-m-d', $start_timestamp ), + 'end' => gmdate( 'Y-m-d', $end_timestamp ), + 'end_time_hour' => gmdate( 'h', $end_timestamp ), + 'end_time_minutes' => gmdate( 'i', $end_timestamp ), + 'end_time_ampm' => gmdate( 'A', $end_timestamp ), + 'repeat_status' => 0, + 'repeat_type' => 'daily', + 'interval' => 1, + 'location_id' => $location_id, + ); + + $post_id = $this->mec_main->save_event( $event ); + + $wp_object = get_post( $post_id ); + + // Call the transformer to make the ActivityStreams representation of the event. + $event_array = \Activitypub\Transformer\Factory::get_transformer( $wp_object )->to_object()->to_array(); + + $this->assertEquals( 'Event', $event_array['type'] ); + $this->assertEquals( 'MEC Test Event', $event_array['name'] ); + $this->assertEquals( 'This is the content of the MEC!', wp_strip_all_tags( $event_array['content'] ) ); + $this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] ); + $this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] ); + $this->assertTrue( $event_array['commentsEnabled'] ); + $this->assertEquals( 'allow_all', $event_array['repliesModerationOption'] ); + $this->assertEquals( 'external', $event_array['joinMode'] ); + $this->assertEquals( get_permalink( $wp_object ), $event_array['externalParticipationUrl'] ); + $this->assertArrayHasKey( 'location', $event_array ); + $this->assertEquals( 'MEETING', $event_array['category'] ); + $this->assertEquals( $location['address'], $event_array['location']['address'] ); + $this->assertEquals( $location['name'], $event_array['location']['name'] ); + $this->assertEquals( $location['latitude'], $event_array['location']['latitude'] ); + $this->assertEquals( $location['longitude'], $event_array['location']['longitude'] ); + } +}