2024-07-05 13:29:13 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Replace the default ActivityPub Transformer
|
|
|
|
*
|
2024-10-02 21:54:03 +02:00
|
|
|
* @package ActivityPub_Event_Bridge
|
2024-07-05 13:29:13 +02:00
|
|
|
* @license AGPL-3.0-or-later
|
|
|
|
*/
|
|
|
|
|
2024-10-02 21:54:03 +02:00
|
|
|
namespace ActivityPub_Event_Bridge\Activitypub\Transformer;
|
2024-07-05 13:29:13 +02:00
|
|
|
|
2024-09-28 13:14:10 +02:00
|
|
|
// Exit if accessed directly.
|
|
|
|
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
|
|
|
|
2024-10-11 10:53:12 +02:00
|
|
|
use Activitypub\Activity\Activity;
|
2024-07-05 13:29:13 +02:00
|
|
|
use Activitypub\Activity\Extended_Object\Event as Event_Object;
|
2024-09-25 13:59:31 +02:00
|
|
|
use Activitypub\Activity\Extended_Object\Place;
|
2024-07-05 13:29:13 +02:00
|
|
|
use Activitypub\Transformer\Post;
|
2024-09-28 16:38:29 +02:00
|
|
|
use DateTime;
|
2024-07-05 13:29:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Base transformer for WordPress event post types to ActivityPub events.
|
2024-08-27 21:23:33 +02:00
|
|
|
*
|
|
|
|
* Everything that transforming several WordPress post types that represent events
|
|
|
|
* have in common, as well as sane defaults for events should be defined here.
|
2024-10-02 19:15:28 +02:00
|
|
|
*
|
|
|
|
* BeforeFirstRelease:
|
|
|
|
* [ ] remove link at the end of the content.
|
|
|
|
* [ ] add organizer.
|
|
|
|
* [ ] do add Cancelled reason in the content.
|
2024-07-05 13:29:13 +02:00
|
|
|
*/
|
2024-09-25 13:59:31 +02:00
|
|
|
abstract class Event extends Post {
|
2024-08-27 21:23:33 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The WordPress event taxonomy.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $wp_taxonomy;
|
|
|
|
|
2024-07-05 13:29:13 +02:00
|
|
|
/**
|
|
|
|
* Returns the ActivityStreams 2.0 Object-Type for an Event.
|
|
|
|
*
|
|
|
|
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event
|
|
|
|
*
|
|
|
|
* @return string The Event Object-Type.
|
|
|
|
*/
|
2024-09-25 11:27:17 +02:00
|
|
|
protected function get_type(): string {
|
2024-07-05 13:29:13 +02:00
|
|
|
return 'Event';
|
|
|
|
}
|
|
|
|
|
2024-09-25 11:27:17 +02:00
|
|
|
/**
|
|
|
|
* Get a sane default for whether comments are enabled.
|
|
|
|
*/
|
|
|
|
protected function get_comments_enabled(): ?bool {
|
|
|
|
return comments_open( $this->wp_object );
|
|
|
|
}
|
|
|
|
|
2024-10-02 19:15:28 +02:00
|
|
|
/**
|
|
|
|
* Set a hardcoded template for the content.
|
|
|
|
*
|
|
|
|
* This actually disabled templates for the content.
|
|
|
|
* Maybe this independent templates for events will be added later.
|
|
|
|
*/
|
|
|
|
protected function get_post_content_template(): string {
|
|
|
|
return '[ap_content]';
|
|
|
|
}
|
|
|
|
|
2024-09-18 21:26:53 +02:00
|
|
|
/**
|
|
|
|
* Returns the title of the event.
|
|
|
|
*
|
|
|
|
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-name
|
|
|
|
*
|
|
|
|
* @return string The name.
|
|
|
|
*/
|
2024-09-25 11:27:17 +02:00
|
|
|
protected function get_name(): string {
|
2024-09-18 21:26:53 +02:00
|
|
|
return $this->wp_object->post_title;
|
|
|
|
}
|
|
|
|
|
2024-08-28 17:48:15 +02:00
|
|
|
/**
|
|
|
|
* Extend the construction of the Post Transformer to also set the according taxonomy of the event post type.
|
|
|
|
*
|
|
|
|
* @param WP_Post $wp_object The WordPress post object (event).
|
|
|
|
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
|
|
|
*/
|
2024-10-02 19:15:28 +02:00
|
|
|
public function __construct( $wp_object, $wp_taxonomy = 'category' ) {
|
2024-08-28 17:48:15 +02:00
|
|
|
parent::__construct( $wp_object );
|
|
|
|
$this->wp_taxonomy = $wp_taxonomy;
|
|
|
|
}
|
|
|
|
|
2024-09-24 17:14:39 +02:00
|
|
|
/**
|
|
|
|
* Extract the join mode.
|
|
|
|
*
|
|
|
|
* Currently we don't handle joins, we always mark events as external.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2024-09-25 11:27:17 +02:00
|
|
|
public function get_join_mode(): ?string {
|
2024-09-24 17:14:39 +02:00
|
|
|
return 'external';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract the external participation url.
|
|
|
|
*
|
|
|
|
* Currently we don't handle joins, we always mark events as external.
|
|
|
|
* We just link back to the events HTML representation on our WordPress site.
|
|
|
|
*
|
2024-09-25 11:27:17 +02:00
|
|
|
* @return ?string The external participation URL.
|
2024-09-24 17:14:39 +02:00
|
|
|
*/
|
2024-09-25 11:27:17 +02:00
|
|
|
public function get_external_participation_url(): ?string {
|
2024-09-24 17:14:39 +02:00
|
|
|
return 'external' === $this->get_join_mode() ? $this->get_url() : null;
|
|
|
|
}
|
|
|
|
|
2024-08-27 21:23:33 +02:00
|
|
|
/**
|
|
|
|
* Set the event category, via the mapping setting.
|
2024-10-02 19:15:28 +02:00
|
|
|
*
|
|
|
|
* @return ?string
|
2024-08-27 21:23:33 +02:00
|
|
|
*/
|
2024-09-25 11:27:17 +02:00
|
|
|
public function get_category(): ?string {
|
2024-10-02 19:15:28 +02:00
|
|
|
if ( is_null( $this->wp_taxonomy ) ) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-10-02 21:54:03 +02:00
|
|
|
$current_category_mapping = \get_option( 'activitypub_event_bridge_event_category_mappings', array() );
|
2024-08-27 21:23:33 +02:00
|
|
|
$terms = \get_the_terms( $this->wp_object, $this->wp_taxonomy );
|
2024-08-28 17:48:15 +02:00
|
|
|
|
|
|
|
// Check if the event has a category set and if that category has a specific mapping return that one.
|
|
|
|
if ( ! is_wp_error( $terms ) && $terms && array_key_exists( $terms[0]->slug, $current_category_mapping ) ) {
|
|
|
|
return sanitize_text_field( $current_category_mapping[ $terms[0]->slug ] );
|
2024-08-27 21:23:33 +02:00
|
|
|
} else {
|
2024-08-28 17:48:15 +02:00
|
|
|
// Return the default event category.
|
2024-10-02 21:54:03 +02:00
|
|
|
return sanitize_text_field( \get_option( 'activitypub_event_bridge_default_event_category', 'MEETING' ) );
|
2024-08-27 21:23:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-25 13:59:31 +02:00
|
|
|
/**
|
|
|
|
* Retrieves the excerpt text (may be HTML). Used for constructing the summary.
|
|
|
|
*
|
|
|
|
* @return ?string
|
|
|
|
*/
|
2024-10-02 19:15:28 +02:00
|
|
|
protected function retrieve_excerpt(): ?string {
|
|
|
|
if ( $this->wp_object->post_excerpt ) {
|
2024-09-25 13:59:31 +02:00
|
|
|
return $this->wp_object->post_excerpt;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the start time.
|
|
|
|
*
|
|
|
|
* This is mandatory and must be implemented in the final event transformer class.
|
|
|
|
*/
|
2024-10-11 10:53:12 +02:00
|
|
|
abstract public function get_start_time(): string;
|
2024-09-25 13:59:31 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the end time.
|
|
|
|
*
|
|
|
|
* This is not mandatory and therefore just return null by default.
|
|
|
|
*/
|
|
|
|
protected function get_end_time(): ?string {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a default for the location.
|
|
|
|
*
|
|
|
|
* This should be overridden in the actual event transformer.
|
|
|
|
*/
|
|
|
|
protected function get_location(): ?Place {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-10-02 22:40:12 +02:00
|
|
|
/**
|
|
|
|
* Default value for the event status.
|
|
|
|
*/
|
|
|
|
protected function get_status(): ?string {
|
|
|
|
return 'CONFIRMED';
|
|
|
|
}
|
|
|
|
|
2024-09-25 13:59:31 +02:00
|
|
|
/**
|
|
|
|
* Compose a human readable formatted start time.
|
2024-10-02 19:15:28 +02:00
|
|
|
*/
|
|
|
|
protected function format_start_time(): string {
|
|
|
|
return $this->format_time( $this->get_start_time() );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compose a human readable formatted end time.
|
|
|
|
*/
|
|
|
|
protected function format_end_time(): string {
|
|
|
|
return $this->format_time( $this->get_end_time() );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compose a human readable formatted time.
|
2024-09-25 14:25:27 +02:00
|
|
|
*
|
2024-10-02 19:15:28 +02:00
|
|
|
* @param ?string $time The time which needs to be formatted.
|
2024-09-25 13:59:31 +02:00
|
|
|
*/
|
2024-10-02 19:15:28 +02:00
|
|
|
private static function format_time( $time ) {
|
2024-09-25 13:59:31 +02:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Format a human readable address.
|
|
|
|
*/
|
|
|
|
protected function format_address(): string {
|
|
|
|
$location = $this->get_location();
|
|
|
|
if ( is_null( $location ) ) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
$address = $location->get_address();
|
|
|
|
if ( ! $address ) {
|
|
|
|
return $location->get_name();
|
|
|
|
}
|
|
|
|
if ( is_string( $address ) ) {
|
|
|
|
return $address;
|
|
|
|
}
|
|
|
|
if ( ! is_array( $address ) ) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
return isset( $address['locality'] ) ? $address['locality'] : '';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Format the category using the translation.
|
|
|
|
*/
|
2024-10-02 19:15:28 +02:00
|
|
|
protected function format_categories(): string {
|
|
|
|
if ( is_null( $this->wp_taxonomy ) ) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
$categories = array();
|
|
|
|
|
|
|
|
// Add the federated category string.
|
2024-10-02 21:54:03 +02:00
|
|
|
require_once ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . '/includes/event-categories.php';
|
2024-10-02 19:15:28 +02:00
|
|
|
$federated_category = $this->get_category();
|
2024-10-02 21:54:03 +02:00
|
|
|
if ( array_key_exists( $federated_category, ACTIVITYPUB_EVENT_BRIDGE_EVENT_CATEGORIES ) ) {
|
|
|
|
$categories[] = ACTIVITYPUB_EVENT_BRIDGE_EVENT_CATEGORIES[ $federated_category ];
|
2024-09-25 13:59:31 +02:00
|
|
|
}
|
2024-10-02 19:15:28 +02:00
|
|
|
|
|
|
|
// Add all category terms.
|
|
|
|
$terms = \get_the_terms( $this->wp_object, $this->wp_taxonomy );
|
|
|
|
if ( $terms && ! is_wp_error( $terms ) ) {
|
|
|
|
foreach ( $terms as $term ) {
|
|
|
|
$categories[] = $term->name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! empty( $categories ) ) {
|
|
|
|
return implode( ' · ', array_unique( $categories ) );
|
|
|
|
}
|
|
|
|
return '';
|
2024-09-25 13:59:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a custom summary.
|
|
|
|
*
|
|
|
|
* It contains also the most important meta-information. The summary is often used when the
|
|
|
|
* ActivityPub object type 'Event' is not supported, e.g. in Mastodon.
|
|
|
|
*
|
|
|
|
* @return string $summary The custom event summary.
|
|
|
|
*/
|
|
|
|
public function get_summary(): ?string {
|
2024-10-02 19:15:28 +02:00
|
|
|
add_filter( 'activitypub_object_content_template', array( self::class, 'remove_ap_permalink_from_template' ), 2, 2 );
|
|
|
|
$excerpt = $this->retrieve_excerpt();
|
2024-09-25 13:59:31 +02:00
|
|
|
// BeforeFirstRelease: decide whether this should be a admin setting.
|
|
|
|
$fallback_to_content = true;
|
|
|
|
if ( is_null( $excerpt ) && $fallback_to_content ) {
|
2024-10-02 19:15:28 +02:00
|
|
|
$excerpt = parent::get_content();
|
2024-09-25 13:59:31 +02:00
|
|
|
}
|
2024-09-26 15:44:22 +02:00
|
|
|
remove_filter( 'activitypub_object_content_template', array( self::class, 'remove_ap_permalink_from_template' ) );
|
|
|
|
|
2024-10-02 19:15:28 +02:00
|
|
|
$category = $this->format_categories();
|
|
|
|
$start_time = $this->format_start_time();
|
|
|
|
$end_time = $this->format_end_time();
|
2024-09-25 13:59:31 +02:00
|
|
|
$address = $this->format_address();
|
|
|
|
|
|
|
|
$formatted_items = array();
|
|
|
|
if ( ! empty( $category ) ) {
|
2024-10-02 21:54:03 +02:00
|
|
|
$formatted_items[] = '🏷️ ' . __( 'Category', 'activitypub-event-bridge' ) . ': ' . $category;
|
2024-09-25 13:59:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! empty( $start_time ) ) {
|
2024-10-02 21:54:03 +02:00
|
|
|
$formatted_items[] = '🗓️ ' . __( 'Start', 'activitypub-event-bridge' ) . ': ' . $start_time;
|
2024-09-25 13:59:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! empty( $end_time ) ) {
|
2024-10-02 21:54:03 +02:00
|
|
|
$formatted_items[] = '⏳ ' . __( 'End', 'activitypub-event-bridge' ) . ': ' . $end_time;
|
2024-09-25 13:59:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! empty( $address ) ) {
|
2024-10-02 21:54:03 +02:00
|
|
|
$formatted_items[] = '📍 ' . __( 'Address', 'activitypub-event-bridge' ) . ': ' . $address;
|
2024-09-25 13:59:31 +02:00
|
|
|
}
|
2024-10-02 19:15:28 +02:00
|
|
|
|
2024-09-25 13:59:31 +02:00
|
|
|
// Compose the summary based on the number of meta items.
|
|
|
|
if ( count( $formatted_items ) > 1 ) {
|
|
|
|
$summary = '<ul><li>' . implode( '</li><li>', $formatted_items ) . '</li></ul>';
|
|
|
|
} elseif ( 1 === count( $formatted_items ) ) {
|
|
|
|
$summary = $formatted_items[0]; // Just the one item without <ul><li>.
|
|
|
|
} else {
|
|
|
|
$summary = ''; // No items, so no output.
|
|
|
|
}
|
|
|
|
|
|
|
|
$summary .= $excerpt;
|
|
|
|
return $summary;
|
|
|
|
}
|
|
|
|
|
2024-10-02 19:49:46 +02:00
|
|
|
/**
|
|
|
|
* By default set the timezone of the WordPress site.
|
|
|
|
*
|
|
|
|
* This is likely to be overwritten by the actual transformer.
|
|
|
|
*
|
|
|
|
* @return string The timezone string of the site.
|
|
|
|
*/
|
|
|
|
public function get_timezone(): string {
|
|
|
|
return wp_timezone_string();
|
|
|
|
}
|
|
|
|
|
2024-09-26 15:51:23 +02:00
|
|
|
/**
|
|
|
|
* Remove the permalink shortcode from a WordPress template.
|
|
|
|
*
|
|
|
|
* This used for the summary template, because the summary usually gets,
|
|
|
|
* used when converting a object, where the URL is usually appended anyway.
|
|
|
|
*
|
2024-10-02 19:15:28 +02:00
|
|
|
* @param string $template The template string.
|
|
|
|
* @param WP_Post|WP_Comment $wp_object The wp_object which was used to select the template.
|
2024-09-26 15:51:23 +02:00
|
|
|
*/
|
2024-10-02 19:15:28 +02:00
|
|
|
public static function remove_ap_permalink_from_template( $template, $wp_object ) {
|
|
|
|
|
|
|
|
// we could override the template here, to get out custom template from an option.
|
|
|
|
|
|
|
|
if ( 'event' === $wp_object->post_type ) {
|
|
|
|
$template = str_replace( '[ap_permalink]', '', $template );
|
|
|
|
$template = str_replace( '[ap_permalink type="html"]', '', $template );
|
|
|
|
}
|
2024-09-26 15:44:22 +02:00
|
|
|
|
|
|
|
return $template;
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:29:13 +02:00
|
|
|
/**
|
|
|
|
* Generic function that converts an WP-Event object to an ActivityPub-Event object.
|
|
|
|
*
|
|
|
|
* @return Event_Object
|
|
|
|
*/
|
2024-09-25 11:27:17 +02:00
|
|
|
public function to_object(): Event_Object {
|
2024-07-05 13:29:13 +02:00
|
|
|
$activitypub_object = new Event_Object();
|
|
|
|
$activitypub_object = $this->transform_object_properties( $activitypub_object );
|
|
|
|
|
2024-10-02 19:15:28 +02:00
|
|
|
// maybe move the following logic (till end of the function) into getter functions.
|
|
|
|
|
2024-07-05 13:29:13 +02:00
|
|
|
$published = \strtotime( $this->wp_object->post_date_gmt );
|
|
|
|
|
|
|
|
$activitypub_object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
|
|
|
|
|
|
|
|
$updated = \strtotime( $this->wp_object->post_modified_gmt );
|
|
|
|
|
|
|
|
if ( $updated > $published ) {
|
|
|
|
$activitypub_object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
$activitypub_object->set_content_map(
|
|
|
|
array(
|
|
|
|
$this->get_locale() => $this->get_content(),
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$activitypub_object->set_to(
|
|
|
|
array(
|
|
|
|
'https://www.w3.org/ns/activitystreams#Public',
|
2024-10-02 19:15:28 +02:00
|
|
|
$this->get_actor_object()->get_followers(), // this fails on my machine.
|
2024-07-05 13:29:13 +02:00
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
return $activitypub_object;
|
|
|
|
}
|
2024-10-11 10:53:12 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
2024-07-05 13:29:13 +02:00
|
|
|
}
|