diff --git a/.forgejo/workflows/phpunit.yml b/.forgejo/workflows/phpunit.yml index 5bdae8d..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-4 + key: cache-wordpress-9 - name: Cache Composer id: cache-composer-phpunit @@ -91,5 +91,20 @@ jobs: - name: Run Integration tests for Events Manager run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=events_manager + env: + PHP_VERSION: ${{ matrix.php-version }} + + - name: Run Integration tests for WP Event Manager + run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=wp_event_manager + env: + PHP_VERSION: ${{ matrix.php-version }} + + - 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/.wordpress-org/banner-1544x500.jpg b/.wordpress-org/banner-1544x500.jpg new file mode 100644 index 0000000..0f1839b Binary files /dev/null and b/.wordpress-org/banner-1544x500.jpg differ diff --git a/.wordpress-org/banner-772x250.jpg b/.wordpress-org/banner-772x250.jpg new file mode 100644 index 0000000..9f5a437 Binary files /dev/null and b/.wordpress-org/banner-772x250.jpg differ diff --git a/.wordpress-org/icon-128x128.gif b/.wordpress-org/icon-128x128.gif new file mode 100644 index 0000000..25ebcc0 Binary files /dev/null and b/.wordpress-org/icon-128x128.gif differ diff --git a/.wordpress-org/icon-256x256.gif b/.wordpress-org/icon-256x256.gif new file mode 100644 index 0000000..9385c37 Binary files /dev/null and b/.wordpress-org/icon-256x256.gif differ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9a605a7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2024-10-20 + +### Added + +* Initial version tag. diff --git a/README.md b/README.md index 3462494..07aceea 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,19 @@ These platforms create public event calendars by pulling in events from various Logo

