Compare commits

..

1 commit
main ... rename

Author SHA1 Message Date
88d1860a90 Bump version to 0.3.0 2024-12-05 16:32:08 +01:00
34 changed files with 111 additions and 665 deletions

View file

@ -75,11 +75,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 ${{ matrix.wordpress-version }} false true true true
- name: Run General Tests
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=event_bridge_for_activitypub
env:
PHP_VERSION: ${{ matrix.php-version }}
- name: Run Integration tests for The Events Calendar
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=the_events_calendar
env:

View file

@ -9,9 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
* Add custom summary via shortcodes
* Integration for Event Organiser
* Integration for EventPrime Events Calendar, Bookings and Tickets
## [0.3.2] - 2024-12-12
### Fixed
* Fixed that transformer hook function might also return `null`.
## [0.2.1] - 2024-11-16
### Added
* Initial release on WordPress.org
### Fixed
* Applied some WordPress best practices
## [0.2.0] - 2024-10-20
### Added
* Initial submission to WordPress.org

View file

@ -3,7 +3,7 @@
**Tags:** events, fediverse, activitypub, calendar
**Requires at least:** 6.5
**Tested up to:** 6.7
**Stable tag:** 0.3.2
**Stable tag:** 0.3.0
**Requires PHP:** 7.4
**License:** AGPL-3.0-or-later
**License URI:** https://www.gnu.org/licenses/agpl-3.0.html
@ -44,7 +44,7 @@ The Event Federation plugin ensures that users from those platforms are provided
**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. Advanced users can create custom summaries via a set of shortcodes.
**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.
@ -102,7 +102,7 @@ We're always interested in your feedback. Feel free to reach out to us via [E-Ma
## Changelog ##
### [0.3.2] 2024-12-12 ###
### [0.3.0] 2024-12-05 ###
* Initial release on https://wordpress.org/

View file

@ -177,11 +177,3 @@ code.event-bridge-for-activitypub-settings-example-url {
overflow-x: auto;
word-break: break-all;
}
#event_bridge_for_activitypub_summary_type_custom-details {
display: none;
}
#event_bridge_for_activitypub_summary_type_custom-details > details {
padding: 0.5em;
}

View file

@ -0,0 +1,14 @@
jQuery( function( $ ) {
// Accordion handling in various areas.
$( '.event-bridge-for-activitypub-settings-accordion' ).on( 'click', '.event-bridge-for-activitypub-settings-accordion-trigger', function() {
var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) );
if ( isExpanded ) {
$( this ).attr( 'aria-expanded', 'false' );
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true );
} else {
$( this ).attr( 'aria-expanded', 'true' );
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
}
} );
} );

View file

@ -1,34 +0,0 @@
jQuery( function( $ ) {
// Accordion handling in various areas.
$( '.event-bridge-for-activitypub-settings-accordion' ).on( 'click', '.event-bridge-for-activitypub-settings-accordion-trigger', function() {
var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) );
if ( isExpanded ) {
$( this ).attr( 'aria-expanded', 'false' );
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true );
} else {
$( this ).attr( 'aria-expanded', 'true' );
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
}
} );
// Function to toggle visibility of custom details based on selected radio button.
function toggleCustomDetailsForSummary() {
if ($("#event_bridge_for_activitypub_summary_type_custom").is(':checked')) {
$("#event_bridge_for_activitypub_summary_type_custom-details").show();
} else {
$("#event_bridge_for_activitypub_summary_type_custom-details").hide();
}
}
// Run the toggle function on page load.
$(document).ready(function() {
window.console.log("test");
toggleCustomDetailsForSummary(); // Set the correct state on load.
// Listen for changes on the radio buttons
$("input[name=event_bridge_for_activitypub_summary_type]").change(function() {
toggleCustomDetailsForSummary(); // Update visibility on change.
});
});
} );

View file

@ -60,7 +60,7 @@
],
"test-debug": [
"@prepare-test",
"@test-event-bridge-for-activitypub-shortcodes"
"@test-event-organiser"
],
"test-vs-event-list": "phpunit --filter=vs_event_list",
"test-the-events-calendar": "phpunit --filter=the_events_calendar",
@ -71,7 +71,6 @@
"test-modern-events-calendar-lite": "phpunit --filter=modern_events_calendar_lite",
"test-eventprime": "phpunit --filter=eventprime",
"test-event-organiser": "phpunit --filter=event_organiser",
"test-event-bridge-for-activitypub-shortcodes": "phpunit --filter=event_bridge_for_activitypub_shortcodes",
"test-all": "phpunit"
}
}

View file

@ -3,7 +3,7 @@
* Plugin Name: Event Bridge for ActivityPub
* Description: Integrating popular event plugins with the ActivityPub plugin.
* Plugin URI: https://event-federation.eu/
* Version: 0.3.2
* Version: 0.3.0
* Author: André Menrath
* Author URI: https://graz.social/@linos
* Text Domain: event-bridge-for-activitypub
@ -27,8 +27,6 @@ define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_VERSION', current( get_file_data( __FILE__, array( 'Version' ), 'plugin' ) ) );
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_DOMAIN', 'event-bridge-for-activitypub' );
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_ACTIVITYPUB_PLUGIN_MIN_VERSION', '3.2.2' );
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_CUSTOM_SUMMARY', "<ul>\n <li>[ap_start_time]</li>\n <li>[ap_end_time]</li>\n <li>[ap_location]</li>\n</ul>\n[ap_hashcats] [ap_hashtags]" );
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_DEFAULT_SUMMARY_TYPE', 'preset' );
// Include and register the autoloader class for automatic loading of plugin classes.
require_once EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . '/includes/class-autoloader.php';

