WIP: Add Event Sources Logic (ActivityPub follows) #86
13 changed files with 497 additions and 178 deletions
|
@ -194,3 +194,16 @@ code.event-bridge-for-activitypub-settings-example-url {
|
|||
.event_bridge_for_activitypub-admin-table-container {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-admin-table-top > h2 {
|
||||
display: inline-block;
|
||||
font-size: 23px;
|
||||
font-weight: 400;
|
||||
margin: 0 10px 0 2px;
|
||||
padding: 9px 0 4px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-admin-table-top > a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
|
@ -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.2.4
|
||||
* Author: André Menrath
|
||||
* Author URI: https://graz.social/@linos
|
||||
* Text Domain: event-bridge-for-activitypub
|
||||
|
|
|
@ -13,7 +13,6 @@ namespace Event_Bridge_For_ActivityPub\ActivityPub;
|
|||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Accept;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Announce;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Update;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Create;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Delete;
|
||||
|
@ -27,7 +26,6 @@ class Handler {
|
|||
*/
|
||||
public static function register_handlers() {
|
||||
Accept::init();
|
||||
Announce::init();
|
||||
Update::init();
|
||||
Create::init();
|
||||
Delete::init();
|
||||
|
|
|
@ -34,8 +34,6 @@ class Event_Sources {
|
|||
\add_action( 'event_bridge_for_activitypub_unfollow', array( self::class, 'activitypub_unfollow_actor' ), 10, 2 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Register the post type used to store the external event sources (i.e., followed ActivityPub actors).
|
||||
*/
|
||||
|
@ -159,9 +157,22 @@ class Event_Sources {
|
|||
|
||||
self::queue_follow_actor( $actor );
|
||||
|
||||
self::delete_event_source_transients();
|
||||
|
||||
return $event_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all transients related to the event sources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_event_source_transients(): void {
|
||||
delete_transient( 'event_bridge_for_activitypub_event_sources' );
|
||||
delete_transient( 'event_bridge_for_activitypub_event_sources_hosts' );
|
||||
delete_transient( 'event_bridge_for_activitypub_event_sources_ids' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an Event Source (=Followed ActivityPub actor).
|
||||
*
|
||||
|
@ -171,39 +182,68 @@ class Event_Sources {
|
|||
*/
|
||||
public static function remove_event_source( $actor ) {
|
||||
$actor = true;
|
||||
self::delete_event_source_transients();
|
||||
return $actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array will all unique hosts of all Event-Sources.
|
||||
*
|
||||
* @return array The Term list of Event Sources.
|
||||
*/
|
||||
public static function get_event_sources_hosts() {
|
||||
$hosts = get_transient( 'event_bridge_for_activitypub_event_sources_hosts' );
|
||||
|
||||
if ( $hosts ) {
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
$actors = self::get_event_sources_with_count()['actors'];
|
||||
|
||||
$hosts = array();
|
||||
foreach ( $actors as $actor ) {
|
||||
$url = wp_parse_url( $actor->get_id() );
|
||||
if ( isset( $url['host'] ) ) {
|
||||
$hosts[] = $url['host'];
|
||||
}
|
||||
}
|
||||
|
||||
set_transient( 'event_bridge_for_activitypub_event_sources_hosts', $hosts );
|
||||
|
||||
return array_unique( $hosts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get add Event Sources ActivityPub IDs.
|
||||
*
|
||||
* @return array The Term list of Event Sources.
|
||||
*/
|
||||
public static function get_event_sources_ids() {
|
||||
$ids = get_transient( 'event_bridge_for_activitypub_event_sources_ids' );
|
||||
|
||||
if ( $ids ) {
|
||||
return $ids;
|
||||
}
|
||||
|
||||
$actors = self::get_event_sources_with_count()['actors'];
|
||||
|
||||
$ids = array();
|
||||
foreach ( $actors as $actor ) {
|
||||
$ids[] = $actor->get_id();
|
||||
}
|
||||
|
||||
set_transient( 'event_bridge_for_activitypub_event_sources_ids', $ids );
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Event-Sources.
|
||||
*
|
||||
* @return array The Term list of Event Sources.
|
||||
*/
|
||||
public static function get_event_sources() {
|
||||
$args = array(
|
||||
'post_type' => self::POST_TYPE,
|
||||
'posts_per_page' => -1,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'DESC',
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => 'activitypub_actor_id',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$query = new WP_Query( $args );
|
||||
|
||||
$event_sources = array_map(
|
||||
function ( $post ) {
|
||||
return Event_Source::init_from_cpt( $post );
|
||||
},
|
||||
$query->get_posts()
|
||||
);
|
||||
|
||||
return $event_sources;
|
||||
return self::get_event_sources_with_count()['actors'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,6 +261,12 @@ class Event_Sources {
|
|||
* }
|
||||
*/
|
||||
public static function get_event_sources_with_count( $number = -1, $page = null, $args = array() ) {
|
||||
$event_sources = get_transient( 'event_bridge_for_activitypub_event_sources' );
|
||||
|
||||
if ( $event_sources ) {
|
||||
return $event_sources;
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'post_type' => self::POST_TYPE,
|
||||
'posts_per_page' => $number,
|
||||
|
@ -239,7 +285,11 @@ class Event_Sources {
|
|||
$query->get_posts()
|
||||
);
|
||||
|
||||
return compact( 'actors', 'total' );
|
||||
$event_sources = compact( 'actors', 'total' );
|
||||
|
||||
set_transient( 'event_bridge_for_activitypub_event_sources', $event_sources );
|
||||
|
||||
return $event_sources;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -389,4 +439,15 @@ class Event_Sources {
|
|||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::APPLICATION_USER_ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Event Sources hosts to allowed hosts used by safe redirect.
|
||||
*
|
||||
* @param array $hosts The hosts before the filter.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_event_sources_hosts_to_allowed_redirect_hosts( $hosts ) {
|
||||
$event_sources_hosts = self::get_event_sources_hosts();
|
||||
return array_merge( $hosts, $event_sources_hosts );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Accept handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use Activitypub\Notification;
|
||||
use Activitypub\Collection\Actors;
|
||||
|
||||
/**
|
||||
* Handle Accept requests.
|
||||
*/
|
||||
class Announce {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_inbox_announce',
|
||||
array( self::class, 'handle_announce' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Announce" requests.
|
||||
*
|
||||
* @param array $activity The activity object.
|
||||
*/
|
||||
public static function handle_announce( $activity ) {
|
||||
if ( ! isset( $activity['object'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$object = Actors::get_by_resource( $activity['object'] );
|
||||
|
||||
if ( ! $object || is_wp_error( $object ) ) {
|
||||
// If we can not find a actor, we handle the `Accept` activity.
|
||||
return;
|
||||
}
|
||||
|
||||
// We only expect `Accept` activities being answers to follow requests by the application actor.
|
||||
if ( Actors::APPLICATION_USER_ID !== $object->get__id() ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
use function Activitypub\is_activity_public;
|
||||
|
@ -46,6 +47,10 @@ class Create {
|
|||
return;
|
||||
}
|
||||
|
||||
if ( ! self::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Activity is public or not.
|
||||
if ( ! is_activity_public( $activity ) ) {
|
||||
return;
|
||||
|
@ -88,10 +93,11 @@ class Create {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
'Create' !== $json_params['type'] || 'Update' !== $json_params['type'] ||
|
||||
is_wp_error( $request )
|
||||
) {
|
||||
if ( empty( $json_params['actor'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $json_params['type'], array( 'Create', 'Update', 'Delete', 'Announce' ), true ) || is_wp_error( $request ) ) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
|
@ -103,11 +109,10 @@ class Create {
|
|||
|
||||
$required = array(
|
||||
'id',
|
||||
'startTime',
|
||||
'name',
|
||||
);
|
||||
|
||||
// Limit this as a safety measure.
|
||||
add_filter( 'wp_revisions_to_keep', array( 'revisions_to_keep' ) );
|
||||
|
||||
if ( array_intersect( $required, array_keys( $object ) ) !== $required ) {
|
||||
return false;
|
||||
}
|
||||
|
@ -116,11 +121,18 @@ class Create {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the number of revisions to keep.
|
||||
* Check if an ActivityPub actor is an event source.
|
||||
*
|
||||
* @return int The number of revisions to keep.
|
||||
* @param string $actor_id The actor ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function revisions_to_keep() {
|
||||
return 3;
|
||||
public static function actor_is_event_source( $actor_id ) {
|
||||
$event_sources = Event_Sources::get_event_sources();
|
||||
foreach ( $event_sources as $event_source ) {
|
||||
if ( $actor_id === $event_source->get_id() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use Activitypub\Notification;
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
/**
|
||||
* Handle Delete requests.
|
||||
|
@ -20,30 +20,40 @@ class Delete {
|
|||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_inbox_delete',
|
||||
array( self::class, 'handle_delete' )
|
||||
array( self::class, 'handle_delete' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Follow" requests.
|
||||
*
|
||||
* @param array $activity The activity object.
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_delete( $activity ) {
|
||||
if ( ! isset( $activity['object'] ) ) {
|
||||
public static function handle_delete( $activity, $user_id ) {
|
||||
// We only process activities that are target to the application user.
|
||||
if ( Actors::APPLICATION_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$object = Actors::get_by_resource( $activity['object'] );
|
||||
|
||||
if ( ! $object || is_wp_error( $object ) ) {
|
||||
// If we can not find a actor, we handle the `Delete` activity.
|
||||
if ( ! Create::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We only expect `Delete` activities being answers to follow requests by the application actor.
|
||||
if ( Actors::APPLICATION_USER_ID !== $object->get__id() ) {
|
||||
// Check if an object is set.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier_class = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier_class ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier = new $transmogrifier_class( $activity['object'] );
|
||||
$transmogrifier->delete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ use Activitypub\Activity\Actor;
|
|||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
use WP_Error;
|
||||
|
||||
use function Activitypub\sanitize_url;
|
||||
|
||||
/**
|
||||
* Event-Source (=ActivityPub Actor that is followed) model.
|
||||
*/
|
||||
|
@ -122,7 +124,7 @@ class Event_Source extends Actor {
|
|||
*/
|
||||
protected function get_post_meta_input() {
|
||||
$meta_input = array();
|
||||
$meta_input['activitypub_inbox'] = $this->get_shared_inbox();
|
||||
$meta_input['activitypub_inbox'] = sanitize_url( $this->get_shared_inbox() );
|
||||
$meta_input['activitypub_actor_json'] = $this->to_json();
|
||||
|
||||
return $meta_input;
|
||||
|
@ -150,7 +152,7 @@ class Event_Source extends Actor {
|
|||
*/
|
||||
public function save() {
|
||||
if ( ! $this->is_valid() ) {
|
||||
return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ), array( 'status' => 400 ) );
|
||||
return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'event-bridge-for-activitypub' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
if ( ! $this->get__id() ) {
|
||||
|
@ -215,7 +217,7 @@ class Event_Source extends Actor {
|
|||
$icon = $this->get_icon();
|
||||
|
||||
if ( isset( $icon['url'] ) ) {
|
||||
$image = media_sideload_image( $icon['url'], $post_id, null, 'id' );
|
||||
$image = media_sideload_image( sanitize_url( $icon['url'] ), $post_id, null, 'id' );
|
||||
}
|
||||
if ( isset( $image ) && ! is_wp_error( $image ) ) {
|
||||
set_post_thumbnail( $post_id, $image );
|
||||
|
|
|
@ -13,6 +13,10 @@ namespace Event_Bridge_For_ActivityPub\Activitypub\Transmogrifier;
|
|||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use DateTime;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
use function Activitypub\sanitize_url;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
@ -57,8 +61,222 @@ class GatherPress {
|
|||
*/
|
||||
private function get_post_id_from_activitypub_id() {
|
||||
global $wpdb;
|
||||
$id = $this->activitypub_event->get_id();
|
||||
return $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE guid=%s AND post_type=%s", $id, 'gatherpress_event' ) );
|
||||
return $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
|
||||
esc_sql( $this->activitypub_event->get_id() )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image URL and alt-text of an ActivityPub object.
|
||||
*
|
||||
* @param array $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 ) {
|
||||
$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.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_featured_image() {
|
||||
$event = $this->activitypub_event;
|
||||
$image = $event->get_image();
|
||||
if ( $image ) {
|
||||
return self::extract_image_alt_and_url( $image );
|
||||
}
|
||||
$attachment = $event->get_attachment();
|
||||
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
|
||||
*/
|
||||
public static function maybe_sideload_image( $post_id, $url = '' ) {
|
||||
global $wpdb;
|
||||
|
||||
// Include necessary WordPress file for media handling.
|
||||
if ( ! function_exists( 'media_sideload_image' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
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", sanitize_url( $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( sanitize_url( $url ), $post_id, sanitize_url( $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 The attachment ID
|
||||
*/
|
||||
private 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', sanitize_text_field( $alt_text ) );
|
||||
}
|
||||
|
||||
return $image_id; // Return the attachment ID for further use if needed.
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tags to post.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private function add_tags_to_post( $post_id ) {
|
||||
$tags_array = $this->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, 'gatherpress_topic', true ); // 'true' appends to existing terms.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add venue.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private function add_venue( $post_id ) {
|
||||
$location = $this->activitypub_event->get_location();
|
||||
|
||||
if ( ! $location ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['name'] ) ) {
|
||||
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();
|
||||
|
||||
$venue_information['fullAddress'] = $location['address'] ?? ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$venue_information['phone_number'] = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$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', true ); // 'true' appends to existing terms.
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,12 +286,12 @@ class GatherPress {
|
|||
// Insert new GatherPress Event post.
|
||||
$post_id = wp_insert_post(
|
||||
array(
|
||||
'post_title' => $this->activitypub_event->get_name(),
|
||||
'post_title' => sanitize_text_field( $this->activitypub_event->get_name() ),
|
||||
'post_type' => 'gatherpress_event',
|
||||
'post_content' => $this->activitypub_event->get_content(),
|
||||
'post_excerpt' => $this->activitypub_event->get_summary(),
|
||||
'post_content' => wp_kses_post( $this->activitypub_event->get_content() ) . '<!-- wp:gatherpress/venue /-->',
|
||||
'post_excerpt' => wp_kses_post( $this->activitypub_event->get_summary() ),
|
||||
'post_status' => 'publish',
|
||||
'guid' => $this->activitypub_event->get_id(),
|
||||
'guid' => sanitize_url( $this->activitypub_event->get_id() ),
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -81,12 +299,31 @@ class GatherPress {
|
|||
return;
|
||||
}
|
||||
|
||||
$event = new \GatherPress\Core\Event( $post_id );
|
||||
// Insert the Dates.
|
||||
$event = new GatherPress_Event( $post_id );
|
||||
$start_time = $this->activitypub_event->get_start_time();
|
||||
$end_time = $this->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' => $this->activitypub_event->get_start_time(),
|
||||
'datetime_end' => $this->activitypub_event->get_end_time(),
|
||||
'datetime_start' => $start_time,
|
||||
'datetime_end' => $end_time,
|
||||
'timezone' => $this->activitypub_event->get_timezone(),
|
||||
);
|
||||
|
||||
// Insert featured image.
|
||||
$image = $this->get_featured_image();
|
||||
self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
|
||||
|
||||
// Add hashtags as terms.
|
||||
$this->add_tags_to_post( $post_id );
|
||||
|
||||
$this->add_venue( $post_id );
|
||||
|
||||
// Sanitization of the params is done in the save_datetimes function just in time.
|
||||
$event->save_datetimes( $params );
|
||||
}
|
||||
|
||||
|
@ -94,18 +331,21 @@ class GatherPress {
|
|||
* Save the ActivityPub event object as GatherPress Event.
|
||||
*/
|
||||
public function update() {
|
||||
// Limit this as a safety measure.
|
||||
add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
$post_id = $this->get_post_id_from_activitypub_id();
|
||||
|
||||
// Insert new GatherPress Event post.
|
||||
$post_id = wp_update_post(
|
||||
array(
|
||||
'ID' => $post_id,
|
||||
'post_title' => $this->activitypub_event->get_name(),
|
||||
'post_title' => sanitize_text_field( $this->activitypub_event->get_name() ),
|
||||
'post_type' => 'gatherpress_event',
|
||||
'post_content' => $this->activitypub_event->get_content(),
|
||||
'post_excerpt' => $this->activitypub_event->get_summary(),
|
||||
'post_content' => wp_kses_post( $this->activitypub_event->get_content() ) . '<!-- wp:gatherpress/venue /-->',
|
||||
'post_excerpt' => wp_kses_post( $this->activitypub_event->get_summary() ),
|
||||
'post_status' => 'publish',
|
||||
'guid' => $this->activitypub_event->get_id(),
|
||||
'guid' => sanitize_url( $this->activitypub_event->get_id() ),
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -113,12 +353,57 @@ class GatherPress {
|
|||
return;
|
||||
}
|
||||
|
||||
$event = new \GatherPress\Core\Event( $post_id );
|
||||
// Insert the dates.
|
||||
$event = new GatherPress_Event( $post_id );
|
||||
$start_time = $this->activitypub_event->get_start_time();
|
||||
$end_time = $this->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' => $this->activitypub_event->get_start_time(),
|
||||
'datetime_end' => $this->activitypub_event->get_end_time(),
|
||||
'datetime_start' => $start_time,
|
||||
'datetime_end' => $end_time,
|
||||
'timezone' => $this->activitypub_event->get_timezone(),
|
||||
);
|
||||
// Sanitization of the params is done in the save_datetimes function just in time.
|
||||
$event->save_datetimes( $params );
|
||||
|
||||
// Insert featured image.
|
||||
$image = $this->get_featured_image();
|
||||
self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
|
||||
|
||||
// Add hashtags.
|
||||
$this->add_tags_to_post( $post_id );
|
||||
|
||||
$this->add_venue( $post_id );
|
||||
|
||||
// Limit this as a safety measure.
|
||||
remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object as GatherPress event.
|
||||
*/
|
||||
public function delete() {
|
||||
$post_id = $this->get_post_id_from_activitypub_id();
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
|
||||
wp_delete_post( $post_id, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of revisions to keep.
|
||||
*
|
||||
* @return int The number of revisions to keep.
|
||||
*/
|
||||
public static function revisions_to_keep() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace Event_Bridge_For_ActivityPub;
|
|||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
use function Activitypub\is_activitypub_request;
|
||||
|
@ -21,38 +22,6 @@ use function Activitypub\is_activitypub_request;
|
|||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
class Event_Sources {
|
||||
/**
|
||||
* The custom post type.
|
||||
*/
|
||||
const POST_TYPE = 'event_bridge_follow';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
\add_action( 'activitypub_inbox', array( $this, 'handle_activitypub_inbox' ), 15, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ActivityPub Inbox.
|
||||
*
|
||||
* @param array $data The raw post data JSON object as an associative array.
|
||||
* @param int $user_id The target user id.
|
||||
* @param string $type The activity type.
|
||||
*/
|
||||
public static function handle_activitypub_inbox( $data, $user_id, $type ) {
|
||||
if ( Actors::APPLICATION_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! in_array( $type, array( 'Create', 'Delete', 'Announce', 'Undo Announce' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = Event::init_from_array( $data );
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata of ActivityPub Actor by ID/URL.
|
||||
*
|
||||
|
@ -71,26 +40,6 @@ class Event_Sources {
|
|||
return get_remote_metadata_by_actor( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to the Ajax request to fetch feeds
|
||||
*/
|
||||
public function ajax_fetch_events() {
|
||||
if ( ! isset( $_POST['Event_Bridge_For_ActivityPub'] ) ) {
|
||||
wp_send_json_error( 'missing-parameters' );
|
||||
}
|
||||
|
||||
check_ajax_referer( 'fetch-events-' . sanitize_user( wp_unslash( $_POST['actor'] ) ) );
|
||||
|
||||
$actor = Actors::get_by_resource( sanitize_user( wp_unslash( $_POST['actor'] ) ) );
|
||||
if ( ! $actor ) {
|
||||
wp_send_json_error( 'unknown-actor' );
|
||||
}
|
||||
|
||||
$actor->retrieve();
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Application actor via FEP-2677.
|
||||
*
|
||||
|
@ -125,6 +74,39 @@ class Event_Sources {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter that cached external posts are not scheduled via the ActivityPub plugin.
|
||||
*
|
||||
* Posts that are actually just external events are treated as cache. They are displayed in
|
||||
* the frontend HTML view and redirected via ActivityPub request, but we do not own them.
|
||||
*
|
||||
* @param bool $disabled If it is disabled already by others (the upstream ActivityPub plugin).
|
||||
* @param WP_Post $post The WordPress post object.
|
||||
* @return bool True if the post can be federated via ActivityPub.
|
||||
*/
|
||||
public static function is_cached_external_post( $disabled, $post = null ): bool {
|
||||
if ( $disabled || ! $post ) {
|
||||
return $disabled;
|
||||
}
|
||||
if ( ! str_starts_with( \get_site_url(), $post->guid ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a WP post is a cached external event.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_cached_external_event( $post ): bool {
|
||||
if ( ! str_starts_with( \get_site_url(), $post->guid ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the ActivityPub template for EventPrime.
|
||||
*
|
||||
|
@ -150,8 +132,8 @@ class Event_Sources {
|
|||
return $template;
|
||||
}
|
||||
|
||||
if ( ! str_starts_with( \get_site_url(), $post->guid ) ) {
|
||||
\wp_redirect( $post->guid, 301 );
|
||||
if ( self::is_cached_external_event( $post ) ) {
|
||||
\wp_safe_redirect( $post->guid, 301 );
|
||||
exit;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Event_Bridge_For_ActivityPub;
|
|||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
use Event_Bridge_For_ActivityPub\Admin\Event_Plugin_Admin_Notices;
|
||||
|
@ -241,7 +242,9 @@ class Setup {
|
|||
if ( get_option( 'event_bridge_for_activitypub_event_sources_active' ) ) {
|
||||
add_action( 'init', array( Event_Sources_Collection::class, 'init' ) );
|
||||
add_action( 'activitypub_register_handlers', array( Handler::class, 'register_handlers' ) );
|
||||
add_action( 'admin_init', array( User_Interface::class, 'init' ) );
|
||||
// add_action( 'admin_init', array( User_Interface::class, 'init' ) );
|
||||
add_filter( 'allowed_redirect_hosts', array( Event_Sources_Collection::class, 'add_event_sources_hosts_to_allowed_redirect_hosts' ) );
|
||||
add_filter( 'activitypub_is_post_disabled', array( Event_Sources::class, 'is_cached_external_post' ), 10, 2 );
|
||||
}
|
||||
\add_filter( 'template_include', array( \Event_Bridge_For_ActivityPub\Event_Sources::class, 'redirect_activitypub_requests_for_cached_external_events' ), 100 );
|
||||
|
||||
|
|
|
@ -20,12 +20,14 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
|||
?>
|
||||
|
||||
<div class="wrap event_bridge_for_activitypub-admin-table-container">
|
||||
|
||||
<h2> <?php esc_html_e( 'List of Event Sources', 'event-bridge-for-activitypub' ); ?> </h2>
|
||||
<!-- Table title with add new button like on post edit pages -->
|
||||
<div class="event_bridge_for_activitypub-admin-table-top">
|
||||
<h2 class="wp-heading-inline"> <?php esc_html_e( 'List of Event Sources', 'event-bridge-for-activitypub' ); ?> </h2>
|
||||
<!-- Button that triggers ThickBox -->
|
||||
<a href="#TB_inline?width=600&height=400&inlineId=Event_Bridge_For_ActivityPub_add_new_source" class="thickbox page-title-action">
|
||||
<?php esc_html_e( 'Add Event Source', 'event-bridge-for-activitypub' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- ThickBox content (hidden initially) -->
|
||||
<div id="Event_Bridge_For_ActivityPub_add_new_source" style="display:none;">
|
||||
|
|
|
@ -100,7 +100,7 @@ $current_category_mapping = \get_option( 'event_bridge_for_activitypub_ev
|
|||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h3><?php \esc_html_e( 'Configuration of the Event Sources feature', 'activitypub' ); ?></h3>
|
||||
<h2><?php \esc_html_e( 'Event Sources', 'event-bridge-for-activitypub' ); ?></h2>
|
||||
<?php
|
||||
if ( count( $event_plugins_supporting_event_sources ) ) {
|
||||
?>
|
||||
|
|
Loading…
Reference in a new issue