WIP: proof of concept
Some checks failed
PHP Code Checker / PHP Code Checker (pull_request) Successful in 46s
PHPUnit / PHPUnit – PHP 7.4 (pull_request) Failing after 52s
PHPUnit / PHPUnit – PHP 8.0 (pull_request) Failing after 55s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Failing after 53s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Failing after 52s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Failing after 50s

This commit is contained in:
André Menrath 2024-11-19 21:05:21 +01:00
parent 7eb011859a
commit 02a3eb7cef
8 changed files with 406 additions and 8 deletions

View file

@ -3,7 +3,7 @@
* Plugin Name: ActivityPub Event Bridge * Plugin Name: ActivityPub Event Bridge
* Description: Integrating popular event plugins with the ActivityPub plugin. * Description: Integrating popular event plugins with the ActivityPub plugin.
* Plugin URI: https://event-federation.eu/ * Plugin URI: https://event-federation.eu/
* Version: 0.2.1 * Version: 0.2.1.4
* Author: André Menrath * Author: André Menrath
* Author URI: https://graz.social/@linos * Author URI: https://graz.social/@linos
* Text Domain: activitypub-event-bridge * Text Domain: activitypub-event-bridge
@ -27,6 +27,8 @@ define( 'ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_VERSION', current( get_file_data( __FILE__, array( 'Version' ), 'plugin' ) ) ); define( 'ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_VERSION', current( get_file_data( __FILE__, array( 'Version' ), 'plugin' ) ) );
define( 'ACTIVITYPUB_EVENT_BRIDGE_DOMAIN', 'activitypub-event-bridge' ); define( 'ACTIVITYPUB_EVENT_BRIDGE_DOMAIN', 'activitypub-event-bridge' );
define( 'ACTIVITYPUB_EVENT_BRIDGE_ACTIVITYPUB_PLUGIN_MIN_VERSION', '3.2.2' ); define( 'ACTIVITYPUB_EVENT_BRIDGE_ACTIVITYPUB_PLUGIN_MIN_VERSION', '3.2.2' );
define( 'ACTIVITYPUB_EVENT_BRIDGE_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( 'ACTIVITYPUB_EVENT_BRIDGE_DEFAULT_SUMMARY_TYPE', 'preset' );
// Include and register the autoloader class for automatic loading of plugin classes. // Include and register the autoloader class for automatic loading of plugin classes.
require_once ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . '/includes/class-autoloader.php'; require_once ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . '/includes/class-autoloader.php';

View file

@ -177,3 +177,7 @@ code.activitypub-event-bridge-settings-example-url {
overflow-x: auto; overflow-x: auto;
word-break: break-all; word-break: break-all;
} }
#activitypub_summary_type_custom-details {
display: none;
}

View file

@ -11,4 +11,23 @@ jQuery( function( $ ) {
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false ); $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
} }
} ); } );
// Function to toggle visibility of custom details based on selected radio button.
function toggleCustomDetailsForSummary() {
if ($("#activitypub_summary_type_custom").is(':checked')) {
$("#activitypub_summary_type_custom-details").show();
} else {
$("#activitypub_summary_type_custom-details").hide();
}
}
// Run the toggle function on page load.
$(document).ready(function() {
toggleCustomDetailsForSummary(); // Set the correct state on load.
// Listen for changes on the radio buttons
$("input[name=activitypub_summary_type]").change(function() {
toggleCustomDetailsForSummary(); // Update visibility on change.
});
});
} ); } );

View file

