Compare commits

..

9 commits

Author SHA1 Message Date
1bb603331a improve docs and readability
All checks were successful
PHP Code Checker / PHP Code Checker (pull_request) Successful in 34s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m3s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m3s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m2s
2024-10-13 15:39:23 +02:00
670b9aec50 Merge branch 'main' into self_announce
All checks were successful
PHP Code Checker / PHP Code Checker (pull_request) Successful in 34s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m3s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m3s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m3s
2024-10-13 15:23:04 +02:00
d7a5b140ec use builds not dev js
All checks were successful
PHP Code Checker / PHP Code Checker (pull_request) Successful in 35s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m1s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m0s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 58s
2024-10-12 09:42:21 +02:00
ecb6fce8bc Merge branch 'main' into self_announce
All checks were successful
PHP Code Checker / PHP Code Checker (pull_request) Successful in 41s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m7s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m5s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m8s
2024-10-11 19:30:57 +02:00
4263233e10 fix phpcs
All checks were successful
PHP Code Checker / PHP Code Checker (pull_request) Successful in 40s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m4s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m7s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m7s
2024-10-11 19:27:12 +02:00
d5dcfd5e0c exclude js from phpcs
Some checks failed
PHP Code Checker / PHP Code Checker (pull_request) Failing after 41s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m4s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m45s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m8s
2024-10-11 19:20:47 +02:00
2d902806ee phpcs
Some checks failed
PHP Code Checker / PHP Code Checker (pull_request) Failing after 35s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m7s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m2s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m8s
2024-10-11 19:07:07 +02:00
63bf5d26ce add tests
Some checks failed
PHP Code Checker / PHP Code Checker (pull_request) Failing after 41s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m6s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Has been cancelled
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Has been cancelled
2024-10-11 19:05:20 +02:00
92fc3ecec0 first draft for event reminders
Some checks failed
PHP Code Checker / PHP Code Checker (pull_request) Failing after 42s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m10s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m1s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m3s
2024-10-11 10:53:12 +02:00
29 changed files with 570 additions and 452 deletions

View file

@ -37,7 +37,7 @@ jobs:
path: | path: |
${{ env.WP_CORE_DIR }} ${{ env.WP_CORE_DIR }}
${{ env.WP_TESTS_DIR }} ${{ env.WP_TESTS_DIR }}
key: cache-wordpress-9 key: cache-wordpress-8
- name: Cache Composer - name: Cache Composer
id: cache-composer-phpunit id: cache-composer-phpunit
@ -74,6 +74,11 @@ jobs:
if: steps.cache-wordpress.outputs.cache-hit != 'false' 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 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 - name: Run Integration tests for The Events Calendar
run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=the_events_calendar run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=the_events_calendar
env: env:
@ -101,10 +106,5 @@ jobs:
- name: Run Integration tests for Eventin (WP Event Solution) - name: Run Integration tests for Eventin (WP Event Solution)
run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=eventin 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: env:
PHP_VERSION: ${{ matrix.php-version }} PHP_VERSION: ${{ matrix.php-version }}

View file

@ -39,19 +39,9 @@ These platforms create public event calendars by pulling in events from various
<img src="./.wordpress-org/decentralized-event-calenders.gif" alt="Logo" width="250" /> <img src="./.wordpress-org/decentralized-event-calenders.gif" alt="Logo" width="250" />
</p> </p>
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 dont 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. 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 ## 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. 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.
@ -61,10 +51,6 @@ This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/ac
* [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/) * [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/)
* [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/) * [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/)
* [Events Manager](https://de.wordpress.org/plugins/events-manager/) * [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 ## Configuration

View file

@ -226,20 +226,6 @@ install_wp_plugin() {
unzip -q -o "$TMPDIR/$PLUGIN_FILE" -d "$WP_CORE_DIR/wp-content/plugins/" 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() { install_wp_plugins() {
if [ "$SKIP_PLUGINS_INSTALL" = "true" ]; then if [ "$SKIP_PLUGINS_INSTALL" = "true" ]; then
echo "Skipping WordPress plugin installation." echo "Skipping WordPress plugin installation."
@ -254,8 +240,6 @@ install_wp_plugins() {
install_wp_plugin events-manager install_wp_plugin events-manager
install_wp_plugin wp-event-manager install_wp_plugin wp-event-manager
install_wp_plugin wp-event-solution install_wp_plugin wp-event-solution
# Mec is not installable via wordpress.org, we use our own mirror.
install_wp_plugin_mec
} }
install_wp install_wp

View file

@ -0,0 +1,8 @@
{
"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

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

1
build/reminder/plugin.js Normal file
View file

@ -0,0 +1 @@
(()=>{"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,20 +53,19 @@
"@test-gatherpress", "@test-gatherpress",
"@test-events-manager", "@test-events-manager",
"@test-wp-event-manager", "@test-wp-event-manager",
"@test-eventin", "@test-eventin"
"@test-modern-events-calendar-lite"
], ],
"test-debug": [ "test-debug": [
"@prepare-test", "@prepare-test",
"@test-modern-events-calendar-lite" "@test-wp-event-manager"
], ],
"test-features": "phpunit --filter=reminder",
"test-vs-event-list": "phpunit --filter=vs_event_list", "test-vs-event-list": "phpunit --filter=vs_event_list",
"test-the-events-calendar": "phpunit --filter=the_events_calendar", "test-the-events-calendar": "phpunit --filter=the_events_calendar",
"test-gatherpress": "phpunit --filter=gatherpress", "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-wp-event-manager": "phpunit --filter=wp_event_manager",
"test-eventin": "phpunit --filter=eventin", "test-eventin": "phpunit --filter=eventin",
"test-modern-events-calendar-lite": "phpunit --filter=modern_events_calendar_lite",
"test-all": "phpunit" "test-all": "phpunit"
} }
} }

