installed plugin Event Bridge for ActivityPub
version 1.1.0
This commit is contained in:
@ -0,0 +1,378 @@
|
||||
<?php
|
||||
/**
|
||||
* Base class with common functions for transforming an ActivityPub Event object to a WordPress object.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper\Sanitizer;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Base class with common functions for transforming an ActivityPub Event object to a WordPress object.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class Base {
|
||||
/**
|
||||
* Internal function to actually save the event.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event as associative array.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*
|
||||
* @return false|int Post-ID on success, false on failure.
|
||||
*/
|
||||
abstract protected static function save_event( $activitypub_event, $event_source_post_id );
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object within WordPress.
|
||||
*
|
||||
* @param array $activitypub_event The ActivityPub event as associative array.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*/
|
||||
public static function save( $activitypub_event, $event_source_post_id ): void {
|
||||
// Sanitize the incoming event and set only the properties used by the transmogrifier classes.
|
||||
$activitypub_event = Sanitizer::init_and_sanitize_event_object_from_array( $activitypub_event );
|
||||
|
||||
if ( is_wp_error( $activitypub_event ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the saving to the actual Transmogrifier implementation.
|
||||
$post_id = static::save_event( $activitypub_event, $event_source_post_id );
|
||||
|
||||
// Post processing: Logging and marking the imported event's origin.
|
||||
$event_activitypub_id = $activitypub_event->get_id();
|
||||
$event_source_activitypub_id = \get_the_guid( $event_source_post_id );
|
||||
|
||||
if ( $post_id ) {
|
||||
\do_action(
|
||||
'event_bridge_for_activitypub_write_log',
|
||||
array( "[ACTIVITYPUB] Processed incoming event {$event_activitypub_id} from {$event_source_activitypub_id}" )
|
||||
);
|
||||
// Use post meta to remember who we received this event from.
|
||||
\update_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', absint( $event_source_post_id ) );
|
||||
\update_post_meta( $post_id, 'activitypub_content_visibility', defined( 'ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL' ) ? constant( 'ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL' ) : '' );
|
||||
} else {
|
||||
\do_action(
|
||||
'event_bridge_for_activitypub_write_log',
|
||||
array( "[ACTIVITYPUB] Failed processing incoming event {$event_activitypub_id} from {$event_source_activitypub_id}" )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a local event in WordPress that is a cached remote one.
|
||||
*
|
||||
* @param string $activitypub_event_id The ActivityPub events ID.
|
||||
* @return bool|WP_Post|null|WP_Error
|
||||
*/
|
||||
public static function delete( $activitypub_event_id ) {
|
||||
$post_id = static::get_post_id_from_activitypub_id( $activitypub_event_id );
|
||||
|
||||
if ( ! $post_id ) {
|
||||
\do_action(
|
||||
'event_bridge_for_activitypub_write_log',
|
||||
array( "[ACTIVITYPUB] Received delete for event that is not cached locally {$activitypub_event_id}" )
|
||||
);
|
||||
return new WP_Error(
|
||||
'event_bridge_for_activitypub_remote_event_not_found',
|
||||
\__( 'Remote event not found in cache', 'event-bridge-for-activitypub' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
||||
|
||||
if ( $thumbnail_id && ! Event_Sources::is_attachment_featured_image( $thumbnail_id ) ) {
|
||||
wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
|
||||
$result = wp_delete_post( $post_id, true );
|
||||
|
||||
if ( $result ) {
|
||||
\do_action( 'event_bridge_for_activitypub_write_log', array( "[ACTIVITYPUB] Deleted cached event {$activitypub_event_id}" ) );
|
||||
} else {
|
||||
\do_action( 'event_bridge_for_activitypub_write_log', array( "[ACTIVITYPUB] Failed deleting cached event {$activitypub_event_id}" ) );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an ActivityStreams xds:datetime to WordPress GMT format.
|
||||
*
|
||||
* @param string $time_string The ActivityStreams xds:datetime (may include offset).
|
||||
* @return string The GMT string in format 'Y-m-d H:i:s'.
|
||||
*/
|
||||
protected static function format_time_string_to_wordpress_gmt( $time_string ): string {
|
||||
$datetime = new \DateTime( $time_string );
|
||||
$datetime->setTimezone( new \DateTimeZone( 'GMT' ) );
|
||||
return $datetime->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WordPress post by ActivityPub object ID using the guid.
|
||||
*
|
||||
* @param string $activitypub_id The ActivityPub object ID.
|
||||
* @return int The WordPress Post ID, 0 if not post with that ActivityPub object ID (by guid) is found.
|
||||
*/
|
||||
protected static function get_post_id_from_activitypub_id( $activitypub_id ): int {
|
||||
global $wpdb;
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
|
||||
esc_sql( $activitypub_id ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image URL and alt-text of an ActivityPub object.
|
||||
*
|
||||
* @param mixed $data The ActivityPub object as ann associative array.
|
||||
* @return array Array containing the images URL and alt-text.
|
||||
*/
|
||||
private static function extract_image_alt_and_url( $data ): array {
|
||||
$image = array(
|
||||
'url' => null,
|
||||
'alt' => null,
|
||||
);
|
||||
|
||||
// Check whether it is already simple.
|
||||
if ( ! $data || is_string( $data ) ) {
|
||||
$image['url'] = $data;
|
||||
return $image;
|
||||
}
|
||||
|
||||
if ( ! isset( $data['type'] ) ) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
if ( ! in_array( $data['type'], array( 'Document', 'Image' ), true ) ) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
if ( isset( $data['url'] ) ) {
|
||||
$image['url'] = $data['url'];
|
||||
} elseif ( isset( $data['id'] ) ) {
|
||||
$image['id'] = $data['id'];
|
||||
}
|
||||
|
||||
if ( isset( $data['name'] ) ) {
|
||||
$image['alt'] = $data['name'];
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the featured image.
|
||||
*
|
||||
* @param Event $event The ActivityPub event object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_featured_image( $event ): array {
|
||||
// Search for the featured image in the image property.
|
||||
$image = $event->get_image();
|
||||
|
||||
if ( $image ) {
|
||||
return self::extract_image_alt_and_url( $image );
|
||||
}
|
||||
|
||||
// Fallback attachment.
|
||||
$attachment = $event->get_attachment();
|
||||
|
||||
// If attachment is an array get the first fitting one.
|
||||
if ( is_array( $attachment ) && ! empty( $attachment ) ) {
|
||||
$supported_types = array( 'Image', 'Document' );
|
||||
$match = null;
|
||||
|
||||
foreach ( $attachment as $item ) {
|
||||
if ( in_array( $item['type'], $supported_types, true ) ) {
|
||||
$match = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$attachment = $match;
|
||||
}
|
||||
|
||||
return self::extract_image_alt_and_url( $attachment );
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an image URL return an attachment ID. Image will be side-loaded into the media library if it doesn't exist.
|
||||
*
|
||||
* Forked from https://gist.github.com/kingkool68/a66d2df7835a8869625282faa78b489a.
|
||||
*
|
||||
* @param int $post_id The post ID where the image will be set as featured image.
|
||||
* @param string $url The image URL to maybe sideload.
|
||||
* @uses media_sideload_image
|
||||
* @return string|int|WP_Error
|
||||
*/
|
||||
protected static function maybe_sideload_image( $post_id, $url = '' ) {
|
||||
global $wpdb;
|
||||
|
||||
// Include necessary WordPress file for media handling.
|
||||
if ( ! function_exists( 'media_sideload_image' ) ) {
|
||||
// @phpstan-ignore-next-line
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
// @phpstan-ignore-next-line
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
// @phpstan-ignore-next-line
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
}
|
||||
|
||||
// Check to see if the URL has already been fetched, if so return the attachment ID.
|
||||
$attachment_id = $wpdb->get_var(
|
||||
$wpdb->prepare( "SELECT `post_id` FROM {$wpdb->postmeta} WHERE `meta_key` = '_source_url' AND `meta_value` = %s", $url )
|
||||
);
|
||||
if ( ! empty( $attachment_id ) ) {
|
||||
return $attachment_id;
|
||||
}
|
||||
|
||||
$attachment_id = $wpdb->get_var(
|
||||
$wpdb->prepare( "SELECT `ID` FROM {$wpdb->posts} WHERE guid=%s", $url )
|
||||
);
|
||||
if ( ! empty( $attachment_id ) ) {
|
||||
return $attachment_id;
|
||||
}
|
||||
|
||||
// If the URL doesn't exist, sideload it to the media library.
|
||||
return media_sideload_image( $url, $post_id, $url, 'id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sideload an image_url set it as featured image and add the alt-text.
|
||||
*
|
||||
* @param int $post_id The post ID where the image will be set as featured image.
|
||||
* @param string $image_url The image URL.
|
||||
* @param string $alt_text The alt-text of the image.
|
||||
* @return int|WP_Error The attachment ID
|
||||
*/
|
||||
protected static function set_featured_image_with_alt( $post_id, $image_url, $alt_text = '' ) {
|
||||
// Maybe sideload the image or get the Attachment ID of an existing one.
|
||||
$image_id = self::maybe_sideload_image( $post_id, $image_url );
|
||||
|
||||
if ( \is_wp_error( $image_id ) ) {
|
||||
// Handle the error.
|
||||
return $image_id;
|
||||
}
|
||||
|
||||
// Set the image as the featured image for the post.
|
||||
\set_post_thumbnail( $post_id, $image_id );
|
||||
|
||||
// Update the alt text.
|
||||
if ( ! empty( $alt_text ) ) {
|
||||
\update_post_meta( $image_id, '_wp_attachment_image_alt', $alt_text );
|
||||
}
|
||||
|
||||
return $image_id; // Return the attachment ID for further use if needed.
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a PostalAddress to a string.
|
||||
*
|
||||
* @link https://schema.org/PostalAddress
|
||||
*
|
||||
* @param array $postal_address The PostalAddress as an associative array.
|
||||
* @return string
|
||||
*/
|
||||
private static function postal_address_to_string( $postal_address ): string {
|
||||
if ( ! isset( $postal_address['type'] ) || 'PostalAddress' !== $postal_address['type'] ) {
|
||||
_doing_it_wrong(
|
||||
__METHOD__,
|
||||
'The parameter postal_address must be an associate array like schema.org/PostalAddress.',
|
||||
esc_html( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_VERSION )
|
||||
);
|
||||
}
|
||||
|
||||
$address = array();
|
||||
$known_attributes = array(
|
||||
'streetAddress',
|
||||
'postalCode',
|
||||
'addressLocality',
|
||||
'addressState',
|
||||
'addressCountry',
|
||||
);
|
||||
|
||||
foreach ( $known_attributes as $attribute ) {
|
||||
if ( isset( $postal_address[ $attribute ] ) && is_string( $postal_address[ $attribute ] ) ) {
|
||||
$address[] = $postal_address[ $attribute ];
|
||||
}
|
||||
}
|
||||
|
||||
$address_string = implode( ' ,', $address );
|
||||
|
||||
return $address_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an address to a string.
|
||||
*
|
||||
* @param mixed $address The address as an object, string or associative array.
|
||||
* @return string
|
||||
*/
|
||||
protected static function address_to_string( $address ): string {
|
||||
if ( is_string( $address ) ) {
|
||||
return $address;
|
||||
}
|
||||
|
||||
if ( is_object( $address ) ) {
|
||||
$address = (array) $address;
|
||||
}
|
||||
|
||||
if ( ! is_array( $address ) || ! isset( $address['type'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( 'PostalAddress' === $address['type'] ) {
|
||||
return self::postal_address_to_string( $address );
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of revisions to keep.
|
||||
*
|
||||
* @return int The number of revisions to keep.
|
||||
*/
|
||||
public static function revisions_to_keep(): int {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the online event link.
|
||||
*
|
||||
* @param Event $event The ActivityPub event object.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
protected static function get_online_event_link_from_attachments( $event ): ?string {
|
||||
$attachments = $event->get_attachment();
|
||||
|
||||
if ( ! is_array( $attachments ) || empty( $attachments ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ( $attachments as $attachment ) {
|
||||
if ( array_key_exists( 'type', $attachment ) && 'Link' === $attachment['type'] && isset( $attachment['href'] ) ) {
|
||||
return $attachment['href'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transmogrify for the GatherPress event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to GatherPress Events.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use DateTime;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\GatherPress as IntegrationsGatherPress;
|
||||
use GatherPress\Core\Event as GatherPress_Event;
|
||||
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the GatherPress event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to GatherPress Events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class GatherPress extends Base {
|
||||
/**
|
||||
* Add tags to post.
|
||||
*
|
||||
* @param Event $event The ActivityPub event object.
|
||||
* @param int $post_id The post ID.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function add_tags_to_post( $event, $post_id ) {
|
||||
$tags_array = $event->get_tag();
|
||||
|
||||
// Ensure the input is valid.
|
||||
if ( empty( $tags_array ) || ! is_array( $tags_array ) || ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract and process tag names.
|
||||
$tag_names = array();
|
||||
foreach ( $tags_array as $tag ) {
|
||||
if ( isset( $tag['name'] ) && 'Hashtag' === $tag['type'] ) {
|
||||
$tag_names[] = ltrim( $tag['name'], '#' ); // Remove the '#' from the name.
|
||||
}
|
||||
}
|
||||
|
||||
// Add the tags as terms to the post.
|
||||
if ( ! empty( $tag_names ) ) {
|
||||
\wp_set_object_terms( $post_id, $tag_names, IntegrationsGatherPress::get_event_category_taxonomy(), true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add venue.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private static function add_venue( $activitypub_event, $post_id ) {
|
||||
$location = $activitypub_event->get_location();
|
||||
|
||||
if ( ! $location ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $location instanceof Place ) {
|
||||
$location = $location->to_array();
|
||||
}
|
||||
|
||||
if ( ! is_array( $location ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['name'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback for Gancio instances.
|
||||
if ( 'online' === $location['name'] ) {
|
||||
$online_event_link = self::get_online_event_link_from_attachments( $activitypub_event );
|
||||
if ( ! $online_event_link ) {
|
||||
return;
|
||||
}
|
||||
\update_post_meta( $post_id, 'gatherpress_online_event_link', \sanitize_url( $online_event_link ) );
|
||||
\wp_set_object_terms( $post_id, 'online-event', '_gatherpress_venue', false );
|
||||
return;
|
||||
}
|
||||
|
||||
$venue_instance = \GatherPress\Core\Venue::get_instance();
|
||||
$venue_name = \sanitize_title( $location['name'] );
|
||||
$venue_slug = $venue_instance->get_venue_term_slug( $venue_name );
|
||||
$venue_post = $venue_instance->get_venue_post_from_term_slug( $venue_slug );
|
||||
|
||||
if ( ! $venue_post ) {
|
||||
$venue_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => sanitize_text_field( $location['name'] ),
|
||||
'post_type' => 'gatherpress_venue',
|
||||
'post_status' => 'publish',
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$venue_id = $venue_post->ID;
|
||||
}
|
||||
|
||||
$venue_information = array();
|
||||
|
||||
$address_string = isset( $location['address'] ) ? self::address_to_string( $location['address'] ) : '';
|
||||
|
||||
$venue_information['fullAddress'] = $address_string;
|
||||
$venue_information['phone_number'] = '';
|
||||
$venue_information['website'] = '';
|
||||
$venue_information['permalink'] = '';
|
||||
|
||||
$venue_json = \wp_json_encode( $venue_information );
|
||||
|
||||
\update_post_meta( $venue_id, 'gatherpress_venue_information', $venue_json );
|
||||
|
||||
\wp_set_object_terms( $post_id, $venue_slug, '_gatherpress_venue', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object as GatherPress Event.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
protected static function save_event( $activitypub_event, $event_source_post_id ) {
|
||||
// Limit this as a safety measure.
|
||||
\add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
$post_id = self::get_post_id_from_activitypub_id( $activitypub_event->get_id() );
|
||||
|
||||
$args = array(
|
||||
'post_title' => sanitize_text_field( $activitypub_event->get_name() ),
|
||||
'post_type' => 'gatherpress_event',
|
||||
'post_content' => wp_kses_post( $activitypub_event->get_content() ?? '' ) . '<!-- wp:gatherpress/venue /-->',
|
||||
'post_excerpt' => wp_kses_post( $activitypub_event->get_summary() ?? '' ),
|
||||
'post_status' => 'publish',
|
||||
'guid' => sanitize_url( $activitypub_event->get_id() ),
|
||||
);
|
||||
|
||||
if ( $activitypub_event->get_published() ) {
|
||||
$post_date = self::format_time_string_to_wordpress_gmt( $activitypub_event->get_published() );
|
||||
$args['post_date'] = $post_date;
|
||||
$args['post_date_gmt'] = $post_date;
|
||||
}
|
||||
|
||||
if ( $post_id ) {
|
||||
// Update existing GatherPress event post.
|
||||
$args['ID'] = $post_id;
|
||||
\wp_update_post( $args );
|
||||
} else {
|
||||
// Insert new GatherPress event post.
|
||||
$post_id = \wp_insert_post( $args );
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if ( ! $post_id || \is_wp_error( $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert the dates.
|
||||
$gatherpress_event = new GatherPress_Event( $post_id );
|
||||
$start_time = $activitypub_event->get_start_time();
|
||||
$end_time = $activitypub_event->get_end_time();
|
||||
if ( ! $end_time ) {
|
||||
$end_time = new DateTime( $start_time );
|
||||
$end_time->modify( '+1 hour' );
|
||||
$end_time = $end_time->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
$params = array(
|
||||
'datetime_start' => $start_time,
|
||||
'datetime_end' => $end_time,
|
||||
'timezone' => $activitypub_event->get_timezone(),
|
||||
);
|
||||
// Sanitization of the params is done in the save_datetimes function just in time.
|
||||
$gatherpress_event->save_datetimes( $params );
|
||||
|
||||
// Insert featured image.
|
||||
$image = self::get_featured_image( $activitypub_event );
|
||||
self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
|
||||
|
||||
// Add hashtags.
|
||||
self::add_tags_to_post( $activitypub_event, $post_id );
|
||||
|
||||
// Add venue.
|
||||
self::add_venue( $activitypub_event, $post_id );
|
||||
|
||||
// Limit this as a safety measure.
|
||||
\remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
}
|
@ -0,0 +1,371 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transmogrify for the The Events Calendar event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to The Events Calendar Events.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper\The_Events_Calendar_Event_Repository;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper\The_Events_Calendar_Venue_Repository;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the GatherPress event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to GatherPress Events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar extends Base {
|
||||
/**
|
||||
* Save the ActivityPub event object as GatherPress Event.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event as associative array.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
protected static function save_event( $activitypub_event, $event_source_post_id ) {
|
||||
// Limit the number of saved post revisions as a safety measure.
|
||||
add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
$post_id = self::get_post_id_from_activitypub_id( $activitypub_event->get_id() );
|
||||
$duration = self::get_duration( $activitypub_event );
|
||||
$venue_id = self::add_venue( $activitypub_event, $event_source_post_id );
|
||||
$organizer_id = self::add_organizer( $activitypub_event );
|
||||
|
||||
$args = array(
|
||||
'title' => $activitypub_event->get_name(),
|
||||
'content' => $activitypub_event->get_content() ?? '',
|
||||
'start_date' => gmdate( 'Y-m-d H:i:s', strtotime( $activitypub_event->get_start_time() ) ),
|
||||
'duration' => $duration,
|
||||
'status' => 'publish',
|
||||
'guid' => $activitypub_event->get_id(),
|
||||
);
|
||||
|
||||
if ( $venue_id ) {
|
||||
$args['venue'] = $venue_id;
|
||||
$args['VenueID'] = $venue_id;
|
||||
}
|
||||
|
||||
if ( $organizer_id ) {
|
||||
$args['organizer'] = $organizer_id;
|
||||
$args['OrganizerID'] = $organizer_id;
|
||||
}
|
||||
|
||||
if ( $activitypub_event->get_published() ) {
|
||||
$post_date = self::format_time_string_to_wordpress_gmt( $activitypub_event->get_published() );
|
||||
$args['post_date'] = $post_date;
|
||||
$args['post_date_gmt'] = $post_date;
|
||||
}
|
||||
|
||||
$tribe_event = new The_Events_Calendar_Event_Repository();
|
||||
|
||||
if ( $post_id ) {
|
||||
$post = $tribe_event->where( 'id', $post_id )->set_args( $args )->save();
|
||||
} else {
|
||||
$post = $tribe_event->set_args( $args )->create();
|
||||
}
|
||||
|
||||
if ( $post instanceof \WP_Post ) {
|
||||
$post_id = $post->ID;
|
||||
}
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert featured image.
|
||||
$image = self::get_featured_image( $activitypub_event );
|
||||
if ( isset( $image['url'] ) ) {
|
||||
self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
|
||||
}
|
||||
|
||||
// Add tags.
|
||||
self::add_tags_to_post( $activitypub_event, $post_id );
|
||||
|
||||
// Remove revision limit.
|
||||
remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an ActivityStreams Place to the Events Calendar venue.
|
||||
*
|
||||
* @param array|Place $location An ActivityPub location as an associative array or Place object.
|
||||
* @link https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place
|
||||
* @return array
|
||||
*/
|
||||
private static function get_venue_args( $location ): array {
|
||||
$args = array(
|
||||
'venue' => $location['name'],
|
||||
'status' => 'publish',
|
||||
);
|
||||
|
||||
if ( $location instanceof Place ) {
|
||||
$location = $location->to_array();
|
||||
}
|
||||
|
||||
if ( ! isset( $location['address'] ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
if ( is_array( $location['address'] ) ) {
|
||||
$mapping = array(
|
||||
'streetAddress' => 'address',
|
||||
'postalCode' => 'zip',
|
||||
'addressLocality' => 'city',
|
||||
'addressState' => 'state',
|
||||
'addressCountry' => 'country',
|
||||
'url' => 'website',
|
||||
);
|
||||
|
||||
foreach ( $mapping as $postal_address_key => $venue_key ) {
|
||||
if ( isset( $location['address'][ $postal_address_key ] ) ) {
|
||||
$args[ $venue_key ] = $location['address'][ $postal_address_key ];
|
||||
}
|
||||
}
|
||||
} elseif ( is_string( $location['address'] ) ) {
|
||||
// Use the address field for a solely text address.
|
||||
$args['address'] = $location['address'];
|
||||
}
|
||||
|
||||
if ( isset( $location['id'] ) ) {
|
||||
$args['guid'] = $location['id'];
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add venue.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $event_source_post_id The WordPress Post ID of the event source.
|
||||
*
|
||||
* @return ?int $post_id The venues post ID.
|
||||
*/
|
||||
private static function add_venue( $activitypub_event, $event_source_post_id ): ?int {
|
||||
$location = $activitypub_event->get_location();
|
||||
|
||||
// Make sure we have a valid location in the right format.
|
||||
if ( ! $location ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( $location instanceof Place ) {
|
||||
$location = $location->to_array();
|
||||
}
|
||||
|
||||
if ( ! is_array( $location ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['name'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fallback for Gancio instances.
|
||||
if ( 'online' === $location['name'] ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tribe_venue = new The_Events_Calendar_Venue_Repository();
|
||||
|
||||
// If the venue already exists try to find it's post id.
|
||||
$post_id = null;
|
||||
|
||||
// Search if we already got this venue/place in our database.
|
||||
if ( isset( $location['id'] ) ) {
|
||||
global $wpdb;
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$post_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s AND post_type=%s",
|
||||
$location['id'],
|
||||
\Tribe__Events__Venue::POSTTYPE
|
||||
)
|
||||
);
|
||||
if ( $post_id ) {
|
||||
$post_id = (int) $post_id;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $post_id ) {
|
||||
// Try to find a match by searching.
|
||||
$results = $tribe_venue->search( $location['name'] )->all();
|
||||
|
||||
foreach ( $results as $potential_matching_post_id ) {
|
||||
// @phpstan-ignore-next-line
|
||||
if ( $potential_matching_post_id instanceof \WP_Post ) {
|
||||
$potential_matching_post_id = $potential_matching_post_id->ID;
|
||||
}
|
||||
// Only accept a match for the venue/location if it was received by the same actor.
|
||||
if ( \get_post_meta( $potential_matching_post_id, '_event_bridge_for_activitypub_event_source', true ) === $event_source_post_id ) {
|
||||
$post_id = $potential_matching_post_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $post_id ) {
|
||||
// Update if we found a match.
|
||||
$result = $tribe_venue->where( 'id', $post_id )->set_args( self::get_venue_args( $location ) )->save();
|
||||
if ( array_key_exists( $post_id, $result ) && $result[ $post_id ] ) {
|
||||
return $post_id;
|
||||
}
|
||||
} else {
|
||||
// Create a new venue.
|
||||
$post = $tribe_venue->set_args( self::get_venue_args( $location ) )->create();
|
||||
if ( $post ) {
|
||||
$post_id = $post->ID;
|
||||
update_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', $event_source_post_id );
|
||||
}
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add organizer.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
*
|
||||
* @return int|bool $post_id The organizers post ID.
|
||||
*/
|
||||
private static function add_organizer( $activitypub_event ) {
|
||||
// This might likely change, because of FEP-8a8e.
|
||||
$actor = $activitypub_event->get_attributed_to();
|
||||
|
||||
if ( is_null( $actor ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actor_id = object_to_uri( $actor );
|
||||
$event_source = Event_Source::get_by_id( $actor_id );
|
||||
|
||||
// As long as we do not support announces, we expect the attributedTo to be an existing event source.
|
||||
if ( ! $event_source ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare arguments for inserting/updating the organizer post.
|
||||
$args = array(
|
||||
'organizer' => $event_source->get_name(),
|
||||
'description' => $event_source->get_summary(),
|
||||
'website' => $event_source->get_url(),
|
||||
'excerpt' => $event_source->get_summary(),
|
||||
'post_parent' => $event_source->get__id(), // Maybe just use post meta too here.
|
||||
);
|
||||
|
||||
if ( $event_source->get_published() ) {
|
||||
$post_date = self::format_time_string_to_wordpress_gmt( $event_source->get_published() );
|
||||
$args['post_date'] = $post_date;
|
||||
$args['post_date_gmt'] = $post_date;
|
||||
}
|
||||
|
||||
// Get organizer if it is already present.
|
||||
$children = \get_children(
|
||||
array(
|
||||
'post_parent' => $event_source->get__id(),
|
||||
'post_type' => \Tribe__Events__Organizer::POSTTYPE,
|
||||
),
|
||||
);
|
||||
|
||||
if ( count( $children ) ) {
|
||||
// Update organizer post.
|
||||
$child = array_pop( $children );
|
||||
$tribe_organizer_post_ids = \tribe_organizers()->where( 'id', $child->ID )->set_args( $args )->save();
|
||||
|
||||
// Fallback to delete duplicates.
|
||||
foreach ( $children as $to_delete ) {
|
||||
\wp_delete_post( $to_delete->ID, true );
|
||||
}
|
||||
|
||||
// If updating failed return.
|
||||
if ( 1 !== count( $tribe_organizer_post_ids ) || ! reset( $tribe_organizer_post_ids ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tribe_organizer_post_id = array_key_first( $tribe_organizer_post_ids );
|
||||
} else {
|
||||
// Create new organizer post.
|
||||
$tribe_organizer_post = \tribe_organizers()->set_args( $args )->create();
|
||||
|
||||
if ( ! $tribe_organizer_post ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tribe_organizer_post_id = $tribe_organizer_post->ID;
|
||||
|
||||
// Make a relationship between the event source WP_Post and the organizer WP_Post.
|
||||
\update_post_meta( $tribe_organizer_post_id, '_event_bridge_for_activitypub_event_source', true );
|
||||
}
|
||||
|
||||
// Add the thumbnail of the event source to the organizer.
|
||||
if ( \get_post_thumbnail_id( $event_source->get__id() ) ) {
|
||||
\set_post_thumbnail( $tribe_organizer_post_id, \get_post_thumbnail_id( $event_source->get__id() ) );
|
||||
}
|
||||
|
||||
return $tribe_organizer_post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tags to post.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private static function add_tags_to_post( $activitypub_event, $post_id ): bool {
|
||||
$tags_array = $activitypub_event->get_tag();
|
||||
|
||||
// Ensure the input is valid.
|
||||
if ( empty( $tags_array ) || ! is_array( $tags_array ) || ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract and process tag names.
|
||||
$tag_names = array();
|
||||
foreach ( $tags_array as $tag ) {
|
||||
if ( isset( $tag['name'] ) && 'Hashtag' === $tag['type'] ) {
|
||||
$tag_names[] = ltrim( $tag['name'], '#' ); // Remove the '#' from the name.
|
||||
}
|
||||
}
|
||||
|
||||
// Add the tags as terms to the post.
|
||||
if ( ! empty( $tag_names ) ) {
|
||||
\wp_set_object_terms( $post_id, $tag_names, 'post_tag', true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the events duration in seconds.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function get_duration( $activitypub_event ): int {
|
||||
$end_time = $activitypub_event->get_end_time();
|
||||
if ( ! $end_time ) {
|
||||
return 2 * HOUR_IN_SECONDS;
|
||||
}
|
||||
return abs( strtotime( $end_time ) - strtotime( $activitypub_event->get_start_time() ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the VS Event List event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to events of VS Event List.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/very-simple-event-list/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\VS_Event_List as IntegrationsVS_Event_List;
|
||||
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the VS Event List event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to events of VS Event List.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/very-simple-event-list/
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class VS_Event_List extends Base {
|
||||
/**
|
||||
* Extract location and address as string.
|
||||
*
|
||||
* @param mixed $location The ActivityStreams location.
|
||||
* @return string The location and address formatted as a single string.
|
||||
*/
|
||||
private static function get_location_as_string( $location ): string {
|
||||
$location_string = '';
|
||||
|
||||
if ( $location instanceof Place ) {
|
||||
$location = $location->to_array();
|
||||
}
|
||||
|
||||
// Return empty string when location is not an associative array.
|
||||
if ( ! is_array( $location ) || 0 === count( $location ) ) {
|
||||
return $location_string;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['type'] ) || 'Place' !== $location['type'] ) {
|
||||
return $location_string;
|
||||
}
|
||||
|
||||
// Add name of the location.
|
||||
if ( isset( $location['name'] ) ) {
|
||||
$location_string .= $location['name'];
|
||||
}
|
||||
|
||||
// Add delimiter between name and address if both are set.
|
||||
if ( isset( $location['name'] ) && isset( $location['address'] ) ) {
|
||||
$location_string .= ' – ';
|
||||
}
|
||||
|
||||
// Add address.
|
||||
if ( isset( $location['address'] ) ) {
|
||||
$location_string .= self::address_to_string( $location['address'] );
|
||||
}
|
||||
return $location_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tags to post.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private static function add_tags_to_post( $activitypub_event, $post_id ) {
|
||||
$tags_array = $activitypub_event->get_tag();
|
||||
|
||||
// Ensure the input is valid.
|
||||
if ( empty( $tags_array ) || ! is_array( $tags_array ) || ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract and process tag names.
|
||||
$tag_names = array();
|
||||
foreach ( $tags_array as $tag ) {
|
||||
if ( isset( $tag['name'] ) && 'Hashtag' === $tag['type'] ) {
|
||||
$tag_names[] = ltrim( $tag['name'], '#' ); // Remove the '#' from the name.
|
||||
}
|
||||
}
|
||||
|
||||
// Add the tags as terms to the post.
|
||||
if ( ! empty( $tag_names ) ) {
|
||||
\wp_set_object_terms( $post_id, $tag_names, IntegrationsVS_Event_List::get_event_category_taxonomy(), true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object as VS Event List event.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
protected static function save_event( $activitypub_event, $event_source_post_id ) {
|
||||
// Limit this as a safety measure.
|
||||
\add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
$post_id = self::get_post_id_from_activitypub_id( $activitypub_event->get_id() );
|
||||
|
||||
$args = array(
|
||||
'post_title' => $activitypub_event->get_name(),
|
||||
'post_type' => \Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::get_post_type(),
|
||||
'post_content' => $activitypub_event->get_content() ?? '',
|
||||
'post_excerpt' => $activitypub_event->get_summary() ?? '',
|
||||
'post_status' => 'publish',
|
||||
'guid' => $activitypub_event->get_id(),
|
||||
'meta_input' => array(
|
||||
'event-start-date' => \strtotime( $activitypub_event->get_start_time() ),
|
||||
'event-link' => $activitypub_event->get_url() ?? $activitypub_event->get_id(),
|
||||
'event-link-label' => \sanitize_text_field( __( 'Original Website', 'event-bridge-for-activitypub' ) ),
|
||||
'event-link-target' => 'yes', // Open in new window.
|
||||
'event-link-title' => 'no', // Whether to redirect event title to original source.
|
||||
'event-link-image' => 'no', // Whether to redirect events featured image to original source.
|
||||
),
|
||||
);
|
||||
|
||||
if ( $activitypub_event->get_published() ) {
|
||||
$post_date = self::format_time_string_to_wordpress_gmt( $activitypub_event->get_published() );
|
||||
$args['post_date'] = $post_date;
|
||||
$args['post_date_gmt'] = $post_date;
|
||||
}
|
||||
|
||||
// Add end time.
|
||||
$end_time = $activitypub_event->get_end_time();
|
||||
if ( $end_time ) {
|
||||
$args['meta_input']['event-date'] = \strtotime( $end_time );
|
||||
}
|
||||
|
||||
// Maybe add location.
|
||||
$location = self::get_location_as_string( $activitypub_event->get_location() );
|
||||
if ( $location ) {
|
||||
$args['meta_input']['event-location'] = $location;
|
||||
}
|
||||
|
||||
if ( $post_id ) {
|
||||
// Update existing event post.
|
||||
$args['ID'] = $post_id;
|
||||
$post_id = \wp_update_post( $args );
|
||||
} else {
|
||||
// Insert new event post.
|
||||
$post_id = \wp_insert_post( $args );
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if ( 0 === $post_id || \is_wp_error( $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert featured image.
|
||||
$image = self::get_featured_image( $activitypub_event );
|
||||
self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
|
||||
|
||||
// Add hashtags.
|
||||
self::add_tags_to_post( $activitypub_event, $post_id );
|
||||
|
||||
// Limit this as a safety measure.
|
||||
\remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
<?php
|
||||
/**
|
||||
* Collection of functions that sanitize an incoming event.
|
||||
*
|
||||
* We do a lot of duck-typing. We just discard/ignore attributes/properties we do not know.
|
||||
* Replacing this with defining a schema and using rest_sanitize_value_from_schema is a future goal.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use WP_Error;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* Collection of functions that sanitize an incoming event.
|
||||
*
|
||||
* We do a lot of duck-typing. We just discard/ignore attributes/properties we do not know.
|
||||
* Replacing this with defining a schema and using rest_sanitize_value_from_schema is a future goal.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
class Sanitizer {
|
||||
/**
|
||||
* Convert input array to an Event.
|
||||
*
|
||||
* @param mixed $data The object array.
|
||||
*
|
||||
* @return Event|WP_Error An Object built from the input array or WP_Error when it's not an array.
|
||||
*/
|
||||
public static function init_and_sanitize_event_object_from_array( $data ) {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return new WP_Error( 'invalid_array', __( 'Invalid array', 'event-bridge-for-activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$event = new Event();
|
||||
|
||||
// Straightforward sanitization of all attributes we possible make use of.
|
||||
if ( isset( $data['content'] ) ) {
|
||||
$event->set_content( \wp_kses_post( $data['content'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['summary'] ) ) {
|
||||
$event->set_summary( \wp_kses_post( $data['summary'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['name'] ) ) {
|
||||
$event->set_name( \sanitize_text_field( $data['name'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['startTime'] ) ) {
|
||||
$event->set_start_time( \sanitize_text_field( $data['startTime'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['endTime'] ) ) {
|
||||
$event->set_end_time( \sanitize_text_field( $data['endTime'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['published'] ) ) {
|
||||
$event->set_published( \sanitize_text_field( $data['published'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['id'] ) ) {
|
||||
$event->set_id( \sanitize_url( $data['id'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['url'] ) ) {
|
||||
$event->set_url( \sanitize_url( $data['url'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['attributedTo'] ) ) {
|
||||
$event->set_attributed_to( self::sanitize_attributed_to( $data['attributedTo'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['location'] ) ) {
|
||||
$event->set_location( self::sanitize_place_object_from_array( $data['location'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['attachment'] ) ) {
|
||||
$event->set_attachment( self::sanitize_attachment( $data['attachment'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['tag'] ) ) {
|
||||
$event->set_tag( self::sanitize_attachment( $data['tag'] ) );
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize attributedTo.
|
||||
*
|
||||
* Currently only multiple attributedTo's are not supported.
|
||||
*
|
||||
* @param mixed $data The object array.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function sanitize_attributed_to( $data ): string {
|
||||
if ( is_array( $data ) && self::array_is_list( $data ) ) {
|
||||
$data = reset( $data );
|
||||
}
|
||||
|
||||
return object_to_uri( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize attachments.
|
||||
*
|
||||
* @param mixed $data The object array.
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
private static function sanitize_attachment( $data ): ?array {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! self::array_is_list( $data ) ) {
|
||||
$data = array( $data );
|
||||
}
|
||||
|
||||
$attachment = array();
|
||||
|
||||
foreach ( $data as $item ) {
|
||||
$sanitized_item = array();
|
||||
|
||||
// Straightforward sanitization of all attributes we possible make use of.
|
||||
if ( isset( $item['name'] ) ) {
|
||||
$sanitized_item['name'] = \sanitize_text_field( $item['name'] );
|
||||
}
|
||||
if ( isset( $item['url'] ) ) {
|
||||
$sanitized_item['url'] = \sanitize_url( $item['url'] );
|
||||
}
|
||||
if ( isset( $item['id'] ) ) {
|
||||
$sanitized_item['id'] = \sanitize_url( $item['id'] );
|
||||
}
|
||||
if ( isset( $item['type'] ) ) {
|
||||
$sanitized_item['type'] = \sanitize_text_field( $item['type'] );
|
||||
}
|
||||
if ( isset( $item['href'] ) ) {
|
||||
$sanitized_item['href'] = \sanitize_text_field( $item['href'] );
|
||||
}
|
||||
|
||||
if ( isset( $sanitized_item['url'] ) || isset( $sanitized_item['href'] ) || isset( $sanitized_item['name'] ) ) {
|
||||
$attachment[] = $sanitized_item;
|
||||
}
|
||||
}
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback for PHP version prior to 8.1 for array_is_list.
|
||||
*
|
||||
* @param array $arr The array to check.
|
||||
* @return bool
|
||||
*/
|
||||
private static function array_is_list( $arr ) {
|
||||
if ( ! function_exists( 'array_is_list' ) ) {
|
||||
if ( array() === $arr ) {
|
||||
return true;
|
||||
}
|
||||
return array_keys( $arr ) === range( 0, count( $arr ) - 1 );
|
||||
}
|
||||
return array_is_list( $arr );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert input array to an Location.
|
||||
*
|
||||
* @param mixed $data The object array.
|
||||
*
|
||||
* @return ?Place An Object built from the input array or null.
|
||||
*/
|
||||
private static function sanitize_place_object_from_array( $data ): ?Place {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the array is a list, work with the first item.
|
||||
if ( array_key_exists( 0, $data ) ) {
|
||||
$data = $data[0];
|
||||
}
|
||||
|
||||
$place = new Place();
|
||||
|
||||
if ( isset( $data['name'] ) ) {
|
||||
$place->set_name( \sanitize_text_field( $data['name'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['id'] ) ) {
|
||||
$place->set_id( \sanitize_url( $data['id'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['url'] ) ) {
|
||||
$place->set_url( \sanitize_url( $data['url'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['address'] ) ) {
|
||||
if ( is_string( $data['address'] ) ) {
|
||||
$place->set_address( \sanitize_text_field( $data['address'] ) );
|
||||
}
|
||||
if ( is_array( $data['address'] ) && isset( $data['address']['type'] ) && 'PostalAddress' === $data['address']['type'] ) {
|
||||
$address = array();
|
||||
if ( isset( $data['address']['streetAddress'] ) ) {
|
||||
$address['streetAddress'] = \sanitize_text_field( $data['address']['streetAddress'] );
|
||||
}
|
||||
if ( isset( $data['address']['postalCode'] ) ) {
|
||||
$address['postalCode'] = \sanitize_text_field( $data['address']['postalCode'] );
|
||||
}
|
||||
if ( isset( $data['address']['addressLocality'] ) ) {
|
||||
$address['addressLocality'] = \sanitize_text_field( $data['address']['addressLocality'] );
|
||||
}
|
||||
if ( isset( $data['address']['addressState'] ) ) {
|
||||
$address['addressState'] = \sanitize_text_field( $data['address']['addressState'] );
|
||||
}
|
||||
if ( isset( $data['address']['addressCountry'] ) ) {
|
||||
$address['addressCountry'] = \sanitize_text_field( $data['address']['addressCountry'] );
|
||||
}
|
||||
if ( isset( $data['address']['url'] ) ) {
|
||||
$address['url'] = \sanitize_url( $data['address']['url'] );
|
||||
}
|
||||
$place->set_address( $address );
|
||||
}
|
||||
}
|
||||
|
||||
return $place;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Extending the Tribe Events API to allow setting of the guid.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Extending the Tribe Events API to allow setting of the guid.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar_Event_Repository extends \Tribe__Events__Repositories__Event {
|
||||
/**
|
||||
* Override diff: allow setting of guid.
|
||||
*
|
||||
* @var array An array of keys that cannot be updated on this repository.
|
||||
*/
|
||||
protected static $blocked_keys = array(
|
||||
'ID',
|
||||
'post_type',
|
||||
'comment_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the current key can be updated by this repository or not.
|
||||
*
|
||||
* @since 4.7.19
|
||||
*
|
||||
* @param string $key The key.
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_be_updated( $key ): bool {
|
||||
return ! in_array( $key, self::$blocked_keys, true );
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Extending the Organizer Venue API to allow setting of the guid.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Extending the Organizer Venue API to allow setting of the guid.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar_Organizer_Repository extends \Tribe__Events__Repositories__Organizer {
|
||||
/**
|
||||
* Override diff: allow setting of guid.
|
||||
*
|
||||
* @var array An array of keys that cannot be updated on this repository.
|
||||
*/
|
||||
protected static $blocked_keys = array(
|
||||
'ID',
|
||||
'post_type',
|
||||
'comment_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the current key can be updated by this repository or not.
|
||||
*
|
||||
* @since 4.7.19
|
||||
*
|
||||
* @param string $key The key.
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_be_updated( $key ): bool {
|
||||
return ! in_array( $key, self::$blocked_keys, true );
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Extending the Tribe Venue API to allow setting of the guid.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Extending the Tribe Venue API to allow setting of the guid.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar_Venue_Repository extends \Tribe__Events__Repositories__Venue {
|
||||
/**
|
||||
* Override diff: allow setting of guid.
|
||||
*
|
||||
* @var array An array of keys that cannot be updated on this repository.
|
||||
*/
|
||||
protected static $blocked_keys = array(
|
||||
'ID',
|
||||
'post_type',
|
||||
'comment_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the current key can be updated by this repository or not.
|
||||
*
|
||||
* @since 4.7.19
|
||||
*
|
||||
* @param string $key The key.
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_be_updated( $key ): bool {
|
||||
return ! in_array( $key, self::$blocked_keys, true );
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user