Compare commits

..

3 commits

Author SHA1 Message Date
7e8346cf7b Add Transformer and Integration tests for Modern Events Calendar Lite (#66)
All checks were successful
PHP Code Checker / PHP Code Checker (push) Successful in 37s
PHPUnit / PHPUnit – PHP 8.1 (push) Successful in 1m0s
PHPUnit / PHPUnit – PHP 8.2 (push) Successful in 1m4s
PHPUnit / PHPUnit – PHP 8.3 (push) Successful in 1m8s
https://webnus.net/modern-events-calendar/
Reviewed-on: #66
Co-authored-by: André Menrath <andre.menrath@posteo.de>
Co-committed-by: André Menrath <andre.menrath@posteo.de>
2024-10-18 13:54:53 +02:00
29536e7a4d remove non-unicode characters in readme
All checks were successful
PHP Code Checker / PHP Code Checker (push) Successful in 32s
PHPUnit / PHPUnit – PHP 8.1 (push) Successful in 58s
PHPUnit / PHPUnit – PHP 8.2 (push) Successful in 1m0s
PHPUnit / PHPUnit – PHP 8.3 (push) Successful in 1m5s
PHP Code Checker / PHP Code Checker (pull_request) Successful in 39s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m4s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m4s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m3s
2024-10-13 16:09:55 +02:00
cc04a1e7dd Improve Readme and Documentation (#65)
Some checks failed
PHP Code Checker / PHP Code Checker (push) Successful in 32s
PHPUnit / PHPUnit – PHP 8.1 (push) Successful in 57s
PHPUnit / PHPUnit – PHP 8.2 (push) Successful in 57s
PHPUnit / PHPUnit – PHP 8.3 (push) Has been cancelled
Reviewed-on: #65
Co-authored-by: André Menrath <andre.menrath@posteo.de>
Co-committed-by: André Menrath <andre.menrath@posteo.de>
2024-10-13 16:03:10 +02:00
29 changed files with 452 additions and 570 deletions

View file

@ -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
@ -74,11 +74,6 @@ jobs:
if: steps.cache-wordpress.outputs.cache-hit != 'false'
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 6.6 false true true true
- name: Run Feature tests of the ActivityPub Event Bridge
run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=reminder
env:
PHP_VERSION: ${{ matrix.php-version }}
- name: Run Integration tests for The Events Calendar
run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=the_events_calendar
env:
@ -108,3 +103,8 @@ jobs:
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 }}

View file

@ -39,9 +39,19 @@ These platforms create public event calendars by pulling in events from various
<img src="./.wordpress-org/decentralized-event-calenders.gif" alt="Logo" width="250" />
</p>
Even platforms that dont 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/)
## Configuration

View file

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

View file

@ -1,8 +0,0 @@
{
"name": "reminder",
"title": "Reminder Plugin: not a block, but block.json is very useful.",
"category": "widgets",
"icon": "admin-comments",
"keywords": [],
"editorScript": "file:./plugin.js"
}

View file

@ -1 +0,0 @@
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-components', 'wp-core-data', 'wp-data', 'wp-editor', 'wp-i18n', 'wp-plugins'), 'version' => 'd491284dfb7e5078a777');

View file

@ -1 +0,0 @@
(()=>{"use strict";const e=window.wp.editor,t=window.wp.plugins,i=window.wp.components,n=window.wp.data,a=window.wp.coreData,r=window.wp.i18n,d=window.ReactJSXRuntime,p=activityPubEventBridge.reminderTypeGap;(0,t.registerPlugin)("activitypub-event-bridge-reminder",{render:()=>{const t=(0,n.useSelect)((e=>e("core/editor").getCurrentPostType()),[]),[_,b]=(0,a.useEntityProp)("postType",t,"meta"),u=_?.activitypub_event_bridge_reminder_time_gap?_?.activitypub_event_bridge_reminder_time_gap:p;return(0,d.jsx)(e.PluginDocumentSettingPanel,{name:"activitypub",title:(0,r.__)("Send reminder before event's start","activitypub"),children:(0,d.jsx)(i.SelectControl,{label:(0,r.__)("Time gap","activitypub"),value:u,options:[{label:(0,r.__)("Disabled","activitypub-event-bridge"),value:0},{label:(0,r.__)("6 hours","activitypub-event-bridge"),value:21600},{label:(0,r.__)("1 day","activitypub-event-bridge"),value:86400},{label:(0,r.__)("3 days","activitypub-event-bridge"),value:259200},{label:(0,r.__)("1 week","activitypub-event-bridge"),value:604800}],onChange:e=>{b({..._,activitypub_event_bridge_reminder_time_gap:e})},__nextHasNoMarginBottom:!0})})}})})();

