Compare commits

..

1 commit

Author SHA1 Message Date
64ad7d61ef wip 2024-11-18 16:07:09 +01:00
18 changed files with 736 additions and 59 deletions

View file

@ -22,7 +22,6 @@ jobs:
strategy: strategy:
matrix: matrix:
php-version: ['7.4', '8.0', '8.1', '8.2', '8.3'] php-version: ['7.4', '8.0', '8.1', '8.2', '8.3']
wordpress-version: ['6.7']
name: PHPUnit PHP ${{ matrix.php-version }} name: PHPUnit PHP ${{ matrix.php-version }}
env: env:
extensions: mysql extensions: mysql
@ -38,7 +37,7 @@ jobs:
path: | path: |
${{ env.WP_CORE_DIR }} ${{ env.WP_CORE_DIR }}
${{ env.WP_TESTS_DIR }} ${{ env.WP_TESTS_DIR }}
key: cache-wordpress-67-2 key: cache-wordpress-9
- name: Cache Composer - name: Cache Composer
id: cache-composer-phpunit id: cache-composer-phpunit
@ -69,11 +68,11 @@ jobs:
- name: Setup Test Environment - name: Setup Test Environment
if: steps.cache-wordpress.outputs.cache-hit != 'true' if: steps.cache-wordpress.outputs.cache-hit != 'true'
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wordpress-version }} false false false false run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 6.6 false false false false
- name: Initialize WordPress test database - name: Initialize WordPress test database
if: steps.cache-wordpress.outputs.cache-hit != 'false' if: steps.cache-wordpress.outputs.cache-hit != 'false'
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wordpress-version }} false true true true run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 6.6 false true true true
- name: Run Integration tests for The Events Calendar - name: Run Integration tests for The Events Calendar
run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=the_events_calendar run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=the_events_calendar

View file

@ -5,18 +5,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.2.1] - 2024-11-16 ## [0.1.0] - 2024-10-20
### Added ### Added
* Initial release on WordPress.org * Initial version tag.
### Fixed
* Applied some WordPress best practices
## [0.2.0] - 2024-10-20
### Added
* Initial submission to WordPress.org

View file

