2024-11-18 16:07:09 +01:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Event-Source (=ActivityPub Actor that is followed) model.
|
|
|
|
*
|
2024-12-16 17:36:23 +01:00
|
|
|
* This class holds methods needed for relating an ActivityPub actor
|
|
|
|
* that is followed with the custom post type structure how it is
|
|
|
|
* stored within WordPress.
|
|
|
|
*
|
2024-12-05 17:50:17 +01:00
|
|
|
* @package Event_Bridge_For_ActivityPub
|
2024-11-18 16:07:09 +01:00
|
|
|
* @license AGPL-3.0-or-later
|
|
|
|
*/
|
|
|
|
|
2024-12-07 20:07:47 +01:00
|
|
|
namespace Event_Bridge_For_ActivityPub\ActivityPub\Model;
|
2024-11-18 16:07:09 +01:00
|
|
|
|
|
|
|
use Activitypub\Activity\Actor;
|
2024-12-05 17:50:17 +01:00
|
|
|
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
2024-11-18 16:07:09 +01:00
|
|
|
use WP_Error;
|
2024-12-24 11:55:06 +01:00
|
|
|
use WP_Post;
|
2024-11-18 16:07:09 +01:00
|
|
|
|
2024-12-14 14:46:00 +01:00
|
|
|
use function Activitypub\sanitize_url;
|
|
|
|
|
2024-11-18 16:07:09 +01:00
|
|
|
/**
|
|
|
|
* Event-Source (=ActivityPub Actor that is followed) model.
|
2024-12-16 17:36:23 +01:00
|
|
|
*
|
|
|
|
* This class holds methods needed for relating an ActivityPub actor
|
|
|
|
* that is followed with the custom post type structure how it is
|
|
|
|
* stored within WordPress.
|
2024-11-18 16:07:09 +01:00
|
|
|
*/
|
|
|
|
class Event_Source extends Actor {
|
2024-12-08 17:38:05 +01:00
|
|
|
const ACTIVITYPUB_USER_HANDLE_REGEXP = '(?:([A-Za-z0-9_.-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))';
|
|
|
|
|
|
|
|
/**
|
2025-01-04 12:27:09 +01:00
|
|
|
* The WordPress Post ID which stores the event source.
|
2024-12-08 17:38:05 +01:00
|
|
|
*
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
|
|
|
|
2024-11-18 16:07:09 +01:00
|
|
|
/**
|
|
|
|
* Get the Icon URL (Avatar).
|
|
|
|
*
|
|
|
|
* @return string The URL to the Avatar.
|
|
|
|
*/
|
|
|
|
public function get_icon_url() {
|
|
|
|
$icon = $this->get_icon();
|
|
|
|
|
|
|
|
if ( ! $icon ) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( is_array( $icon ) ) {
|
|
|
|
return $icon['url'];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $icon;
|
|
|
|
}
|
|
|
|
|
2024-12-23 21:23:03 +01:00
|
|
|
/**
|
|
|
|
* Return the Post-IDs of all events cached by this event source.
|
|
|
|
*/
|
|
|
|
public static function get_cached_events(): array {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2024-12-24 11:55:06 +01:00
|
|
|
/**
|
|
|
|
* Getter for URL attribute.
|
|
|
|
*
|
|
|
|
* @return string The URL.
|
|
|
|
*/
|
|
|
|
public function get_url() {
|
|
|
|
if ( $this->url ) {
|
|
|
|
return $this->url;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->id;
|
|
|
|
}
|
|
|
|
|
2025-01-03 19:31:58 +01:00
|
|
|
/**
|
|
|
|
* Get the outbox.
|
|
|
|
*
|
|
|
|
* @return ?string The outbox URL.
|
|
|
|
*/
|
|
|
|
public function get_outbox() {
|
|
|
|
if ( $this->outbox ) {
|
|
|
|
return $this->outbox;
|
|
|
|
}
|
|
|
|
|
|
|
|
$actor_json = \get_post_meta( $this->get__id(), 'activitypub_actor_json', true );
|
|
|
|
|
|
|
|
if ( ! $actor_json ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$actor = \json_decode( $actor_json, true );
|
|
|
|
|
|
|
|
if ( ! isset( $actor['outbox'] ) ) {
|
2025-01-04 10:39:12 +01:00
|
|
|
\do_action( 'event_bridge_for_activitypub_write_log', array( "[ACTIVITYPUB] Did not find outbox URL for actor {$actor}" ) );
|
2025-01-03 19:31:58 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $actor['outbox'];
|
|
|
|
}
|
|
|
|
|
2024-12-15 16:12:24 +01:00
|
|
|
/**
|
2024-12-25 22:54:43 +01:00
|
|
|
* Get the Event Source by the ActivityPub ID.
|
2024-12-15 16:12:24 +01:00
|
|
|
*
|
2025-01-04 12:17:58 +01:00
|
|
|
* @param string $activitypub_actor_id The ActivityPub actor ID.
|
|
|
|
* @return Event_Source|false The Event Source Actor, if a WordPress Post representing it is found, false otherwise.
|
2024-12-15 16:12:24 +01:00
|
|
|
*/
|
2025-01-04 12:17:58 +01:00
|
|
|
public static function get_by_id( $activitypub_actor_id ) {
|
|
|
|
$event_sources = Event_Sources::get_event_sources();
|
2024-12-15 16:12:24 +01:00
|
|
|
|
2025-01-04 12:17:58 +01:00
|
|
|
if ( ! array_key_exists( $activitypub_actor_id, $event_sources ) ) {
|
|
|
|
return false;
|
2024-12-15 16:12:24 +01:00
|
|
|
}
|
2025-01-04 12:17:58 +01:00
|
|
|
|
|
|
|
return $event_sources[ $activitypub_actor_id ];
|
2024-12-08 17:38:05 +01:00
|
|
|
}
|
|
|
|
|
2024-11-18 16:07:09 +01:00
|
|
|
/**
|
2024-12-05 17:50:17 +01:00
|
|
|
* Convert a Custom-Post-Type input to an \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source.
|
2024-11-18 16:07:09 +01:00
|
|
|
*
|
|
|
|
* @param \WP_Post $post The post object.
|
2024-12-05 17:50:17 +01:00
|
|
|
* @return \Event_Bridge_For_ActivityPub\ActivityPub\Event_Source|WP_Error
|
2024-11-18 16:07:09 +01:00
|
|
|
*/
|
|
|
|
public static function init_from_cpt( $post ) {
|
|
|
|
if ( Event_Sources::POST_TYPE !== $post->post_type ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$actor_json = get_post_meta( $post->ID, 'activitypub_actor_json', true );
|
|
|
|
$object = self::init_from_json( $actor_json );
|
|
|
|
$object->set__id( $post->ID );
|
|
|
|
$object->set_id( $post->guid );
|
|
|
|
$object->set_name( $post->post_title );
|
|
|
|
$object->set_summary( $post->post_excerpt );
|
2025-01-04 18:41:33 +01:00
|
|
|
$object->set_published( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_date ) ) );
|
|
|
|
$object->set_updated( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) ) );
|
2024-12-08 17:38:05 +01:00
|
|
|
$thumbnail_id = get_post_thumbnail_id( $post );
|
|
|
|
if ( $thumbnail_id ) {
|
|
|
|
$object->set_icon(
|
|
|
|
array(
|
|
|
|
'type' => 'Image',
|
|
|
|
'url' => wp_get_attachment_image_url( $thumbnail_id, 'thumbnail', true ),
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2024-11-18 16:07:09 +01:00
|
|
|
|
|
|
|
return $object;
|
|
|
|
}
|
2024-12-08 17:38:05 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Validate the current Event Source ActivityPub actor object.
|
|
|
|
*
|
|
|
|
* @return boolean True if the verification was successful.
|
|
|
|
*/
|
|
|
|
public function is_valid() {
|
|
|
|
// The minimum required attributes.
|
|
|
|
$required_attributes = array(
|
|
|
|
'id',
|
|
|
|
'preferredUsername',
|
|
|
|
'inbox',
|
|
|
|
'publicKey',
|
|
|
|
'publicKeyPem',
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ( $required_attributes as $attribute ) {
|
|
|
|
if ( ! $this->get( $attribute ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-12-08 18:38:47 +01:00
|
|
|
/**
|
|
|
|
* Update the post meta.
|
|
|
|
*/
|
|
|
|
protected function get_post_meta_input() {
|
|
|
|
$meta_input = array();
|
2024-12-14 14:46:00 +01:00
|
|
|
$meta_input['activitypub_inbox'] = sanitize_url( $this->get_shared_inbox() );
|
2024-12-08 18:38:47 +01:00
|
|
|
$meta_input['activitypub_actor_json'] = $this->to_json();
|
|
|
|
|
|
|
|
return $meta_input;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the shared inbox, with a fallback to the inbox.
|
|
|
|
*
|
|
|
|
* @return string|null The URL to the shared inbox, the inbox or null.
|
|
|
|
*/
|
|
|
|
public function get_shared_inbox() {
|
|
|
|
if ( ! empty( $this->get_endpoints()['sharedInbox'] ) ) {
|
|
|
|
return $this->get_endpoints()['sharedInbox'];
|
|
|
|
} elseif ( ! empty( $this->get_inbox() ) ) {
|
|
|
|
return $this->get_inbox();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-12-08 17:38:05 +01:00
|
|
|
/**
|
|
|
|
* Save the current Event Source object to Database within custom post type.
|
|
|
|
*
|
|
|
|
* @return int|WP_Error The post ID or an WP_Error.
|
|
|
|
*/
|
|
|
|
public function save() {
|
|
|
|
if ( ! $this->is_valid() ) {
|
2024-12-14 14:46:00 +01:00
|
|
|
return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'event-bridge-for-activitypub' ), array( 'status' => 400 ) );
|
2024-12-08 17:38:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! $this->get__id() ) {
|
|
|
|
global $wpdb;
|
|
|
|
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
|
|
|
$post_id = $wpdb->get_var(
|
|
|
|
$wpdb->prepare(
|
|
|
|
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
|
|
|
|
esc_sql( $this->get_id() )
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( $post_id ) {
|
|
|
|
$post = get_post( $post_id );
|
|
|
|
$this->set__id( $post->ID );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$post_id = $this->get__id();
|
|
|
|
|
|
|
|
$args = array(
|
|
|
|
'ID' => $post_id,
|
|
|
|
'guid' => esc_url_raw( $this->get_id() ),
|
|
|
|
'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ),
|
|
|
|
'post_author' => 0,
|
|
|
|
'post_type' => Event_Sources::POST_TYPE,
|
|
|
|
'post_name' => esc_url_raw( $this->get_id() ),
|
|
|
|
'post_excerpt' => sanitize_text_field( wp_kses( $this->get_summary(), 'user_description' ) ),
|
2025-01-03 19:31:58 +01:00
|
|
|
'post_status' => 'pending',
|
2024-12-08 17:38:05 +01:00
|
|
|
'meta_input' => $this->get_post_meta_input(),
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( ! empty( $post_id ) ) {
|
|
|
|
// If this is an update, prevent the "added" date from being overwritten by the current date.
|
|
|
|
$post = get_post( $post_id );
|
|
|
|
$args['post_date'] = $post->post_date;
|
|
|
|
$args['post_date_gmt'] = $post->post_date_gmt;
|
|
|
|
}
|
|
|
|
|
|
|
|
$post_id = wp_insert_post( $args );
|
|
|
|
$this->_id = $post_id;
|
|
|
|
|
|
|
|
// Abort if inserting or updating the post didn't work.
|
|
|
|
if ( 0 === $post_id || is_wp_error( $post_id ) ) {
|
|
|
|
return $post_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete old icon.
|
|
|
|
// Check if the post has a thumbnail.
|
|
|
|
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
|
|
|
|
|
|
|
if ( $thumbnail_id ) {
|
|
|
|
// Remove the thumbnail from the post.
|
|
|
|
delete_post_thumbnail( $post_id );
|
|
|
|
|
|
|
|
// Delete the attachment (and its files) from the media library.
|
|
|
|
wp_delete_attachment( $thumbnail_id, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set new icon.
|
|
|
|
$icon = $this->get_icon();
|
|
|
|
|
|
|
|
if ( isset( $icon['url'] ) ) {
|
2024-12-14 14:46:00 +01:00
|
|
|
$image = media_sideload_image( sanitize_url( $icon['url'] ), $post_id, null, 'id' );
|
2024-12-08 17:38:05 +01:00
|
|
|
}
|
|
|
|
if ( isset( $image ) && ! is_wp_error( $image ) ) {
|
|
|
|
set_post_thumbnail( $post_id, $image );
|
|
|
|
}
|
|
|
|
|
|
|
|
return $post_id;
|
|
|
|
}
|
2025-01-04 12:27:09 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete an Event Source and it's profile image.
|
|
|
|
*/
|
|
|
|
public function delete() {
|
|
|
|
$post_id = $this->get__id();
|
|
|
|
|
|
|
|
if ( ! $post_id ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
|
|
|
|
|
|
|
if ( $thumbnail_id ) {
|
|
|
|
wp_delete_attachment( $thumbnail_id, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
return wp_delete_post( $post_id, false ) ?? false;
|
|
|
|
}
|
2024-11-18 16:07:09 +01:00
|
|
|
}
|