View file

@ -44,21 +44,21 @@ final class Event_Organiser extends Event {
/**
* Get the end time from the event object.
*/
public function get_end_time(): ?string {
protected function get_end_time(): ?string {
return eo_get_the_end( 'Y-m-d\TH:i:s\Z', $this->wp_object->ID, $this->wp_object->occurrence_id );
}
/**
* Get the end time from the event object.
*/
public function get_start_time(): string {
protected function get_start_time(): string {
return eo_get_the_start( 'Y-m-d\TH:i:s\Z', $this->wp_object->ID, $this->wp_object->occurrence_id );
}
/**
* Get location from the event object.
*/
public function get_location(): ?Place {
protected function get_location(): ?Place {
$venue_id = eo_get_venue( $this->wp_object->ID );
if ( ! $venue_id ) {

View file

@ -13,9 +13,7 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Activity\Extended_Object\Event as Event_Object;
use Activitypub\Activity\Extended_Object\Place;
use Activitypub\Shortcodes;
use Activitypub\Transformer\Post;
use DateTime;
/**
@ -150,14 +148,14 @@ 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.
*
* This is not mandatory and therefore just return null by default.
*/
public function get_end_time(): ?string {
protected function get_end_time(): ?string {
return null;
}
@ -166,14 +164,14 @@ abstract class Event extends Post {
*
* This should be overridden in the actual event transformer.
*/
public function get_location(): ?Place {
protected function get_location(): ?Place {
return null;
}
/**
* Default value for the event status.
*/
public function get_status(): ?string {
protected function get_status(): ?string {
return 'CONFIRMED';
}
@ -196,7 +194,7 @@ abstract class Event extends Post {
*
* @param ?string $time The time which needs to be formatted.
*/
protected static function format_time( $time ) {
private static function format_time( $time ) {
if ( is_null( $time ) ) {
return '';
}
@ -207,160 +205,24 @@ abstract class Event extends Post {
}
/**
* Generates output for the 'ap_start_time' shortcode.
*
* @param ?array $atts The shortcode's attributes.
* @return string The formatted start date and time of the event.
* Format a human readable address.
*/
public function shortcode_start_time( $atts ) {
$start_timestamp = $this->get_start_time();
return $this->generate_time_output( $start_timestamp, $atts, '🗓️', __( 'Start', 'event-bridge-for-activitypub' ) );
}
/**
* Generates output for the 'ap_end_time' shortcode.
*
* @param ?array $atts The shortcode's attributes.
* @return string The formatted end date and time of the event.
*/
public function shortcode_end_time( $atts ) {
$end_timestamp = $this->get_end_time();
return $this->generate_time_output( $end_timestamp, $atts, '⏳', __( 'End', 'event-bridge-for-activitypub' ) );
}
/**
* Generates the formatted time output for a shortcode.
*
* @param int|null $timestamp The timestamp for the event time.
* @param array $atts The shortcode attributes.
* @param string $icon The icon to display.
* @param string $label The label to display (e.g., 'Start', 'End').
* @return string The formatted date and time, or an empty string if the timestamp is invalid.
*/
private function generate_time_output( $timestamp, $atts, $icon, $label ) {
if ( ! $timestamp ) {
return '';
}
$args = shortcode_atts(
array(
'icon' => 'true',
'label' => 'true',
),
$atts
);
$args['icon'] = filter_var( $args['icon'], FILTER_VALIDATE_BOOLEAN );
$args['label'] = filter_var( $args['label'], FILTER_VALIDATE_BOOLEAN );
$output = array();
if ( $args['icon'] ) {
$output[] = $icon;
}
if ( $args['label'] ) {
$output[] = $label . ':';
}
$output[] = self::format_time( $timestamp );
return implode( ' ', $output );
}
/**
* Generates output for the 'ap_location' shortcode.
*
* @param ?array $atts The shortcode's attributes.
* @return string The formatted location/address of the event.
*/
public function shortcode_location( $atts ) {
$args = shortcode_atts(
array(
'icon' => 'true',
'label' => 'true',
'country' => 'true',
'zip' => 'true',
'city' => 'true',
'street' => 'true',
'name' => 'true',
),
$atts,
'ap_location'
);
// Convert attributes to booleans.
$args = array_map(
function ( $value ) {
return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
},
$args
);
protected function format_address(): string {
$location = $this->get_location();
if ( ! $location ) {
if ( is_null( $location ) ) {
return '';
}
$output = array();
if ( $args['icon'] ) {
$output[] = '📍';
$address = $location->get_address();
if ( ! $address ) {
return $location->get_name();
}
if ( $args['label'] ) {
$output[] = esc_html__( 'Location', 'event-bridge-for-activitypub' ) . ':';
}
$output[] = self::format_address( $location->get_address(), $args );
// Join output array into a single string with spaces and return.
return implode( ' ', array_filter( $output ) );
}
/**
* Formats the address based on provided arguments.
*
* @param mixed $address The address data, either as a string or an array.
* @param array $args The arguments for which components to include.
* @return string The formatted address.
*/
protected static function format_address( $address, $args = null ) {
if ( is_string( $address ) ) {
return esc_html( $address );
return $address;
}
if ( is_null( $args ) ) {
$args = array(
'icon' => 'true',
'title' => 'true',
'country' => 'true',
'zip' => 'true',
'city' => 'true',
'street' => 'true',
'name' => 'true',
);
if ( ! is_array( $address ) ) {
return '';
}
if ( is_array( $address ) ) {
$address_parts = array();
$components = array(
'name' => 'name',
'street' => 'streetAddress',
'zip' => 'postalCode',
'city' => 'addressLocality',
'country' => 'addressCountry',
);
foreach ( $components as $arg_key => $address_key ) {
if ( $args[ $arg_key ] && ! empty( $address[ $address_key ] ) ) {
$address_parts[] = esc_html( $address[ $address_key ] );
}
}
return implode( ', ', $address_parts );
}
return '';
return isset( $address['locality'] ) ? $address['locality'] : '';
}
/**
@ -393,68 +255,6 @@ abstract class Event extends Post {
return '';
}
/**
* Register the shortcodes.
*/
public function register_shortcodes() {
foreach ( get_class_methods( self::class ) as $function ) {
if ( 'shortcode_' === substr( $function, 0, 10 ) ) {
add_shortcode( 'ap_' . substr( $function, 10, strlen( $function ) ), array( $this, $function ) );
}
}
}
/**
* Register the shortcodes.
*/
public function unregister_shortcodes() {
foreach ( get_class_methods( self::class ) as $function ) {
if ( 'shortcode_' === substr( $function, 0, 10 ) ) {
remove_shortcode( 'ap_' . substr( $function, 10, strlen( $function ) ), array( $this, $function ) );
}
}
}
/**
* Get the summary.
*/
public function get_summary(): ?string {
if ( 'preset' === get_option( 'event_bridge_for_activitypub_summary_type', 'preset' ) ) {
return $this->format_preset_summary();
}
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$post = $this->wp_object;
$summary = $this->get_event_summary_template();
// It seems that shortcodes are only applied to published posts.
if ( is_preview() ) {
$post->post_status = 'publish';
}
// Register our shortcodes just in time.
Shortcodes::register();
$this->register_shortcodes();
// Fill in the shortcodes.
\setup_postdata( $post );
$summary = \do_shortcode( $summary );
\wp_reset_postdata();
$summary = \wpautop( $summary );
$summary = \preg_replace( '/[\n\r\t]/', '', $summary );
$summary = \trim( $summary );
$summary = \apply_filters( 'event_bridge_for_activitypub_the_summary', $summary, $post );
// Unregister the shortcodes.
Shortcodes::unregister();
$this->unregister_shortcodes();
return $summary;
}
/**
* Create a custom summary.
*
@ -463,7 +263,7 @@ abstract class Event extends Post {
*
* @return string $summary The custom event summary.
*/
public function format_preset_summary(): ?string {
public function get_summary(): ?string {
add_filter( 'activitypub_object_content_template', array( self::class, 'remove_ap_permalink_from_template' ), 2, 2 );
$excerpt = $this->retrieve_excerpt();
// BeforeFirstRelease: decide whether this should be a admin setting.
@ -474,13 +274,9 @@ abstract class Event extends Post {
remove_filter( 'activitypub_object_content_template', array( self::class, 'remove_ap_permalink_from_template' ) );
$category = $this->format_categories();
$start_time = $this->get_start_time();
$end_time = $this->get_end_time();
$address = $this->format_address( $this->get_location() );
$time_atts = array(
'icon' => true,
'label' => true,
);
$start_time = $this->format_start_time();
$end_time = $this->format_end_time();
$address = $this->format_address();
$formatted_items = array();
if ( ! empty( $category ) ) {
@ -488,11 +284,11 @@ abstract class Event extends Post {
}
if ( ! empty( $start_time ) ) {
$formatted_items[] = $this->generate_time_output( $start_time, $time_atts, '🗓️', __( 'Start', 'event-bridge-for-activitypub' ) );
$formatted_items[] = '🗓️ ' . __( 'Start', 'event-bridge-for-activitypub' ) . ': ' . $start_time;
}
if ( ! empty( $end_time ) ) {
$formatted_items[] = $this->generate_time_output( $end_time, $time_atts, '⏳', __( 'End', 'event-bridge-for-activitypub' ) );
$formatted_items[] = '⏳ ' . __( 'End', 'event-bridge-for-activitypub' ) . ': ' . $end_time;
}
if ( ! empty( $address ) ) {
@ -512,18 +308,6 @@ abstract class Event extends Post {
return $summary;
}
/**
* Gets the template to use to generate the summary of the ActivityStreams representation of an event post.
*
* @return string The Template.
*/
protected function get_event_summary_template() {
$summary = \get_option( 'event_bridge_for_activitypub_custom_summary', EVENT_BRIDGE_FOR_ACTIVITYPUB_CUSTOM_SUMMARY );
$template = $summary ?? EVENT_BRIDGE_FOR_ACTIVITYPUB_CUSTOM_SUMMARY;
return apply_filters( 'event_bridge_for_activitypub_summary_template', $template, $this->wp_object );
}
/**
* By default set the timezone of the WordPress site.
*

View file

@ -23,7 +23,7 @@ final class EventPrime extends Event {
/**
* Get the end time from the event object.
*/
public function get_end_time(): ?string {
protected function get_end_time(): ?string {
$timestamp = get_post_meta( $this->wp_object->ID, 'em_end_date', true );
if ( $timestamp ) {
return \gmdate( 'Y-m-d\TH:i:s\Z', $timestamp );
@ -35,7 +35,7 @@ final class EventPrime extends Event {
/**
* Get the end time from the event object.
*/
public function get_start_time(): string {
protected function get_start_time(): string {
$timestamp = get_post_meta( $this->wp_object->ID, 'em_start_date', true );
if ( $timestamp ) {
return \gmdate( 'Y-m-d\TH:i:s\Z', $timestamp );
@ -47,7 +47,7 @@ final class EventPrime extends Event {
/**
* Get location from the event object.
*/
public function get_location(): ?Place {
protected function get_location(): ?Place {
$venue_term_id = get_post_meta( $this->wp_object->ID, 'em_venue', true );
if ( ! $venue_term_id ) {
return null;

View file

@ -73,14 +73,14 @@ final class GatherPress extends Event {
/**
* Get the end time from the event object.
*/
public function get_end_time(): ?string {
protected function get_end_time(): ?string {
return $this->gp_event->get_datetime_end( 'Y-m-d\TH:i:s\Z' );
}
/**
* 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

@ -72,7 +72,7 @@ final class The_Events_Calendar extends Event {
/**
* Get the end time from the event object.
*/
public function get_end_time(): ?string {
protected function get_end_time(): ?string {
if ( empty( $this->tribe_event->end_date ) ) {
return null;
}
@ -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

@ -44,7 +44,7 @@ final class VS_Event_List extends Event_Transformer {
/**
* Get the end time from the events metadata.
*/
public function get_end_time(): ?string {
protected function get_end_time(): ?string {
if ( 'yes' === get_post_meta( $this->wp_object->ID, 'event-hide-end-time', true ) ) {
return null;
}
@ -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

@ -69,15 +69,7 @@ class Event_Plugin_Admin_Notices {
* @return void
*/
private function do_admin_notice_post_type_not_activitypub_enabled(): void {
$all_plugins = get_plugins();
$event_plugin_file = $this->event_plugin::get_relative_plugin_file();
if ( isset( $all_plugins[ $event_plugin_file ]['Name'] ) ) {
$event_plugin_name = $all_plugins[ $event_plugin_file ]['Name'];
} elseif ( isset( get_mu_plugins()[ $event_plugin_file ]['Name'] ) ) {
$event_plugin_name = get_mu_plugins()[ $event_plugin_file ]['Name'];
} else {
return;
}
$event_plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $this->event_plugin::get_plugin_file() );
$activitypub_plugin_data = get_plugin_data( ACTIVITYPUB_PLUGIN_FILE );
$notice = sprintf(
/* translators: 1: the name of the event plugin a admin notice is shown. 2: The name of the ActivityPub plugin. */
@ -87,7 +79,7 @@ class Event_Plugin_Admin_Notices {
'event-bridge-for-activitypub'
),
esc_html( $activitypub_plugin_data['Name'] ),
esc_html( $event_plugin_name ),
esc_html( $event_plugin_data['Name'] ),
admin_url( 'options-general.php?page=activitypub&tab=settings' )
);
$allowed_html = array(

View file

@ -114,9 +114,7 @@ class Health_Check {
*/
public static function get_most_recent_event_posts( $event_post_type = null, $number_of_posts = 5 ) {
if ( ! $event_post_type ) {
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
$active_event_plugin = reset( $active_event_plugins );
$event_post_type = $active_event_plugin->get_post_type();
$event_post_type = Setup::get_instance()->get_active_event_plugins()[0]->get_post_type();
}
$args = array(
@ -150,7 +148,7 @@ class Health_Check {
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
$info = array();
foreach ( $active_event_plugins as $active_event_plugin ) {
$event_plugin_file = $active_event_plugin->get_relative_plugin_file();
$event_plugin_file = $active_event_plugin->get_plugin_file();
$event_plugin_data = \get_plugin_data( $event_plugin_file );
$event_plugin_name = isset( $event_plugin_data['Plugin Name'] ) ? $event_plugin_data['Plugin Name'] : 'Name not found';
$event_plugin_version = isset( $event_plugin_version['Plugin Version'] ) ? $event_plugin_version['Plugin Version'] : 'Version not found';

View file

@ -97,9 +97,6 @@ class Settings_Page {
$tab = sanitize_key( $_GET['tab'] );
}
// Fallback to always re-scan active event plugins, when user visits admin area of this plugin.
Setup::get_instance()->redetect_active_event_plugins();
switch ( $tab ) {
case 'settings':
$plugin_setup = Setup::get_instance();

View file

@ -71,28 +71,6 @@ class Settings {
'default' => 1,
)
);
\register_setting(
'event-bridge-for-activitypub',
'event_bridge_for_activitypub_summary_type',
array(
'type' => 'string',
'description' => \__( 'Summary type to use for ActivityStreams', 'event-bridge-for-activitypub' ),
'show_in_rest' => true,
'default' => 'preset',
)
);
\register_setting(
'event-bridge-for-activitypub',
'event_bridge_for_activitypub_custom_summary',
array(
'type' => 'string',
'description' => \__( 'Define your own custom summary template for events', 'event-bridge-for-activitypub' ),
'show_in_rest' => true,
'default' => EVENT_BRIDGE_FOR_ACTIVITYPUB_CUSTOM_SUMMARY,
)
);
}
/**

View file

@ -67,8 +67,9 @@ class Setup {
// deactivate_plugins( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE );
// return;
// }.
$this->active_event_plugins = self::detect_active_event_plugins();
$this->activitypub_plugin_version = self::get_activitypub_plugin_version();
add_action( 'plugins_loaded', array( $this, 'setup_hooks' ) );
$this->setup_hooks();
}
/**
@ -105,7 +106,8 @@ class Setup {
if ( defined( 'ACTIVITYPUB_PLUGIN_VERSION' ) ) {
return constant( 'ACTIVITYPUB_PLUGIN_VERSION' );
}
return '0.0.0';
$version = get_file_data( WP_PLUGIN_DIR . '/activitypub/activitypub.php', array( 'Version' ) )[0];
return $version ?? '0.0.0';
}
/**
@ -134,47 +136,23 @@ class Setup {
'\Event_Bridge_For_ActivityPub\Integrations\Event_Organiser',
);
/**
* Force the re-scan for active event plugins without using the cached transient.
*
* @return void
*/
public function redetect_active_event_plugins(): void {
delete_transient( 'event_bridge_for_activitypub_active_event_plugins' );
$this->detect_active_event_plugins();
}
/**
* Function that checks for supported activated event plugins.
*
* @return array List of supported event plugins as keys from the SUPPORTED_EVENT_PLUGINS const.
*/
public function detect_active_event_plugins(): array {
$active_event_plugins = get_transient( 'event_bridge_for_activitypub_active_event_plugins' );
if ( $active_event_plugins ) {
$this->active_event_plugins = $active_event_plugins;
return $active_event_plugins;
}
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$all_plugins = array_merge( get_plugins(), get_mu_plugins() );
public static function detect_active_event_plugins(): array {
$active_event_plugins = array();
foreach ( self::EVENT_PLUGIN_CLASSES as $event_plugin_class ) {
$event_plugin_file = call_user_func( array( $event_plugin_class, 'get_relative_plugin_file' ) );
if ( ! $event_plugin_file ) {
if ( ! class_exists( $event_plugin_class ) || ! method_exists( $event_plugin_class, 'get_plugin_file' ) ) {
continue;
}
if ( array_key_exists( $event_plugin_file, $all_plugins ) && \is_plugin_active( $event_plugin_file ) ) {
$active_event_plugins[ $event_plugin_file ] = new $event_plugin_class();
$event_plugin_file = call_user_func( array( $event_plugin_class, 'get_plugin_file' ) );
if ( \is_plugin_active( $event_plugin_file ) ) {
$active_event_plugins[] = new $event_plugin_class();
}
}
set_transient( 'event_bridge_for_activitypub_active_event_plugins', $active_event_plugins );
$this->active_event_plugins = $active_event_plugins;
return $active_event_plugins;
}
@ -187,14 +165,9 @@ class Setup {
*
* @return void
*/
public function setup_hooks(): void {
$this->detect_active_event_plugins();
protected function setup_hooks(): void {
register_activation_hook( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE, array( $this, 'activate' ) );
add_action( 'activated_plugin', array( $this, 'redetect_active_event_plugins' ) );
add_action( 'deactivated_plugin', array( $this, 'redetect_active_event_plugins' ) );
add_action( 'admin_init', array( $this, 'do_admin_notices' ) );
add_action( 'admin_init', array( Settings::class, 'register_settings' ) );
add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_styles' ) );
@ -330,7 +303,6 @@ class Setup {
* @return void
*/
public function activate(): void {
$this->redetect_active_event_plugins();
// Don't allow plugin activation, when the ActivityPub plugin is not activated yet.
if ( ! $this->activitypub_plugin_is_active ) {
deactivate_plugins( plugin_basename( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) );

View file

@ -27,7 +27,7 @@ final class Event_Organiser extends Event_Plugin {
*
* @return string
*/
public static function get_relative_plugin_file(): string {
public static function get_plugin_file(): string {
return 'event-organiser/event-organiser.php';
}

View file

@ -24,11 +24,11 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
*/
abstract class Event_Plugin {
/**
* Returns the plugin file relative to the plugins dir.
* Returns the full plugin file.
*
* @return string
*/
abstract public static function get_relative_plugin_file(): string;
abstract public static function get_plugin_file(): string;
/**
* Returns the event post type of the plugin.
@ -57,9 +57,9 @@ abstract class Event_Plugin {
* Get the plugins name from the main plugin-file's top-level-file-comment.
*/
final public static function get_plugin_name(): string {
$all_plugins = array_merge( get_plugins(), get_mu_plugins() );
if ( isset( $all_plugins[ static::get_relative_plugin_file() ]['Name'] ) ) {
return $all_plugins[ static::get_relative_plugin_file() ]['Name'];
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . static::get_plugin_file() );
if ( isset( $plugin_data['Name'] ) ) {
return $plugin_data['Name'];
} else {
return '';
}

View file

@ -27,7 +27,7 @@ final class Eventin extends Event_plugin {
*
* @return string
*/
public static function get_relative_plugin_file(): string {
public static function get_plugin_file(): string {
return 'wp-event-solution/eventin.php';
}

View file

@ -33,7 +33,7 @@ final class EventPrime extends Event_Plugin {
*
* @return string
*/
public static function get_relative_plugin_file(): string {
public static function get_plugin_file(): string {
return 'eventprime-event-calendar-management/event-prime.php';
}
@ -75,9 +75,6 @@ final class EventPrime extends Event_Plugin {
/**
* Determine whether the current request is an EventPrime ActivityPub request.
*
* Forked from https://github.com/Automattic/wordpress-activitypub/blob/trunk/includes/functions.php
* the function is_activitypub_request.
*/
private static function is_eventprime_activitypub_request() {
global $wp_query;
@ -195,7 +192,7 @@ final class EventPrime extends Event_Plugin {
* @see https://www.w3.org/wiki/SocialCG/ActivityPub/Primer/Authentication_Authorization#Authorized_fetch
* @see https://swicg.github.io/activitypub-http-signature/#authorized-fetch
*/
if ( $activitypub_template && defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) && constant( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
if ( $activitypub_template && ACTIVITYPUB_AUTHORIZED_FETCH ) {
$verification = Signature::verify_http_signature( $_SERVER );
if ( \is_wp_error( $verification ) ) {
header( 'HTTP/1.1 401 Unauthorized' );

View file

@ -27,7 +27,7 @@ final class Events_Manager extends Event_Plugin {
*
* @return string
*/
public static function get_relative_plugin_file(): string {
public static function get_plugin_file(): string {
return 'events-manager/events-manager.php';
}

View file

@ -27,7 +27,7 @@ final class GatherPress extends Event_Plugin {
*
* @return string
*/
public static function get_relative_plugin_file(): string {
public static function get_plugin_file(): string {
return 'gatherpress/gatherpress.php';
}

View file

@ -27,7 +27,7 @@ final class Modern_Events_Calendar_Lite extends Event_plugin {
*
* @return string
*/
public static function get_relative_plugin_file(): string {
public static function get_plugin_file(): string {
return 'modern-events-calendar-lite/modern-events-calendar-lite.php';
}

View file

@ -27,7 +27,7 @@ final class The_Events_Calendar extends Event_plugin {
*
* @return string
*/
public static function get_relative_plugin_file(): string {
public static function get_plugin_file(): string {
return 'the-events-calendar/the-events-calendar.php';
}

View file

@ -30,7 +30,7 @@ final class VS_Event_List extends Event_Plugin {
*
* @return string
*/
public static function get_relative_plugin_file(): string {
public static function get_plugin_file(): string {
return 'very-simple-event-list/vsel.php';
}

View file

@ -30,7 +30,7 @@ final class WP_Event_Manager extends Event_Plugin {
*
* @return string
*/
public static function get_relative_plugin_file(): string {
public static function get_plugin_file(): string {
return 'wp-event-manager/wp-event-manager.php';
}

View file

@ -109,6 +109,7 @@
<properties>
<property name="text_domain" type="array">
<element value="event-bridge-for-activitypub"/>
<element value="activitypub"/>
</property>
</properties>
</rule>

View file

@ -3,7 +3,7 @@ Contributors: andremenrath
Tags: events, fediverse, activitypub, calendar
Requires at least: 6.5
Tested up to: 6.7
Stable tag: 0.3.2
Stable tag: 0.3.0
Requires PHP: 7.4
License: AGPL-3.0-or-later
License URI: https://www.gnu.org/licenses/agpl-3.0.html
@ -38,7 +38,7 @@ The Event Federation plugin ensures that users from those platforms are provided
**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. Advanced users can create custom summaries via a set of shortcodes.
**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.
@ -96,6 +96,6 @@ We're always interested in your feedback. Feel free to reach out to us via [E-Ma
== Changelog ==
= [0.3.2] 2024-12-12 =
= [0.3.0] 2024-12-05 =
* Initial release on https://wordpress.org/

View file

@ -42,52 +42,6 @@ $current_category_mapping = \get_option( 'event_bridge_for_activitypub_ev
<div class="event-bridge-for-activitypub-settings event-bridge-for-activitypub-settings-page hide-if-no-js">
<form method="post" action="options.php">
<?php \settings_fields( 'event-bridge-for-activitypub' ); ?>
<div class="box">
<h2> <?php esc_html_e( 'Event Summary Text', 'event-bridge-for-activitypub' ); ?> </h2>
<p><?php esc_html_e( 'Many Fediverse applications (e.g., Mastodon) don\'t fully support events, instead they will show a summary text along with the events title and the URL to your Website.', 'event-bridge-for-activitypub' ); ?></p>
<p>
<label for="event_bridge_for_activitypub_summary_type_preset">
<input type="radio" name="event_bridge_for_activitypub_summary_type" id="event_bridge_for_activitypub_summary_type_preset" value="preset" <?php echo \checked( 'preset', \get_option( 'event_bridge_for_activitypub_summary_type', EVENT_BRIDGE_FOR_ACTIVITYPUB_DEFAULT_SUMMARY_TYPE ) ); ?> />
<?php \esc_html_e( 'Automatic (default)', 'event-bridge-for-activitypub' ); ?>
-
<span class="description">
<?php \esc_html_e( 'Let the plugin compose a summary for you. ', 'event-bridge-for-activitypub' ); ?>
</span>
</label>
</p>
<p>
<label for="event_bridge_for_activitypub_summary_type_custom">
<input type="radio" name="event_bridge_for_activitypub_summary_type" id="event_bridge_for_activitypub_summary_type_custom" value="custom" <?php echo \checked( 'custom', \get_option( 'event_bridge_for_activitypub_summary_type', EVENT_BRIDGE_FOR_ACTIVITYPUB_DEFAULT_SUMMARY_TYPE ) ); ?> />
<?php \esc_html_e( 'Custom', 'event-bridge-for-activitypub' ); ?>
-
<span class="description">
<?php \esc_html_e( 'For advanced users: compose your custom summary via shortcodes.', 'event-bridge-for-activitypub' ); ?>
</span>
</label>
</p>
<div id="event_bridge_for_activitypub_summary_type_custom-details">
<textarea name="event_bridge_for_activitypub_custom_summary" id="event_bridge_for_activitypub_custom_summary" rows="10" cols="50" class="large-text" placeholder="<?php echo wp_kses( EVENT_BRIDGE_FOR_ACTIVITYPUB_CUSTOM_SUMMARY, 'post' ); ?>"><?php echo esc_textarea( wp_kses( \get_option( 'event_bridge_for_activitypub_custom_summary', EVENT_BRIDGE_FOR_ACTIVITYPUB_CUSTOM_SUMMARY ), 'post' ) ); ?></textarea>
<details>
<summary><?php esc_html_e( 'See a list Template Tags available for the summary.', 'event-bridge-for-activitypub' ); ?></summary>
<div class="description">
<dl>
<dt><code>[ap_start_time]</code><dt>
<dd><?php \esc_html_e( 'The events title.', 'event-bridge-for-activitypub' ); ?></dd>
<dt><code>[ap_end_time]</code><dt>
<dd><?php \esc_html_e( 'The events content.', 'event-bridge-for-activitypub' ); ?></dd>
<dt><code>[ap_location]</code><dt>
<dd><?php \esc_html_e( 'The events location.', 'event-bridge-for-activitypub' ); ?></dd>
<dt><code>[ap_hashtags]</code><dt>
<dd><?php \esc_html_e( 'The events tags as hashtags.', 'event-bridge-for-activitypub' ); ?></dd>
<dt><code>[ap_excerpt]</code><dt>
<dd><?php \esc_html_e( 'The events excerpt (may be truncated).', 'event-bridge-for-activitypub' ); ?></dd>
<dt><code>[ap_content]</code><dt>
<dd><?php \esc_html_e( 'The events description.', 'event-bridge-for-activitypub' ); ?></dd>
</dl>
</div>
</details>
</div>
</div>
<div class="box">
<h2> <?php esc_html_e( 'ActivityPub Event Category', 'event-bridge-for-activitypub' ); ?> </h2>

View file

@ -25,21 +25,6 @@ if ( ! file_exists( "{$_tests_dir}/includes/functions.php" ) ) {
// Give access to tests_add_filter() function.
require_once "{$_tests_dir}/includes/functions.php";
/**
* Function to manually load an event plugin.
*
* @param string $plugin_file The main plugin file of the event plugin.
*/
function _manually_load_event_plugin( $plugin_file ) {
$plugin_dir = ABSPATH . '/wp-content/plugins/';
require_once $plugin_dir . $plugin_file;
update_option( 'purchase_history_table_structure_migration_done', true );
$current = get_option( 'active_plugins', array() );
$current[] = $plugin_file;
sort( $current );
update_option( 'active_plugins', $current );
}
/**
* Manually load the plugin being tested and its integrations.
*/
@ -95,12 +80,13 @@ function _manually_load_plugin() {
}
if ( $plugin_file ) {
_manually_load_event_plugin( $plugin_file );
} else {
// For all other tests we mainly use the Events Calendar as a reference.
_manually_load_event_plugin( 'the-events-calendar/the-events-calendar.php' );
_manually_load_event_plugin( 'very-simple-event-list/vsel.php' );
// Manually load the event plugin.
require_once $plugin_dir . $plugin_file;
update_option( 'purchase_history_table_structure_migration_done', true );
$current = get_option( 'active_plugins', array() );
$current[] = $plugin_file;
sort( $current );
update_option( 'active_plugins', $current );
}
// Hot fix that allows using Events Manager within unit tests, because the em_init() is later not run as admin.

View file

@ -1,193 +0,0 @@
<?php
/**
* Test file for Activitypub Shortcodes.
*
* @package Event_Bridge_For_ActivityPub
* @license AGPL-3.0-or-later
*/
use Activitypub\Shortcodes;
/**
* Test class for Activitypub Shortcodes.
*
* @coversDefaultClass \Activitypub\Shortcodes
*/
class Test_Activitypub_Event_Bridge_Shortcodes extends WP_UnitTestCase {
/**
* 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 plugin is needed to test Event Shortcodes' );
}
// Make sure that ActivityPub support is enabled for The Events Calendar.
$aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
$aec->activate_activitypub_support_for_active_event_plugins();
// Delete all posts afterwards.
_delete_all_posts();
}
/**
* Test the shortcode for rendering the events start time.
*/
public function test_start_time() {
// Create a The Events Calendar Event without content.
$wp_object = tribe_events()
->set_args( Test_The_Events_Calendar::MOCKUP_EVENTS['minimal_event'] )
->create();
// Call the transformer Factory.
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
return;
}
$transformer->register_shortcodes();
$summary = '[ap_start_time]';
$summary = do_shortcode( $summary );
$this->assertEquals( '🗓️ Start: December 1, 2024 3:00 pm', $summary );
$summary = '[ap_start_time icon="false"]';
$summary = do_shortcode( $summary );
$this->assertEquals( 'Start: December 1, 2024 3:00 pm', $summary );
$summary = '[ap_start_time icon="false" label="false"]';
$summary = do_shortcode( $summary );
$this->assertEquals( 'December 1, 2024 3:00 pm', $summary );
$transformer->unregister_shortcodes();
}
/**
* Test the shortcode for rendering the events end time.
*/
public function test_end_time() {
// Create a The Events Calendar Event without content.
$wp_object = tribe_events()
->set_args( Test_The_Events_Calendar::MOCKUP_EVENTS['minimal_event'] )
->create();
// Call the transformer Factory.
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
return;
}
$transformer->register_shortcodes();
$summary = '[ap_end_time]';
$summary = do_shortcode( $summary );
$this->assertEquals( '⏳ End: December 1, 2024 4:00 pm', $summary );
$summary = '[ap_end_time icon="false"]';
$summary = do_shortcode( $summary );
$this->assertEquals( 'End: December 1, 2024 4:00 pm', $summary );
$summary = '[ap_end_time icon="false" label="false"]';
$summary = do_shortcode( $summary );
$this->assertEquals( 'December 1, 2024 4:00 pm', $summary );
$transformer->unregister_shortcodes();
}
/**
* Test the shortcode for rendering the events location when no location is set.
*/
public function test_location_when_no_location_is_set() {
// Create a The Events Calendar Event without content.
$wp_object = tribe_events()
->set_args( Test_The_Events_Calendar::MOCKUP_EVENTS['minimal_event'] )
->create();
// Call the transformer Factory.
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
return;
}
$transformer->register_shortcodes();
$summary = '[ap_location]';
$summary = do_shortcode( $summary );
$this->assertEquals( '', $summary );
$transformer->unregister_shortcodes();
}
/**
* Test the shortcode for rendering the events location when location is set.
*/
public function test_location_when_location_is_set() {
// Create Venue.
$venue = tribe_venues()->set_args( Test_The_Events_Calendar::MOCKUP_VENUS['minimal_venue'] )->create();
// Create a The Events Calendar Event.
$wp_object = tribe_events()
->set_args( Test_The_Events_Calendar::MOCKUP_EVENTS['complex_event'] )
->set( 'venue', $venue->ID )
->create();
// Call the transformer Factory.
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
return;
}
$transformer->register_shortcodes();
$summary = '[ap_location]';
$summary = do_shortcode( $summary );
$this->assertEquals( '📍 Location: Minimal Venue', $summary );
$summary = '[ap_location icon="false"]';
$summary = do_shortcode( $summary );
$this->assertEquals( 'Location: Minimal Venue', $summary );
$summary = '[ap_location icon="false" label="false"]';
$summary = do_shortcode( $summary );
$this->assertEquals( 'Minimal Venue', $summary );
$transformer->unregister_shortcodes();
}
/**
* Test the shortcode for rendering the events location when location with detailed address is set.
*/
public function test_location_when_detailed_location_is_set() {
// Create Venue.
$venue = tribe_venues()->set_args( Test_The_Events_Calendar::MOCKUP_VENUS['complex_venue'] )->create();
// Create a The Events Calendar Event.
$wp_object = tribe_events()
->set_args( Test_The_Events_Calendar::MOCKUP_EVENTS['complex_event'] )
->set( 'venue', $venue->ID )
->create();
// Call the transformer Factory.
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
return;
}
$transformer->register_shortcodes();
$summary = '[ap_location]';
$summary = do_shortcode( $summary );
$this->assertEquals( '📍 Location: Complex Venue, Venue address, Venue zip, Venue city, Venue country', $summary );
$summary = '[ap_location country="false"]';
$summary = do_shortcode( $summary );
$this->assertEquals( '📍 Location: Complex Venue, Venue address, Venue zip, Venue city', $summary );
$transformer->unregister_shortcodes();
}
}