Draft logic of storing Event Sources as custom post type
Some checks failed
PHP Code Checker / PHP Code Checker (pull_request) Failing after 46s
PHPUnit / PHPUnit – PHP 7.4 (pull_request) Successful in 1m7s
PHPUnit / PHPUnit – PHP 8.0 (pull_request) Successful in 1m8s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m3s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m3s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m6s
PHPUnit / PHPUnit – PHP 8.4 (pull_request) Successful in 1m6s
Some checks failed
PHP Code Checker / PHP Code Checker (pull_request) Failing after 46s
PHPUnit / PHPUnit – PHP 7.4 (pull_request) Successful in 1m7s
PHPUnit / PHPUnit – PHP 8.0 (pull_request) Successful in 1m8s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Successful in 1m3s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Successful in 1m3s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Successful in 1m6s
PHPUnit / PHPUnit – PHP 8.4 (pull_request) Successful in 1m6s
This commit is contained in:
parent
e8b2b4c899
commit
1eda885719
9 changed files with 463 additions and 159 deletions
|
@ -22,7 +22,7 @@ class Event_Sources {
|
||||||
/**
|
/**
|
||||||
* The custom post type.
|
* The custom post type.
|
||||||
*/
|
*/
|
||||||
const POST_TYPE = 'Event_Bridge_For_ActivityPub_follow';
|
const POST_TYPE = 'ebap_event_source';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the post type used to store the external event sources (i.e., followed ActivityPub actors).
|
* Register the post type used to store the external event sources (i.e., followed ActivityPub actors).
|
||||||
|
@ -47,7 +47,7 @@ class Event_Sources {
|
||||||
|
|
||||||
\register_post_meta(
|
\register_post_meta(
|
||||||
self::POST_TYPE,
|
self::POST_TYPE,
|
||||||
'activitypub_inbox',
|
'activitypub_actor_id',
|
||||||
array(
|
array(
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'single' => true,
|
'single' => true,
|
||||||
|
@ -71,18 +71,6 @@ class Event_Sources {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
\register_post_meta(
|
|
||||||
self::POST_TYPE,
|
|
||||||
'activitypub_user_id',
|
|
||||||
array(
|
|
||||||
'type' => 'string',
|
|
||||||
'single' => false,
|
|
||||||
'sanitize_callback' => function ( $value ) {
|
|
||||||
return esc_sql( $value );
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
\register_post_meta(
|
\register_post_meta(
|
||||||
self::POST_TYPE,
|
self::POST_TYPE,
|
||||||
'activitypub_actor_json',
|
'activitypub_actor_json',
|
||||||
|
@ -94,12 +82,36 @@ class Event_Sources {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
\register_post_meta(
|
||||||
|
self::POST_TYPE,
|
||||||
|
'event_source_active',
|
||||||
|
array(
|
||||||
|
'type' => 'bool',
|
||||||
|
'single' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
\register_post_meta(
|
||||||
|
self::POST_TYPE,
|
||||||
|
'event_source_utilize_announces',
|
||||||
|
array(
|
||||||
|
'type' => 'string',
|
||||||
|
'single' => true,
|
||||||
|
'sanitize_callback' => function ( $value ) {
|
||||||
|
if ( 'same_origin' === $value ) {
|
||||||
|
return 'same_origin';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add new Event Source.
|
* Add new Event Source.
|
||||||
*
|
*
|
||||||
* @param string $actor The Actor ID.
|
* @param string $actor The Actor URL/ID.
|
||||||
*
|
*
|
||||||
* @return Event_Source|WP_Error The Followed (WP_Post array) or an WP_Error.
|
* @return Event_Source|WP_Error The Followed (WP_Post array) or an WP_Error.
|
||||||
*/
|
*/
|
||||||
|
@ -143,22 +155,78 @@ class Event_Sources {
|
||||||
*
|
*
|
||||||
* @return array The Term list of Event Sources.
|
* @return array The Term list of Event Sources.
|
||||||
*/
|
*/
|
||||||
public static function get_all_followers() {
|
public static function get_event_sources() {
|
||||||
$args = array(
|
$args = array(
|
||||||
'nopaging' => true,
|
'post_type' => self::POST_TYPE,
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'orderby' => 'ID',
|
||||||
|
'order' => 'DESC',
|
||||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||||
'meta_query' => array(
|
'meta_query' => array(
|
||||||
'relation' => 'AND',
|
|
||||||
array(
|
array(
|
||||||
'key' => 'activitypub_inbox',
|
'key' => 'activitypub_actor_id',
|
||||||
'compare' => 'EXISTS',
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'key' => 'activitypub_actor_json',
|
|
||||||
'compare' => 'EXISTS',
|
'compare' => 'EXISTS',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return self::get_event_sources( null, null, null, $args );
|
|
||||||
|
$query = new WP_Query( $args );
|
||||||
|
|
||||||
|
$event_sources = array_map(
|
||||||
|
function ( $post ) {
|
||||||
|
return Event_Source::init_from_cpt( $post );
|
||||||
|
},
|
||||||
|
$query->get_posts()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $event_sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Event Sources along with a total count for pagination purposes.
|
||||||
|
*
|
||||||
|
* @param int $number Maximum number of results to return.
|
||||||
|
* @param int $page Page number.
|
||||||
|
* @param array $args The WP_Query arguments.
|
||||||
|
*
|
||||||
|
* @return array {
|
||||||
|
* Data about the followers.
|
||||||
|
*
|
||||||
|
* @type array $followers List of `Follower` objects.
|
||||||
|
* @type int $total Total number of followers.
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
public static function get_event_sources_with_count( $number = -1, $page = null, $args = array() ) {
|
||||||
|
$defaults = array(
|
||||||
|
'post_type' => self::POST_TYPE,
|
||||||
|
'posts_per_page' => $number,
|
||||||
|
'paged' => $page,
|
||||||
|
'orderby' => 'ID',
|
||||||
|
'order' => 'DESC',
|
||||||
|
);
|
||||||
|
|
||||||
|
$args = wp_parse_args( $args, $defaults );
|
||||||
|
$query = new WP_Query( $args );
|
||||||
|
$total = $query->found_posts;
|
||||||
|
$actors = array_map(
|
||||||
|
function ( $post ) {
|
||||||
|
return Event_Source::init_from_cpt( $post );
|
||||||
|
},
|
||||||
|
$query->get_posts()
|
||||||
|
);
|
||||||
|
|
||||||
|
return compact( 'actors', 'total' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a Follower.
|
||||||
|
*
|
||||||
|
* @param string $event_source The Actor URL.
|
||||||
|
*
|
||||||
|
* @return mixed True on success, false on failure.
|
||||||
|
*/
|
||||||
|
public static function remove( $event_source ) {
|
||||||
|
$post_id = Event_Source::get_wp_post_from_activitypub_actor_id( $event_source );
|
||||||
|
return wp_delete_post( $post_id, true );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Model;
|
namespace Event_Bridge_For_ActivityPub\ActivityPub\Model;
|
||||||
|
|
||||||
use Activitypub\Activity\Actor;
|
use Activitypub\Activity\Actor;
|
||||||
|
use Activitypub\Webfinger;
|
||||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
|
|
||||||
|
@ -16,6 +17,15 @@ use WP_Error;
|
||||||
* Event-Source (=ActivityPub Actor that is followed) model.
|
* Event-Source (=ActivityPub Actor that is followed) model.
|
||||||
*/
|
*/
|
||||||
class Event_Source extends Actor {
|
class Event_Source extends Actor {
|
||||||
|
const ACTIVITYPUB_USER_HANDLE_REGEXP = '(?:([A-Za-z0-9_.-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The complete remote ActivityPub profile of the Event Source.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Icon URL (Avatar).
|
* Get the Icon URL (Avatar).
|
||||||
*
|
*
|
||||||
|
@ -35,6 +45,24 @@ class Event_Source extends Actor {
|
||||||
return $icon;
|
return $icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the WordPress post which stores the Event Source by the ActivityPub actor id of the event source.
|
||||||
|
*
|
||||||
|
* @param string $actor_id The ActivityPub actor ID.
|
||||||
|
* @return ?int The WordPress post ID if the actor is found, null if not.
|
||||||
|
*/
|
||||||
|
public static function get_wp_post_from_activitypub_actor_id( $actor_id ) {
|
||||||
|
global $wpdb;
|
||||||
|
$post_id = $wpdb->get_var(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT ID FROM $wpdb->posts WHERE guid=%s AND post_type=%s",
|
||||||
|
esc_sql( $actor_id ),
|
||||||
|
Event_Sources::POST_TYPE
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return $post_id ? intval( $post_id ) : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a Custom-Post-Type input to an \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source.
|
* Convert a Custom-Post-Type input to an \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source.
|
||||||
*
|
*
|
||||||
|
@ -53,7 +81,121 @@ class Event_Source extends Actor {
|
||||||
$object->set_summary( $post->post_excerpt );
|
$object->set_summary( $post->post_excerpt );
|
||||||
$object->set_published( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_date ) ) );
|
$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 ) ) );
|
$object->set_updated( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) ) );
|
||||||
|
$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 ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return $object;
|
return $object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() ) {
|
||||||
|
return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ), array( 'status' => 400 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
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' ) ),
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'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'] ) ) {
|
||||||
|
$image = media_sideload_image( $icon['url'], $post_id, null, 'id' );
|
||||||
|
}
|
||||||
|
if ( isset( $image ) && ! is_wp_error( $image ) ) {
|
||||||
|
set_post_thumbnail( $post_id, $image );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $post_id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Event Sources.
|
|
||||||
*
|
|
||||||
* @package Event_Bridge_For_ActivityPub
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Event_Bridge_For_ActivityPub\Admin;
|
|
||||||
|
|
||||||
use Activitypub\Collection\Actors;
|
|
||||||
use Activitypub\Activity\Extended_Object\Event;
|
|
||||||
|
|
||||||
use function Activitypub\get_remote_metadata_by_actor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manage following other Event Sources (ActivityPub actors) and importing their Events.
|
|
||||||
*/
|
|
||||||
class Event_Sources {
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*/
|
|
||||||
public function __construct() {
|
|
||||||
\add_action( 'init', array( $this, 'register_post_meta' ) );
|
|
||||||
|
|
||||||
\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.
|
|
||||||
*
|
|
||||||
* @param string $url The URL.
|
|
||||||
*/
|
|
||||||
public static function get_metadata( $url ) {
|
|
||||||
if ( ! is_string( $url ) ) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( false !== strpos( $url, '@' ) ) {
|
|
||||||
if ( false === strpos( $url, '/' ) && preg_match( '#^https?://#', $url, $m ) ) {
|
|
||||||
$url = substr( $url, strlen( $m[0] ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,6 +14,10 @@ namespace Event_Bridge_For_ActivityPub\Admin;
|
||||||
// Exit if accessed directly.
|
// Exit if accessed directly.
|
||||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||||
|
|
||||||
|
use Activitypub\Webfinger;
|
||||||
|
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||||
|
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||||
|
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Source_Collection;
|
||||||
use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin;
|
use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin;
|
||||||
use Event_Bridge_For_ActivityPub\Setup;
|
use Event_Bridge_For_ActivityPub\Setup;
|
||||||
|
|
||||||
|
@ -35,6 +39,10 @@ class Settings_Page {
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function admin_menu(): void {
|
public static function admin_menu(): void {
|
||||||
|
add_action(
|
||||||
|
'admin_init',
|
||||||
|
array( self::STATIC, 'maybe_add_event_source' ),
|
||||||
|
);
|
||||||
\add_options_page(
|
\add_options_page(
|
||||||
'Event Bridge for ActivityPub',
|
'Event Bridge for ActivityPub',
|
||||||
__( 'Event Bridge for ActivityPub', 'event-bridge-for-activitypub' ),
|
__( 'Event Bridge for ActivityPub', 'event-bridge-for-activitypub' ),
|
||||||
|
@ -44,6 +52,65 @@ class Settings_Page {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the current request wants to add an event source (ActivityPub follow) and passed on to actual handler.
|
||||||
|
*/
|
||||||
|
public static function maybe_add_event_source() {
|
||||||
|
if ( ! isset( $_POST['event_bridge_for_activitypub_event_source'] ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and verify request and check capabilities.
|
||||||
|
if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'event-bridge-for-activitypub-event-sources-options' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event_source = sanitize_text_field( $_POST['event_bridge_for_activitypub_event_source'] );
|
||||||
|
|
||||||
|
$actor_url = false;
|
||||||
|
|
||||||
|
$url = wp_parse_url( $event_source );
|
||||||
|
|
||||||
|
if ( isset( $url['path'] ) && isset( $url['host'] ) && isset( $url['scheme'] ) ) {
|
||||||
|
$actor_url = $event_source;
|
||||||
|
} else {
|
||||||
|
if ( preg_match( '/^@?' . Event_Source::ACTIVITYPUB_USER_HANDLE_REGEXP . '$/i', $event_source ) ) {
|
||||||
|
$actor_url = Webfinger::resolve( $event_source );
|
||||||
|
if ( is_wp_error( $actor_url ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ( ! isset( $url['path'] ) && isset( $url['host'] ) ) {
|
||||||
|
$actor_url = Event_Sources::get_application_actor( $url['host'] );
|
||||||
|
}
|
||||||
|
if ( self::is_domain( $event_source ) ) {
|
||||||
|
$actor_url = Event_Sources::get_application_actor( $event_source );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $actor_url ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Event_Source_Collection::add_event_source( $actor_url );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid domain name.
|
||||||
|
*
|
||||||
|
* @param string $domain The input string which might be a domain.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private static function is_domain( $domain ): bool {
|
||||||
|
$pattern = '/^(?!\-)(?:(?:[a-zA-Z\d](?:[a-zA-Z\d\-]{0,61}[a-zA-Z\d])?)\.)+(?!\d+$)[a-zA-Z\d]{2,63}$/';
|
||||||
|
return 1 === preg_match( $pattern, $domain );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds Link to the settings page in the plugin page.
|
* Adds Link to the settings page in the plugin page.
|
||||||
* It's called via apply_filter('plugin_action_links_' . PLUGIN_NAME).
|
* It's called via apply_filter('plugin_action_links_' . PLUGIN_NAME).
|
||||||
|
|
|
@ -7,9 +7,12 @@
|
||||||
|
|
||||||
namespace Event_Bridge_For_ActivityPub;
|
namespace Event_Bridge_For_ActivityPub;
|
||||||
|
|
||||||
|
use Activitypub\Activity\Extended_Object\Event;
|
||||||
|
use Activitypub\Collection\Actors;
|
||||||
use Activitypub\Http;
|
use Activitypub\Http;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
|
use function Activitypub\get_remote_metadata_by_actor;
|
||||||
use function register_post_type;
|
use function register_post_type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,6 +26,15 @@ class Event_Sources {
|
||||||
*/
|
*/
|
||||||
const POST_TYPE = 'event_bridge_follow';
|
const POST_TYPE = 'event_bridge_follow';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
\add_action( 'init', array( $this, 'register_post_meta' ) );
|
||||||
|
|
||||||
|
\add_action( 'activitypub_inbox', array( $this, 'handle_activitypub_inbox' ), 15, 3 );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the post type used to store the external event sources (i.e., followed ActivityPub actors).
|
* Register the post type used to store the external event sources (i.e., followed ActivityPub actors).
|
||||||
*/
|
*/
|
||||||
|
@ -94,4 +106,96 @@ class Event_Sources {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @param string $url The URL or ID of the ActivityPub actor.
|
||||||
|
*/
|
||||||
|
public static function get_metadata( $url ) {
|
||||||
|
if ( ! is_string( $url ) ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( false !== strpos( $url, '@' ) ) {
|
||||||
|
if ( false === strpos( $url, '/' ) && preg_match( '#^https?://#', $url, $m ) ) {
|
||||||
|
$url = substr( $url, strlen( $m[0] ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param string $domain The domain without scheme.
|
||||||
|
* @return bool|string The URL/ID of the application actor, false if not found.
|
||||||
|
*/
|
||||||
|
public static function get_application_actor( $domain ) {
|
||||||
|
$result = wp_remote_get( 'https://' . $domain . '/.well-known/nodeinfo' );
|
||||||
|
|
||||||
|
if ( is_wp_error( $result ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = wp_remote_retrieve_body( $result );
|
||||||
|
|
||||||
|
$nodeinfo = json_decode( $body, true );
|
||||||
|
|
||||||
|
// Check if 'links' exists and is an array.
|
||||||
|
if ( isset( $nodeinfo['links'] ) && is_array( $nodeinfo['links'] ) ) {
|
||||||
|
foreach ( $nodeinfo['links'] as $link ) {
|
||||||
|
// Check if this link matches the application actor rel.
|
||||||
|
if ( isset( $link['rel'] ) && 'https://www.w3.org/ns/activitystreams#Application' === $link['rel'] ) {
|
||||||
|
if ( is_string( $link['href'] ) ) {
|
||||||
|
return $link['href'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return false if no application actor is found.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,16 @@ class Settings {
|
||||||
'default' => EVENT_BRIDGE_FOR_ACTIVITYPUB_CUSTOM_SUMMARY,
|
'default' => EVENT_BRIDGE_FOR_ACTIVITYPUB_CUSTOM_SUMMARY,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
\register_setting(
|
||||||
|
'event-bridge-for-activitypub-event-sources',
|
||||||
|
'event_bridge_for_activitypub_event_sources_active',
|
||||||
|
array(
|
||||||
|
'type' => 'boolean',
|
||||||
|
'description' => \__( 'Whether the event sources feature is activated.', 'event-bridge-for-activitypub' ),
|
||||||
|
'default' => 1,
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
namespace Event_Bridge_For_ActivityPub\Table;
|
namespace Event_Bridge_For_ActivityPub\Table;
|
||||||
|
|
||||||
use WP_List_Table;
|
use WP_List_Table;
|
||||||
use Activitypub\Collection\Followers as FollowerCollection;
|
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
|
||||||
|
|
||||||
use function Activitypub\object_to_uri;
|
use function Activitypub\object_to_uri;
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@ if ( ! \class_exists( '\WP_List_Table' ) ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Followers Table-Class.
|
* Event Sources Table-Class.
|
||||||
*/
|
*/
|
||||||
class Event_Sources extends WP_List_Table {
|
class Event_Sources extends WP_List_Table {
|
||||||
/**
|
/**
|
||||||
|
@ -42,9 +41,9 @@ class Event_Sources extends WP_List_Table {
|
||||||
public function get_columns() {
|
public function get_columns() {
|
||||||
return array(
|
return array(
|
||||||
'cb' => '<input type="checkbox" />',
|
'cb' => '<input type="checkbox" />',
|
||||||
'avatar' => \__( 'Avatar', 'event-bridge-for-activitypub' ),
|
'icon' => \__( 'Icon', 'event-bridge-for-activitypub' ),
|
||||||
'post_title' => \__( 'Name', 'event-bridge-for-activitypub' ),
|
'name' => \__( 'Name', 'event-bridge-for-activitypub' ),
|
||||||
'username' => \__( 'Username', 'event-bridge-for-activitypub' ),
|
'active' => \__( 'Active', 'event-bridge-for-activitypub' ),
|
||||||
'url' => \__( 'URL', 'event-bridge-for-activitypub' ),
|
'url' => \__( 'URL', 'event-bridge-for-activitypub' ),
|
||||||
'published' => \__( 'Followed', 'event-bridge-for-activitypub' ),
|
'published' => \__( 'Followed', 'event-bridge-for-activitypub' ),
|
||||||
'modified' => \__( 'Last updated', 'event-bridge-for-activitypub' ),
|
'modified' => \__( 'Last updated', 'event-bridge-for-activitypub' ),
|
||||||
|
@ -58,7 +57,7 @@ class Event_Sources extends WP_List_Table {
|
||||||
*/
|
*/
|
||||||
public function get_sortable_columns() {
|
public function get_sortable_columns() {
|
||||||
return array(
|
return array(
|
||||||
'post_title' => array( 'post_title', true ),
|
'name' => array( 'name', true ),
|
||||||
'modified' => array( 'modified', false ),
|
'modified' => array( 'modified', false ),
|
||||||
'published' => array( 'published', false ),
|
'published' => array( 'published', false ),
|
||||||
);
|
);
|
||||||
|
@ -96,22 +95,7 @@ class Event_Sources extends WP_List_Table {
|
||||||
}
|
}
|
||||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||||
|
|
||||||
$dummy_event_sources = array(
|
$event_sources = Event_Sources_Collection::get_event_sources_with_count($per_page, $page_num, $args );
|
||||||
'total' => 1,
|
|
||||||
'actors' => array(
|
|
||||||
Event_Source::init_from_array(
|
|
||||||
array(
|
|
||||||
'id' => 'https://graz.social/@linos',
|
|
||||||
'url' => 'https://graz.social/@linos',
|
|
||||||
'preferredUsername' => 'linos',
|
|
||||||
'name' => 'André Menrath',
|
|
||||||
'icon' => 'https://graz.social/system/accounts/avatars/000/000/001/original/fe1c795256720361.jpeg',
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
$event_sources = $dummy_event_sources;
|
|
||||||
$actors = $event_sources['actors'];
|
$actors = $event_sources['actors'];
|
||||||
$counter = $event_sources['total'];
|
$counter = $event_sources['total'];
|
||||||
|
|
||||||
|
@ -127,9 +111,9 @@ class Event_Sources extends WP_List_Table {
|
||||||
foreach ( $actors as $actor ) {
|
foreach ( $actors as $actor ) {
|
||||||
$item = array(
|
$item = array(
|
||||||
'icon' => esc_attr( $actor->get_icon_url() ),
|
'icon' => esc_attr( $actor->get_icon_url() ),
|
||||||
'post_title' => esc_attr( $actor->get_name() ),
|
'name' => esc_attr( $actor->get_name() ),
|
||||||
'username' => esc_attr( $actor->get_preferred_username() ),
|
'url' => esc_attr( object_to_uri( $actor->get_id() ) ),
|
||||||
'url' => esc_attr( object_to_uri( $actor->get_url() ) ),
|
'active' => esc_attr( get_post_meta( $actor->get__id(), 'event_source_active', true) ),
|
||||||
'identifier' => esc_attr( $actor->get_id() ),
|
'identifier' => esc_attr( $actor->get_id() ),
|
||||||
'published' => esc_attr( $actor->get_published() ),
|
'published' => esc_attr( $actor->get_published() ),
|
||||||
'modified' => esc_attr( $actor->get_updated() ),
|
'modified' => esc_attr( $actor->get_updated() ),
|
||||||
|
@ -170,7 +154,7 @@ class Event_Sources extends WP_List_Table {
|
||||||
* @param array $item Item.
|
* @param array $item Item.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function column_avatar( $item ) {
|
public function column_icon( $item ) {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<img src="%s" width="25px;" />',
|
'<img src="%s" width="25px;" />',
|
||||||
$item['icon']
|
$item['icon']
|
||||||
|
@ -198,14 +182,32 @@ class Event_Sources extends WP_List_Table {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function column_cb( $item ) {
|
public function column_cb( $item ) {
|
||||||
return sprintf( '<input type="checkbox" name="followers[]" value="%s" />', esc_attr( $item['identifier'] ) );
|
return sprintf( '<input type="checkbox" name="event_sources[]" value="%s" />', esc_attr( $item['identifier'] ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column action.
|
||||||
|
*
|
||||||
|
* @param array $item Item.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function column_active( $item ) {
|
||||||
|
if ( $item['active'] ) {
|
||||||
|
$action = 'true';
|
||||||
|
} else {
|
||||||
|
$action = 'false';
|
||||||
|
}
|
||||||
|
return sprintf(
|
||||||
|
'%s',
|
||||||
|
$action
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process action.
|
* Process action.
|
||||||
*/
|
*/
|
||||||
public function process_action() {
|
public function process_action() {
|
||||||
if ( ! isset( $_REQUEST['followers'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
|
if ( ! isset( $_REQUEST['event_sources'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
|
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
|
||||||
|
@ -213,28 +215,19 @@ class Event_Sources extends WP_List_Table {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! current_user_can( 'edit_user', $this->user_id ) ) {
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$followers = $_REQUEST['followers']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
$event_sources = $_REQUEST['event_sources']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||||
|
|
||||||
if ( $this->current_action() === 'delete' ) {
|
if ( 'delete' === $this->current_action() ) {
|
||||||
if ( ! is_array( $followers ) ) {
|
if ( ! is_array( $event_sources ) ) {
|
||||||
$followers = array( $followers );
|
$event_sources = array( $event_sources );
|
||||||
}
|
}
|
||||||
foreach ( $followers as $follower ) {
|
foreach ( $event_sources as $event_source ) {
|
||||||
Event_Source::remove( $this->user_id, $follower );
|
Event_Sources_Collection::remove( $event_source );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns user count.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function get_user_count() {
|
|
||||||
return FollowerCollection::count_followers( $this->user_id );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,14 +34,19 @@ $table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources();
|
||||||
<!-- ThickBox content (hidden initially) -->
|
<!-- ThickBox content (hidden initially) -->
|
||||||
<div id="Event_Bridge_For_ActivityPub_add_new_source" style="display:none;">
|
<div id="Event_Bridge_For_ActivityPub_add_new_source" style="display:none;">
|
||||||
<h2><?php esc_html_e( 'Add new ActivityPub follow', 'event-bridge-for-activitypub' ); ?> </h2>
|
<h2><?php esc_html_e( 'Add new ActivityPub follow', 'event-bridge-for-activitypub' ); ?> </h2>
|
||||||
<p> <?php esc_html_e( 'Here you can enter either a handle or instance URL.', 'event-bridge-for-activitypub' ); ?> </p>
|
<p> <?php esc_html_e( 'Here you can enter either a Fediverse handle (@username@example.social), URL of an ActivityPub Account (https://example.social/user/username) or instance URL.', 'event-bridge-for-activitypub' ); ?> </p>
|
||||||
|
<form method="post" action="options.php">
|
||||||
|
<?php \settings_fields( 'event-bridge-for-activitypub-event-sources' ); ?>
|
||||||
|
<input type="text" name="event_bridge_for_activitypub_event_source" id="event_bridge_for_activitypub_event_source" value="test">
|
||||||
|
<?php \submit_button(); ?>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wrap activitypub-followers-page">
|
<div class="wrap activitypub-followers-page">
|
||||||
<form method="get">
|
<form method="get">
|
||||||
<input type="hidden" name="page" value="activitypub" />
|
<input type="hidden" name="page" value="event-bridge-for-activitypub" />
|
||||||
<input type="hidden" name="tab" value="followers" />
|
<input type="hidden" name="tab" value="event-sources" />
|
||||||
<?php
|
<?php
|
||||||
$table->prepare_items();
|
$table->prepare_items();
|
||||||
$table->search_box( 'Search', 'search' );
|
$table->search_box( 'Search', 'search' );
|
||||||
|
|
|
@ -144,7 +144,7 @@ $current_category_mapping = \get_option( 'event_bridge_for_activitypub_ev
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<!-- This disables the setup wizard. -->
|
<!-- This disables the setup wizard. -->
|
||||||
<div class="hidden">
|
<div class="hidden" aria-hidden="true">
|
||||||
<input type="checkbox" id="event_bridge_for_activitypub_initially_activated" name="event_bridge_for_activitypub_initially_activated"/>
|
<input type="checkbox" id="event_bridge_for_activitypub_initially_activated" name="event_bridge_for_activitypub_initially_activated"/>
|
||||||
</div>
|
</div>
|
||||||
<?php \submit_button(); ?>
|
<?php \submit_button(); ?>
|
||||||
|
|
Loading…
Reference in a new issue