Compare commits
1 commit
main
...
event_sour
Author | SHA1 | Date | |
---|---|---|---|
64ad7d61ef |
18 changed files with 736 additions and 59 deletions
|
@ -22,7 +22,6 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.0', '8.1', '8.2', '8.3']
|
||||
wordpress-version: ['6.7']
|
||||
name: PHPUnit – PHP ${{ matrix.php-version }}
|
||||
env:
|
||||
extensions: mysql
|
||||
|
@ -38,7 +37,7 @@ jobs:
|
|||
path: |
|
||||
${{ env.WP_CORE_DIR }}
|
||||
${{ env.WP_TESTS_DIR }}
|
||||
key: cache-wordpress-67-2
|
||||
key: cache-wordpress-9
|
||||
|
||||
- name: Cache Composer
|
||||
id: cache-composer-phpunit
|
||||
|
@ -69,11 +68,11 @@ jobs:
|
|||
|
||||
- name: Setup Test Environment
|
||||
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
|
||||
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
|
||||
run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=the_events_calendar
|
||||
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -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/),
|
||||
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
|
||||
|
||||
* Initial release on WordPress.org
|
||||
|
||||
### Fixed
|
||||
|
||||
* Applied some WordPress best practices
|
||||
|
||||
## [0.2.0] - 2024-10-20
|
||||
|
||||
### Added
|
||||
|
||||
* Initial submission to WordPress.org
|
||||
* Initial version tag.
|
||||
|
|
11
README.md
11
README.md
|
@ -2,8 +2,8 @@
|
|||
**Contributors:** [andremenrath](https://profiles.wordpress.org/andremenrath/)
|
||||
**Tags:** events, fediverse, activitypub, calendar
|
||||
**Requires at least:** 6.5
|
||||
**Tested up to:** 6.7
|
||||
**Stable tag:** 0.2.1
|
||||
**Tested up to:** 6.6
|
||||
**Stable tag:** 0.2.0
|
||||
**Requires PHP:** 7.4
|
||||
**License:** AGPL-3.0-or-later
|
||||
**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? ###
|
||||
|
||||
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? ###
|
||||
|
||||
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 ##
|
||||
|
||||
### [0.2.1] 2024-11-16 ###
|
||||
|
||||
* Initial release on https://wordpress.org/
|
||||
|
||||
### [0.2.0] 2024-10-29 ###
|
||||
|
||||
* Initial submission to https://wordpress.org/
|
||||
* Initial release on https://wordpress.org/
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Plugin Name: ActivityPub Event Bridge
|
||||
* Description: Integrating popular event plugins with the ActivityPub plugin.
|
||||
* Plugin URI: https://event-federation.eu/
|
||||
* Version: 0.2.1
|
||||
* Version: 0.2.0
|
||||
* Author: André Menrath
|
||||
* Author URI: https://graz.social/@linos
|
||||
* Text Domain: activitypub-event-bridge
|
||||
|
@ -11,7 +11,7 @@
|
|||
* License URI: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
* 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
|
||||
* @license AGPL-3.0-or-later
|
||||
|
|
|
@ -208,15 +208,9 @@ install_wp_plugin() {
|
|||
fi
|
||||
|
||||
# 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 }')
|
||||
PLUGIN_VERSION=$LATEST_TAG
|
||||
else
|
||||
PLUGIN_VERSION=$2
|
||||
fi
|
||||
|
||||
if [ -n "$PLUGIN_VERSION" ]; then
|
||||
PLUGIN_FILE="$PLUGIN_NAME.$PLUGIN_VERSION.zip"
|
||||
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_FILE="$PLUGIN_NAME.$LATEST_TAG.zip"
|
||||
else
|
||||
PLUGIN_FILE="$PLUGIN_NAME.zip"
|
||||
fi
|
||||
|
@ -254,12 +248,12 @@ install_wp_plugins() {
|
|||
# Install the one and only ActivityPub plugin (greetings @pfefferle).
|
||||
install_wp_plugin activitypub
|
||||
# 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 gatherpress
|
||||
install_wp_plugin events-manager "6.6.3"
|
||||
install_wp_plugin wp-event-manager "3.1.45.1"
|
||||
install_wp_plugin wp-event-solution "4.0.14"
|
||||
install_wp_plugin events-manager
|
||||
install_wp_plugin wp-event-manager
|
||||
install_wp_plugin wp-event-solution
|
||||
# Mec is not installable via wordpress.org, we use our own mirror.
|
||||
install_wp_plugin_mec
|
||||
}
|
||||
|
|
|
@ -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`.
|
||||
|
||||
```php
|
||||
switch ( $activitypub_event_bridge_integration_filter ) {
|
||||
switch ( $activitypub_event_extension_integration_filter ) {
|
||||
...
|
||||
case 'my_event_plugin':
|
||||
$plugin_file = 'my-event-plugin/my-event-plugin.php';
|
||||
|
|
164
includes/activitypub/collection/class-event-sources.php
Normal file
164
includes/activitypub/collection/class-event-sources.php
Normal 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 );
|
||||
}
|
||||
}
|
59
includes/activitypub/model/class-event-source.php
Normal file
59
includes/activitypub/model/class-event-source.php
Normal 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;
|
||||
}
|
||||
}
|
85
includes/admin/class-event-sources.php
Normal file
85
includes/admin/class-event-sources.php
Normal 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();
|
||||
}
|
||||
}
|
|
@ -116,6 +116,11 @@ class Settings_Page {
|
|||
|
||||
\load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/settings.php', true, $args );
|
||||
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':
|
||||
default:
|
||||
wp_enqueue_script( 'plugin-install' );
|
||||
|
|
97
includes/class-event-sources.php
Normal file
97
includes/class-event-sources.php
Normal 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 );
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -181,6 +181,7 @@ class Setup {
|
|||
}
|
||||
|
||||
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.
|
||||
if ( ! version_compare( $this->activitypub_plugin_version, ACTIVITYPUB_EVENT_BRIDGE_ACTIVITYPUB_PLUGIN_MIN_VERSION ) ) {
|
||||
|
|
|
@ -38,7 +38,7 @@ final class Modern_Events_Calendar_Lite extends Event_plugin {
|
|||
*/
|
||||
public static function get_post_type(): string {
|
||||
// See MEC_feature_events->get_main_post_type().
|
||||
return 'mec-events';
|
||||
return apply_filters( 'mec_post_type_name', 'mec-events' ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
240
includes/table/class-event-sources.php
Normal file
240
includes/table/class-event-sources.php
Normal 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 );
|
||||
}
|
||||
}
|
11
readme.txt
11
readme.txt
|
@ -2,8 +2,8 @@
|
|||
Contributors: andremenrath
|
||||
Tags: events, fediverse, activitypub, calendar
|
||||
Requires at least: 6.5
|
||||
Tested up to: 6.7
|
||||
Stable tag: 0.2.1
|
||||
Tested up to: 6.6
|
||||
Stable tag: 0.2.0
|
||||
Requires PHP: 7.4
|
||||
License: AGPL-3.0-or-later
|
||||
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? =
|
||||
|
||||
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? =
|
||||
|
||||
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 ==
|
||||
|
||||
= [0.2.1] 2024-11-16 =
|
||||
|
||||
* Initial release on https://wordpress.org/
|
||||
|
||||
= [0.2.0] 2024-10-29 =
|
||||
|
||||
* Initial submission to https://wordpress.org/
|
||||
* Initial release on https://wordpress.org/
|
||||
|
|
|
@ -5,15 +5,13 @@
|
|||
* @package ActivityPub_Event_Bridge
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/* @var array $args Template arguments. */
|
||||
$args = wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'welcome' => '',
|
||||
'settings' => '',
|
||||
'welcome' => '',
|
||||
'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'] ); ?>">
|
||||
<?php \esc_html_e( 'Settings', 'activitypub-event-bridge' ); ?>
|
||||
</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>
|
||||
</div>
|
||||
<hr class="wp-header-end">
|
||||
|
|
51
templates/event-sources.php
Normal file
51
templates/event-sources.php
Normal 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>
|
|
@ -35,10 +35,10 @@ function _manually_load_plugin() {
|
|||
require_once $plugin_dir . 'activitypub/activitypub.php';
|
||||
|
||||
// Capture the --filter argument.
|
||||
$activitypub_event_bridge_integration_filter = null;
|
||||
$activitypub_event_extension_integration_filter = null;
|
||||
foreach ( $_SERVER['argv'] as $arg ) {
|
||||
if ( strpos( $arg, '--filter=' ) === 0 ) {
|
||||
$activitypub_event_bridge_integration_filter = substr( $arg, strlen( '--filter=' ) );
|
||||
$activitypub_event_extension_integration_filter = substr( $arg, strlen( '--filter=' ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ function _manually_load_plugin() {
|
|||
|
||||
$plugin_file = null;
|
||||
// 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':
|
||||
$plugin_file = 'the-events-calendar/the-events-calendar.php';
|
||||
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.
|
||||
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';
|
||||
em_create_events_table();
|
||||
em_create_events_meta_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';
|
||||
$mec_factory = new MEC_factory();
|
||||
$mec_factory->install();
|
||||
|
|
Loading…
Reference in a new issue