View file

@ -1,16 +0,0 @@
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,6 +11,7 @@ namespace ActivityPub_Event_Bridge\Activitypub\Transformer;
// Exit if accessed directly. // Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Activity\Activity;
use Activitypub\Activity\Extended_Object\Event as Event_Object; use Activitypub\Activity\Extended_Object\Event as Event_Object;
use Activitypub\Activity\Extended_Object\Place; use Activitypub\Activity\Extended_Object\Place;
use Activitypub\Transformer\Post; use Activitypub\Transformer\Post;
@ -148,7 +149,7 @@ abstract class Event extends Post {
* *
* This is mandatory and must be implemented in the final event transformer class. * This is mandatory and must be implemented in the final event transformer class.
*/ */
abstract protected function get_start_time(): string; abstract public function get_start_time(): string;
/** /**
* Get the end time. * Get the end time.
@ -376,4 +377,19 @@ abstract class Event extends Post {
return $activitypub_object; 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. * Get the end time from the event object.
*/ */
protected function get_start_time(): string { public function get_start_time(): string {
return $this->gp_event->get_datetime_start( 'Y-m-d\TH:i:s\Z' ); return $this->gp_event->get_datetime_start( 'Y-m-d\TH:i:s\Z' );
} }

View file

@ -1,114 +0,0 @@
<?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. * Get the end time from the event object.
*/ */
protected function get_start_time(): string { public function get_start_time(): string {
$date = date_create( $this->tribe_event->start_date, wp_timezone() ); $date = date_create( $this->tribe_event->start_date, wp_timezone() );
return \gmdate( 'Y-m-d\TH:i:s\Z', $date->getTimestamp() ); 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. * Get the end time from the events metadata.
*/ */
protected function get_start_time(): string { public function get_start_time(): string {
$start_time = get_post_meta( $this->wp_object->ID, 'event-start-date', true ); $start_time = get_post_meta( $this->wp_object->ID, 'event-start-date', true );
return \gmdate( 'Y-m-d\TH:i:s\Z', $start_time ); return \gmdate( 'Y-m-d\TH:i:s\Z', $start_time );
} }

194
includes/class-reminder.php Normal file
View file

@ -0,0 +1,194 @@
<?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', 'activitypub_event_bridge_default_event_category',
array( array(
'type' => 'string', 'type' => 'string',
'description' => \__( 'Define your own custom post template', 'activitypub' ), 'description' => \__( 'Default standardized federated event category.s', 'activitypub' ),
'show_in_rest' => true, 'show_in_rest' => true,
'default' => self::DEFAULT_EVENT_CATEGORY, 'default' => self::DEFAULT_EVENT_CATEGORY,
'sanitize_callback' => array( self::class, 'sanitize_mapped_event_category' ), 'sanitize_callback' => array( self::class, 'sanitize_mapped_event_category' ),
@ -56,11 +56,22 @@ class Settings {
'activitypub_event_bridge_event_category_mappings', 'activitypub_event_bridge_event_category_mappings',
array( array(
'type' => 'array', 'type' => 'array',
'description' => \__( 'Define your own custom post template', 'activitypub' ), 'description' => \__( 'Category mappings to standardized federated event categories.', 'activitypub' ),
'default' => array(), 'default' => array(),
'sanitize_callback' => array( self::class, 'sanitize_event_category_mappings' ), '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,6 +18,7 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use ActivityPub_Event_Bridge\Admin\Event_Plugin_Admin_Notices; use ActivityPub_Event_Bridge\Admin\Event_Plugin_Admin_Notices;
use ActivityPub_Event_Bridge\Admin\General_Admin_Notices; use ActivityPub_Event_Bridge\Admin\General_Admin_Notices;
use ActivityPub_Event_Bridge\Admin\Settings_Page; use ActivityPub_Event_Bridge\Admin\Settings_Page;
use ActivityPub_Event_Bridge\Reminder;
use ActivityPub_Event_Bridge\Plugins\Event_Plugin; use ActivityPub_Event_Bridge\Plugins\Event_Plugin;
require_once ABSPATH . 'wp-admin/includes/plugin.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php';
@ -118,6 +119,38 @@ class Setup {
return $this->active_event_plugins; 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. * Holds all the classes for the supported event plugins.
* *
@ -130,7 +163,6 @@ class Setup {
'\ActivityPub_Event_Bridge\Plugins\VS_Event_List', '\ActivityPub_Event_Bridge\Plugins\VS_Event_List',
'\ActivityPub_Event_Bridge\Plugins\WP_Event_Manager', '\ActivityPub_Event_Bridge\Plugins\WP_Event_Manager',
'\ActivityPub_Event_Bridge\Plugins\Eventin', '\ActivityPub_Event_Bridge\Plugins\Eventin',
'\ActivityPub_Event_Bridge\Plugins\Modern_Events_Calendar_Lite',
); );
/** /**
@ -187,6 +219,8 @@ class Setup {
return; return;
} }
add_action( 'init', array( Reminder::class, 'init' ) );
add_filter( 'activitypub_transformer', array( $this, 'register_activitypub_event_transformer' ), 10, 3 ); add_filter( 'activitypub_transformer', array( $this, 'register_activitypub_event_transformer' ), 10, 3 );
} }

View file

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

View file

@ -1,61 +0,0 @@
<?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';
}
}

19
package.json Executable file
View file

@ -0,0 +1,19 @@
{
"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,6 +26,12 @@
<!-- Exclude the Node Modules directory. --> <!-- Exclude the Node Modules directory. -->
<exclude-pattern>/node_modules/*</exclude-pattern> <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 minified Javascript files. -->
<exclude-pattern>*.min.js</exclude-pattern> <exclude-pattern>*.min.js</exclude-pattern>

View file

@ -32,16 +32,6 @@ These platforms create public event calendars by pulling in events from various
![](./.wordpress-org/decentralized-event-calenders.gif) ![](./.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 == == Installation ==
This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/). Additionally, you need to use one of the supported event Plugins. This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/). Additionally, you need to use one of the supported event Plugins.
@ -51,9 +41,6 @@ This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/ac
* [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/) * [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/)
* [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/) * [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/)
* [Events Manager](https://de.wordpress.org/plugins/events-manager/) * [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 == == Configuration ==

9
src/reminder/block.json Normal file
View file

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

43
src/reminder/plugin.js Normal file
View file

@ -0,0 +1,43 @@
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,6 +29,15 @@ require_once ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . '/includes/event-categories.p
$selected_default_event_category = \get_option( 'activitypub_event_bridge_default_event_category', 'MEETING' ); $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() ); $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"> <div class="activitypub-settings-header">
@ -98,6 +107,17 @@ $current_category_mapping = \get_option( 'activitypub_event_bridge_event_
</table> </table>
</div> </div>
<?php endif; ?> <?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(); ?> <?php \submit_button(); ?>
</form> </form>
</div> </div>

View file

@ -50,9 +50,6 @@ function _manually_load_plugin() {
$plugin_file = null; $plugin_file = null;
// See if we want to run integration tests for a specific event-plugin. // See if we want to run integration tests for a specific event-plugin.
switch ( $activitypub_event_extension_integration_filter ) { switch ( $activitypub_event_extension_integration_filter ) {
case 'the_events_calendar':
$plugin_file = 'the-events-calendar/the-events-calendar.php';
break;
case 'vs_event_list': case 'vs_event_list':
$plugin_file = 'very-simple-event-list/vsel.php'; $plugin_file = 'very-simple-event-list/vsel.php';
break; break;
@ -62,15 +59,15 @@ function _manually_load_plugin() {
case 'eventin': case 'eventin':
$plugin_file = 'wp-event-solution/eventin.php'; $plugin_file = 'wp-event-solution/eventin.php';
break; break;
case 'modern_events_calendar_lite':
$plugin_file = 'modern-events-calendar-lite/modern-events-calendar-lite.php';
break;
case 'gatherpress': case 'gatherpress':
$plugin_file = 'gatherpress/gatherpress.php'; $plugin_file = 'gatherpress/gatherpress.php';
break; break;
case 'wp_event_manager': case 'wp_event_manager':
$plugin_file = 'wp-event-manager/wp-event-manager.php'; $plugin_file = 'wp-event-manager/wp-event-manager.php';
break; break;
default:
// By default we test other stuff using The Events Calendar.
$plugin_file = 'the-events-calendar/the-events-calendar.php';
} }
if ( $plugin_file ) { if ( $plugin_file ) {
@ -91,12 +88,6 @@ function _manually_load_plugin() {
em_create_locations_table(); 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. // At last manually load our WordPress plugin.
require dirname( __DIR__ ) . '/activitypub-event-bridge.php'; require dirname( __DIR__ ) . '/activitypub-event-bridge.php';
} }

View file

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

View file

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

View file

@ -1,185 +0,0 @@
<?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

@ -0,0 +1,183 @@
<?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 );
}
}