View file

@ -53,19 +53,20 @@
"@test-gatherpress",
"@test-events-manager",
"@test-wp-event-manager",
"@test-eventin"
"@test-eventin",
"@test-modern-events-calendar-lite"
],
"test-debug": [
"@prepare-test",
"@test-wp-event-manager"
"@test-modern-events-calendar-lite"
],
"test-features": "phpunit --filter=reminder",
"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-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"
}
}

16
improvements.md Normal file
View file

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

View file

@ -11,7 +11,6 @@ namespace ActivityPub_Event_Bridge\Activitypub\Transformer;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Activity\Activity;
use Activitypub\Activity\Extended_Object\Event as Event_Object;
use Activitypub\Activity\Extended_Object\Place;
use Activitypub\Transformer\Post;
@ -149,7 +148,7 @@ abstract class Event extends Post {
*
* This is mandatory and must be implemented in the final event transformer class.
*/
abstract public function get_start_time(): string;
abstract protected function get_start_time(): string;
/**
* Get the end time.
@ -377,19 +376,4 @@ abstract class Event extends Post {
return $activitypub_object;
}
/**
* Creates an activity for announcing itself.
*
* @return Activity The Activity.
*/
public function to_announce_self_activity() {
$activity = new Activity();
$activity->set_type( 'Announce' );
// Pre-fill the Activity with data (for example cc and to).
$activity->set_object( $this->get_id() );
return $activity;
}
}

View file

