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

This commit is contained in:
André Menrath 2024-12-08 17:38:05 +01:00
parent e8b2b4c899
commit 1eda885719
9 changed files with 463 additions and 159 deletions

View file

@ -22,7 +22,7 @@ class Event_Sources {
/**
* 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).
@ -47,7 +47,7 @@ class Event_Sources {
\register_post_meta(
self::POST_TYPE,
'activitypub_inbox',
'activitypub_actor_id',
array(
'type' => 'string',
'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(
self::POST_TYPE,
'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.
*
* @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.
*/
@ -143,22 +155,78 @@ class Event_Sources {
*
* @return array The Term list of Event Sources.
*/
public static function get_all_followers() {
public static function get_event_sources() {
$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
'meta_query' => array(
'relation' => 'AND',
'meta_query' => array(
array(
'key' => 'activitypub_inbox',
'compare' => 'EXISTS',
),
array(
'key' => 'activitypub_actor_json',
'key' => 'activitypub_actor_id',
'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 );
}
}

View file

@ -9,6 +9,7 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Model;
use Activitypub\Activity\Actor;
use Activitypub\Webfinger;
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
use WP_Error;
@ -16,6 +17,15 @@ use WP_Error;
* Event-Source (=ActivityPub Actor that is followed) model.
*/
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).
*
@ -35,6 +45,24 @@ class Event_Source extends Actor {
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.
*
@ -53,7 +81,121 @@ class Event_Source extends Actor {
$object->set_summary( $post->post_excerpt );
$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 ) ) );
$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;
}
/**
* 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;
}
}

View file

@ -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();
}
}

View file

@ -14,6 +14,10 @@ namespace Event_Bridge_For_ActivityPub\Admin;
// Exit if accessed directly.
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\Setup;
@ -35,6 +39,10 @@ class Settings_Page {
* @return void
*/
public static function admin_menu(): void {
add_action(
'admin_init',
array( self::STATIC, 'maybe_add_event_source' ),
);
\add_options_page(
'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.
* It's called via apply_filter('plugin_action_links_' . PLUGIN_NAME).

View file

@ -7,9 +7,12 @@
namespace Event_Bridge_For_ActivityPub;
use Activitypub\Activity\Extended_Object\Event;
use Activitypub\Collection\Actors;
use Activitypub\Http;
use Exception;
use function Activitypub\get_remote_metadata_by_actor;
use function register_post_type;
/**
@ -23,6 +26,15 @@ class Event_Sources {
*/
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).
*/
@ -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;
}
}

View file

@ -93,6 +93,16 @@ class Settings {
'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,
)
);
}
/**

View file

@ -8,8 +8,7 @@
namespace Event_Bridge_For_ActivityPub\Table;
use WP_List_Table;
use Activitypub\Collection\Followers as FollowerCollection;
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
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 {
/**
@ -42,9 +41,9 @@ class Event_Sources extends WP_List_Table {
public function get_columns() {
return array(
'cb' => '<input type="checkbox" />',
'avatar' => \__( 'Avatar', 'event-bridge-for-activitypub' ),
'post_title' => \__( 'Name', 'event-bridge-for-activitypub' ),
'username' => \__( 'Username', 'event-bridge-for-activitypub' ),
'icon' => \__( 'Icon', 'event-bridge-for-activitypub' ),
'name' => \__( 'Name', 'event-bridge-for-activitypub' ),
'active' => \__( 'Active', 'event-bridge-for-activitypub' ),
'url' => \__( 'URL', 'event-bridge-for-activitypub' ),
'published' => \__( 'Followed', '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() {
return array(
'post_title' => array( 'post_title', true ),
'name' => array( 'name', true ),
'modified' => array( 'modified', false ),
'published' => array( 'published', false ),
);
@ -96,22 +95,7 @@ class Event_Sources extends WP_List_Table {
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
$dummy_event_sources = array(
'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;
$event_sources = Event_Sources_Collection::get_event_sources_with_count($per_page, $page_num, $args );
$actors = $event_sources['actors'];
$counter = $event_sources['total'];
@ -127,9 +111,9 @@ class Event_Sources extends WP_List_Table {
foreach ( $actors as $actor ) {
$item = array(
'icon' => esc_attr( $actor->get_icon_url() ),
'post_title' => esc_attr( $actor->get_name() ),
'username' => esc_attr( $actor->get_preferred_username() ),
'url' => esc_attr( object_to_uri( $actor->get_url() ) ),
'name' => esc_attr( $actor->get_name() ),
'url' => esc_attr( object_to_uri( $actor->get_id() ) ),
'active' => esc_attr( get_post_meta( $actor->get__id(), 'event_source_active', true) ),
'identifier' => esc_attr( $actor->get_id() ),
'published' => esc_attr( $actor->get_published() ),
'modified' => esc_attr( $actor->get_updated() ),
@ -170,7 +154,7 @@ class Event_Sources extends WP_List_Table {
* @param array $item Item.
* @return string
*/
public function column_avatar( $item ) {
public function column_icon( $item ) {
return sprintf(
'<img src="%s" width="25px;" />',
$item['icon']
@ -198,14 +182,32 @@ class Event_Sources extends WP_List_Table {
* @return string
*/
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.
*/
public function process_action() {
if ( ! isset( $_REQUEST['followers'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
if ( ! isset( $_REQUEST['event_sources'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
return;
}
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
@ -213,28 +215,19 @@ class Event_Sources extends WP_List_Table {
return;
}
if ( ! current_user_can( 'edit_user', $this->user_id ) ) {
if ( ! current_user_can( 'manage_options' ) ) {
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 ( ! is_array( $followers ) ) {
$followers = array( $followers );
if ( 'delete' === $this->current_action() ) {
if ( ! is_array( $event_sources ) ) {
$event_sources = array( $event_sources );
}
foreach ( $followers as $follower ) {
Event_Source::remove( $this->user_id, $follower );
foreach ( $event_sources as $event_source ) {
Event_Sources_Collection::remove( $event_source );
}
}
}
/**
* Returns user count.
*
* @return int
*/
public function get_user_count() {
return FollowerCollection::count_followers( $this->user_id );
}
}

View file

@ -34,14 +34,19 @@ $table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources();
<!-- ThickBox content (hidden initially) -->
<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>
<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 class="wrap activitypub-followers-page">
<form method="get">
<input type="hidden" name="page" value="activitypub" />
<input type="hidden" name="tab" value="followers" />
<input type="hidden" name="page" value="event-bridge-for-activitypub" />
<input type="hidden" name="tab" value="event-sources" />
<?php
$table->prepare_items();
$table->search_box( 'Search', 'search' );

View file

@ -144,7 +144,7 @@ $current_category_mapping = \get_option( 'event_bridge_for_activitypub_ev
<?php endif; ?>
</div>
<!-- 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"/>
</div>
<?php \submit_button(); ?>