-Even platforms that don’t yet fully support events, like [Mastodon](https://joinmastodon.org), will still receive a detailed, well-composed summary of your event. +Even platforms that don't yet fully support events, like [Mastodon](https://joinmastodon.org), will still receive a detailed, well-composed summary of your event. The Event Federation plugin ensures that users from those platforms are provided with all important information about an event. +### Features for Your WordPress Events and the Fediverse + +**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. + +**Improved Event Discoverability:** Your custom event categories are mapped to a set of default categories used in the Fediverse, helping your events reach a wider audience. This improves the chances that users searching for similar events on other platforms will find yours. + +**Event Reminders for Your Followers:** Often, events are planned well in advance. To keep your followers informed right in time, you can set up reminders that are supposed to trigger the events showing up in their timelines right before the event starts. At the moment this reminder is implemented as a self-boost of your original event post. While this feature may behave differently across various platforms, we are working on a more robust solution that will let you schedule dedicated reminder notes that appear in all followers' timelines. + ## Installation This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/). Additionally, you need to use one of the supported event plugins. See below. @@ -51,6 +61,10 @@ This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/ac * [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/) * [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/) * [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/) +* [GatherPress](https://gatherpress.org/) ## Configuration diff --git a/activitypub-event-bridge.php b/activitypub-event-bridge.php index a4e0cad..b1cb2ea 100644 --- a/activitypub-event-bridge.php +++ b/activitypub-event-bridge.php @@ -3,7 +3,7 @@ * Plugin Name: ActivityPub Event Bridge * Description: Integrating popular event plugins with the ActivityPub plugin. * Plugin URI: https://event-federation.eu/ - * Version: 0.1.0 + * Version: 0.1.1 * Author: André Menrath * Author URI: https://graz.social/@linos * Text Domain: activitypub-event-bridge @@ -11,7 +11,7 @@ * License URI: https://www.gnu.org/licenses/agpl-3.0.de.html * Requires PHP: 8.1 * - * Requires at least ActivityPub plugin with version >= 3.2.2. ActivityPub plugin tested up to: 3.2.2. + * Requires at least ActivityPub plugin with version >= 3.2.2. ActivityPub plugin tested up to: 3.3.3. * * @package ActivityPub_Event_Bridge * @license AGPL-3.0-or-later diff --git a/assets/css/activitypub-event-bridge-admin.css b/assets/css/activitypub-event-bridge-admin.css new file mode 100644 index 0000000..c89ec6e --- /dev/null +++ b/assets/css/activitypub-event-bridge-admin.css @@ -0,0 +1,179 @@ +.settings_page_activitypub-event-bridge #wpcontent { + padding-left: 0; +} + +.activitypub-event-bridge-settings-page .box { + border: 1px solid #c3c4c7; + background-color: #fff; + padding: 1em 1.5em; + margin-bottom: 1.5em; +} + +.activitypub-event-bridge-settings-page .box ul.activitypub-event-bridge-list { + margin-left: 0.6em; +} + +.activitypub-event-bridge-settings-page .box pre { + padding: 1rem; + min-height: 200px; + box-shadow: none; + border-radius: 15px; + border: 1px solid #dfe0e2; + background-color: #f7f7f7; +} + +.activitypub-event-bridge-settings { + max-width: 800px; + margin: 0 auto; +} + +.activitypub-event-bridge-settings-header { + text-align: center; + margin: 0 0 1rem; + background: #fff; + border-bottom: 1px solid #dcdcde; +} + +.activitypub-event-bridge-settings-title-section { + display: flex; + align-items: center; + justify-content: center; + clear: both; + padding-top: 8px; +} + +.activitypub-event-bridge-settings-tabs-wrapper { + display: -ms-inline-grid; + -ms-grid-columns: auto auto auto auto; + vertical-align: top; + display: inline-grid; + grid-template-columns: auto auto auto auto; +} + +.activitypub-event-bridge-settings-tab.active { + box-shadow: inset 0 -3px #3582c4; + font-weight: 600; +} + +.activitypub-event-bridge-settings-tab { + display: block; + text-decoration: none; + color: inherit; + padding: .5rem 1rem 1rem; + margin: 0 1rem; + transition: box-shadow .5s ease-in-out; +} + +.activitypub-event-bridge-settings .box h3 { + font-size: 1.15em; + margin-bottom: 0em; +} + +#activitypub_event_bridge_initially_activated { + display: hidden; +} + +/* Accordions for admin pages */ +.activitypub-event-bridge-settings-accordion { + border: 1px solid #c3c4c7; +} + +.activitypub-event-bridge-settings-accordion-heading { + margin: 0; + border-top: 1px solid #c3c4c7; + font-size: inherit; + line-height: inherit; + font-weight: 600; + color: inherit; +} + +.activitypub-event-bridge-settings-accordion-heading:first-child { + border-top: none; +} + +.activitypub-event-bridge-settings-accordion-panel { + margin: 0; + padding: 1em 1.5em; + background: #fff; +} + +.activitypub-event-bridge-settings-accordion-trigger { + background: #fff; + border: 0; + color: #2c3338; + cursor: pointer; + display: flex; + font-weight: 400; + margin: 0; + padding: 1em 3.5em 1em 1.5em; + min-height: 46px; + position: relative; + text-align: left; + width: 100%; + align-items: center; + justify-content: space-between; + -webkit-user-select: auto; + user-select: auto; +} + +.activitypub-event-bridge-settings-accordion-trigger { + color: #2c3338; + cursor: pointer; + font-weight: 400; + text-align: left; +} + +.activitypub-event-bridge-settings-accordion-trigger .title { + pointer-events: none; + font-weight: 600; + flex-grow: 1; +} + +.activitypub-event-bridge-settings-accordion-trigger .icon, +.activitypub-event-bridge-settings-accordion-viewed .icon { + border: solid #50575e medium; + border-width: 0 2px 2px 0; + height: .5rem; + pointer-events: none; + position: absolute; + right: 1.5em; + top: 50%; + transform: translateY(-70%) rotate(45deg); + width: .5rem; +} + +.activitypub-event-bridge-settings-accordion-trigger[aria-expanded="true"] .icon { + transform: translateY(-30%) rotate(-135deg); +} + +.activitypub-event-bridge-settings-accordion-trigger:active, +.activitypub-event-bridge-settings-accordion-trigger:hover { + background: #f6f7f7; +} + +.activitypub-event-bridge-settings-accordion-trigger:focus { + color: #1d2327; + border: none; + box-shadow: none; + outline-offset: -1px; + outline: 2px solid #2271b1; + background-color: #f6f7f7; +} + +.activitypub-event-bridge-settings-inline-icon { + width: 1.5em; + height: 1.5em; + vertical-align: middle; + margin: 0 0.3em; +} + +code.activitypub-event-bridge-settings-example-url { + display: block; + background: rgb(28, 29, 33); + padding: 8px; + margin: 10px 0px 10px 0; + border-radius: 7px; + color: #d5d5d6; + overflow-x: auto; + word-break: break-all; +} diff --git a/assets/css/activitypub-event-extensions-admin.css b/assets/css/activitypub-event-extensions-admin.css deleted file mode 100644 index c59d1a1..0000000 --- a/assets/css/activitypub-event-extensions-admin.css +++ /dev/null @@ -1,6 +0,0 @@ -.activitypub-event-bridge-settings-page .box { - border: 1px solid #c3c4c7; - background-color: #fff; - padding: 1em 1.5em; - margin-bottom: 1.5em; -} diff --git a/assets/img/activitypub.svg b/assets/img/activitypub.svg new file mode 100644 index 0000000..f56d428 --- /dev/null +++ b/assets/img/activitypub.svg @@ -0,0 +1,288 @@ + + + + + ActivityPub logo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + ActivityPub logo + + 2017-04-15 + + + Robert Martinez + + + + + ActivityPub + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/img/fediverse.svg b/assets/img/fediverse.svg new file mode 100644 index 0000000..a789df2 --- /dev/null +++ b/assets/img/fediverse.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/img/mastodon.svg b/assets/img/mastodon.svg new file mode 100644 index 0000000..0f8baeb --- /dev/null +++ b/assets/img/mastodon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/img/mobilizon.svg b/assets/img/mobilizon.svg new file mode 100644 index 0000000..8b5d57e --- /dev/null +++ b/assets/img/mobilizon.svg @@ -0,0 +1 @@ + diff --git a/assets/js/activitypub-event-bridge-admin.js b/assets/js/activitypub-event-bridge-admin.js new file mode 100644 index 0000000..bf00a1c --- /dev/null +++ b/assets/js/activitypub-event-bridge-admin.js @@ -0,0 +1,14 @@ +jQuery( function( $ ) { + // Accordion handling in various areas. + $( '.activitypub-event-bridge-settings-accordion' ).on( 'click', '.activitypub-event-bridge-settings-accordion-trigger', function() { + var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) ); + + if ( isExpanded ) { + $( this ).attr( 'aria-expanded', 'false' ); + $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true ); + } else { + $( this ).attr( 'aria-expanded', 'true' ); + $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false ); + } + } ); +} ); diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh index 8d495bf..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." @@ -238,6 +252,10 @@ install_wp_plugins() { install_wp_plugin very-simple-event-list install_wp_plugin gatherpress 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 44b5406..6236dfc 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "menrath/wordpress-activitypub-event-bridge", + "version": "1.0.0", "description": "The ActivityPub Event Bridge help for event custom post types to federate properly.", "type": "wordpress-plugin", "require": { @@ -50,7 +51,10 @@ "@test-vs-event-list", "@test-the-events-calendar", "@test-gatherpress", - "@test-events-manager" + "@test-events-manager", + "@test-wp-event-manager", + "@test-eventin", + "@test-modern-events-calendar-lite" ], "test-debug": [ "@prepare-test", @@ -59,6 +63,10 @@ "test-vs-event-list": "phpunit --filter=vs_event_list", "test-the-events-calendar": "phpunit --filter=the_events_calendar", "test-gatherpress": "phpunit --filter=gatherpress", - "test-events-manager": "phpunit --filter=events_manager" + "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/docker-compose.yml b/docker-compose.yml index dc71501..d39760b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,9 @@ version: '3' # This files purpose is to run the PHPunit tests locally. +# Prerequisites: +# Run "composer install" to generate a composer.lock file and install PHP dev dependencies. +# # Install docker and docker compose and than just run: # docker compose up diff --git a/docs/add_your_event_plugin.md b/docs/add_your_event_plugin.md index 8c387a4..44ec1f4 100644 --- a/docs/add_your_event_plugin.md +++ b/docs/add_your_event_plugin.md @@ -28,6 +28,15 @@ First you need to add some basic information about your event plugin. Just creat final class My_Event_Plugin extends Event_Plugin { ``` +Then you need to tell the ActivityPub Event Bridge about that class by adding it to the `EVENT_PLUGIN_CLASSES` constant in the `includes/setup.php` file: + +```php + private const EVENT_PLUGIN_CLASSES = array( + ... + '\ActivityPub_Event_Bridge\Plugins\My_Event_Plugin', + ); +``` + The ActivityPub Event Bridge then takes care of applying the transformer, so you can jump right into implementing it. ## Writing an event transformer class @@ -107,3 +116,107 @@ In order to ensure your events are compatible with other ActivityPub Event imple * **`get_tag`** * **`timezone`** * **`commentsEnabled`** + +## Writing integration tests + +Create a new tests class in `tests/test-class-plugin-my-event-plugin.php`. + +``` +/** + * Sample test case. + */ +class Test_My_Event_Plugin extends WP_UnitTestCase { +``` + +Implement a check whether your event plugin is active in the `set_up` function. It may be the presence of a class, function or constant. + +```php + /** + * Override the setup function, so that tests don't run if the Events Calendar is not active. + */ + public function set_up() { + parent::set_up(); + + if ( ! ) { + self::markTestSkipped( 'The Events Calendar plugin is not active.' ); + } + + // Make sure that ActivityPub support is enabled for The Events Calendar. + $aec = \ActivityPub_Event_Bridge\Setup::get_instance(); + $aec->activate_activitypub_support_for_active_event_plugins(); + + // Delete all posts afterwards. + _delete_all_posts(); + } +``` + +## Running the tests for your plugin/ add the tests to the CI pipeline + +### Install the plugin in the CI + +The tests are set up by the bash script in `bin/install-wp-tests.sh`. Make sure your WordPress Event plugin is installed within the function `install_wp_plugins`. + +### Add a composer script for your plugin + +In the pipeline we want to run each event plugins integration tests in a single command, to achieve that, we use phpunit's filters. + +```json +{ + "scripts": { + ... + "test": [ + ... + "@test-my-event-plugin" + ], + ... + "@test-my-event-plugin": "phpunit --filter=my_event_plugin", + ] + } +} +``` + +### Load your plugin during the tests + +To activate/load your plugin add it to the switch statement within the function `_manually_load_plugin()` within `tests/bootstrap.php`. + +```php + switch ( $activitypub_event_extension_integration_filter ) { + ... + case 'my_event_plugin': + $plugin_file = 'my-event-plugin/my-event-plugin.php'; + break; +``` + +If you want to run your tests locally just change the `test-debug` script in the `composer.json` file: + +```json + "test-debug": [ + "@prepare-test", + "@test-my-event-plugin" + ], +``` + +Now you just can execute `docker compose up` to run the tests (make sure you have the latest docker and docker-compose installed). + +### Debugging the tests + +If you are using Visual Studio Code or VSCodium you can step-debug within the tests by adding this configuration to your `.vscode/launch.json`: + +```json +{ + "version": "0.2.0", + "configurations": [ + ..., + { + "name": "Listen for PHPUnit", + "type": "php", + "request": "launch", + "port": 9003, + "pathMappings": { + "/app/": "${workspaceRoot}/wp-content/plugins/activitypub-event-bridge/", + "/tmp/wordpress/": "${workspaceRoot}/" + }, + } + ] +} +``` 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-event.php b/includes/activitypub/transformer/class-event.php index 5fb09f4..babf2b9 100644 --- a/includes/activitypub/transformer/class-event.php +++ b/includes/activitypub/transformer/class-event.php @@ -267,9 +267,9 @@ abstract class Event extends Post { add_filter( 'activitypub_object_content_template', array( self::class, 'remove_ap_permalink_from_template' ), 2, 2 ); $excerpt = $this->retrieve_excerpt(); // BeforeFirstRelease: decide whether this should be a admin setting. - $fallback_to_content = true; + $fallback_to_content = false; if ( is_null( $excerpt ) && $fallback_to_content ) { - $excerpt = parent::get_content(); + $excerpt = $this->get_content(); } remove_filter( 'activitypub_object_content_template', array( self::class, 'remove_ap_permalink_from_template' ) ); diff --git a/includes/activitypub/transformer/class-eventin.php b/includes/activitypub/transformer/class-eventin.php new file mode 100644 index 0000000..b47d93e --- /dev/null +++ b/includes/activitypub/transformer/class-eventin.php @@ -0,0 +1,164 @@ +event_model = new Event_Model( $this->wp_object->ID ); + } + + /** + * Get the end time from the event object. + */ + public function get_start_time(): string { + return \gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $this->event_model->get_start_datetime() ) ); + } + + /** + * Get the end time from the event object. + */ + public function get_end_time(): string { + return \gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $this->event_model->get_end_datetime() ) ); + } + + /** + * Get the timezone of the event. + */ + public function get_timezone(): string { + return $this->event_model->get_timezone(); + } + + /** + * Get whether the event is online. + * + * @return bool + */ + public function get_is_online(): bool { + return 'online' === $this->event_model->__get( 'event_type' ) ? true : false; + } + + /** + * Maybe add online link to attachments. + * + * @return array + */ + public function get_attachment(): array { + $attachment = parent::get_attachment(); + + $location = (array) $this->event_model->__get( 'location' ); + if ( array_key_exists( 'integration', $location ) && array_key_exists( $location['integration'], $location ) ) { + $online_link = array( + 'type' => 'Link', + 'mediaType' => 'text/html', + 'name' => $location[ $location['integration'] ], + 'href' => $location[ $location['integration'] ], + ); + $attachment[] = $online_link; + } + return $attachment; + } + + /** + * Compose the events tags. + */ + public function get_tag() { + // The parent tag function also fetches the mentions. + $tags = parent::get_tag(); + + $post_tags = \wp_get_post_terms( $this->wp_object->ID, 'etn_tags' ); + $post_categories = \wp_get_post_terms( $this->wp_object->ID, 'etn_category' ); + + if ( ! is_wp_error( $post_tags ) && $post_tags ) { + foreach ( $post_tags as $term ) { + $tag = array( + 'type' => 'Hashtag', + 'href' => \esc_url( \get_tag_link( $term->term_id ) ), + 'name' => esc_hashtag( $term->name ), + ); + $tags[] = $tag; + } + } + + if ( ! is_wp_error( $post_categories ) && $post_categories ) { + foreach ( $post_categories as $term ) { + $tag = array( + 'type' => 'Hashtag', + 'href' => \esc_url( \get_tag_link( $term->term_id ) ), + 'name' => esc_hashtag( $term->name ), + ); + $tags[] = $tag; + } + } + + if ( empty( $tags ) ) { + return null; + } + + return $tags; + } + + /** + * Get the location. + * + * @return ?Place + */ + public function get_location(): ?Place { + $location = (array) $this->event_model->__get( 'location' ); + + if ( ! array_key_exists( 'address', $location ) ) { + return null; + } + + $place = new Place(); + + $address = $location['address']; + + $place->set_name( $address ); + $place->set_address( $address ); + $place->set_sensitive( null ); + + return $place; + } +} diff --git a/includes/activitypub/transformer/class-events-manager.php b/includes/activitypub/transformer/class-events-manager.php index 3acf6aa..1def3f7 100644 --- a/includes/activitypub/transformer/class-events-manager.php +++ b/includes/activitypub/transformer/class-events-manager.php @@ -11,7 +11,6 @@ namespace ActivityPub_Event_Bridge\Activitypub\Transformer; // Exit if accessed directly. defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore -use Activitypub\Activity\Extended_Object\Event; use Activitypub\Activity\Extended_Object\Place; use ActivityPub_Event_Bridge\Activitypub\Transformer\Event as Event_Transformer; use DateTime; diff --git a/includes/activitypub/transformer/class-gatherpress.php b/includes/activitypub/transformer/class-gatherpress.php index e3ec013..756524a 100644 --- a/includes/activitypub/transformer/class-gatherpress.php +++ b/includes/activitypub/transformer/class-gatherpress.php @@ -13,7 +13,6 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore use Activitypub\Activity\Extended_Object\Event as Event_Object; use Activitypub\Activity\Extended_Object\Place; -use Activitypub\Model\Blog; use ActivityPub_Event_Bridge\Activitypub\Transformer\Event; use GatherPress\Core\Event as GatherPress_Event; @@ -125,7 +124,7 @@ final class GatherPress extends Event { */ public static function filter_gatherpress_blocks( $block_content, $block ) { // Check if the block name starts with 'gatherpress'. - if ( strpos( $block['blockName'], 'gatherpress/' ) === 0 ) { + if ( isset( $block['blockName'] ) && 0 === strpos( $block['blockName'], 'gatherpress/' ) ) { return ''; // Skip rendering this block. } 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..8622de4 --- /dev/null +++ b/includes/activitypub/transformer/class-modern-events-calendar-lite.php @@ -0,0 +1,122 @@ +mec_main = MEC::getInstance( 'app.libraries.main' ); + $this->mec_event = new MEC_Event( $wp_object ); + } + + /** + * Retrieves the content without the plugins rendered shortcodes. + */ + public function get_content(): string { + $content = wpautop( $this->wp_object->post_content ); + return $content; + } + + /** + * 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/activitypub/transformer/class-the-events-calendar.php b/includes/activitypub/transformer/class-the-events-calendar.php index 72e25c8..2fe7274 100644 --- a/includes/activitypub/transformer/class-the-events-calendar.php +++ b/includes/activitypub/transformer/class-the-events-calendar.php @@ -11,7 +11,6 @@ namespace ActivityPub_Event_Bridge\Activitypub\Transformer; // Exit if accessed directly. defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore -use Activitypub\Activity\Extended_Object\Event as Event_Object; use Activitypub\Activity\Extended_Object\Place; use ActivityPub_Event_Bridge\Activitypub\Transformer\Event; use WP_Post; @@ -174,7 +173,7 @@ final class The_Events_Calendar extends Event { } else { $location->set_address( $venue->post_title ); } - $location->set_id( $venue->permalink ); + $location->set_id( $venue->ID ); $location->set_name( $venue->post_title ); return $location; diff --git a/includes/activitypub/transformer/class-vs-event-list.php b/includes/activitypub/transformer/class-vs-event-list.php index 35d33a2..c476404 100644 --- a/includes/activitypub/transformer/class-vs-event-list.php +++ b/includes/activitypub/transformer/class-vs-event-list.php @@ -11,7 +11,6 @@ namespace ActivityPub_Event_Bridge\Activitypub\Transformer; // Exit if accessed directly. defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore -use Activitypub\Activity\Extended_Object\Event; use Activitypub\Activity\Extended_Object\Place; use ActivityPub_Event_Bridge\Activitypub\Transformer\Event as Event_Transformer; diff --git a/includes/activitypub/transformer/class-wp-event-manager.php b/includes/activitypub/transformer/class-wp-event-manager.php new file mode 100644 index 0000000..6330152 --- /dev/null +++ b/includes/activitypub/transformer/class-wp-event-manager.php @@ -0,0 +1,140 @@ +wp_object->ID, '_event_online', true ); + $is_online = false; + // Radio buttons. + if ( 'yes' === $is_online_text ) { + $is_online = true; + } + // Checkbox. + if ( '1' === $is_online_text ) { + $is_online = true; + } + return $is_online; + } + + /** + * Get the event location. + * + * @return array The Place. + */ + public function get_location(): ?Place { + $location_name = get_post_meta( $this->wp_object->ID, '_event_location', true ); + + if ( $location_name ) { + $location = new Place(); + $location->set_name( $location_name ); + $location->set_sensitive( null ); + $location->set_address( $location_name ); + + return $location; + } + return null; + } + + /** + * Get the end time from the events metadata. + * + * @return ?string The events end-datetime if is set, null otherwise. + */ + public function get_end_time(): ?string { + $end_date = get_post_meta( $this->wp_object->ID, '_event_end_date', true ); + if ( $end_date ) { + $end_datetime = new DateTime( $end_date ); + return \gmdate( 'Y-m-d\TH:i:s\Z', $end_datetime->getTimestamp() ); + } + return null; + } + + /** + * Get the end time from the events metadata. + */ + public function get_start_time(): string { + $start_date = get_post_meta( $this->wp_object->ID, '_event_start_date', true ); + if ( ! is_numeric( $start_date ) ) { + $start_datetime = new DateTime( $start_date ); + $start_timestamp = $start_datetime->getTimestamp(); + } else { + $start_timestamp = (int) $start_date; + } + + return \gmdate( 'Y-m-d\TH:i:s\Z', $start_timestamp ); + } + + /** + * Get the event link as an ActivityPub Link object, but as an associative array. + * + * @return ?array + */ + private function get_event_link_attachment(): ?array { + $event_link_url = get_post_meta( $this->wp_object->ID, '_event_video_url', true ); + + if ( str_starts_with( $event_link_url, 'http' ) ) { + return array( + 'type' => 'Link', + 'name' => \esc_html__( 'Video URL', 'activitypub-event-bridge' ), + 'href' => \esc_url( $event_link_url ), + 'mediaType' => 'text/html', + ); + } else { + return null; + } + } + + /** + * Overrides/extends the get_attachments function to also add the event Link. + */ + protected function get_attachment() { + // Get attachments via parent function. + $attachments = parent::get_attachment(); + + // The first attachment is the featured image, make sure it is compatible with Mobilizon. + if ( count( $attachments ) ) { + $attachments[0]['type'] = 'Document'; + $attachments[0]['name'] = 'Banner'; + } + + if ( $this->get_event_link_attachment() ) { + $attachments[] = $this->get_event_link_attachment(); + } + return $attachments; + } + + /** + * Get the events title/name. + * + * @return string + */ + protected function get_name(): string { + return $this->wp_object->post_title; + } +} diff --git a/includes/admin/class-general-admin-notices.php b/includes/admin/class-general-admin-notices.php index 43cb4f3..7ab1f2b 100644 --- a/includes/admin/class-general-admin-notices.php +++ b/includes/admin/class-general-admin-notices.php @@ -98,6 +98,24 @@ class General_Admin_Notices { ); } + /** + * Warning to fix status issues first. + * + * @return string + */ + public static function get_admin_notice_status_not_ok(): string { + return sprintf( + /* translators: 1: An URL to the list of supported event plugins. */ + _x( + 'The Plugin ActivityPub Event Bridge is of no use, because you do not have installed and activated a supported Event Plugin. +
For a list of supported Event Plugins see here.', + 'admin notice', + 'activitypub-event-bridge' + ), + esc_html( self::ACTIVITYPUB_EVENT_BRIDGE_SUPPORTED_EVENT_PLUGINS_URL ) + ); + } + /** * Warning if the plugin is Active and the ActivityPub plugin is not. * diff --git a/includes/admin/class-health-check.php b/includes/admin/class-health-check.php new file mode 100644 index 0000000..5fa3f9a --- /dev/null +++ b/includes/admin/class-health-check.php @@ -0,0 +1,185 @@ + __( 'ActivityPub Event Transformer Test', 'activitypub-event-bridge' ), + 'test' => array( self::class, 'test_event_transformation' ), + ); + + return $tests; + } + + /** + * The the transformation of the most recent event posts. + * + * @return array + */ + public static function test_event_transformation() { + $result = array( + 'label' => \__( 'Transformation of Events to a valid ActivityStreams representation.', 'activitypub' ), + 'status' => 'good', + 'badge' => array( + 'label' => \__( 'ActivityPub Event Bridge', 'activitypub-event-bridge' ), + 'color' => 'green', + ), + 'description' => \sprintf( + '

%s

', + \__( 'The transformation of your most recent events was successful.', 'activitypub-event-bridge' ) + ), + 'actions' => '', + 'test' => 'test_event_transformation', + ); + + $check = self::transform_most_recent_event_posts(); + + if ( true === $check ) { + return $result; + } + + $result['status'] = 'critical'; + $result['label'] = \__( 'One or more of your most recent events failed to transform to ActivityPub', 'activitypub-event-bridge' ); + $result['badge']['color'] = 'red'; + $result['description'] = \sprintf( + '

%s

', + $check->get_error_message() + ); + + return $result; + } + + /** + * Test if right transformer gets applied. + * + * @param Event_Plugin $event_plugin The event plugin definition. + * + * @return bool True if the check passed. + */ + public static function test_if_event_transformer_is_used( $event_plugin ) { + // Get a (random) event post. + $event_posts = self::get_most_recent_event_posts( $event_plugin->get_post_type(), 1 ); + + // If no post is found, we can not do this test. + if ( ! $event_posts || is_wp_error( $event_posts ) || empty( $event_posts ) ) { + return true; + } + + // Call the transformer Factory. + $transformer = Transformer_Factory::get_transformer( $event_posts[0] ); + // Check that we got the right transformer. + $desired_transformer_class = $event_plugin::get_activitypub_event_transformer_class(); + if ( $transformer instanceof $desired_transformer_class ) { + return true; + } + return false; + } + + /** + * Retrieves the most recently published event posts of a certain event post type. + * + * @param ?string $event_post_type The post type of the events. + * @param ?int $number_of_posts The maximum number of events to return. + * + * @return WP_Post[]|false Array of event posts, or false if none are found. + */ + public static function get_most_recent_event_posts( $event_post_type = null, $number_of_posts = 5 ) { + if ( ! $event_post_type ) { + $event_post_type = Setup::get_instance()->get_active_event_plugins()[0]->get_post_type(); + } + + $args = array( + 'numberposts' => $number_of_posts, + 'category' => 0, + 'orderby' => 'date', + 'order' => 'DESC', + 'include' => array(), + 'exclude' => array(), + 'meta_key' => '', + 'meta_value' => '', + 'post_type' => $event_post_type, + 'suppress_filters' => true, + ); + + $query = new WP_Query(); + return $query->query( $args ); + } + + /** + * Transform the most recent event posts. + */ + public static function transform_most_recent_event_posts() { + return true; + } + + /** + * Retrieves information like name and version from active event plugins. + */ + private static function get_info_about_active_event_plugins() { + $active_event_plugins = Setup::get_instance()->get_active_event_plugins(); + $info = array(); + foreach ( $active_event_plugins as $active_event_plugin ) { + $event_plugin_file = $active_event_plugin->get_plugin_file(); + $event_plugin_data = \get_plugin_data( $event_plugin_file ); + $event_plugin_name = isset( $event_plugin_data['Plugin Name'] ) ? $event_plugin_data['Plugin Name'] : 'Name not found'; + $event_plugin_version = isset( $event_plugin_version['Plugin Version'] ) ? $event_plugin_version['Plugin Version'] : 'Version not found'; + + $info[] = array( + 'event_plugin_name' => $event_plugin_name, + 'event_plugin_version' => $event_plugin_version, + 'event_plugin_file' => $event_plugin_file, + ); + } + } + + /** + * Static function for generating site debug data when required. + * + * @param array $info The debug information to be added to the core information page. + * @return array The extended information. + */ + public static function add_debug_information( $info ) { + $info['activitypub_event_bridge'] = array( + 'label' => __( 'ActivityPub Event Bridge', 'activitypub-event-bridge' ), + 'fields' => array( + 'plugin_version' => array( + 'label' => __( 'Plugin Version', 'activitypub' ), + 'value' => ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_VERSION, + 'private' => true, + ), + 'active_event_plugins' => self::get_info_about_active_event_plugins(), + ), + ); + + return $info; + } +} diff --git a/includes/admin/class-settings-page.php b/includes/admin/class-settings-page.php index 08a36d6..b2dbb3e 100644 --- a/includes/admin/class-settings-page.php +++ b/includes/admin/class-settings-page.php @@ -3,7 +3,7 @@ * General settings class. * * This file contains the General class definition, which handles the "General" settings - * page for the ActivityPub Event Extension Plugin, providing options for configuring various general settings. + * page for the Activitypub Event Bridge Plugin, providing options for configuring various general settings. * * @package ActivityPub_Event_Bridge * @since 1.0.0 @@ -18,9 +18,9 @@ use ActivityPub_Event_Bridge\Plugins\Event_Plugin; use ActivityPub_Event_Bridge\Setup; /** - * Class responsible for the ActivityPub Event Extension related Settings. + * Class responsible for the Activitypub Event Bridge related Settings. * - * Class which handles the "General" settings page for the ActivityPub Event Extension Plugin, + * Class which handles the "General" settings page for the Activitypub Event Bridge Plugin, * providing options for configuring various general settings. * * @since 1.0.0 @@ -36,11 +36,11 @@ class Settings_Page { */ public static function admin_menu(): void { \add_options_page( - 'Activitypub Event Extension', - __( 'ActivityPub Events', 'activitypub-event-bridge' ), + 'Activitypub Event Bridge', + __( 'ActivityPub Event Bridge', 'activitypub-event-bridge' ), 'manage_options', self::SETTINGS_SLUG, - array( self::STATIC, 'settings_page' ) + array( self::STATIC, 'settings_page' ), ); } @@ -89,21 +89,41 @@ class Settings_Page { * @return void */ public static function settings_page(): void { - $plugin_setup = Setup::get_instance(); - - $event_plugins = $plugin_setup->get_active_event_plugins(); - - $event_terms = array(); - - foreach ( $event_plugins as $event_plugin ) { - $event_terms = array_merge( $event_terms, self::get_event_terms( $event_plugin ) ); + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( empty( $_GET['tab'] ) ) { + $tab = 'welcome'; + } else { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $tab = sanitize_key( $_GET['tab'] ); } - $args = array( - 'slug' => self::SETTINGS_SLUG, - 'event_terms' => $event_terms, - ); + switch ( $tab ) { + case 'settings': + $plugin_setup = Setup::get_instance(); - \load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/settings.php', true, $args ); + $event_plugins = $plugin_setup->get_active_event_plugins(); + + $event_terms = array(); + + foreach ( $event_plugins as $event_plugin ) { + $event_terms = array_merge( $event_terms, self::get_event_terms( $event_plugin ) ); + } + + $args = array( + 'slug' => self::SETTINGS_SLUG, + 'event_terms' => $event_terms, + ); + + \load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/settings.php', true, $args ); + break; + case 'welcome': + default: + wp_enqueue_script( 'plugin-install' ); + add_thickbox(); + wp_enqueue_script( 'updates' ); + + \load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/welcome.php', true ); + break; + } } } diff --git a/includes/class-settings.php b/includes/class-settings.php index 922531f..3367dd1 100644 --- a/includes/class-settings.php +++ b/includes/class-settings.php @@ -3,7 +3,7 @@ * General settings class. * * This file contains the General class definition, which handles the "General" settings - * page for the ActivityPub Event Extension Plugin, providing options for configuring various general settings. + * page for the Activitypub Event Bridge Plugin, providing options for configuring various general settings. * * @package ActivityPub_Event_Bridge * @since 1.0.0 @@ -61,6 +61,16 @@ class Settings { 'sanitize_callback' => array( self::class, 'sanitize_event_category_mappings' ), ) ); + + \register_setting( + 'activitypub-event-bridge', + 'activitypub_event_bridge_initially_activated', + array( + 'type' => 'boolean', + 'description' => \__( 'Whether the plugin just got activated for the first time.', 'activitypub' ), + 'default' => 1, + ) + ); } /** diff --git a/includes/class-setup.php b/includes/class-setup.php index fc742e5..c717619 100644 --- a/includes/class-setup.php +++ b/includes/class-setup.php @@ -17,6 +17,7 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore use ActivityPub_Event_Bridge\Admin\Event_Plugin_Admin_Notices; use ActivityPub_Event_Bridge\Admin\General_Admin_Notices; +use ActivityPub_Event_Bridge\Admin\Health_Check; use ActivityPub_Event_Bridge\Admin\Settings_Page; use ActivityPub_Event_Bridge\Plugins\Event_Plugin; @@ -129,6 +130,9 @@ class Setup { '\ActivityPub_Event_Bridge\Plugins\The_Events_Calendar', '\ActivityPub_Event_Bridge\Plugins\VS_Event_List', '\ActivityPub_Event_Bridge\Plugins\My_Calendar', + '\ActivityPub_Event_Bridge\Plugins\WP_Event_Manager', + '\ActivityPub_Event_Bridge\Plugins\Eventin', + '\ActivityPub_Event_Bridge\Plugins\Modern_Events_Calendar_Lite', ); /** @@ -165,20 +169,19 @@ class Setup { add_action( 'admin_init', array( $this, 'do_admin_notices' ) ); add_action( 'admin_init', array( Settings::class, 'register_settings' ) ); + add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_styles' ) ); + add_action( 'admin_menu', array( Settings_Page::class, 'admin_menu' ) ); + add_filter( + 'plugin_action_links_' . ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_BASENAME, + array( Settings_Page::class, 'settings_link' ) + ); // If we don't have any active event plugins, or the ActivityPub plugin is not enabled, abort here. if ( empty( $this->active_event_plugins ) || ! $this->activitypub_plugin_is_active ) { return; } - add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_styles' ) ); - - add_action( 'admin_menu', array( Settings_Page::class, 'admin_menu' ) ); - - add_filter( - 'plugin_action_links_' . ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_BASENAME, - array( Settings_Page::class, 'settings_link' ) - ); + add_action( 'init', array( Health_Check::class, 'init' ) ); // Check if the minimum required version of the ActivityPub plugin is installed. if ( ! version_compare( $this->activitypub_plugin_version, ACTIVITYPUB_EVENT_BRIDGE_ACTIVITYPUB_PLUGIN_MIN_VERSION ) ) { @@ -206,6 +209,16 @@ class Setup { array(), ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_VERSION ); + wp_enqueue_script( + 'activitypub-event-bridge-admin-script', + plugins_url( + 'assets/js/activitypub-event-bridge-admin.js', + ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_FILE + ), + array( 'jquery' ), + ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_VERSION, + false + ); } } @@ -285,7 +298,7 @@ class Setup { * This method handles the activation of the ActivityPub Event Bridge plugin. * * @since 1.0.0 - * + * @see register_activation_hook() * @return void */ public function activate(): void { diff --git a/includes/plugins/class-event-plugin.php b/includes/plugins/class-event-plugin.php index f70cfa0..a5c5734 100644 --- a/includes/plugins/class-event-plugin.php +++ b/includes/plugins/class-event-plugin.php @@ -45,12 +45,24 @@ abstract class Event_Plugin { abstract public static function get_event_category_taxonomy(): string; /** - * Returns the ID of the main settings page of the plugin. + * Returns the IDs of the admin pages of the plugin. * - * @return string The settings page url. + * @return array The IDs of one or several admin/settings pages. */ - public static function get_settings_page(): string { - return ''; + public static function get_settings_pages(): array { + return array(); + } + + /** + * Get the plugins name from the main plugin-file's top-level-file-comment. + */ + final public static function get_plugin_name(): string { + $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . static::get_plugin_file() ); + if ( isset( $plugin_data['Name'] ) ) { + return $plugin_data['Name']; + } else { + return ''; + } } /** @@ -62,7 +74,7 @@ abstract class Event_Plugin { // Check if we are on a edit page for the event, or on the settings page of the event plugin. $is_event_plugins_edit_page = 'edit' === $screen->base && static::get_post_type() === $screen->post_type; - $is_event_plugins_settings_page = static::get_settings_page() === $screen->id; + $is_event_plugins_settings_page = in_array( $screen->id, static::get_settings_pages(), true ); return $is_event_plugins_edit_page || $is_event_plugins_settings_page; } diff --git a/includes/plugins/class-eventin.php b/includes/plugins/class-eventin.php new file mode 100644 index 0000000..6584e85 --- /dev/null +++ b/includes/plugins/class-eventin.php @@ -0,0 +1,60 @@ +get_main_post_type(). + return apply_filters( 'mec_post_type_name', 'mec-events' ); // phpcs:ignore + } + + /** + * Returns the IDs of the admin pages of the plugin. + * + * @return array The settings page urls. + */ + public static function get_settings_pages(): array { + return array( 'MEC-settings', 'MEC-support', 'MEC-ix', 'MEC-wizard', 'MEC-addons', 'mec-intro' ); + } + + /** + * 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/includes/plugins/class-the-events-calendar.php b/includes/plugins/class-the-events-calendar.php index ef07590..36ab8e0 100644 --- a/includes/plugins/class-the-events-calendar.php +++ b/includes/plugins/class-the-events-calendar.php @@ -41,17 +41,17 @@ final class The_Events_Calendar extends Event_plugin { } /** - * Returns the ID of the main settings page of the plugin. + * Returns the IDs of the admin pages of the plugin. * - * @return string The settings page url. + * @return array The settings page urls. */ - public static function get_settings_page(): string { + public static function get_settings_pages(): array { if ( class_exists( '\Tribe\Events\Admin\Settings' ) ) { $page = \Tribe\Events\Admin\Settings::$settings_page_id; } else { $page = 'tec-events-settings'; } - return sprintf( 'edit.php?post_type=tribe_events&page=%s', $page ); + return array( $page ); } /** diff --git a/includes/plugins/class-vs-event-list.php b/includes/plugins/class-vs-event-list.php index 0e1b0dc..f1bd96b 100644 --- a/includes/plugins/class-vs-event-list.php +++ b/includes/plugins/class-vs-event-list.php @@ -44,12 +44,12 @@ final class VS_Event_List extends Event_Plugin { } /** - * Returns the ID of the main settings page of the plugin. + * Returns the IDs of the admin pages of the plugin. * - * @return string The settings page url. + * @return array The settings page urls. */ - public static function get_settings_page(): string { - return 'settings_page_vsel'; + public static function get_settings_pages(): array { + return array( 'settings_page_vsel' ); } /** diff --git a/includes/plugins/class-wp-event-manager.php b/includes/plugins/class-wp-event-manager.php new file mode 100644 index 0000000..28a852a --- /dev/null +++ b/includes/plugins/class-wp-event-manager.php @@ -0,0 +1,72 @@ + '', + 'settings' => '', + ) +); +?> + +
+
+

+
+ + +
+
diff --git a/templates/settings.php b/templates/settings.php index 8eb382e..309590f 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -13,6 +13,14 @@ // Exit if accessed directly. defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore +\load_template( + __DIR__ . '/admin-header.php', + true, + array( + 'settings' => 'active', + ) +); + use Activitypub\Activity\Extended_Object\Event; if ( ! isset( $args ) || ! array_key_exists( 'event_terms', $args ) ) { @@ -31,19 +39,12 @@ $selected_default_event_category = \get_option( 'activitypub_event_bridge_defaul $current_category_mapping = \get_option( 'activitypub_event_bridge_event_category_mappings', array() ); ?> -
-
-

-
-
-
- -
+
-

+

@@ -59,11 +60,9 @@ $current_category_mapping = \get_option( 'activitypub_event_bridge_event_
-
- -
-

+ +

@@ -96,8 +95,12 @@ $current_category_mapping = \get_option( 'activitypub_event_bridge_event_
+ +
+ + -
diff --git a/templates/welcome.php b/templates/welcome.php new file mode 100644 index 0000000..cb63f5c --- /dev/null +++ b/templates/welcome.php @@ -0,0 +1,237 @@ + 'active', + ) +); + +$active_event_plugins = Setup::get_instance()->get_active_event_plugins(); +$activitypub_event_bridge_status_ok = true; +$example_event_post = Health_Check::get_most_recent_event_posts(); + +if ( empty( $example_event_post ) ) { + $example_event_post = 'https://yoursite.com/events/event-name'; + $example_event_post_is_dummy = true; +} else { + $example_event_post = \get_permalink( $example_event_post[0] ); + $example_event_post_is_dummy = false; +} + +global $wp_filesystem; +WP_Filesystem(); + +?> + +
+
+

+

+ +

get_plugin_name() ); ?>:

+
    +
  • + %2$s is enabled in the %1$s settings.', + 'admin notice', + 'activitypub-event-bridge' + ), + esc_html( get_plugin_data( ACTIVITYPUB_PLUGIN_FILE )['Name'] ), + esc_html( $active_event_plugin->get_plugin_name() ), + admin_url( 'options-general.php?page=activitypub&tab=settings' ) + ); + } else { + $activitypub_event_bridge_status_ok = false; + echo '❌ '; + $status_message_post_type_enabled = sprintf( + /* translators: 1: the name of the event plugin a admin notice is shown. 2: The name of the ActivityPub plugin. */ + _x( + 'The post type for events of the plugin %2$s is not enabled in the %1$s settings.', + 'admin notice', + 'activitypub-event-bridge' + ), + esc_html( get_plugin_data( ACTIVITYPUB_PLUGIN_FILE )['Name'] ), + esc_html( $active_event_plugin->get_plugin_name() ), + admin_url( 'options-general.php?page=activitypub&tab=settings' ) + ); + } + $allowed_html = array( + 'a' => array( + 'href' => true, + 'title' => true, + ), + 'b' => array(), + 'i' => array(), + ); + echo \wp_kses( $status_message_post_type_enabled, $allowed_html ); + ?> +
  • +
  • + +
  • +
+ +
+ + + + + + + + +
+

+

' . \esc_html__( 'Please fix the status issues above first.', 'activitypub-event-bridge' ) . '

'; + } + ?> +

+
+

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +
+
+ +
+

+
+			get_contents( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . '/CHANGELOG.md' );
+			echo esc_html( substr( $changelog, strpos( $changelog, "\n", 180 ) + 1 ) );
+			?>
+		
+
+ + +
+ diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f17b5f9..df5b41e 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -43,6 +43,10 @@ function _manually_load_plugin() { } } + // Hot fixes for eventin. + update_option( 'purchase_history_table_structure_migration_done', true ); + update_option( 'etn_wizard', 'active' ); + $plugin_file = null; // See if we want to run integration tests for a specific event-plugin. switch ( $activitypub_event_extension_integration_filter ) { @@ -55,14 +59,24 @@ function _manually_load_plugin() { case 'events_manager': $plugin_file = 'events-manager/events-manager.php'; break; + 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; + case 'wp_event_manager': + $plugin_file = 'wp-event-manager/wp-event-manager.php'; + break; } if ( $plugin_file ) { // Manually load the event plugin. require_once $plugin_dir . $plugin_file; + update_option( 'purchase_history_table_structure_migration_done', true ); $current = get_option( 'active_plugins', array() ); $current[] = $plugin_file; sort( $current ); @@ -77,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-eventin.php b/tests/test-class-plugin-eventin.php new file mode 100644 index 0000000..d844922 --- /dev/null +++ b/tests/test-class-plugin-eventin.php @@ -0,0 +1,175 @@ + 'publish', + 'post_title' => 'Eventin Test Event Title', + 'post_content' => 'Eventin Test Event Description', + 'etn_start_date' => \gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ), + 'etn_end_date' => \gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) ), + 'etn_start_time' => \gmdate( 'H:i', strtotime( '+10 days 15:00:00' ) ), + 'etn_end_time' => \gmdate( 'H:i', strtotime( '+10 days 16:00:00' ) ), + 'event_timezone' => 'Europe/Vienna', + ); + } + + /** + * Override the setup function, so that tests don't run if the Events Calendar is not active. + */ + public function set_up() { + parent::set_up(); + + if ( ! class_exists( '\Wpeventin' ) ) { + self::markTestSkipped( 'Eventin plugin is not active.' ); + } + + // Make sure that ActivityPub support is enabled for The Events Calendar. + $aec = \ActivityPub_Event_Bridge\Setup::get_instance(); + $aec->activate_activitypub_support_for_active_event_plugins(); + + // Delete all posts afterwards. + _delete_all_posts(); + } + + /** + * Test that the right transformer gets applied. + */ + public function test_eventin_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( 'etn', get_option( 'activitypub_support_post_types' ) ); + + // Create a Eventin Event without content. + $event = new \Etn\Core\Event\Event_Model(); + $event->create( $this->get_mockup_event() ); + + // Call the transformer Factory. + $transformer = \Activitypub\Transformer\Factory::get_transformer( get_post( $event->id ) ); + + // Check that we got the right transformer. + $this->assertInstanceOf( \ActivityPub_Event_Bridge\Activitypub\Transformer\Eventin::class, $transformer ); + } + + /** + * Test that the right transformer gets applied. + */ + public function test_eventin_test_minimal_event() { + // Create a Eventin Event without content. + $event = new \Etn\Core\Event\Event_Model(); + $event->create( $this->get_mockup_event() ); + + // Call the transformer Factory. + $event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $event->id ) )->to_object()->to_array(); + + $this->assertEquals( 'Event', $event_array['type'] ); + $this->assertEquals( 'Eventin Test Event Title', $event_array['name'] ); + $this->assertEquals( 'Eventin Test Event Description', wp_strip_all_tags( $event_array['content'] ) ); + $this->assertEquals( gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '+10 days 15:00:00' ) ), $event_array['startTime'] ); + $this->assertEquals( gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '+10 days 16:00:00' ) ), $event_array['endTime'] ); + $this->assertEquals( 'Europe/Vienna', $event_array['timezone'] ); + $this->assertEquals( comments_open( $event->id ), $event_array['commentsEnabled'] ); + $this->assertEquals( comments_open( $event->id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] ); + $this->assertEquals( 'external', $event_array['joinMode'] ); + $this->assertArrayNotHasKey( 'location', $event_array ); + $this->assertEquals( 'MEETING', $event_array['category'] ); + $this->assertEquals( false, $event_array['isOnline'] ); + } + + /** + * Test that the right transformer gets applied. + */ + public function test_eventin_test_online_event_with_custom_link() { + // Create a Eventin Event without content. + $event = new \Etn\Core\Event\Event_Model(); + $args = array_merge( + $this->get_mockup_event(), + array( + 'event_type' => 'online', + 'location' => array( + 'integration' => 'custom_url', + 'custom_url' => 'https://jit.si/eventmeeting', + ), + ) + ); + $event->create( $args ); + + // Call the transformer Factory. + $event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $event->id ) )->to_object()->to_array(); + + $this->assertEquals( 'Event', $event_array['type'] ); + $this->assertEquals( 'Eventin Test Event Title', $event_array['name'] ); + $this->assertEquals( 'Eventin Test Event Description', wp_strip_all_tags( $event_array['content'] ) ); + $this->assertEquals( gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '+10 days 15:00:00' ) ), $event_array['startTime'] ); + $this->assertEquals( gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '+10 days 16:00:00' ) ), $event_array['endTime'] ); + $this->assertEquals( 'Europe/Vienna', $event_array['timezone'] ); + $this->assertEquals( comments_open( $event->id ), $event_array['commentsEnabled'] ); + $this->assertEquals( comments_open( $event->id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] ); + $this->assertEquals( 'external', $event_array['joinMode'] ); + $this->assertArrayNotHasKey( 'location', $event_array ); + $this->assertEquals( 'MEETING', $event_array['category'] ); + $this->assertEquals( true, $event_array['isOnline'] ); + $this->assertContains( + array( + 'type' => 'Link', + 'mediaType' => 'text/html', + 'name' => 'https://jit.si/eventmeeting', + 'href' => 'https://jit.si/eventmeeting', + ), + $event_array['attachment'] + ); + } + + + /** + * Test that the right transformer gets applied. + */ + public function test_eventin_test_online_event_with_physical_location() { + // Create a Eventin Event without content. + $event = new \Etn\Core\Event\Event_Model(); + $args = array_merge( + $this->get_mockup_event(), + array( + 'event_type' => 'offline', + 'location' => array( + 'address' => 'The NlNet center', + ), + ) + ); + $event->create( $args ); + + // Call the transformer Factory. + $event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $event->id ) )->to_object()->to_array(); + + $this->assertEquals( 'Event', $event_array['type'] ); + $this->assertEquals( 'Eventin Test Event Title', $event_array['name'] ); + $this->assertEquals( 'Eventin Test Event Description', wp_strip_all_tags( $event_array['content'] ) ); + $this->assertEquals( gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '+10 days 15:00:00' ) ), $event_array['startTime'] ); + $this->assertEquals( gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '+10 days 16:00:00' ) ), $event_array['endTime'] ); + $this->assertEquals( 'Europe/Vienna', $event_array['timezone'] ); + $this->assertEquals( comments_open( $event->id ), $event_array['commentsEnabled'] ); + $this->assertEquals( comments_open( $event->id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] ); + $this->assertEquals( 'external', $event_array['joinMode'] ); + $this->assertArrayHasKey( 'location', $event_array ); + $this->assertEquals( 'MEETING', $event_array['category'] ); + $this->assertEquals( false, $event_array['isOnline'] ); + $this->assertEquals( 'The NlNet center', $event_array['location']['address'] ); + $this->assertEquals( 'The NlNet center', $event_array['location']['name'] ); + } +} 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'] ); + } +} diff --git a/tests/test-class-plugin-vs-event-list.php b/tests/test-class-plugin-vs-event-list.php index 48e513d..d15e7ef 100644 --- a/tests/test-class-plugin-vs-event-list.php +++ b/tests/test-class-plugin-vs-event-list.php @@ -44,7 +44,7 @@ class Test_VS_Event_List extends WP_UnitTestCase { $wp_post_id = wp_insert_post( array( 'post_title' => 'VSEL Test Event', - 'post_status' => 'published', + 'post_status' => 'publish', 'post_type' => 'event', 'meta_input' => array( 'event-start-date' => strtotime( '+10 days 15:00:00' ), @@ -69,7 +69,7 @@ class Test_VS_Event_List extends WP_UnitTestCase { $wp_post_id = wp_insert_post( array( 'post_title' => 'VSEL Test Event', - 'post_status' => 'published', + 'post_status' => 'publish', 'post_type' => 'event', 'meta_input' => array( 'event-start-date' => strtotime( '+10 days 15:00:00' ), @@ -102,7 +102,7 @@ class Test_VS_Event_List extends WP_UnitTestCase { $wp_post_id = wp_insert_post( array( 'post_title' => 'VSEL Test Event', - 'post_status' => 'published', + 'post_status' => 'publish', 'post_type' => 'event', 'meta_input' => array( 'event-start-date' => strtotime( '+10 days 15:00:00' ), @@ -147,7 +147,7 @@ class Test_VS_Event_List extends WP_UnitTestCase { $wp_post_id = wp_insert_post( array( 'post_title' => 'VSEL Test Event', - 'post_status' => 'published', + 'post_status' => 'publish', 'post_type' => 'event', 'meta_input' => array( 'event-start-date' => strtotime( '+10 days 15:00:00' ), @@ -182,7 +182,7 @@ class Test_VS_Event_List extends WP_UnitTestCase { $wp_post_id = wp_insert_post( array( 'post_title' => 'VSEL Test Event', - 'post_status' => 'published', + 'post_status' => 'publish', 'post_type' => 'event', 'meta_input' => array( 'event-start-date' => strtotime( '+10 days 15:00:00' ), diff --git a/tests/test-class-plugin-wp-event-manager.php b/tests/test-class-plugin-wp-event-manager.php new file mode 100644 index 0000000..318a295 --- /dev/null +++ b/tests/test-class-plugin-wp-event-manager.php @@ -0,0 +1,182 @@ +activate_activitypub_support_for_active_event_plugins(); + + // Delete all posts afterwards. + _delete_all_posts(); + } + + /** + * Test that the right transformer gets applied. + */ + public function test_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( 'event_listing', get_option( 'activitypub_support_post_types' ) ); + + // Insert a new Event. + $wp_post_id = wp_insert_post( + array( + 'post_title' => 'WP Event Manager TestEvent', + 'post_status' => 'publish', + 'post_type' => 'event_listing', + 'meta_input' => array( + 'event-start-date' => strtotime( '+10 days 15:00:00' ), + ), + ) + ); + + $wp_object = get_post( $wp_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\WP_Event_Manager::class, $transformer ); + } + + /** + * Test the transformation to ActivityStreams of minimal event. + */ + public function test_transform_of_minimal_event() { + // Insert a new Event. + $wp_post_id = wp_insert_post( + array( + 'post_title' => 'WP Event Manager TestEvent', + 'post_status' => 'publish', + 'post_type' => 'event_listing', + 'post_content' => 'Come to my WP Event Manager event!', + 'meta_input' => array( + '_event_start_date' => strtotime( '+10 days 15:00:00' ), + ), + ) + ); + + // Transform the event to ActivityStreams. + $event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $wp_post_id ) )->to_object()->to_array(); + + // Check that the event ActivityStreams representation contains everything as expected. + $this->assertEquals( 'Event', $event_array['type'] ); + $this->assertEquals( 'WP Event Manager TestEvent', $event_array['name'] ); + $this->assertEquals( 'Come to my WP Event Manager event!', 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->assertArrayNotHasKey( 'endTime', $event_array ); + $this->assertEquals( comments_open( $wp_post_id ), $event_array['commentsEnabled'] ); + $this->assertEquals( comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] ); + $this->assertEquals( 'external', $event_array['joinMode'] ); + $this->assertEquals( esc_url( get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] ); + $this->assertArrayNotHasKey( 'location', $event_array ); + $this->assertEquals( 'MEETING', $event_array['category'] ); + } + + /** + * Test the transformation to ActivityStreams of minimal event. + */ + public function test_transform_of_full_online_event() { + // Insert a new Event. + $wp_post_id = wp_insert_post( + array( + 'post_title' => 'WP Event Manager TestEvent', + 'post_status' => 'publish', + 'post_type' => 'event_listing', + 'post_content' => 'Come to my WP Event Manager event!', + 'meta_input' => array( + '_event_start_date' => \gmdate( 'Y-m-d H:i:s', strtotime( '+10 days 15:00:00' ) ), + '_event_end_date' => \gmdate( 'Y-m-d H:i:s', strtotime( '+10 days 16:00:00' ) ), + '_event_video_url' => 'https://event-federation.eu/meeting-room', + '_event_online' => 'yes', + ), + ) + ); + + // Transform the event to ActivityStreams. + $event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $wp_post_id ) )->to_object()->to_array(); + + // Check that the event ActivityStreams representation contains everything as expected. + $this->assertEquals( 'Event', $event_array['type'] ); + $this->assertEquals( 'WP Event Manager TestEvent', $event_array['name'] ); + $this->assertEquals( 'Come to my WP Event Manager event!', 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 15:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] ); + $this->assertEquals( comments_open( $wp_post_id ), $event_array['commentsEnabled'] ); + $this->assertEquals( comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] ); + $this->assertEquals( 'external', $event_array['joinMode'] ); + $this->assertEquals( true, $event_array['isOnline'] ); + $this->assertEquals( esc_url( get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] ); + $this->assertArrayNotHasKey( 'location', $event_array ); + $this->assertEquals( 'MEETING', $event_array['category'] ); + $this->assertContains( + array( + 'type' => 'Link', + 'name' => __( 'Video URL', 'activitypub-event-bridge' ), + 'href' => 'https://event-federation.eu/meeting-room', + 'mediaType' => 'text/html', + ), + $event_array['attachment'] + ); + } + + /** + * Test the transformation to ActivityStreams of minimal event. + */ + public function test_transform_of_event_with_location() { + // Insert a new Event. + $wp_post_id = wp_insert_post( + array( + 'post_title' => 'WP Event Manager TestEvent', + 'post_status' => 'publish', + 'post_type' => 'event_listing', + 'post_content' => 'Come to my WP Event Manager event!', + 'meta_input' => array( + '_event_start_date' => \gmdate( 'Y-m-d H:i:s', strtotime( '+10 days 15:00:00' ) ), + '_event_end_date' => \gmdate( 'Y-m-d H:i:s', strtotime( '+10 days 16:00:00' ) ), + '_event_location' => 'Some text location', + '_event_online' => 'no', + ), + ) + ); + + // Transform the event to ActivityStreams. + $event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $wp_post_id ) )->to_object()->to_array(); + + // Check that the event ActivityStreams representation contains everything as expected. + $this->assertEquals( 'Event', $event_array['type'] ); + $this->assertEquals( 'WP Event Manager TestEvent', $event_array['name'] ); + $this->assertEquals( 'Come to my WP Event Manager event!', 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 15:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] ); + $this->assertEquals( comments_open( $wp_post_id ), $event_array['commentsEnabled'] ); + $this->assertEquals( comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] ); + $this->assertEquals( 'external', $event_array['joinMode'] ); + $this->assertEquals( false, $event_array['isOnline'] ); + $this->assertEquals( esc_url( get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] ); + $this->assertArrayHasKey( 'location', $event_array ); + $this->assertEquals( 'Some text location', $event_array['location']['address'] ); + } +}