@ -80,7 +80,7 @@ final class GatherPress extends Event {
/**
* Get the end time from the event object.
*/
public function get_start_time(): string {
protected function get_start_time(): string {
return $this->gp_event->get_datetime_start( 'Y-m-d\TH:i:s\Z' );
}

View file

@ -0,0 +1,114 @@
<?php
/**
* ActivityPub Tribe Transformer
*
* @package ActivityPub_Event_Bridge
* @license AGPL-3.0-or-later
*/
namespace ActivityPub_Event_Bridge\Activitypub\Transformer;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Activity\Extended_Object\Place;
use ActivityPub_Event_Bridge\Activitypub\Transformer\Event;
use MEC;
use MEC\Events\Event as MEC_Event;
use MEC_main;
/**
* ActivityPub Tribe Transformer
*
* @since 1.0.0
*/
final class Modern_Events_Calendar_Lite extends Event {
/**
* The MEC Event object.
*
* @var MEC_Event|null
*/
protected $mec_event;
/**
* The MEC main instance.
*
* @var MEC_main|null
*/
protected $mec_main;
/**
* Extend the constructor, to also set the tribe object.
*
* This is a special class object form The Events Calendar which
* has a lot of useful functions, we make use of our getter functions.
*
* @param WP_Post $wp_object The WordPress object.
* @param string $wp_taxonomy The taxonomy slug of the event post type.
*/
public function __construct( $wp_object, $wp_taxonomy ) {
parent::__construct( $wp_object, $wp_taxonomy );
$this->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;
}
}

View file

@ -83,7 +83,7 @@ final class The_Events_Calendar extends Event {
/**
* Get the end time from the event object.
*/
public function get_start_time(): string {
protected function get_start_time(): string {
$date = date_create( $this->tribe_event->start_date, wp_timezone() );
return \gmdate( 'Y-m-d\TH:i:s\Z', $date->getTimestamp() );
}

View file

@ -58,7 +58,7 @@ final class VS_Event_List extends Event_Transformer {
/**
* Get the end time from the events metadata.
*/
public function get_start_time(): string {
protected function get_start_time(): string {
$start_time = get_post_meta( $this->wp_object->ID, 'event-start-date', true );
return \gmdate( 'Y-m-d\TH:i:s\Z', $start_time );
}

View file

@ -1,194 +0,0 @@
<?php
/**
* 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.
*
* @package ActivityPub_Event_Bridge
* @since 1.0.0
*/
namespace ActivityPub_Event_Bridge;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Activity_Dispatcher;
use Activitypub\Transformer\Factory as Transformer_Factory;
use ActivityPub_Event_Bridge\Setup;
use ActivityPub_Event_Bridge\Activitypub\Transformer\Event as Event_Transformer;
use DateTime;
use function Activitypub\is_user_disabled;
/**
* Adds automatic announcing or sending of reminders before the events start time.
*/
class Reminder {
/**
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
// Post transitions.
\add_action( 'transition_post_status', array( self::class, 'maybe_schedule_event_reminder' ), 33, 3 );
\add_action( 'delete_post', array( self::class, 'unschedule_event_reminder' ), 33, 1 );
// Send an event reminder.
\add_action( 'activitypub_event_bridge_send_event_reminder', array( self::class, 'send_event_reminder' ), 10, 1 );
// Load the block which allows overriding the reminder time for an individual event in the post settings.
\add_action( 'enqueue_block_editor_assets', array( self::class, 'enqueue_editor_assets' ) );
// Register the post-meta which stores per-event overrides of the side-wide default of the reminder time gap.
\add_action( 'init', array( self::class, 'register_postmeta' ), 11 );
}
/**
* Register post meta for controlling whether and when a reminder is scheduled for an individual event.
*/
public static function register_postmeta() {
$ap_post_types = \get_post_types_by_support( 'activitypub' );
foreach ( $ap_post_types as $post_type ) {
\register_post_meta(
$post_type,
'activitypub_event_bridge_reminder_time_gap',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'integer',
'sanitize_callback' => 'absint',
)
);
}
}
/**
* Enqueue the block editor assets.
*/
public static function enqueue_editor_assets() {
// Check for our supported post types.
$current_screen = \get_current_screen();
$event_post_types = Setup::get_instance()->get_active_event_plugins_post_types();
if ( ! $current_screen || ! in_array( $current_screen->post_type, $event_post_types, true ) ) {
return;
}
$asset_data = include ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'build/reminder/plugin.asset.php';
$plugin_url = plugins_url( 'build/reminder/plugin.js', ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_FILE );
wp_enqueue_script( 'activitypub-event-bridge-reminder', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
// Pass the the default site wide time gap option to the settings block on the events edit page.
wp_localize_script(
'activitypub-event-bridge-reminder',
'activityPubEventBridge',
array(
'reminderTypeGap' => \get_option( 'activitypub_event_bridge_reminder_time_gap', 0 ),
)
);
}
/**
* Schedule Activities.
*
* @param string $new_status New post status.
* @param string $old_status Old post status.
* @param WP_Post $post Post object.
*/
public static function maybe_schedule_event_reminder( $new_status, $old_status, $post ): void {
// Re-Check that we got a valid post.
$post = get_post( $post );
if ( ! $post ) {
return;
}
// At first always unschedule the reminder for this event, it will be added again, in case.
self::unschedule_event_reminder( $post->ID );
// Do not set reminders if post is password protected.
if ( \post_password_required( $post ) ) {
return;
}
// Only schedule an reminder for event post types.
if ( ! Setup::get_instance()->is_post_type_event_of_active_event_plugin( $post->post_type ) ) {
return;
}
// Do not schedule a reminder if the event is not published.
if ( 'publish' !== $new_status ) {
return;
}
// See if a reminder time gap is set for the event individually in the events post-meta.
$reminder_time_gap = (int) get_post_meta( $post->ID, 'activitypub_event_bridge_reminder_time_gap', true );
// If not fallback to the global reminder time gap.
if ( ! $reminder_time_gap ) {
$reminder_time_gap = \get_option( 'activitypub_event_bridge_reminder_time_gap', 0 );
}
// Any non positive integer means that this feature is not active for this event post.
if ( 0 === $reminder_time_gap || ! is_int( $reminder_time_gap ) ) {
return;
}
// Get start time of the event.
$event_transformer = Transformer_Factory::get_transformer( $post );
if ( \is_wp_error( $event_transformer ) || ! $event_transformer instanceof Event_Transformer ) {
return;
}
$start_time = $event_transformer->get_start_time();
$start_datetime = new DateTime( $start_time );
$start_timestamp = $start_datetime->getTimestamp();
// Get the time when the reminder of the event's start should be sent.
$schedule_time = $start_timestamp - $reminder_time_gap;
// If the reminder time has already passed "now" skip it.
if ( $schedule_time < \time() ) {
return;
}
// All checks passed: schedule a single event which will trigger the sending of the reminder for this event post.
\wp_schedule_single_event( $schedule_time, 'activitypub_event_bridge_send_event_reminder', array( $post->ID ) );
}
/**
* Unschedule the event reminder.
*
* @param int $post_id The WordPress post ID of the event post.
*/
public static function unschedule_event_reminder( $post_id ): void {
\wp_clear_scheduled_hook( 'activitypub_event_bridge_send_event_reminder', array( $post_id ) );
}
/**
* Send a reminder for an event post.
*
* This currently sends an Announce activity.
*
* @param int $post_id The WordPress post ID of the event post.
*/
public static function send_event_reminder( $post_id ): void {
$post = \get_post( $post_id );
$transformer = Transformer_Factory::get_transformer( $post );
if ( \is_wp_error( $transformer ) || ! $transformer instanceof Event_Transformer ) {
return;
}
$user_id = $transformer->get_wp_user_id();
if ( $user_id > 0 && is_user_disabled( $user_id ) ) {
return;
}
$activity = $transformer->to_announce_self_activity( 'Announce' );
Activity_Dispatcher::send_activity_to_followers( $activity, $user_id, $post );
}
}

View file

@ -44,7 +44,7 @@ class Settings {
'activitypub_event_bridge_default_event_category',
array(
'type' => 'string',
'description' => \__( 'Default standardized federated event category.s', 'activitypub' ),
'description' => \__( 'Define your own custom post template', 'activitypub' ),
'show_in_rest' => true,
'default' => self::DEFAULT_EVENT_CATEGORY,
'sanitize_callback' => array( self::class, 'sanitize_mapped_event_category' ),
@ -56,22 +56,11 @@ class Settings {
'activitypub_event_bridge_event_category_mappings',
array(
'type' => 'array',
'description' => \__( 'Category mappings to standardized federated event categories.', 'activitypub' ),
'description' => \__( 'Define your own custom post template', 'activitypub' ),
'default' => array(),
'sanitize_callback' => array( self::class, 'sanitize_event_category_mappings' ),
)
);
\register_setting(
'activitypub-event-bridge',
'activitypub_event_bridge_reminder_time_gap',
array(
'type' => 'array',
'description' => \__( 'Time gap in seconds when a reminder is triggered that the event is about to start.', 'activitypub' ),
'default' => 0, // Zero leads to this feature being deactivated.
'sanitize_callback' => 'absint',
)
);
}
/**

View file

@ -18,7 +18,6 @@ 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\Settings_Page;
use ActivityPub_Event_Bridge\Reminder;
use ActivityPub_Event_Bridge\Plugins\Event_Plugin;
require_once ABSPATH . 'wp-admin/includes/plugin.php';
@ -119,38 +118,6 @@ class Setup {
return $this->active_event_plugins;
}
/**
* Getter function for the active event plugins post types.
*
* @return array List of event post types of the active event plugins.
*/
public function get_active_event_plugins_post_types() {
$post_types = array();
foreach ( $this->active_event_plugins as $event_plugin ) {
$post_types[] = $event_plugin->get_post_type();
}
return $post_types;
}
/**
* Function to check whether a post type is an event post type of an active event plugin.
*
* @param string $post_type The post type.
*
* @return bool True if it is an event post type.
*/
public function is_post_type_event_of_active_event_plugin( $post_type ) {
foreach ( $this->active_event_plugins as $event_plugin ) {
if ( $post_type === $event_plugin->get_post_type() ) {
return true;
}
}
return false;
}
/**
* Holds all the classes for the supported event plugins.
*
@ -163,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',
);
/**
@ -219,8 +187,6 @@ class Setup {
return;
}
add_action( 'init', array( Reminder::class, 'init' ) );
add_filter( 'activitypub_transformer', array( $this, 'register_activitypub_event_transformer' ), 10, 3 );
}

View file

@ -69,8 +69,6 @@ abstract class Event_Plugin {
/**
* Returns the Activitypub transformer for the event plugins event post type.
*
* @return string
*/
public static function get_activitypub_event_transformer_class(): string {
return str_replace( 'Plugins', 'Activitypub\Transformer', static::class );

View file

@ -0,0 +1,61 @@
<?php
/**
* Modern Events Calendar (Lite)
*
* Defines all the necessary meta information for the Modern Events Calendar (Lite).
*
* @link https://webnus.net/modern-events-calendar/
* @package ActivityPub_Event_Bridge
* @since 1.0.0
*/
namespace ActivityPub_Event_Bridge\Plugins;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
/**
* Interface for a supported event plugin.
*
* This interface defines which information is necessary for a supported event plugin.
*
* @since 1.0.0
*/
final class Modern_Events_Calendar_Lite extends Event_plugin {
/**
* Returns the full plugin file.
*
* @return string
*/
public static function get_plugin_file(): string {
return 'modern-events-calendar-lite/modern-events-calendar-lite.php';
}
/**
* Returns the event post type of the plugin.
*
* @return string
*/
public static function get_post_type(): string {
// See MEC_feature_events->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';
}
}

View file

@ -1,19 +0,0 @@
{
"name": "activitypub-poll",
"version": "0.1.0",
"author": {
"name": "André Menrath",
"web": "https://graz.social/@linos"
},
"scripts": {
"dev": "wp-scripts start",
"build": "wp-scripts build",
"readme": "grunt wp_readme_to_markdown"
},
"devDependencies": {
"@wordpress/scripts": "^30.0.2",
"grunt-wp-readme-to-markdown": "^2.0.1",
"classnames": "^2.3.2"
}
}

View file

@ -26,12 +26,6 @@
<!-- Exclude the Node Modules directory. -->
<exclude-pattern>/node_modules/*</exclude-pattern>
<!-- Exclude Javascript files. -->
<exclude-pattern>*.js</exclude-pattern>
<!-- Exclude build assets from js blocks. -->
<exclude-pattern>/build/*/*asset.php</exclude-pattern>
<!-- Exclude minified Javascript files. -->
<exclude-pattern>*.min.js</exclude-pattern>

View file

@ -32,6 +32,16 @@ These platforms create public event calendars by pulling in events from various
![](./.wordpress-org/decentralized-event-calenders.gif)
= 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.
**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.
@ -41,6 +51,9 @@ 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/)
== Configuration ==

View file

@ -1,9 +0,0 @@
{
"name": "reminder",
"title": "Reminder Plugin: not a block, but block.json is very useful.",
"category": "widgets",
"icon": "admin-comments",
"keywords": [
],
"editorScript": "file:./plugin.js"
}

View file

@ -1,43 +0,0 @@
import { PluginDocumentSettingPanel } from '@wordpress/editor';
import { registerPlugin } from '@wordpress/plugins';
import { SelectControl } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useEntityProp } from '@wordpress/core-data';
import { __ } from '@wordpress/i18n';
const reminderTimeGapDefault = activityPubEventBridge.reminderTypeGap;
const Reminder = () => {
const postType = useSelect(
( select ) => select( 'core/editor' ).getCurrentPostType(),
[]
);
const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );
const reminderTimeGap = meta?.activitypub_event_bridge_reminder_time_gap ? meta?.activitypub_event_bridge_reminder_time_gap : reminderTimeGapDefault;
return (
<PluginDocumentSettingPanel
name="activitypub"
title={ __( 'Send reminder before event\'s start', 'activitypub' ) }
>
<SelectControl
label={ __( 'Time gap', 'activitypub' ) }
value={ reminderTimeGap }
options={ [
{ label: __( 'Disabled', 'activitypub-event-bridge' ), value: 0 },
{ label: __( '6 hours', 'activitypub-event-bridge' ), value: 21600 },
{ label: __( '1 day', 'activitypub-event-bridge' ), value: 86400 },
{ label: __( '3 days', 'activitypub-event-bridge' ), value: 259200 },
{ label: __( '1 week', 'activitypub-event-bridge' ), value: 604800 }
] }
onChange={ ( value ) => {
setMeta( { ...meta, activitypub_event_bridge_reminder_time_gap: value } );
} }
__nextHasNoMarginBottom
/>
</PluginDocumentSettingPanel>
);
}
registerPlugin( 'activitypub-event-bridge-reminder', { render: Reminder } );

View file

@ -29,15 +29,6 @@ require_once ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . '/includes/event-categories.p
$selected_default_event_category = \get_option( 'activitypub_event_bridge_default_event_category', 'MEETING' );
$current_category_mapping = \get_option( 'activitypub_event_bridge_event_category_mappings', array() );
$reminder_time_gap = \get_option( 'activitypub_event_bridge_reminder_time_gap', 0 );
$reminder_time_gap_choices = array(
0 => __( 'Disabled', 'activitypub-event-bridge' ),
HOUR_IN_SECONDS * 6 => __( '6 hours', 'activitypub-event-bridge' ),
DAY_IN_SECONDS => __( '1 day', 'activitypub-event-bridge' ),
DAY_IN_SECONDS * 3 => __( '3 days', 'activitypub-event-bridge' ),
WEEK_IN_SECONDS => __( '1 week', 'activitypub-event-bridge' ),
)
?>
<div class="activitypub-settings-header">
@ -107,17 +98,6 @@ $reminder_time_gap_choices = array(
</table>
</div>
<?php endif; ?>
<div class="box">
<h2> <?php esc_html_e( 'Send reminder before event', 'activitypub-event-bridge' ); ?> </h2>
<p> <?php esc_html_e( 'Specify a time interval before the event starts to trigger a reminder. This reminder automatically boosts the event, making it reappear in users\' timelines at the defined time before the event to increase visibility just before the event begins.', 'activitypub-event-bridge' ); ?> </p>
<select id="activitypub_event_bridge_reminder_time_gap" name="activitypub_event_bridge_reminder_time_gap">';
<?php
foreach ( $reminder_time_gap_choices as $value => $label ) {
echo '<option value="' . esc_attr( $value ) . '" ' . selected( $reminder_time_gap, $value, false ) . '>' . esc_html( $label ) . '</option>';
}
?>
</select>
</div>
<?php \submit_button(); ?>
</form>
</div>

View file

@ -50,6 +50,9 @@ function _manually_load_plugin() {
$plugin_file = null;
// See if we want to run integration tests for a specific event-plugin.
switch ( $activitypub_event_extension_integration_filter ) {
case 'the_events_calendar':
$plugin_file = 'the-events-calendar/the-events-calendar.php';
break;
case 'vs_event_list':
$plugin_file = 'very-simple-event-list/vsel.php';
break;
@ -59,15 +62,15 @@ 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;
case 'wp_event_manager':
$plugin_file = 'wp-event-manager/wp-event-manager.php';
break;
default:
// By default we test other stuff using The Events Calendar.
$plugin_file = 'the-events-calendar/the-events-calendar.php';
}
if ( $plugin_file ) {
@ -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';
}

View file

@ -16,7 +16,7 @@ class Test_Events_Manager extends WP_UnitTestCase {
parent::set_up();
if ( ! class_exists( 'EM_Events' ) ) {
self::markTestSkipped( 'Events Manager plugin is not active.' );
self::markTestSkipped( 'VS Event List plugin is not active.' );
}
// For tests allow every user to create new events.

View file

@ -1,12 +1,12 @@
<?php
/**
* Tests for GatherPress.
* Class SampleTest
*
* @package ActivityPub_Event_Bridge
*/
/**
* Test class for testing the GatherPress integration.
* Sample test case.
*/
class Test_GatherPress extends WP_UnitTestCase {
/**

View file

@ -0,0 +1,185 @@
<?php
/**
* Tests or Modern Events Calendar Lite
*
* @package ActivityPub_Event_Bridge
*/
/**
* Sample test case.
*/
class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
/**
* The MEC main instance.
*
* @var \MEC_main|null
*/
protected $mec_main;
/**
* 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( '\MEC' ) ) {
self::markTestSkipped( 'Modern Events Calendar Lite 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();
$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'] );
}
}

View file

@ -1,183 +0,0 @@
<?php
/**
* Test for Reminder class.
*
* @package ActivityPub_Event_Bridge
*/
/**
* Test class for testing the scheduling of reminder Activities.
*/
class Test_Reminder extends WP_UnitTestCase {
/**
* Mockup events of certain complexity.
*/
public const MOCKUP_VENUE = array(
'venue' => 'Minimal Venue',
'status' => 'publish',
);
public const MOCKUP_EVENT = array(
'title' => 'My Event',
'content' => 'Come to my event!',
'start_date' => '+10 days 15:00:00',
'duration' => HOUR_IN_SECONDS,
'status' => 'publish',
);
/**
* 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( '\Tribe__Events__Main' ) ) {
self::markTestSkipped( 'The Events Calendar is not active.' );
}
// For tests allow every user to create new events.
update_option( 'dbem_events_anonymous_submissions', true );
// Make sure that ActivityPub support is enabled for Events Manager.
$aec = \ActivityPub_Event_Bridge\Setup::get_instance();
$aec->activate_activitypub_support_for_active_event_plugins();
// Delete all posts afterwards.
_delete_all_posts();
}
/**
* Test that with the default reminder setting (time-gap is zero) no reminder event is scheduled.
*/
public function test_event_reminder_not_being_scheduled_by_default() {
// Create a The Events Calendar Event.
$wp_object = tribe_events()
->set_args( self::MOCKUP_EVENT )
->create();
$scheduled_event = \wp_get_scheduled_event( 'activitypub_event_bridge_send_event_reminder', array( $wp_object->ID ) );
$this->assertEquals( false, $scheduled_event );
}
/**
* Test that with a side-wide option the reminder is scheduled.
*/
public function test_event_reminder_scheduled_with_site_wide_option() {
\update_option( 'activitypub_event_bridge_reminder_time_gap', DAY_IN_SECONDS );
// Create a The Events Calendar Event.
$wp_object = tribe_events()
->set_args( self::MOCKUP_EVENT )
->create();
$scheduled_event = \wp_get_scheduled_event( 'activitypub_event_bridge_send_event_reminder', array( $wp_object->ID ) );
$this->assertNotEquals( false, $scheduled_event );
$this->assertEquals( strtotime( self::MOCKUP_EVENT['start_date'] ) - DAY_IN_SECONDS, $scheduled_event->timestamp );
$this->assertEquals( false, $scheduled_event->schedule );
$this->assertEquals( 'activitypub_event_bridge_send_event_reminder', $scheduled_event->hook );
}
/**
* Test that a specific event can override the side-wide reminder default.
*/
public function test_event_reminder_scheduled_with_per_event_override() {
\update_option( 'activitypub_event_bridge_reminder_time_gap', DAY_IN_SECONDS );
// Create a The Events Calendar Event.
$wp_object = tribe_events()
->set_args(
array_merge(
self::MOCKUP_EVENT,
array( 'activitypub_event_bridge_reminder_time_gap' => DAY_IN_SECONDS * 3 ),
)
)
->create();
$scheduled_event = \wp_get_scheduled_event( 'activitypub_event_bridge_send_event_reminder', array( $wp_object->ID ) );
$this->assertNotEquals( false, $scheduled_event );
$this->assertEquals( strtotime( self::MOCKUP_EVENT['start_date'] ) - DAY_IN_SECONDS * 3, $scheduled_event->timestamp );
$this->assertEquals( false, $scheduled_event->schedule );
$this->assertEquals( 'activitypub_event_bridge_send_event_reminder', $scheduled_event->hook );
// Now update the option once more to see if the schedule got updated too.
$post_id = array_key_first(
tribe_events( $wp_object->ID )
->set_args(
array( 'activitypub_event_bridge_reminder_time_gap' => HOUR_IN_SECONDS ),
)
->save()
);
$scheduled_event = \wp_get_scheduled_event( 'activitypub_event_bridge_send_event_reminder', array( $post_id ) );
$this->assertNotEquals( false, $scheduled_event );
$this->assertEquals( strtotime( self::MOCKUP_EVENT['start_date'] ) - HOUR_IN_SECONDS, $scheduled_event->timestamp );
}
/**
* Test that the scheduled reminder is removed when the event is deleted.
*/
public function test_event_reminder_deleted_event() {
\update_option( 'activitypub_event_bridge_reminder_time_gap', DAY_IN_SECONDS );
// Create a The Events Calendar Event.
$wp_object = tribe_events()
->set_args(
array_merge(
self::MOCKUP_EVENT,
array( 'activitypub_event_bridge_reminder_time_gap' => DAY_IN_SECONDS * 3 ),
)
)
->create();
$scheduled_event = \wp_get_scheduled_event( 'activitypub_event_bridge_send_event_reminder', array( $wp_object->ID ) );
$this->assertNotEquals( false, $scheduled_event );
$this->assertEquals( strtotime( self::MOCKUP_EVENT['start_date'] ) - DAY_IN_SECONDS * 3, $scheduled_event->timestamp );
$this->assertEquals( false, $scheduled_event->schedule );
$this->assertEquals( 'activitypub_event_bridge_send_event_reminder', $scheduled_event->hook );
// Now delete the event.
tribe_events( $wp_object->ID )->delete();
$scheduled_event = \wp_get_scheduled_event( 'activitypub_event_bridge_send_event_reminder', array( $wp_object->ID ) );
$this->assertEquals( false, $scheduled_event );
}
/**
* Test that the scheduled reminder is removed when the event is moved to trash.
*/
public function test_event_reminder_event_moved_to_trash() {
\update_option( 'activitypub_event_bridge_reminder_time_gap', DAY_IN_SECONDS );
// Create a The Events Calendar Event.
$wp_object = tribe_events()
->set_args(
array_merge(
self::MOCKUP_EVENT,
array( 'activitypub_event_bridge_reminder_time_gap' => DAY_IN_SECONDS * 3 ),
)
)
->create();
$scheduled_event = \wp_get_scheduled_event( 'activitypub_event_bridge_send_event_reminder', array( $wp_object->ID ) );
$this->assertNotEquals( false, $scheduled_event );
$this->assertEquals( strtotime( self::MOCKUP_EVENT['start_date'] ) - DAY_IN_SECONDS * 3, $scheduled_event->timestamp );
$this->assertEquals( false, $scheduled_event->schedule );
$this->assertEquals( 'activitypub_event_bridge_send_event_reminder', $scheduled_event->hook );
// Now move the event to the trash.
$post_id = array_key_first(
tribe_events( $wp_object->ID )
->set_args(
array( 'post_status' => 'trash' ),
)
->save()
);
$scheduled_event = \wp_get_scheduled_event( 'activitypub_event_bridge_send_event_reminder', array( $wp_object->ID ) );
$this->assertEquals( false, $scheduled_event );
}
}