@ -2,8 +2,8 @@
**Contributors:** [andremenrath](https://profiles.wordpress.org/andremenrath/) **Contributors:** [andremenrath](https://profiles.wordpress.org/andremenrath/)
**Tags:** events, fediverse, activitypub, calendar **Tags:** events, fediverse, activitypub, calendar
**Requires at least:** 6.5 **Requires at least:** 6.5
**Tested up to:** 6.7 **Tested up to:** 6.6
**Stable tag:** 0.2.1 **Stable tag:** 0.2.0
**Requires PHP:** 7.4 **Requires PHP:** 7.4
**License:** AGPL-3.0-or-later **License:** AGPL-3.0-or-later
**License URI:** https://www.gnu.org/licenses/agpl-3.0.html **License URI:** https://www.gnu.org/licenses/agpl-3.0.html
@ -73,7 +73,6 @@ If you're new to the [ActivityPub plugin](https://wordpress.org/plugins/activity
### Do I need to install another event plugin to use the Event Federation Plugin? ### ### Do I need to install another event plugin to use the Event Federation Plugin? ###
Yes, this plugin works as an add-on and requires both the ActivityPub plugin and a supported event plugin such as The Events Calendar, VS Event List, or Events Manager to manage your events. It just fills the missing gap between event plugins and the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/). Yes, this plugin works as an add-on and requires both the ActivityPub plugin and a supported event plugin such as The Events Calendar, VS Event List, or Events Manager to manage your events. It just fills the missing gap between event plugins and the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/).
### What platforms can follow my events? ### ### What platforms can follow my events? ###
Your events can be followed on platforms that support ActivityPub like [Mobilizon](https://joinmobilizon.org/), [Gancio](https://gancio.org), [Friendica](https://friendi.ca), [Hubzilla](https://hubzilla.org), and [Pleroma](https://pleroma.social/). Even other applications like [Mastodon](https://joinmastodon.org), which don't fully support events yet, will display all important information about the events. Your events can be followed on platforms that support ActivityPub like [Mobilizon](https://joinmobilizon.org/), [Gancio](https://gancio.org), [Friendica](https://friendi.ca), [Hubzilla](https://hubzilla.org), and [Pleroma](https://pleroma.social/). Even other applications like [Mastodon](https://joinmastodon.org), which don't fully support events yet, will display all important information about the events.
@ -100,10 +99,6 @@ We're always interested in your feedback. Feel free to reach out to us via [E-Ma
## Changelog ## ## Changelog ##
### [0.2.1] 2024-11-16 ###
* Initial release on https://wordpress.org/
### [0.2.0] 2024-10-29 ### ### [0.2.0] 2024-10-29 ###
* Initial submission to https://wordpress.org/ * Initial release on https://wordpress.org/

View file

@ -3,7 +3,7 @@
* Plugin Name: ActivityPub Event Bridge * Plugin Name: ActivityPub Event Bridge
* Description: Integrating popular event plugins with the ActivityPub plugin. * Description: Integrating popular event plugins with the ActivityPub plugin.
* Plugin URI: https://event-federation.eu/ * Plugin URI: https://event-federation.eu/
* Version: 0.2.1 * Version: 0.2.0
* Author: André Menrath * Author: André Menrath
* Author URI: https://graz.social/@linos * Author URI: https://graz.social/@linos
* Text Domain: activitypub-event-bridge * Text Domain: activitypub-event-bridge
@ -11,7 +11,7 @@
* License URI: https://www.gnu.org/licenses/agpl-3.0.html * License URI: https://www.gnu.org/licenses/agpl-3.0.html
* Requires PHP: 7.4 * Requires PHP: 7.4
* *
* Requires at least ActivityPub plugin with version >= 3.2.2. ActivityPub plugin tested up to: 4.2.0. * Requires at least ActivityPub plugin with version >= 3.2.2. ActivityPub plugin tested up to: 4.0.1.
* *
* @package ActivityPub_Event_Bridge * @package ActivityPub_Event_Bridge
* @license AGPL-3.0-or-later * @license AGPL-3.0-or-later

View file

@ -208,15 +208,9 @@ install_wp_plugin() {
fi fi
# Get the latest tag. # Get the latest tag.
if [ -z "$2" ]; then LATEST_TAG=$(svn log https://plugins.svn.wordpress.org/$PLUGIN_NAME/tags --limit 1 | awk 'NR == 4 { print $4 }')
LATEST_TAG=$(svn log https://plugins.svn.wordpress.org/$PLUGIN_NAME/tags --limit 1 | awk 'NR == 4 { print $4 }') if [ -n "$LATEST_TAG" ]; then
PLUGIN_VERSION=$LATEST_TAG PLUGIN_FILE="$PLUGIN_NAME.$LATEST_TAG.zip"
else
PLUGIN_VERSION=$2
fi
if [ -n "$PLUGIN_VERSION" ]; then
PLUGIN_FILE="$PLUGIN_NAME.$PLUGIN_VERSION.zip"
else else
PLUGIN_FILE="$PLUGIN_NAME.zip" PLUGIN_FILE="$PLUGIN_NAME.zip"
fi fi
@ -254,12 +248,12 @@ install_wp_plugins() {
# Install the one and only ActivityPub plugin (greetings @pfefferle). # Install the one and only ActivityPub plugin (greetings @pfefferle).
install_wp_plugin activitypub install_wp_plugin activitypub
# Install (not-activate) all supported event plugins. # Install (not-activate) all supported event plugins.
install_wp_plugin the-events-calendar "6.8.1" install_wp_plugin the-events-calendar
install_wp_plugin very-simple-event-list install_wp_plugin very-simple-event-list
install_wp_plugin gatherpress install_wp_plugin gatherpress
install_wp_plugin events-manager "6.6.3" install_wp_plugin events-manager
install_wp_plugin wp-event-manager "3.1.45.1" install_wp_plugin wp-event-manager
install_wp_plugin wp-event-solution "4.0.14" install_wp_plugin wp-event-solution
# Mec is not installable via wordpress.org, we use our own mirror. # Mec is not installable via wordpress.org, we use our own mirror.
install_wp_plugin_mec install_wp_plugin_mec
} }

View file

@ -180,7 +180,7 @@ In the pipeline we want to run each event plugins integration tests in a single
To activate/load your plugin add it to the switch statement within the function `_manually_load_plugin()` within `tests/bootstrap.php`. To activate/load your plugin add it to the switch statement within the function `_manually_load_plugin()` within `tests/bootstrap.php`.
```php ```php
switch ( $activitypub_event_bridge_integration_filter ) { switch ( $activitypub_event_extension_integration_filter ) {
... ...
case 'my_event_plugin': case 'my_event_plugin':
$plugin_file = 'my-event-plugin/my-event-plugin.php'; $plugin_file = 'my-event-plugin/my-event-plugin.php';

View file

@ -0,0 +1,164 @@
<?php
/**
* Event sources collection file.
*
* @package ActivityPub_Event_Bridge
* @license AGPL-3.0-or-later
*/
namespace ActivityPub_Event_Bridge\ActivityPub\Collection;
use WP_Error;
use WP_Query;
use ActivityPub_Event_Bridge\ActivityPub\Event_Source;
use function Activitypub\is_tombstone;
use function Activitypub\get_remote_metadata_by_actor;
/**
* ActivityPub Event Sources (=Followed Actors) Collection.
*/
class Event_Sources {
/**
* The custom post type.
*/
const POST_TYPE = 'activitypub_event_bridge_follow';
/**
* Register the post type used to store the external event sources (i.e., followed ActivityPub actors).
*/
public static function register_post_type() {
register_post_type(
self::POST_TYPE,
array(
'labels' => array(
'name' => _x( 'Event Sources', 'post_type plural name', 'activitypub' ),
'singular_name' => _x( 'Event Source', 'post_type single name', 'activitypub' ),
),
'public' => false,
'hierarchical' => false,
'rewrite' => false,
'query_var' => false,
'delete_with_user' => false,
'can_export' => true,
'supports' => array(),
)
);
\register_post_meta(
self::POST_TYPE,
'activitypub_inbox',
array(
'type' => 'string',
'single' => true,
'sanitize_callback' => 'sanitize_url',
)
);
\register_post_meta(
self::POST_TYPE,
'activitypub_errors',
array(
'type' => 'string',
'single' => false,
'sanitize_callback' => function ( $value ) {
if ( ! is_string( $value ) ) {
throw new Exception( 'Error message is no valid string' );
}
return esc_sql( $value );
},
)
);
\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',
array(
'type' => 'string',
'single' => true,
'sanitize_callback' => function ( $value ) {
return sanitize_text_field( $value );
},
)
);
}
/**
* Add new Event Source.
*
* @param string $actor The Actor ID.
*
* @return Event_Source|WP_Error The Followed (WP_Post array) or an WP_Error.
*/
public static function add_event_source( $actor ) {
$meta = get_remote_metadata_by_actor( $actor );
if ( is_tombstone( $meta ) ) {
return $meta;
}
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ), array( 'status' => 400 ) );
}
$event_source = new Event_Source();
$event_source->from_array( $meta );
$post_id = $event_source->save();
if ( is_wp_error( $post_id ) ) {
return $post_id;
}
return $event_source;
}
/**
* Remove an Event Source (=Followed ActivityPub actor).
*
* @param string $actor The Actor URL.
*
* @return bool True on success, false on failure.
*/
public static function remove_event_source( $actor ) {
$actor = true;
return $actor;
}
/**
* Get all Followers.
*
* @return array The Term list of Followers.
*/
public static function get_all_followers() {
$args = array(
'nopaging' => true,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'activitypub_inbox',
'compare' => 'EXISTS',
),
array(
'key' => 'activitypub_actor_json',
'compare' => 'EXISTS',
),
),
);
return self::get_followers( null, null, null, $args );
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* Event-Source (=ActivityPub Actor that is followed) model.
*
* @package ActivityPub_Event_Bridge
* @license AGPL-3.0-or-later
*/
namespace ActivityPub_Event_Bridge\ActivityPub;
use Activitypub\Activity\Actor;
use ActivityPub_Event_Bridge\ActivityPub\Collection\Event_Sources;
use WP_Error;
/**
* Event-Source (=ActivityPub Actor that is followed) model.
*/
class Event_Source extends Actor {
/**
* 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;
}
/**
* Convert a Custom-Post-Type input to an \ActivityPub_Event_Bridge\ActivityPub\Model\Event_Source.
*
* @param \WP_Post $post The post object.
* @return \ActivityPub_Event_Bridge\ActivityPub\Event_Source|WP_Error
*/
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 );
$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 ) ) );
return $object;
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* Event Sources.
*
* @package Activitypub_Event_Bridge
*/
namespace ActivityPub_Event_Bridge\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['activitypub_event_bridge'] ) ) {
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

@ -116,6 +116,11 @@ class Settings_Page {
\load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/settings.php', true, $args ); \load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/settings.php', true, $args );
break; break;
case 'event-sources':
wp_enqueue_script( 'thickbox' );
wp_enqueue_style( 'thickbox' );
\load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/event-sources.php', true );
break;
case 'welcome': case 'welcome':
default: default:
wp_enqueue_script( 'plugin-install' ); wp_enqueue_script( 'plugin-install' );

View file

@ -0,0 +1,97 @@
<?php
/**
* Class for handling and saving the ActivityPub event sources (i.e. follows).
*
* @package ActivityPub_Event_Bridge
*/
namespace ActivityPub_Event_Bridge;
use Activitypub\Http;
use Exception;
use function register_post_type;
/**
* Class for handling and saving the ActivityPub event sources (i.e. follows).
*
* @package ActivityPub_Event_Bridge
*/
class Event_Sources {
/**
* The custom post type.
*/
const POST_TYPE = 'activitypub_event_bridge_follow';
/**
* Register the post type used to store the external event sources (i.e., followed ActivityPub actors).
*/
public static function register_post_type() {
register_post_type(
self::POST_TYPE,
array(
'labels' => array(
'name' => _x( 'Event Sources', 'post_type plural name', 'activitypub' ),
'singular_name' => _x( 'Event Source', 'post_type single name', 'activitypub' ),
),
'public' => false,
'hierarchical' => false,
'rewrite' => false,
'query_var' => false,
'delete_with_user' => false,
'can_export' => true,
'supports' => array(),
)
);
\register_post_meta(
self::POST_TYPE,
'activitypub_inbox',
array(
'type' => 'string',
'single' => true,
'sanitize_callback' => 'sanitize_url',
)
);
\register_post_meta(
self::POST_TYPE,
'activitypub_errors',
array(
'type' => 'string',
'single' => false,
'sanitize_callback' => function ( $value ) {
if ( ! is_string( $value ) ) {
throw new Exception( 'Error message is no valid string' );
}
return esc_sql( $value );
},
)
);
\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',
array(
'type' => 'string',
'single' => true,
'sanitize_callback' => function ( $value ) {
return sanitize_text_field( $value );
},
)
);
}
}