@ -14,6 +14,8 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
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;
use ActivityPub_Event_Bridge\Event_Shortcodes;
use DateTime; use DateTime;
/** /**
@ -148,14 +150,14 @@ 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.
* *
* This is not mandatory and therefore just return null by default. * This is not mandatory and therefore just return null by default.
*/ */
protected function get_end_time(): ?string { public function get_end_time(): ?string {
return null; return null;
} }
@ -164,14 +166,14 @@ abstract class Event extends Post {
* *
* This should be overridden in the actual event transformer. * This should be overridden in the actual event transformer.
*/ */
protected function get_location(): ?Place { public function get_location(): ?Place {
return null; return null;
} }
/** /**
* Default value for the event status. * Default value for the event status.
*/ */
protected function get_status(): ?string { public function get_status(): ?string {
return 'CONFIRMED'; return 'CONFIRMED';
} }
@ -255,6 +257,44 @@ abstract class Event extends Post {
return ''; return '';
} }
/**
* Get the summary.
*/
public function get_summary(): ?string {
if ( 'preset' === get_option( 'activitypub_summary_type', 'preset' ) ) {
return $this->get_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.
Event_Shortcodes::register();
// 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( 'activitypub_event_bridge_the_summary', $summary, $post );
// Don't need these anymore, should never appear in a post.
Event_Shortcodes::unregister();
return $summary;
}
/** /**
* Create a custom summary. * Create a custom summary.
* *
@ -263,7 +303,7 @@ abstract class Event extends Post {
* *
* @return string $summary The custom event summary. * @return string $summary The custom event summary.
*/ */
public function get_summary(): ?string { public function get_preset_summary(): ?string {
add_filter( 'activitypub_object_content_template', array( self::class, 'remove_ap_permalink_from_template' ), 2, 2 ); add_filter( 'activitypub_object_content_template', array( self::class, 'remove_ap_permalink_from_template' ), 2, 2 );
$excerpt = $this->retrieve_excerpt(); $excerpt = $this->retrieve_excerpt();
// BeforeFirstRelease: decide whether this should be a admin setting. // BeforeFirstRelease: decide whether this should be a admin setting.
@ -308,6 +348,18 @@ abstract class Event extends Post {
return $summary; 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( 'activitypub_event_bridge_custom_summary', ACTIVITYPUB_EVENT_BRIDGE_CUSTOM_SUMMARY );
$template = $summary ?? ACTIVITYPUB_EVENT_BRIDGE_CUSTOM_SUMMARY;
return apply_filters( 'activitypub_event_bridge_summary_template', $template, $this->wp_object );
}
/** /**
* By default set the timezone of the WordPress site. * By default set the timezone of the WordPress site.
* *

View file

@ -72,7 +72,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_end_time(): ?string { public function get_end_time(): ?string {
if ( empty( $this->tribe_event->end_date ) ) { if ( empty( $this->tribe_event->end_date ) ) {
return null; return null;
} }
@ -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

@ -0,0 +1,253 @@
<?php
/**
* File responsible for defining and registering custom shortcodes.
*
* @package ActivityPub_Event_Bridge
* @license AGPL-3.0-or-later
*/
namespace ActivityPub_Event_Bridge;
use Activitypub\Shortcodes;
use Activitypub\Transformer\Factory as Transformer_Factory;
use ActivityPub_Event_Bridge\Activitypub\Transformer\Event as Event_Transformer;
use DateTime;
/**
* Class responsible for defining and registering custom shortcodes.
*/
class Event_Shortcodes extends Shortcodes {
/**
* Register the shortcodes.
*/
public static function register() {
foreach ( get_class_methods( self::class ) as $shortcode ) {
if ( 'init' !== $shortcode ) {
add_shortcode( 'ap_' . $shortcode, array( self::class, $shortcode ) );
}
}
}
/**
* Unregister the shortcodes.
*/
public static function unregister() {
foreach ( get_class_methods( self::class ) as $shortcode ) {
if ( 'init' !== $shortcode ) {
remove_shortcode( 'ap_' . $shortcode );
}
}
}
/**
* Get the transformer of the current event post.
*
* @return ?Event_Transformer The Event Transformer.
*/
protected static function get_transformer(): ?Event_Transformer {
$post = self::get_item();
if ( ! $post ) {
return null;
}
$transformer = Transformer_Factory::get_transformer( $post );
if ( ! is_subclass_of( $transformer, Event_Transformer::class ) ) {
return null;
}
return $transformer;
}
/**
* Compose a human readable formatted time.
*
* @param ?string $time The time which needs to be formatted.
*/
protected static function format_time( $time ) {
if ( is_null( $time ) ) {
return '';
}
$start_datetime = new DateTime( $time );
$start_timestamp = $start_datetime->getTimestamp();
$datetime_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
return wp_date( $datetime_format, $start_timestamp );
}
/**
* Generates output for the 'apeb_start_time' shortcode.
*
* @param ?array $atts The shortcodes attributes.
*
* @return string The formatted start date and time of the event.
*/
public static function start_time( $atts ) {
$transformer = self::get_transformer();
if ( ! $transformer ) {
return '';
}
$args = shortcode_atts(
array(
'icon' => 'true',
'title' => 'true',
),
$atts,
'ap_start_time'
);
$args['icon'] = filter_var( $args['icon'], FILTER_VALIDATE_BOOLEAN );
$args['title'] = filter_var( $args['title'], FILTER_VALIDATE_BOOLEAN );
$start_timestamp = $transformer->get_start_time();
if ( ! $start_timestamp ) {
return '';
}
$start_time = array();
if ( $args['icon'] ) {
$start_time[] = '🗓️';
}
if ( $args['title'] ) {
$start_time[] = __( 'Start', 'activitypub-event-bridge' ) . ':';
}
$start_time[] = self::format_time( $start_timestamp );
$start_time = implode( ' ', $start_time );
return $start_time;
}
/**
* Generates output for the 'apeb_end_time' shortcode.
*
* @param ?array $atts The shortcodes attributes.
*
* @return string The formatted end date and time of the event.
*/
public static function end_time( $atts ) {
$transformer = self::get_transformer();
if ( ! $transformer ) {
return '';
}
$args = shortcode_atts(
array(
'icon' => 'true',
'title' => 'true',
),
$atts,
'ap_end_time'
);
$args['icon'] = filter_var( $args['icon'], FILTER_VALIDATE_BOOLEAN );
$args['title'] = filter_var( $args['title'], FILTER_VALIDATE_BOOLEAN );
$end_timestamp = $transformer->get_end_time();
if ( ! $end_timestamp ) {
return '';
}
$end_time = array();
if ( $args['icon'] ) {
$end_time[] = '⏳';
}
if ( $args['title'] ) {
$end_time[] = __( 'End', 'activitypub-event-bridge' ) . ':';
}
$end_time[] = self::format_time( $end_timestamp );
$end_time = implode( ' ', $end_time );
return $end_time;
}
/**
* Generates output for the 'apeb_location shortcode.
*
* @param ?array $atts The shortcodes attributes.
*
* @return string The formatted location/address of the event.
*/
public static function location( $atts ) {
$transformer = self::get_transformer();
if ( ! $transformer ) {
return '';
}
$args = shortcode_atts(
array(
'icon' => 'true',
'title' => 'true',
'country' => 'true',
'zip' => 'true',
'city' => 'true',
'street' => 'true',
'name' => 'true',
),
$atts,
'ap_location'
);
foreach ( $args as $arg => $value ) {
$args[ $arg ] = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
}
$location = $transformer->get_location();
if ( ! $location ) {
return '';
}
$output = array();
if ( $args['icon'] ) {
$output[] = '📍';
}
if ( $args['title'] ) {
$output[] = __( 'Location', 'activitypub-event-bridge' ) . ':';
}
$address = $location->get_address();
if ( $address ) {
if ( is_string( $address ) ) {
$output[] = $address;
}
if ( is_array( $address ) ) {
if ( $args['name'] && array_key_exists( 'name', $address ) ) {
$output[] = $address['name'] . ',';
}
if ( $args['street'] && array_key_exists( 'streetAddress', $address ) ) {
$output[] = $address['streetAddress'] . ',';
}
if ( $args['zip'] && array_key_exists( 'postalCode', $address ) ) {
$output[] = $address['postalCode'];
}
if ( $args['city'] && array_key_exists( 'addressLocality', $address ) ) {
$output[] = $address['addressLocality'] . ',';
}
if ( $args['country'] && array_key_exists( 'addressCountry', $address ) ) {
$output[] = $address['addressCountry'];
}
}
}
$output = implode( ' ', $output );
return $output;
}
}

View file

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

View file

@ -42,6 +42,52 @@ $current_category_mapping = \get_option( 'activitypub_event_bridge_event_
<div class="activitypub-event-bridge-settings activitypub-event-bridge-settings-page hide-if-no-js"> <div class="activitypub-event-bridge-settings activitypub-event-bridge-settings-page hide-if-no-js">
<form method="post" action="options.php"> <form method="post" action="options.php">
<?php \settings_fields( 'activitypub-event-bridge' ); ?> <?php \settings_fields( 'activitypub-event-bridge' ); ?>
<div class="box">
<h2> <?php esc_html_e( 'Event Summary Text', 'activitypub-event-bridge' ); ?> </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.', 'activitypub-event-bridge' ); ?></p>
<p>
<label for="activitypub_summary_type_preset">
<input type="radio" name="activitypub_summary_type" id="activitypub_summary_type_preset" value="preset" <?php echo \checked( 'preset', \get_option( 'activitypub_summary_type', ACTIVITYPUB_EVENT_BRIDGE_DEFAULT_SUMMARY_TYPE ) ); ?> />
<?php \esc_html_e( 'Automatic (default)', 'activitypub' ); ?>
-
<span class="description">
<?php \esc_html_e( 'Let the plugin compose a summary for you. ', 'activitypub-event-bridge' ); ?>
</span>
</label>
</p>
<p>
<label for="activitypub_summary_type_custom">
<input type="radio" name="activitypub_summary_type" id="activitypub_summary_type_custom" value="custom" <?php echo \checked( 'custom', \get_option( 'activitypub_summary_type', ACTIVITYPUB_EVENT_BRIDGE_DEFAULT_SUMMARY_TYPE ) ); ?> />
<?php \esc_html_e( 'Custom', 'activitypub-event-bridge' ); ?>
-
<span class="description">
<?php \esc_html_e( 'For advanced users: compose your custom summary via shortcodes.', 'activitypub-event-bridge' ); ?>
</span>
</label>
</p>
<div id="activitypub_summary_type_custom-details">
<textarea name="activitypub_event_bridge_custom_summary" id="activitypub_event_bridge_custom_summary" rows="10" cols="50" class="large-text" placeholder="<?php echo wp_kses( ACTIVITYPUB_EVENT_BRIDGE_CUSTOM_SUMMARY, 'post' ); ?>"><?php echo esc_textarea( wp_kses( \get_option( 'activitypub_event_bridge_custom_summary', ACTIVITYPUB_EVENT_BRIDGE_CUSTOM_SUMMARY ), 'post' ) ); ?></textarea>
<details>
<summary><?php esc_html_e( 'See a list Template Tags available for the summary.', 'activitypub' ); ?></summary>
<div class="description">
<dl>
<dt><code>[ap_start_time]</code><dt>
<dd><?php \esc_html_e( 'The events title.', 'activitypub-event-bridge' ); ?></dd>
<dt><code>[ap_end_time]</code><dt>
<dd><?php \esc_html_e( 'The events content.', 'activitypub-event-bridge' ); ?></dd>
<dt><code>[ap_location]</code><dt>
<dd><?php \esc_html_e( 'The events location.', 'activitypub-event-bridge' ); ?></dd>
<dt><code>[ap_hashtags]</code><dt>
<dd><?php \esc_html_e( 'The events tags as hashtags.', 'activitypub-event-bridge' ); ?></dd>
<dt><code>[ap_excerpt]</code><dt>
<dd><?php \esc_html_e( 'The events excerpt (may be truncated).', 'activitypub-event-bridge' ); ?></dd>
<dt><code>[ap_content]</code><dt>
<dd><?php \esc_html_e( 'The events description.', 'activitypub-event-bridge' ); ?></dd>
</dl>
</div>
</details>
</div>
</div>
<div class="box"> <div class="box">
<h2> <?php esc_html_e( 'ActivityPub Event Category', 'activitypub-event-bridge' ); ?> </h2> <h2> <?php esc_html_e( 'ActivityPub Event Category', 'activitypub-event-bridge' ); ?> </h2>