View file

@ -181,6 +181,7 @@ class Setup {
} }
add_action( 'init', array( Health_Check::class, 'init' ) ); add_action( 'init', array( Health_Check::class, 'init' ) );
add_action( 'init', array( Event_Sources::class, 'register_taxonomy' ) );
// Check if the minimum required version of the ActivityPub plugin is installed. // Check if the minimum required version of the ActivityPub plugin is installed.
if ( ! version_compare( $this->activitypub_plugin_version, ACTIVITYPUB_EVENT_BRIDGE_ACTIVITYPUB_PLUGIN_MIN_VERSION ) ) { if ( ! version_compare( $this->activitypub_plugin_version, ACTIVITYPUB_EVENT_BRIDGE_ACTIVITYPUB_PLUGIN_MIN_VERSION ) ) {

View file

@ -38,7 +38,7 @@ final class Modern_Events_Calendar_Lite extends Event_plugin {
*/ */
public static function get_post_type(): string { public static function get_post_type(): string {
// See MEC_feature_events->get_main_post_type(). // See MEC_feature_events->get_main_post_type().
return 'mec-events'; return apply_filters( 'mec_post_type_name', 'mec-events' ); // phpcs:ignore
} }
/** /**

View file

@ -0,0 +1,240 @@
<?php
/**
* Event Sources Table-Class file.
*
* @package ActivityPub_Event_Bridge
*/
namespace ActivityPub_Event_Bridge\Table;
use WP_List_Table;
use Activitypub\Collection\Followers as FollowerCollection;
use ActivityPub_Event_Bridge\ActivityPub\Event_Source;
use function Activitypub\object_to_uri;
if ( ! \class_exists( '\WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
/**
* Followers Table-Class.
*/
class Event_Sources extends WP_List_Table {
/**
* Constructor.
*/
public function __construct() {
parent::__construct(
array(
'singular' => \__( 'Event Source', 'activitypub' ),
'plural' => \__( 'Event Sources', 'activitypub' ),
'ajax' => false,
)
);
}
/**
* Get columns.
*
* @return array
*/
public function get_columns() {
return array(
'cb' => '<input type="checkbox" />',
'avatar' => \__( 'Avatar', 'activitypub' ),
'post_title' => \__( 'Name', 'activitypub' ),
'username' => \__( 'Username', 'activitypub' ),
'url' => \__( 'URL', 'activitypub' ),
'published' => \__( 'Followed', 'activitypub' ),
'modified' => \__( 'Last updated', 'activitypub' ),
);
}
/**
* Returns sortable columns.
*
* @return array
*/
public function get_sortable_columns() {
return array(
'post_title' => array( 'post_title', true ),
'modified' => array( 'modified', false ),
'published' => array( 'published', false ),
);
}
/**
* Prepare items.
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array();
$this->process_action();
$this->_column_headers = array( $columns, $hidden, $this->get_sortable_columns() );
$page_num = $this->get_pagenum();
$per_page = 20;
$args = array();
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['orderby'] ) ) {
$args['orderby'] = sanitize_text_field( wp_unslash( $_GET['orderby'] ) );
}
if ( isset( $_GET['order'] ) ) {
$args['order'] = sanitize_text_field( wp_unslash( $_GET['order'] ) );
}
if ( isset( $_GET['s'] ) && isset( $_REQUEST['_wpnonce'] ) ) {
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
if ( wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
$args['s'] = sanitize_text_field( wp_unslash( $_GET['s'] ) );
}
}
// 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;
$actors = $event_sources['actors'];
$counter = $event_sources['total'];
$this->items = array();
$this->set_pagination_args(
array(
'total_items' => $counter,
'total_pages' => ceil( $counter / $per_page ),
'per_page' => $per_page,
)
);
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() ) ),
'identifier' => esc_attr( $actor->get_id() ),
'published' => esc_attr( $actor->get_published() ),
'modified' => esc_attr( $actor->get_updated() ),
);
$this->items[] = $item;
}
}
/**
* Returns bulk actions.
*
* @return array
*/
public function get_bulk_actions() {
return array(
'delete' => __( 'Delete', 'activitypub' ),
);
}
/**
* Column default.
*
* @param array $item Item.
* @param string $column_name Column name.
* @return string
*/
public function column_default( $item, $column_name ) {
if ( ! array_key_exists( $column_name, $item ) ) {
return __( 'None', 'activitypub' );
}
return $item[ $column_name ];
}
/**
* Column avatar.
*
* @param array $item Item.
* @return string
*/
public function column_avatar( $item ) {
return sprintf(
'<img src="%s" width="25px;" />',
$item['icon']
);
}
/**
* Column url.
*
* @param array $item Item.
* @return string
*/
public function column_url( $item ) {
return sprintf(
'<a href="%s" target="_blank">%s</a>',
esc_url( $item['url'] ),
$item['url']
);
}
/**
* Column cb.
*
* @param array $item Item.
* @return string
*/
public function column_cb( $item ) {
return sprintf( '<input type="checkbox" name="followers[]" value="%s" />', esc_attr( $item['identifier'] ) );
}
/**
* Process action.
*/
public function process_action() {
if ( ! isset( $_REQUEST['followers'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
return;
}
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
if ( ! wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
return;
}
if ( ! current_user_can( 'edit_user', $this->user_id ) ) {
return;
}
$followers = $_REQUEST['followers']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
if ( $this->current_action() === 'delete' ) {
if ( ! is_array( $followers ) ) {
$followers = array( $followers );
}
foreach ( $followers as $follower ) {
FollowerCollection::remove_follower( $this->user_id, $follower );
}
}
}
/**
* Returns user count.
*
* @return int
*/
public function get_user_count() {
return FollowerCollection::count_followers( $this->user_id );
}
}

View file

@ -2,8 +2,8 @@
Contributors: andremenrath Contributors: andremenrath
Tags: events, fediverse, activitypub, calendar Tags: events, fediverse, activitypub, calendar
Requires at least: 6.5 Requires at least: 6.5
Tested up to: 6.7 Tested up to: 6.6
Stable tag: 0.2.1 Stable tag: 0.2.0
Requires PHP: 7.4 Requires PHP: 7.4
License: AGPL-3.0-or-later License: AGPL-3.0-or-later
License URI: https://www.gnu.org/licenses/agpl-3.0.html License URI: https://www.gnu.org/licenses/agpl-3.0.html
@ -67,7 +67,6 @@ If you're new to the [ActivityPub plugin](https://wordpress.org/plugins/activity
= Do I need to install another event plugin to use the Event Federation Plugin? = = Do I need to install another event plugin to use the Event Federation Plugin? =
Yes, this plugin works as an add-on and requires both the ActivityPub plugin and a supported event plugin such as The Events Calendar, VS Event List, or Events Manager to manage your events. It just fills the missing gap between event plugins and the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/). Yes, this plugin works as an add-on and requires both the ActivityPub plugin and a supported event plugin such as The Events Calendar, VS Event List, or Events Manager to manage your events. It just fills the missing gap between event plugins and the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/).
= What platforms can follow my events? = = What platforms can follow my events? =
Your events can be followed on platforms that support ActivityPub like [Mobilizon](https://joinmobilizon.org/), [Gancio](https://gancio.org), [Friendica](https://friendi.ca), [Hubzilla](https://hubzilla.org), and [Pleroma](https://pleroma.social/). Even other applications like [Mastodon](https://joinmastodon.org), which don't fully support events yet, will display all important information about the events. Your events can be followed on platforms that support ActivityPub like [Mobilizon](https://joinmobilizon.org/), [Gancio](https://gancio.org), [Friendica](https://friendi.ca), [Hubzilla](https://hubzilla.org), and [Pleroma](https://pleroma.social/). Even other applications like [Mastodon](https://joinmastodon.org), which don't fully support events yet, will display all important information about the events.
@ -94,10 +93,6 @@ We're always interested in your feedback. Feel free to reach out to us via [E-Ma
== Changelog == == Changelog ==
= [0.2.1] 2024-11-16 =
* Initial release on https://wordpress.org/
= [0.2.0] 2024-10-29 = = [0.2.0] 2024-10-29 =
* Initial submission to https://wordpress.org/ * Initial release on https://wordpress.org/

View file

@ -5,15 +5,13 @@
* @package ActivityPub_Event_Bridge * @package ActivityPub_Event_Bridge
*/ */
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
/* @var array $args Template arguments. */ /* @var array $args Template arguments. */
$args = wp_parse_args( $args = wp_parse_args(
$args, $args,
array( array(
'welcome' => '', 'welcome' => '',
'settings' => '', 'settings' => '',
'event-sources' => '',
) )
); );
?> ?>
@ -31,6 +29,10 @@ $args = wp_parse_args(
<a href="<?php echo \esc_url( admin_url( 'options-general.php?page=activitypub-event-bridge&tab=settings' ) ); ?>" class="activitypub-event-bridge-settings-tab <?php echo \esc_attr( $args['settings'] ); ?>"> <a href="<?php echo \esc_url( admin_url( 'options-general.php?page=activitypub-event-bridge&tab=settings' ) ); ?>" class="activitypub-event-bridge-settings-tab <?php echo \esc_attr( $args['settings'] ); ?>">
<?php \esc_html_e( 'Settings', 'activitypub-event-bridge' ); ?> <?php \esc_html_e( 'Settings', 'activitypub-event-bridge' ); ?>
</a> </a>
<a href="<?php echo \esc_url( admin_url( 'options-general.php?page=activitypub-event-bridge&tab=event-sources' ) ); ?>" class="activitypub-event-bridge-settings-tab <?php echo \esc_attr( $args['event-sources'] ); ?>">
<?php \esc_html_e( 'Event Sources', 'activitypub-event-bridge' ); ?>
</a>
</nav> </nav>
</div> </div>
<hr class="wp-header-end"> <hr class="wp-header-end">

View file

@ -0,0 +1,51 @@
<?php
/**
* Event Sources management page for the ActivityPub Event Bridge.
*
* @package ActivityPub_Event_Bridge
*/
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\load_template(
__DIR__ . '/admin-header.php',
true,
array(
'event-sources' => 'active',
)
);
$table = new \ActivityPub_Event_Bridge\Table\Event_Sources();
?>
<div class="activitypub-event-bridge-settings activitypub-event-bridge-settings-page hide-if-no-js">
<div class="box">
<h2> <?php esc_html_e( 'Federated event sources', 'activitypub-event-bridge' ); ?> </h2>
<p> <?php esc_html_e( 'Here you can add any Fediverse Account.', 'activitypub-event-bridge' ); ?> </p>
<!-- Button that triggers ThickBox -->
<a href="#TB_inline?width=600&height=400&inlineId=activitypub_event_bridge_add_new_source" class="thickbox button button-primary">
<?php esc_html_e( 'Add new', 'activitypub-event-bridge' ); ?>
</a>
<!-- ThickBox content (hidden initially) -->
<div id="activitypub_event_bridge_add_new_source" style="display:none;">
<h2><?php esc_html_e( 'Add new ActivityPub follow', 'activitypub-event-bridge' ); ?> </h2>
<p> <?php esc_html_e( 'Here you can enter either a handle or instance URL.', 'activitypub-event-bridge' ); ?> </p>
</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" />
<?php
$table->prepare_items();
$table->search_box( 'Search', 'search' );
$table->display();
?>
</form>
</div>

View file

@ -35,10 +35,10 @@ function _manually_load_plugin() {
require_once $plugin_dir . 'activitypub/activitypub.php'; require_once $plugin_dir . 'activitypub/activitypub.php';
// Capture the --filter argument. // Capture the --filter argument.
$activitypub_event_bridge_integration_filter = null; $activitypub_event_extension_integration_filter = null;
foreach ( $_SERVER['argv'] as $arg ) { foreach ( $_SERVER['argv'] as $arg ) {
if ( strpos( $arg, '--filter=' ) === 0 ) { if ( strpos( $arg, '--filter=' ) === 0 ) {
$activitypub_event_bridge_integration_filter = substr( $arg, strlen( '--filter=' ) ); $activitypub_event_extension_integration_filter = substr( $arg, strlen( '--filter=' ) );
break; break;
} }
} }
@ -49,7 +49,7 @@ function _manually_load_plugin() {
$plugin_file = null; $plugin_file = null;
// See if we want to run integration tests for a specific event-plugin. // See if we want to run integration tests for a specific event-plugin.
switch ( $activitypub_event_bridge_integration_filter ) { switch ( $activitypub_event_extension_integration_filter ) {
case 'the_events_calendar': case 'the_events_calendar':
$plugin_file = 'the-events-calendar/the-events-calendar.php'; $plugin_file = 'the-events-calendar/the-events-calendar.php';
break; break;
@ -84,14 +84,14 @@ function _manually_load_plugin() {
} }
// Hot fix that allows using Events Manager within unit tests, because the em_init() is later not run as admin. // Hot fix that allows using Events Manager within unit tests, because the em_init() is later not run as admin.
if ( 'events_manager' === $activitypub_event_bridge_integration_filter ) { if ( 'events_manager' === $activitypub_event_extension_integration_filter ) {
require_once $plugin_dir . 'events-manager/em-install.php'; require_once $plugin_dir . 'events-manager/em-install.php';
em_create_events_table(); em_create_events_table();
em_create_events_meta_table(); em_create_events_meta_table();
em_create_locations_table(); em_create_locations_table();
} }
if ( 'modern_events_calendar_lite' === $activitypub_event_bridge_integration_filter ) { if ( 'modern_events_calendar_lite' === $activitypub_event_extension_integration_filter ) {
require_once $plugin_dir . 'modern-events-calendar-lite/app/libraries/factory.php'; require_once $plugin_dir . 'modern-events-calendar-lite/app/libraries/factory.php';
$mec_factory = new MEC_factory(); $mec_factory = new MEC_factory();
$mec_factory->install(); $mec_factory->install();