Compare commits
36 commits
main
...
event_sour
Author | SHA1 | Date | |
---|---|---|---|
984973e18d | |||
423781ff23 | |||
b5a199fe9c | |||
c2856a12bd | |||
e12a0734a5 | |||
33f1ccbc86 | |||
5712706457 | |||
0c0bba5d15 | |||
23f415b401 | |||
db4c72db86 | |||
69f0cd3ccb | |||
96e0d0937c | |||
6e92f2a5b8 | |||
cd451131fa | |||
37043e7a7b | |||
178beb7dd5 | |||
55c70ce831 | |||
076e2619f0 | |||
0c596d9526 | |||
c44d692aa4 | |||
be52d2705f | |||
3f5c57134e | |||
16762b2b31 | |||
ef1248beed | |||
17ca4ff800 | |||
210bf9cc96 | |||
189e7b5f9f | |||
970f3e7754 | |||
d77cdf1430 | |||
1eda885719 | |||
e8b2b4c899 | |||
a5a062bb17 | |||
2e8348a9c8 | |||
d575f6ef70 | |||
24560c136c | |||
64ad7d61ef |
61 changed files with 3572 additions and 405 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ composer.lock
|
|||
.phpunit.result.cache
|
||||
node_modules/
|
||||
package-lock.json
|
||||
.phpdoc
|
||||
|
|
|
@ -185,3 +185,35 @@ code.event-bridge-for-activitypub-settings-example-url {
|
|||
#event_bridge_for_activitypub_summary_type_custom-details > details {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-list {
|
||||
list-style: disc;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-admin-table-container {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-admin-table-top > h2 {
|
||||
display: inline-block;
|
||||
font-size: 23px;
|
||||
font-weight: 400;
|
||||
margin: 0 10px 0 2px;
|
||||
padding: 9px 0 4px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-admin-table-top > a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.settings_page_event-bridge-for-activitypub .notice-warning {
|
||||
background: #fff;
|
||||
border: 1px solid #c3c4c7;
|
||||
border-left-width: 4px;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,.04);
|
||||
margin: 5px 15px 2px;
|
||||
padding: 1px 12px;
|
||||
border-left-color: #dba617;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ jQuery( function( $ ) {
|
|||
|
||||
// Run the toggle function on page load.
|
||||
$(document).ready(function() {
|
||||
window.console.log("test");
|
||||
toggleCustomDetailsForSummary(); // Set the correct state on load.
|
||||
|
||||
// Listen for changes on the radio buttons
|
||||
|
@ -31,4 +30,5 @@ jQuery( function( $ ) {
|
|||
toggleCustomDetailsForSummary(); // Update visibility on change.
|
||||
});
|
||||
});
|
||||
|
||||
} );
|
||||
|
|
|
@ -56,11 +56,12 @@
|
|||
"@test-eventin",
|
||||
"@test-modern-events-calendar-lite",
|
||||
"@test-eventprime",
|
||||
"@test-event-organiser"
|
||||
"@test-event-organiser",
|
||||
"@test-event-bridge-for-activitypub-event-sources"
|
||||
],
|
||||
"test-debug": [
|
||||
"@prepare-test",
|
||||
"@test-event-bridge-for-activitypub-shortcodes"
|
||||
"@test-the-events-calendar"
|
||||
],
|
||||
"test-vs-event-list": "phpunit --filter=vs_event_list",
|
||||
"test-the-events-calendar": "phpunit --filter=the_events_calendar",
|
||||
|
@ -72,6 +73,7 @@
|
|||
"test-eventprime": "phpunit --filter=eventprime",
|
||||
"test-event-organiser": "phpunit --filter=event_organiser",
|
||||
"test-event-bridge-for-activitypub-shortcodes": "phpunit --filter=event_bridge_for_activitypub_shortcodes",
|
||||
"test-event-bridge-for-activitypub-event-sources": "phpunit --filter=event_bridge_for_activitypub_event_sources",
|
||||
"test-all": "phpunit"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,222 +0,0 @@
|
|||
# Write a specialized ActivityPub transformer for an Event-Custom-Post-Type
|
||||
|
||||
> **_NOTE:_** This documentation is also likely to be useful for content types other than events.
|
||||
|
||||
The ActivityPub plugin offers a basic support for all post types out of the box, but it also allows the registration of external transformers. A transformer is a class that implements the [abstract transformer class](https://github.com/Automattic/wordpress-activitypub/blob/fb0e23e8854d149fdedaca7a9ea856f5fd965ec9/includes/transformer/class-base.php) and is responsible for generating the ActivityPub JSON representation of an WordPress post or comment object.
|
||||
|
||||
## How it works
|
||||
|
||||
To make the WordPress ActivityPub plugin use a custom transformer simply add a filter to the `activitypub_transformer` hook which provides access to the transformer factory. The [transformer factory](https://github.com/Automattic/wordpress-activitypub/blob/master/includes/transformer/class-factory.php#L12) determines which transformer is used to transform a WordPress object to ActivityPub. We provide a parent event transformer, that comes with common tasks needed for events. Furthermore, we provide admin notices, to prevent users from misconfiguration issues.
|
||||
|
||||
## Add your event plugin
|
||||
|
||||
First you need to add some basic information about your event plugin. Just create a new file in `./includes/plugins/my-event-plugin.php`. Implement at least all abstract functions of the `Event_Plugin` class.
|
||||
|
||||
```php
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Integration information for My Event Plugin
|
||||
*
|
||||
* This class defines necessary meta information is for the integration of My Event Plugin with the ActivityPub plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class My_Event_Plugin extends Event_Plugin {
|
||||
```
|
||||
|
||||
Then you need to tell the Event Bridge for ActivityPub about that class by adding it to the `EVENT_PLUGIN_CLASSES` constant in the `includes/setup.php` file:
|
||||
|
||||
```php
|
||||
private const EVENT_PLUGIN_CLASSES = array(
|
||||
...
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\My_Event_Plugin',
|
||||
);
|
||||
```
|
||||
|
||||
The Event Bridge for ActivityPub then takes care of applying the transformer, so you can jump right into implementing it.
|
||||
|
||||
## Writing an event transformer class
|
||||
|
||||
Within WordPress most content types are stored as a custom post type in the posts table. The ActivityPub plugin offers a basic support for all post types out of the box. So-called transformers take care of converting WordPress WP_Post objects to ActivityStreams JSON. The ActivityPub plugin offers a generic transformer for all post types. Additionally, custom transformers can be implemented to better fit a custom post type, and they can be easily registered with the ActivityPub plugin.
|
||||
|
||||
If you are writing a transformer for your event post type we recommend to start by extending the provided [event transformer](./includes/activitypub/transformer/class-event.php). It is an extension of the default generic post transformer and inherits useful default implementations for generating the [attachments](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment), rendering a proper [content](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-content) in HTML from either blocks or the classic editor, extracting [tags](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tag) and more. Furthermore, it offers functions which are likely to be shared by multiple event plugins, so you do not need to reimplement those, or you can fork and extend them to your needs.
|
||||
|
||||
So create a new file at `./includes/activitypub/transformer/my-event-plugin.php`.
|
||||
|
||||
```php
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event as Event_Transformer;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for My Event Plugin' event post type.
|
||||
*/
|
||||
class My_Event_Plugin extends Event_Transformer; {
|
||||
```
|
||||
|
||||
The main function which controls the transformation is `to_object`. This one is called by the ActivityPub plugin to get the resulting ActivityStreams represented by a PHP-object (`\Activitypub\Activity\Object\Extended_Object\Event`). The conversion to the actual JSON-LD takes place later, and you don't need to cover that (> `to_array` > associative array > `to_json` > JSON).
|
||||
The chances are good that you will not need to override that function.
|
||||
|
||||
|
||||
```php
|
||||
/**
|
||||
* Transform the WordPress Object into an ActivityPub Event Object.
|
||||
*
|
||||
* @return Activitypub\Activity\Extended_Object\Event
|
||||
*/
|
||||
public function to_object() {
|
||||
$activitypub_object = parent::to_object();
|
||||
// ... your additions.
|
||||
return $activitypub_object;
|
||||
}
|
||||
```
|
||||
|
||||
We also recommend extending the constructor of the transformer class and set a specialized API object of the event, if it is available. For instance:
|
||||
|
||||
```php
|
||||
public function __construct( $wp_object, $wp_taxonomy ) {
|
||||
parent::__construct( $wp_object, $wp_taxonomy );
|
||||
$this->event_api = new My_Event_Object_API( $wp_object );
|
||||
}
|
||||
```
|
||||
|
||||
The ActivityPub object classes contain dynamic getter and setter functions: `set_<property>()` and `get_<property>()`. The function `transform_object_properties()` usually called by `to_object()` tries to set all properties known to the target ActivityPub object where a function called `get_<property>` exists in the current transformer class.
|
||||
|
||||
### How to add new properties
|
||||
|
||||
Adding new properties is not encouraged to do at the transformer level. It's recommended to create a proper target ActivityPub object first. The target ActivityPub object also controls the JSON-LD context via the constant `JSON_LD_CONTEXT`. [Example](https://github.com/Automattic/wordpress-activitypub/blob/fb0e23e8854d149fdedaca7a9ea856f5fd965ec9/includes/activity/extended-object/class-event.php#L21).
|
||||
|
||||
|
||||
### Properties
|
||||
|
||||
> **_NOTE:_** Within PHP all properties are snake_case, they will be transformed to the according CamelCase by the ActivityPub plugin. So if to you set `start_time` by using the ActivityPub objects class function `set_start_time` or implementing a getter function in the transformer class called `get_start_time` the property `startTime` will be set accordingly in the JSON representation of the resulting ActivityPub object.
|
||||
|
||||
You can find all available event related properties in the [event class](https://github.com/Automattic/wordpress-activitypub/blob/master/includes/activity/extended-object/class-event.php) along documentation and with links to the specifications.
|
||||
|
||||
#### Mandatory fields
|
||||
|
||||
In order to ensure your events are compatible with other ActivityPub Event implementations there are several required properties that must be set by your transformer.
|
||||
|
||||
* **[`type`](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-type)**: if using the `Activitypub\Activity\Extended_Object\Event` class the type will default to `Event` without doing anything.
|
||||
|
||||
* **[`startTime`](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-startTime)**: the events start time
|
||||
|
||||
* **[`name`](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-name)**: the title of the event
|
||||
|
||||
#### Checklist for properties you SHOULD at least consider writing a getter functions for
|
||||
|
||||
* **`endTime`**
|
||||
* **`location`** – Note: the `address` within can be both a `string` or a `PostalAddress`.
|
||||
* **`isOnline`**
|
||||
* **`status`**
|
||||
* **`get_tag`**
|
||||
* **`timezone`**
|
||||
* **`commentsEnabled`**
|
||||
|
||||
## Writing integration tests
|
||||
|
||||
Create a new tests class in `tests/test-class-plugin-my-event-plugin.php`.
|
||||
|
||||
```
|
||||
/**
|
||||
* Sample test case.
|
||||
*/
|
||||
class Test_My_Event_Plugin extends WP_UnitTestCase {
|
||||
```
|
||||
|
||||
Implement a check whether your event plugin is active in the `set_up` function. It may be the presence of a class, function or constant.
|
||||
|
||||
```php
|
||||
/**
|
||||
* Override the setup function, so that tests don't run if the Events Calendar is not active.
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
if ( ! <TODO:my-event-plugin-is-active> ) {
|
||||
self::markTestSkipped( 'The Events Calendar plugin is not active.' );
|
||||
}
|
||||
|
||||
// Make sure that ActivityPub support is enabled for The Events Calendar.
|
||||
$aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
|
||||
$aec->activate_activitypub_support_for_active_event_plugins();
|
||||
|
||||
// Delete all posts afterwards.
|
||||
_delete_all_posts();
|
||||
}
|
||||
```
|
||||
|
||||
## Running the tests for your plugin/ add the tests to the CI pipeline
|
||||
|
||||
### Install the plugin in the CI
|
||||
|
||||
The tests are set up by the bash script in `bin/install-wp-tests.sh`. Make sure your WordPress Event plugin is installed within the function `install_wp_plugins`.
|
||||
|
||||
### Add a composer script for your plugin
|
||||
|
||||
In the pipeline we want to run each event plugins integration tests in a single command, to achieve that, we use phpunit's filters.
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
...
|
||||
"test": [
|
||||
...
|
||||
"@test-my-event-plugin"
|
||||
],
|
||||
...
|
||||
"@test-my-event-plugin": "phpunit --filter=my_event_plugin",
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Load your plugin during the tests
|
||||
|
||||
To activate/load your plugin add it to the switch statement within the function `_manually_load_plugin()` within `tests/bootstrap.php`.
|
||||
|
||||
```php
|
||||
switch ( $event_bridge_for_activitypub_integration_filter ) {
|
||||
...
|
||||
case 'my_event_plugin':
|
||||
$plugin_file = 'my-event-plugin/my-event-plugin.php';
|
||||
break;
|
||||
```
|
||||
|
||||
If you want to run your tests locally just change the `test-debug` script in the `composer.json` file:
|
||||
|
||||
```json
|
||||
"test-debug": [
|
||||
"@prepare-test",
|
||||
"@test-my-event-plugin"
|
||||
],
|
||||
```
|
||||
|
||||
Now you just can execute `docker compose up` to run the tests (make sure you have the latest docker and docker-compose installed).
|
||||
|
||||
### Debugging the tests
|
||||
|
||||
If you are using Visual Studio Code or VSCodium you can step-debug within the tests by adding this configuration to your `.vscode/launch.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
...,
|
||||
{
|
||||
"name": "Listen for PHPUnit",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9003,
|
||||
"pathMappings": {
|
||||
"/app/": "${workspaceRoot}/wp-content/plugins/event-bridge-for-activitypub/",
|
||||
"/tmp/wordpress/": "${workspaceRoot}/"
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
|
@ -3,7 +3,7 @@
|
|||
* Plugin Name: Event Bridge for ActivityPub
|
||||
* Description: Integrating popular event plugins with the ActivityPub plugin.
|
||||
* Plugin URI: https://event-federation.eu/
|
||||
* Version: 0.3.2
|
||||
* Version: 0.3.2.7
|
||||
* Author: André Menrath
|
||||
* Author URI: https://graz.social/@linos
|
||||
* Text Domain: event-bridge-for-activitypub
|
||||
|
|
125
includes/activitypub/class-handler.php
Normal file
125
includes/activitypub/class-handler.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
/**
|
||||
* Class responsible for registering handlers for incoming activities to the ActivityPub plugin.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Accept;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Update;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Create;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Delete;
|
||||
|
||||
/**
|
||||
* Class responsible for registering handlers for incoming activities to the ActivityPub plugin.
|
||||
*/
|
||||
class Handler {
|
||||
/**
|
||||
* Register all ActivityPub handlers.
|
||||
*/
|
||||
public static function register_handlers() {
|
||||
Accept::init();
|
||||
Update::init();
|
||||
Create::init();
|
||||
Delete::init();
|
||||
\add_filter(
|
||||
'activitypub_validate_object',
|
||||
array( self::class, 'validate_object' ),
|
||||
12,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate the object.
|
||||
*
|
||||
* @param bool $valid The validation state.
|
||||
* @param string $param The object parameter.
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return bool The validation state: true if valid, false if not.
|
||||
*/
|
||||
public static function validate_object( $valid, $param, $request ) {
|
||||
$json_params = $request->get_json_params();
|
||||
|
||||
if ( isset( $json_params['object']['type'] ) && 'Event' === $json_params['object']['type'] ) {
|
||||
$valid = true;
|
||||
} else {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
if ( empty( $json_params['type'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( empty( $json_params['actor'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $json_params['type'], array( 'Create', 'Update', 'Delete', 'Announce' ), true ) || is_wp_error( $request ) ) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
$object = $json_params['object'];
|
||||
|
||||
if ( ! is_array( $object ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$required = array(
|
||||
'id',
|
||||
'startTime',
|
||||
'name',
|
||||
);
|
||||
|
||||
if ( array_intersect( $required, array_keys( $object ) ) !== $required ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given DateTime is already passed.
|
||||
*
|
||||
* @param string $time_string The ActivityPub like time string.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_time_passed( $time_string ) {
|
||||
// Create a DateTime object from the ActivityPub time string.
|
||||
$time = new DateTime( $time_string, new DateTimeZone( 'UTC' ) );
|
||||
|
||||
// Get the current time in UTC.
|
||||
$current_time = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
|
||||
|
||||
// Compare the event time with the current time.
|
||||
return $time < $current_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that an ActivityPub actor is an event source (i.e. it is followed by the ActivityPub blog actor).
|
||||
*
|
||||
* @param string $actor_id The actor ID.
|
||||
* @return bool True if the ActivityPub actor ID is followed, false otherwise.
|
||||
*/
|
||||
public static function actor_is_event_source( $actor_id ) {
|
||||
$event_sources = Event_Sources::get_event_sources();
|
||||
foreach ( $event_sources as $event_source ) {
|
||||
if ( $actor_id === $event_source->get_id() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
476
includes/activitypub/collection/class-event-sources.php
Normal file
476
includes/activitypub/collection/class-event-sources.php
Normal file
|
@ -0,0 +1,476 @@
|
|||
<?php
|
||||
/**
|
||||
* ActivityPub Event Sources (=Followed Actors) Collection.
|
||||
*
|
||||
* The Event Sources are nothing else than follows in the ActivityPub world.
|
||||
* However, this plugins currently only listens to Event object being created,
|
||||
* updated or deleted by a follow.
|
||||
*
|
||||
* For the ActivityPub `Follow` the Blog-Actor from the ActivityPub plugin is used.
|
||||
*
|
||||
* This class is responsible for defining a custom post type in WordPress along
|
||||
* with post-meta fields and methods to easily manage event sources. This includes
|
||||
* handling side effects, like when an event source is added a follow request is sent
|
||||
* or adding them to the `follow` collection o the blog-actor profile.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Collection;
|
||||
|
||||
use Activitypub\Model\Blog;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use WP_Error;
|
||||
use WP_Query;
|
||||
|
||||
use function Activitypub\is_tombstone;
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
|
||||
/**
|
||||
* ActivityPub Event Sources (=Followed Actors) Collection.
|
||||
*
|
||||
* The Event Sources are nothing else than follows in the ActivityPub world.
|
||||
* However, this plugins currently only listens to Event object being created,
|
||||
* updated or deleted by a follow.
|
||||
*
|
||||
* For the ActivityPub `Follow` the Blog-Actor from the ActivityPub plugin is used.
|
||||
*
|
||||
* This class is responsible for defining a custom post type in WordPress along
|
||||
* with post-meta fields and methods to easily manage event sources. This includes
|
||||
* handling side effects, like when an event source is added a follow request is sent
|
||||
* or adding them to the `follow` collection o the blog-actor profile.
|
||||
*/
|
||||
class Event_Sources {
|
||||
/**
|
||||
* The custom post type.
|
||||
*/
|
||||
const POST_TYPE = 'ebap_event_source';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public static function init() {
|
||||
self::register_post_type();
|
||||
\add_filter( 'allowed_redirect_hosts', array( self::class, 'add_event_sources_hosts_to_allowed_redirect_hosts' ) );
|
||||
\add_action( 'event_bridge_for_activitypub_follow', array( self::class, 'activitypub_follow_actor' ), 10, 1 );
|
||||
\add_action( 'event_bridge_for_activitypub_unfollow', array( self::class, 'activitypub_unfollow_actor' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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', 'event-bridge-for-activitypub' ),
|
||||
'singular_name' => _x( 'Event Source', 'post_type single name', 'event-bridge-for-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_actor_id',
|
||||
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_actor_json',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
return sanitize_text_field( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'event_source_active',
|
||||
array(
|
||||
'type' => 'bool',
|
||||
'single' => true,
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'activitypub_inbox',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
)
|
||||
);
|
||||
|
||||
\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 URL/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_actor', __( 'Invalid ActivityPub Actor', 'event-bridge-for-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;
|
||||
}
|
||||
|
||||
$success = self::queue_follow_actor( $actor );
|
||||
|
||||
self::delete_event_source_transients();
|
||||
|
||||
return $event_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all transients related to the event sources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_event_source_transients(): void {
|
||||
delete_transient( 'event_bridge_for_activitypub_event_sources' );
|
||||
delete_transient( 'event_bridge_for_activitypub_event_sources_hosts' );
|
||||
delete_transient( 'event_bridge_for_activitypub_event_sources_ids' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an Event Source (=Followed ActivityPub actor).
|
||||
*
|
||||
* @param string $actor The Actor URL.
|
||||
*
|
||||
* @return WP_Post|false|null Post data on success, false or null on failure.
|
||||
*/
|
||||
public static function remove_event_source( $actor ) {
|
||||
$actor = Event_Source::get_by_id( $actor );
|
||||
|
||||
if ( ! $actor ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = $actor->get__id();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
|
||||
$result = wp_delete_post( $post_id, true );
|
||||
|
||||
// If the deletion was successful delete all transients regarding event sources.
|
||||
if ( $result ) {
|
||||
self::delete_event_source_transients();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array will all unique hosts of all Event-Sources.
|
||||
*
|
||||
* @return array The Term list of Event Sources.
|
||||
*/
|
||||
public static function get_event_sources_hosts() {
|
||||
$hosts = get_transient( 'event_bridge_for_activitypub_event_sources_hosts' );
|
||||
|
||||
if ( $hosts ) {
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
$actors = self::get_event_sources_with_count()['actors'];
|
||||
|
||||
$hosts = array();
|
||||
foreach ( $actors as $actor ) {
|
||||
$url = wp_parse_url( $actor->get_id() );
|
||||
if ( isset( $url['host'] ) ) {
|
||||
$hosts[] = $url['host'];
|
||||
}
|
||||
}
|
||||
|
||||
set_transient( 'event_bridge_for_activitypub_event_sources_hosts', $hosts );
|
||||
|
||||
return array_unique( $hosts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get add Event Sources ActivityPub IDs.
|
||||
*
|
||||
* @return array The Term list of Event Sources.
|
||||
*/
|
||||
public static function get_event_sources_ids() {
|
||||
$ids = get_transient( 'event_bridge_for_activitypub_event_sources_ids' );
|
||||
|
||||
if ( $ids ) {
|
||||
return $ids;
|
||||
}
|
||||
|
||||
$actors = self::get_event_sources_with_count()['actors'];
|
||||
|
||||
$ids = array();
|
||||
foreach ( $actors as $actor ) {
|
||||
$ids[] = $actor->get_id();
|
||||
}
|
||||
|
||||
set_transient( 'event_bridge_for_activitypub_event_sources_ids', $ids );
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Event-Sources.
|
||||
*
|
||||
* @return array The Term list of Event Sources.
|
||||
*/
|
||||
public static function get_event_sources() {
|
||||
return self::get_event_sources_with_count()['actors'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() ) {
|
||||
$event_sources = get_transient( 'event_bridge_for_activitypub_event_sources' );
|
||||
|
||||
if ( $event_sources ) {
|
||||
return $event_sources;
|
||||
}
|
||||
|
||||
$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()
|
||||
);
|
||||
|
||||
$event_sources = compact( 'actors', 'total' );
|
||||
|
||||
set_transient( 'event_bridge_for_activitypub_event_sources', $event_sources );
|
||||
|
||||
return $event_sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a hook to run async.
|
||||
*
|
||||
* @param string $hook The hook name.
|
||||
* @param array $args The arguments to pass to the hook.
|
||||
* @param string $unqueue_hook Optional a hook to unschedule before queuing.
|
||||
* @return void|bool Whether the hook was queued.
|
||||
*/
|
||||
public static function queue( $hook, $args, $unqueue_hook = null ) {
|
||||
if ( $unqueue_hook ) {
|
||||
$hook_timestamp = wp_next_scheduled( $unqueue_hook, $args );
|
||||
if ( $hook_timestamp ) {
|
||||
wp_unschedule_event( $hook_timestamp, $unqueue_hook, $args );
|
||||
}
|
||||
}
|
||||
|
||||
if ( wp_next_scheduled( $hook, $args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return \wp_schedule_single_event( \time(), $hook, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to follow an ActivityPub actor via a scheduled event.
|
||||
*
|
||||
* @param string $actor The ActivityPub actor.
|
||||
*
|
||||
* @return bool|WP_Error Whether the event was queued.
|
||||
*/
|
||||
public static function queue_follow_actor( $actor ) {
|
||||
$queued = self::queue(
|
||||
'event_bridge_for_activitypub_follow',
|
||||
array( $actor ),
|
||||
'event_bridge_for_activitypub_unfollow'
|
||||
);
|
||||
|
||||
return $queued;
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow an ActivityPub actor via the Blog user.
|
||||
*
|
||||
* @param string $actor_id The ID/URL of the Actor.
|
||||
*/
|
||||
public static function activitypub_follow_actor( $actor_id ) {
|
||||
$actor = Event_Source::get_by_id( $actor_id );
|
||||
|
||||
if ( ! $actor ) {
|
||||
return $actor;
|
||||
}
|
||||
|
||||
$inbox = $actor->get_shared_inbox();
|
||||
$to = $actor->get_id();
|
||||
|
||||
$application = new Blog();
|
||||
|
||||
$activity = new \Activitypub\Activity\Activity();
|
||||
$activity->set_type( 'Follow' );
|
||||
$activity->set_to( null );
|
||||
$activity->set_cc( null );
|
||||
$activity->set_actor( $application->get_id() );
|
||||
$activity->set_object( $to );
|
||||
$activity->set_id( $application->get_id() . '#follow-' . \preg_replace( '~^https?://~', '', $to ) );
|
||||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::BLOG_USER_ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to unfollow an actor via a scheduled event.
|
||||
*
|
||||
* @param string $actor The ActivityPub actor ID.
|
||||
*
|
||||
* @return bool|WP_Error Whether the event was queued.
|
||||
*/
|
||||
public static function queue_unfollow_actor( $actor ) {
|
||||
$queued = self::queue(
|
||||
'event_bridge_for_activitypub_unfollow',
|
||||
array( $actor ),
|
||||
'event_bridge_for_activitypub_follow'
|
||||
);
|
||||
|
||||
return $queued;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfollow an ActivityPub actor.
|
||||
*
|
||||
* @param string $actor_id The ActivityPub actor ID.
|
||||
*/
|
||||
public static function activitypub_unfollow_actor( $actor_id ) {
|
||||
$actor = Event_Source::get_by_id( $actor_id );
|
||||
|
||||
if ( ! $actor ) {
|
||||
return $actor;
|
||||
}
|
||||
|
||||
$inbox = $actor->get_shared_inbox();
|
||||
$to = $actor->get_id();
|
||||
|
||||
$application = new Blog();
|
||||
|
||||
if ( is_wp_error( $inbox ) ) {
|
||||
return $inbox;
|
||||
}
|
||||
|
||||
$activity = new \Activitypub\Activity\Activity();
|
||||
$activity->set_type( 'Undo' );
|
||||
$activity->set_to( null );
|
||||
$activity->set_cc( null );
|
||||
$activity->set_actor( $application->get_id() );
|
||||
$activity->set_object(
|
||||
array(
|
||||
'type' => 'Follow',
|
||||
'actor' => $actor,
|
||||
'object' => $to,
|
||||
'id' => $to,
|
||||
)
|
||||
);
|
||||
$activity->set_id( $actor . '#unfollow-' . \preg_replace( '~^https?://~', '', $to ) );
|
||||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::BLOG_USER_ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Event Sources hosts to allowed hosts used by safe redirect.
|
||||
*
|
||||
* @param array $hosts The hosts before the filter.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_event_sources_hosts_to_allowed_redirect_hosts( $hosts ) {
|
||||
$event_sources_hosts = self::get_event_sources_hosts();
|
||||
return array_merge( $hosts, $event_sources_hosts );
|
||||
}
|
||||
}
|
49
includes/activitypub/handler/class-accept.php
Normal file
49
includes/activitypub/handler/class-accept.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
/**
|
||||
* Accept handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later */
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
|
||||
/**
|
||||
* Handle Accept requests.
|
||||
*/
|
||||
class Accept {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Accept` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_inbox_accept',
|
||||
array( self::class, 'handle_accept' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Accept" activities.
|
||||
*
|
||||
* @param array $activity The activity object.
|
||||
*/
|
||||
public static function handle_accept( $activity ) {
|
||||
if ( ! isset( $activity['object'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$object = Actors::get_by_resource( $activity['object'] );
|
||||
|
||||
if ( ! $object || is_wp_error( $object ) ) {
|
||||
// If we can not find a actor, we handle the `Accept` activity.
|
||||
return;
|
||||
}
|
||||
|
||||
// We only expect `Accept` activities being answers to follow requests by the application actor.
|
||||
if ( Actors::BLOG_USER_ID !== $object->get__id() ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
70
includes/activitypub/handler/class-create.php
Normal file
70
includes/activitypub/handler/class-create.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/**
|
||||
* Create handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use function Activitypub\is_activity_public;
|
||||
|
||||
/**
|
||||
* Handle Create requests.
|
||||
*/
|
||||
class Create {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Create` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_inbox_create',
|
||||
array( self::class, 'handle_create' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Create" activities.
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_create( $activity, $user_id ) {
|
||||
// We only process activities that are target to the application user.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! Handler::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Activity is public or not.
|
||||
if ( ! is_activity_public( $activity ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an object is set.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Handler::is_time_passed( $activity['object']['startTime'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier->save( $activity['object'] );
|
||||
}
|
||||
}
|
63
includes/activitypub/handler/class-delete.php
Normal file
63
includes/activitypub/handler/class-delete.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/**
|
||||
* Delete handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
/**
|
||||
* Handle Delete requests.
|
||||
*/
|
||||
class Delete {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Delete` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_inbox_delete',
|
||||
array( self::class, 'handle_delete' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Follow" requests.
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_delete( $activity, $user_id ) {
|
||||
// We only process activities that are target to the application user.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! Handler::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an object is set.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Handler::is_time_passed( $activity['object']['startTime'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier->delete( $activity['object'] );
|
||||
}
|
||||
}
|
70
includes/activitypub/handler/class-update.php
Normal file
70
includes/activitypub/handler/class-update.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/**
|
||||
* Update handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use function Activitypub\is_activity_public;
|
||||
|
||||
/**
|
||||
* Handle Update requests.
|
||||
*/
|
||||
class Update {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Update` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_inbox_update',
|
||||
array( self::class, 'handle_update' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Update" activities..
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_update( $activity, $user_id ) {
|
||||
// We only process activities that are target to the application user.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! Handler::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Activity is public or not.
|
||||
if ( ! is_activity_public( $activity ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an object is set.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Handler::is_time_passed( $activity['object']['startTime'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier->save( $activity['object'] );
|
||||
}
|
||||
}
|
251
includes/activitypub/model/class-event-source.php
Normal file
251
includes/activitypub/model/class-event-source.php
Normal file
|
@ -0,0 +1,251 @@
|
|||
<?php
|
||||
/**
|
||||
* Event-Source (=ActivityPub Actor that is followed) model.
|
||||
*
|
||||
* This class holds methods needed for relating an ActivityPub actor
|
||||
* that is followed with the custom post type structure how it is
|
||||
* stored within WordPress.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Model;
|
||||
|
||||
use Activitypub\Activity\Actor;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
use WP_Error;
|
||||
|
||||
use function Activitypub\sanitize_url;
|
||||
|
||||
/**
|
||||
* Event-Source (=ActivityPub Actor that is followed) model.
|
||||
*
|
||||
* This class holds methods needed for relating an ActivityPub actor
|
||||
* that is followed with the custom post type structure how it is
|
||||
* stored within WordPress.
|
||||
*/
|
||||
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).
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private static function get_wp_post_by_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 ? get_post( $post_id ) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ?Event_Source The WordPress post ID if the actor is found, null if not.
|
||||
*/
|
||||
public static function get_by_id( $actor_id ) {
|
||||
$post = self::get_wp_post_by_activitypub_actor_id( $actor_id );
|
||||
|
||||
if ( $post ) {
|
||||
$actor = self::init_from_cpt( $post );
|
||||
}
|
||||
return $actor ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Custom-Post-Type input to an \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source.
|
||||
*
|
||||
* @param \WP_Post $post The post object.
|
||||
* @return \Event_Bridge_For_ActivityPub\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 ) ) );
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the post meta.
|
||||
*/
|
||||
protected function get_post_meta_input() {
|
||||
$meta_input = array();
|
||||
$meta_input['activitypub_inbox'] = sanitize_url( $this->get_shared_inbox() );
|
||||
$meta_input['activitypub_actor_json'] = $this->to_json();
|
||||
|
||||
return $meta_input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shared inbox, with a fallback to the inbox.
|
||||
*
|
||||
* @return string|null The URL to the shared inbox, the inbox or null.
|
||||
*/
|
||||
public function get_shared_inbox() {
|
||||
if ( ! empty( $this->get_endpoints()['sharedInbox'] ) ) {
|
||||
return $this->get_endpoints()['sharedInbox'];
|
||||
} elseif ( ! empty( $this->get_inbox() ) ) {
|
||||
return $this->get_inbox();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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', 'event-bridge-for-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( sanitize_url( $icon['url'] ), $post_id, null, 'id' );
|
||||
}
|
||||
if ( isset( $image ) && ! is_wp_error( $image ) ) {
|
||||
set_post_thumbnail( $post_id, $image );
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
}
|
|
@ -6,13 +6,13 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for Event Organiser.
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Etn\Core\Event\Event_Model;
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for VS Event
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event as Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event as Event_Transformer;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use EM_Event;
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event as Event_Object;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
use GatherPress\Core\Event as GatherPress_Event;
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
use MEC;
|
||||
use MEC\Events\Event as MEC_Event;
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
use WP_Post;
|
||||
|
||||
use function Activitypub\esc_hashtag;
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event as Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event as Event_Transformer;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for VS Event.
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Activitypub\Transformer;
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event as Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event as Event_Transformer;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
|
|
357
includes/activitypub/transmogrifier/class-base.php
Normal file
357
includes/activitypub/transmogrifier/class-base.php
Normal file
|
@ -0,0 +1,357 @@
|
|||
<?php
|
||||
/**
|
||||
* Base class with common functions for transforming an ActivityPub Event object to a WordPress object.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
use function Activitypub\sanitize_url;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Base class with common functions for transforming an ActivityPub Event object to a WordPress object.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class Base {
|
||||
/**
|
||||
* The current GatherPress Event object.
|
||||
*
|
||||
* @var Event
|
||||
*/
|
||||
protected $activitypub_event;
|
||||
|
||||
/**
|
||||
* Internal function to actually save the event.
|
||||
*
|
||||
* @return false|int Post-ID on success, false on failure.
|
||||
*/
|
||||
abstract protected function save_event();
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object as GatherPress Event.
|
||||
*
|
||||
* @param array $activitypub_event The ActivityPub event as associative array.
|
||||
*/
|
||||
public function save( $activitypub_event ) {
|
||||
$activitypub_event = Event::init_from_array( $activitypub_event );
|
||||
|
||||
if ( is_wp_error( $activitypub_event ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activitypub_event = $activitypub_event;
|
||||
|
||||
$post_id = $this->save_event();
|
||||
|
||||
if ( $post_id ) {
|
||||
update_post_meta( $post_id, 'event_bridge_for_activitypub_is_cached', 'yes' );
|
||||
update_post_meta( $post_id, 'activitypub_content_visibility', constant( 'ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL' ) ?? '' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post.
|
||||
*/
|
||||
protected function get_post_id_from_activitypub_id() {
|
||||
global $wpdb;
|
||||
return $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
|
||||
esc_sql( $this->activitypub_event->get_id() )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a time string if it is according to the ActivityPub specification.
|
||||
*
|
||||
* @param string $time_string The time string.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_valid_activitypub_time_string( $time_string ) {
|
||||
// Try to create a DateTime object from the input string.
|
||||
try {
|
||||
$date = new DateTime( $time_string );
|
||||
} catch ( Exception $e ) {
|
||||
// If parsing fails, it's not valid.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the timezone is correctly formatted (e.g., 'Z' or a valid offset).
|
||||
$timezone = $date->getTimezone();
|
||||
$formatted_timezone = $timezone->getName();
|
||||
|
||||
// Return true only if the time string includes 'Z' or a valid timezone offset.
|
||||
$valid = 'Z' === $formatted_timezone || preg_match( '/^[+-]\d{2}:\d{2}$/ ', $formatted_timezone );
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image URL and alt-text of an ActivityPub object.
|
||||
*
|
||||
* @param array $data The ActivityPub object as ann associative array.
|
||||
* @return ?array Array containing the images URL and alt-text.
|
||||
*/
|
||||
private static function extract_image_alt_and_url( $data ) {
|
||||
$image = array(
|
||||
'url' => null,
|
||||
'alt' => null,
|
||||
);
|
||||
|
||||
// Check whether it is already simple.
|
||||
if ( ! $data || is_string( $data ) ) {
|
||||
$image['url'] = $data;
|
||||
return $image;
|
||||
}
|
||||
|
||||
if ( ! isset( $data['type'] ) ) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
if ( ! in_array( $data['type'], array( 'Document', 'Image' ), true ) ) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
if ( isset( $data['url'] ) ) {
|
||||
$image['url'] = $data['url'];
|
||||
} elseif ( isset( $data['id'] ) ) {
|
||||
$image['id'] = $data['id'];
|
||||
}
|
||||
|
||||
if ( isset( $data['name'] ) ) {
|
||||
$image['alt'] = $data['name'];
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the featured image.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_featured_image() {
|
||||
$event = $this->activitypub_event;
|
||||
$image = $event->get_image();
|
||||
if ( $image ) {
|
||||
return self::extract_image_alt_and_url( $image );
|
||||
}
|
||||
$attachment = $event->get_attachment();
|
||||
if ( is_array( $attachment ) && ! empty( $attachment ) ) {
|
||||
$supported_types = array( 'Image', 'Document' );
|
||||
$match = null;
|
||||
|
||||
foreach ( $attachment as $item ) {
|
||||
if ( in_array( $item['type'], $supported_types, true ) ) {
|
||||
$match = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$attachment = $match;
|
||||
}
|
||||
return self::extract_image_alt_and_url( $attachment );
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an image URL return an attachment ID. Image will be side-loaded into the media library if it doesn't exist.
|
||||
*
|
||||
* Forked from https://gist.github.com/kingkool68/a66d2df7835a8869625282faa78b489a.
|
||||
*
|
||||
* @param int $post_id The post ID where the image will be set as featured image.
|
||||
* @param string $url The image URL to maybe sideload.
|
||||
* @uses media_sideload_image
|
||||
* @return string|int|WP_Error
|
||||
*/
|
||||
protected static function maybe_sideload_image( $post_id, $url = '' ) {
|
||||
global $wpdb;
|
||||
|
||||
// Include necessary WordPress file for media handling.
|
||||
if ( ! function_exists( 'media_sideload_image' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
}
|
||||
|
||||
// Check to see if the URL has already been fetched, if so return the attachment ID.
|
||||
$attachment_id = $wpdb->get_var(
|
||||
$wpdb->prepare( "SELECT `post_id` FROM {$wpdb->postmeta} WHERE `meta_key` = '_source_url' AND `meta_value` = %s", sanitize_url( $url ) )
|
||||
);
|
||||
if ( ! empty( $attachment_id ) ) {
|
||||
return $attachment_id;
|
||||
}
|
||||
|
||||
$attachment_id = $wpdb->get_var(
|
||||
$wpdb->prepare( "SELECT `ID` FROM {$wpdb->posts} WHERE guid=%s", $url )
|
||||
);
|
||||
if ( ! empty( $attachment_id ) ) {
|
||||
return $attachment_id;
|
||||
}
|
||||
|
||||
// If the URL doesn't exist, sideload it to the media library.
|
||||
return media_sideload_image( sanitize_url( $url ), $post_id, sanitize_url( $url ), 'id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sideload an image_url set it as featured image and add the alt-text.
|
||||
*
|
||||
* @param int $post_id The post ID where the image will be set as featured image.
|
||||
* @param string $image_url The image URL.
|
||||
* @param string $alt_text The alt-text of the image.
|
||||
* @return int The attachment ID
|
||||
*/
|
||||
protected static function set_featured_image_with_alt( $post_id, $image_url, $alt_text = '' ) {
|
||||
// Maybe sideload the image or get the Attachment ID of an existing one.
|
||||
$image_id = self::maybe_sideload_image( $post_id, $image_url );
|
||||
|
||||
if ( is_wp_error( $image_id ) ) {
|
||||
// Handle the error.
|
||||
return $image_id;
|
||||
}
|
||||
|
||||
// Set the image as the featured image for the post.
|
||||
set_post_thumbnail( $post_id, $image_id );
|
||||
|
||||
// Update the alt text.
|
||||
if ( ! empty( $alt_text ) ) {
|
||||
update_post_meta( $image_id, '_wp_attachment_image_alt', sanitize_text_field( $alt_text ) );
|
||||
}
|
||||
|
||||
return $image_id; // Return the attachment ID for further use if needed.
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a PostalAddress to a string.
|
||||
*
|
||||
* @link https://schema.org/PostalAddress
|
||||
*
|
||||
* @param array $postal_address The PostalAddress as an associative array.
|
||||
* @return string
|
||||
*/
|
||||
private static function postal_address_to_string( $postal_address ) {
|
||||
if ( ! is_array( $postal_address ) || 'PostalAddress' !== $postal_address['type'] ) {
|
||||
_doing_it_wrong(
|
||||
__METHOD__,
|
||||
'The parameter postal_address must be an associate array like schema.org/PostalAddress.',
|
||||
esc_html( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_VERSION )
|
||||
);
|
||||
}
|
||||
|
||||
$address = array();
|
||||
|
||||
$known_attributes = array(
|
||||
'streetAddress',
|
||||
'postalCode',
|
||||
'addressLocality',
|
||||
'addressState',
|
||||
'addressCountry',
|
||||
);
|
||||
|
||||
foreach ( $known_attributes as $attribute ) {
|
||||
if ( isset( $postal_address[ $attribute ] ) && is_string( $postal_address[ $attribute ] ) ) {
|
||||
$address[] = $postal_address[ $attribute ];
|
||||
}
|
||||
}
|
||||
|
||||
$address_string = implode( ' ,', $address );
|
||||
|
||||
return $address_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an address to a string.
|
||||
*
|
||||
* @param mixed $address The address as an object, string or associative array.
|
||||
* @return string
|
||||
*/
|
||||
protected static function address_to_string( $address ) {
|
||||
if ( is_string( $address ) ) {
|
||||
return $address;
|
||||
}
|
||||
|
||||
if ( is_object( $address ) ) {
|
||||
$address = (array) $address;
|
||||
}
|
||||
|
||||
if ( ! is_array( $address ) || ! isset( $address['type'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( 'PostalAddress' === $address['type'] ) {
|
||||
return self::postal_address_to_string( $address );
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a local event in WordPress that is a cached remote one.
|
||||
*
|
||||
* @param array $activitypub_event The ActivityPub event as associative array.
|
||||
*/
|
||||
public function delete( $activitypub_event ) {
|
||||
$activitypub_event = Event::init_from_array( $activitypub_event );
|
||||
|
||||
if ( is_wp_error( $activitypub_event ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activitypub_event = $activitypub_event;
|
||||
|
||||
$post_id = $this->get_post_id_from_activitypub_id();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return new WP_Error(
|
||||
'event_bridge_for_activitypub_remote_event_not_found',
|
||||
\__( 'Remote event not found in cache', 'event-bridge-for-activitypub' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
|
||||
wp_delete_post( $post_id, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of revisions to keep.
|
||||
*
|
||||
* @return int The number of revisions to keep.
|
||||
*/
|
||||
public static function revisions_to_keep() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the online event link.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
protected function get_online_event_link_from_attachments() {
|
||||
$attachments = $this->activitypub_event->get_attachment();
|
||||
|
||||
if ( ! is_array( $attachments ) || empty( $attachments ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $attachments as $attachment ) {
|
||||
if ( array_key_exists( 'type', $attachment ) && 'Link' === $attachment['type'] && isset( $attachment['href'] ) ) {
|
||||
return $attachment['href'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
184
includes/activitypub/transmogrifier/class-gatherpress.php
Normal file
184
includes/activitypub/transmogrifier/class-gatherpress.php
Normal file
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
/**
|
||||
* ActivityPub Transmogrify for the GatherPress event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to GatherPress Events.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
use DateTime;
|
||||
|
||||
use function Activitypub\sanitize_url;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use GatherPress\Core\Event as GatherPress_Event;
|
||||
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the GatherPress event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to GatherPress Events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class GatherPress extends Base {
|
||||
/**
|
||||
* Add tags to post.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private function add_tags_to_post( $post_id ) {
|
||||
$tags_array = $this->activitypub_event->get_tag();
|
||||
|
||||
// Ensure the input is valid.
|
||||
if ( empty( $tags_array ) || ! is_array( $tags_array ) || ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract and process tag names.
|
||||
$tag_names = array();
|
||||
foreach ( $tags_array as $tag ) {
|
||||
if ( isset( $tag['name'] ) && 'Hashtag' === $tag['type'] ) {
|
||||
$tag_names[] = ltrim( $tag['name'], '#' ); // Remove the '#' from the name.
|
||||
}
|
||||
}
|
||||
|
||||
// Add the tags as terms to the post.
|
||||
if ( ! empty( $tag_names ) ) {
|
||||
wp_set_object_terms( $post_id, $tag_names, 'gatherpress_topic', true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add venue.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private function add_venue( $post_id ) {
|
||||
$location = $this->activitypub_event->get_location();
|
||||
|
||||
if ( ! $location ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['name'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback for Gancio instances.
|
||||
if ( 'online' === $location['name'] ) {
|
||||
$online_event_link = $this->get_online_event_link_from_attachments();
|
||||
if ( ! $online_event_link ) {
|
||||
return;
|
||||
}
|
||||
update_post_meta( $post_id, 'gatherpress_online_event_link', sanitize_url( $online_event_link ) );
|
||||
wp_set_object_terms( $post_id, 'online-event', '_gatherpress_venue', false );
|
||||
return;
|
||||
}
|
||||
|
||||
$venue_instance = \GatherPress\Core\Venue::get_instance();
|
||||
$venue_name = sanitize_title( $location['name'] );
|
||||
$venue_slug = $venue_instance->get_venue_term_slug( $venue_name );
|
||||
$venue_post = $venue_instance->get_venue_post_from_term_slug( $venue_slug );
|
||||
|
||||
if ( ! $venue_post ) {
|
||||
$venue_id = wp_insert_post(
|
||||
array(
|
||||
'post_title' => sanitize_text_field( $location['name'] ),
|
||||
'post_type' => 'gatherpress_venue',
|
||||
'post_status' => 'publish',
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$venue_id = $venue_post->ID;
|
||||
}
|
||||
|
||||
$venue_information = array();
|
||||
|
||||
$address_string = isset( $location['address'] ) ? $this->address_to_string( $location['address'] ) : '';
|
||||
|
||||
$venue_information['fullAddress'] = $address_string;
|
||||
$venue_information['phone_number'] = '';
|
||||
$venue_information['website'] = '';
|
||||
$venue_information['permalink'] = '';
|
||||
|
||||
$venue_json = wp_json_encode( $venue_information );
|
||||
|
||||
update_post_meta( $venue_id, 'gatherpress_venue_information', $venue_json );
|
||||
|
||||
wp_set_object_terms( $post_id, $venue_slug, '_gatherpress_venue', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object as GatherPress Event.
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
protected function save_event() {
|
||||
// Limit this as a safety measure.
|
||||
add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
$post_id = $this->get_post_id_from_activitypub_id();
|
||||
|
||||
$args = array(
|
||||
'post_title' => sanitize_text_field( $this->activitypub_event->get_name() ),
|
||||
'post_type' => 'gatherpress_event',
|
||||
'post_content' => wp_kses_post( $this->activitypub_event->get_content() ) . '<!-- wp:gatherpress/venue /-->',
|
||||
'post_excerpt' => wp_kses_post( $this->activitypub_event->get_summary() ),
|
||||
'post_status' => 'publish',
|
||||
'guid' => sanitize_url( $this->activitypub_event->get_id() ),
|
||||
);
|
||||
|
||||
if ( $post_id ) {
|
||||
// Update existing GatherPress event post.
|
||||
$args['ID'] = $post_id;
|
||||
wp_update_post( $args );
|
||||
} else {
|
||||
// Insert new GatherPress event post.
|
||||
$post_id = wp_insert_post( $args );
|
||||
}
|
||||
|
||||
if ( ! $post_id || is_wp_error( $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert the dates.
|
||||
$event = new GatherPress_Event( $post_id );
|
||||
$start_time = $this->activitypub_event->get_start_time();
|
||||
$end_time = $this->activitypub_event->get_end_time();
|
||||
if ( ! $end_time ) {
|
||||
$end_time = new DateTime( $start_time );
|
||||
$end_time->modify( '+1 hour' );
|
||||
$end_time = $end_time->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
$params = array(
|
||||
'datetime_start' => $start_time,
|
||||
'datetime_end' => $end_time,
|
||||
'timezone' => $this->activitypub_event->get_timezone(),
|
||||
);
|
||||
// Sanitization of the params is done in the save_datetimes function just in time.
|
||||
$event->save_datetimes( $params );
|
||||
|
||||
// Insert featured image.
|
||||
$image = $this->get_featured_image();
|
||||
self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
|
||||
|
||||
// Add hashtags.
|
||||
$this->add_tags_to_post( $post_id );
|
||||
|
||||
$this->add_venue( $post_id );
|
||||
|
||||
// Limit this as a safety measure.
|
||||
remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/**
|
||||
* Extending the Tribe Events API to allow setting of the guid.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
use DateTime;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Extending the Tribe Events API to allow setting of the guid.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar_Event_Repository extends \Tribe__Events__Repositories__Event {
|
||||
/**
|
||||
* Override diff: allow setting of guid.
|
||||
*
|
||||
* @var array An array of keys that cannot be updated on this repository.
|
||||
*/
|
||||
protected static $blocked_keys = array(
|
||||
'ID',
|
||||
'post_type',
|
||||
'post_modified',
|
||||
'post_modified_gmt',
|
||||
'comment_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the current key can be updated by this repository or not.
|
||||
*
|
||||
* @since 4.7.19
|
||||
*
|
||||
* @param string $key The key.
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_be_updated( $key ) {
|
||||
return ! in_array( $key, self::$blocked_keys, true );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
/**
|
||||
* ActivityPub Transmogrify for the The Events Calendar event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to The Events Calendar Events.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
use DateTime;
|
||||
|
||||
use function Activitypub\sanitize_url;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the GatherPress event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to GatherPress Events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar extends Base {
|
||||
/**
|
||||
* Get a list of Post IDs of events that have ended.
|
||||
*
|
||||
* @param int $cache_retention_period Additional time buffer in seconds.
|
||||
* @return ?array
|
||||
*/
|
||||
public static function get_past_events( $cache_retention_period = 0 ): ?array {
|
||||
unset( $cache_retention_period );
|
||||
|
||||
$results = array();
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object as GatherPress Event.
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
public function save_event() {
|
||||
// Limit this as a safety measure.
|
||||
add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
$post_id = $this->get_post_id_from_activitypub_id();
|
||||
|
||||
$duration = $this->get_duration();
|
||||
|
||||
$args = array(
|
||||
'title' => sanitize_text_field( $this->activitypub_event->get_name() ),
|
||||
'content' => wp_kses_post( $this->activitypub_event->get_content() ),
|
||||
'start_date' => gmdate( 'Y-m-d H:i:s', strtotime( $this->activitypub_event->get_start_time() ) ),
|
||||
'duration' => $duration,
|
||||
'status' => 'publish',
|
||||
'guid' => sanitize_url( $this->activitypub_event->get_id() ),
|
||||
);
|
||||
|
||||
$tribe_event = new The_Events_Calendar_Event_Repository();
|
||||
|
||||
if ( $post_id ) {
|
||||
$args['post_title'] = $args['title'];
|
||||
$args['post_content'] = $args['content'];
|
||||
// Update existing GatherPress event post.
|
||||
$post = \Tribe__Events__API::updateEvent( $post_id, $args );
|
||||
} else {
|
||||
$post = $tribe_event->set_args( $args )->create();
|
||||
}
|
||||
|
||||
if ( ! $post ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Limit this as a safety measure.
|
||||
remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
return $post->ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the events duration in seconds.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function get_duration() {
|
||||
$end_time = $this->activitypub_event->get_end_time();
|
||||
if ( ! $end_time ) {
|
||||
return 2 * HOUR_IN_SECONDS;
|
||||
}
|
||||
return abs( strtotime( $end_time ) - strtotime( $this->activitypub_event->get_start_time() ) );
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
/**
|
||||
* Health_Check class.
|
||||
*
|
||||
* @package Activitypub_Event_Bridge
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Admin;
|
||||
|
@ -97,7 +97,7 @@ class Health_Check {
|
|||
// Call the transformer Factory.
|
||||
$transformer = Transformer_Factory::get_transformer( $event_posts[0] );
|
||||
// Check that we got the right transformer.
|
||||
$desired_transformer_class = $event_plugin::get_activitypub_event_transformer_class();
|
||||
$desired_transformer_class = $event_plugin::get_activitypub_event_transformer( $event_posts[0] );
|
||||
if ( $transformer instanceof $desired_transformer_class ) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,13 @@ 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\Integrations\Event_Plugin_Integration;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Feature_Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
/**
|
||||
|
@ -35,6 +41,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 +54,63 @@ 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;
|
||||
} elseif ( 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).
|
||||
|
@ -98,27 +165,39 @@ class Settings_Page {
|
|||
}
|
||||
|
||||
// Fallback to always re-scan active event plugins, when user visits admin area of this plugin.
|
||||
Setup::get_instance()->redetect_active_event_plugins();
|
||||
$plugin_setup = Setup::get_instance();
|
||||
$plugin_setup->redetect_active_event_plugins();
|
||||
$event_plugins = $plugin_setup->get_active_event_plugins();
|
||||
|
||||
switch ( $tab ) {
|
||||
case 'settings':
|
||||
$plugin_setup = Setup::get_instance();
|
||||
|
||||
$event_plugins = $plugin_setup->get_active_event_plugins();
|
||||
|
||||
$event_terms = array();
|
||||
|
||||
foreach ( $event_plugins as $event_plugin ) {
|
||||
$event_terms = array_merge( $event_terms, self::get_event_terms( $event_plugin ) );
|
||||
foreach ( $event_plugins as $event_plugin_integration ) {
|
||||
$event_terms = array_merge( $event_terms, self::get_event_terms( $event_plugin_integration ) );
|
||||
}
|
||||
|
||||
$supports_event_sources = array();
|
||||
|
||||
foreach ( $event_plugins as $event_plugin_integration ) {
|
||||
if ( $event_plugin_integration instanceof Feature_Event_Sources && $event_plugin_integration instanceof Event_Plugin_Integration ) {
|
||||
$supports_event_sources[ $event_plugin_integration::class ] = $event_plugin_integration::get_plugin_name();
|
||||
}
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'slug' => self::SETTINGS_SLUG,
|
||||
'event_terms' => $event_terms,
|
||||
'slug' => self::SETTINGS_SLUG,
|
||||
'event_terms' => $event_terms,
|
||||
'supports_event_sources' => $supports_event_sources,
|
||||
);
|
||||
|
||||
\load_template( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . 'templates/settings.php', true, $args );
|
||||
break;
|
||||
case 'event-sources':
|
||||
wp_enqueue_script( 'thickbox' );
|
||||
wp_enqueue_style( 'thickbox' );
|
||||
\load_template( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . 'templates/event-sources.php', true );
|
||||
break;
|
||||
case 'welcome':
|
||||
default:
|
||||
wp_enqueue_script( 'plugin-install' );
|
||||
|
|
90
includes/admin/class-user-interface.php
Normal file
90
includes/admin/class-user-interface.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
/**
|
||||
* Class responsible for User Interface additions in the Admin UI.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Admin;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Class responsible for Event Plugin related admin notices.
|
||||
*
|
||||
* Notices for guiding to proper configuration of ActivityPub with event plugins.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class User_Interface {
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'page_row_actions', array( self::class, 'row_actions' ), 10, 2 );
|
||||
\add_filter( 'post_row_actions', array( self::class, 'row_actions' ), 10, 2 );
|
||||
\add_filter( 'map_meta_cap', array( self::class, 'disable_editing_for_external_events' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an column that shows the origin of an external event.
|
||||
*
|
||||
* @param array $columns The current columns.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_origin_column( $columns ) {
|
||||
// Add a new column after the title column.
|
||||
$columns['activitypub_origin'] = __( 'ActivityPub origin', 'event-bridge-for-activitypub' );
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "⁂ Preview" link to the row actions.
|
||||
*
|
||||
* @param array $actions The existing actions.
|
||||
* @param \WP_Post $post The post object.
|
||||
*
|
||||
* @return array The modified actions.
|
||||
*/
|
||||
public static function row_actions( $actions, $post ) {
|
||||
// check if the post is enabled for ActivityPub.
|
||||
if ( ! Event_Sources::is_cached_external_event_post( $post ) ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$actions['view_origin'] = sprintf(
|
||||
'<a href="%s" target="_blank">%s</a>',
|
||||
\esc_url( $post->guid ),
|
||||
\esc_html__( 'Open original page', 'event-bridge-for-activitypub' )
|
||||
);
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the user capabilities so that nobody can edit external events.
|
||||
*
|
||||
* @param array $caps Concerned user's capabilities.
|
||||
* @param array $cap Required primitive capabilities for the requested capability.
|
||||
* @param array $user_id The WordPress user ID.
|
||||
* @param array $args Additional args.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function disable_editing_for_external_events( $caps, $cap, $user_id, $args ) {
|
||||
if ( 'edit_post' === $cap && isset( $args[0] ) ) {
|
||||
$post_id = $args[0];
|
||||
$post = get_post( $post_id );
|
||||
if ( $post && Event_Sources::is_cached_external_event_post( $post ) ) {
|
||||
// Deny editing by returning 'do_not_allow'.
|
||||
return array( 'do_not_allow' );
|
||||
}
|
||||
}
|
||||
return $caps;
|
||||
}
|
||||
}
|
243
includes/class-event-sources.php
Normal file
243
includes/class-event-sources.php
Normal file
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
/**
|
||||
* Class for handling and saving the ActivityPub event sources (i.e. follows).
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
use Activitypub\Model\Blog;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
use Event_Bridge_For_ActivityPub\Admin\User_Interface;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin_Integration;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Feature_Event_Sources;
|
||||
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
use function Activitypub\is_activitypub_request;
|
||||
|
||||
/**
|
||||
* Class for handling and saving the ActivityPub event sources (i.e. follows).
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
class Event_Sources {
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public static function init() {
|
||||
// Register the Event Sources Collection which takes care of managing the event sources.
|
||||
\add_action( 'init', array( Event_Sources_Collection::class, 'init' ) );
|
||||
|
||||
// Register handlers for incoming activities to the ActivityPub plugin, e.g. incoming `Event` objects.
|
||||
\add_action( 'activitypub_register_handlers', array( Handler::class, 'register_handlers' ) );
|
||||
|
||||
// Apply modifications to the UI, e.g. disable editing of remote event posts.
|
||||
\add_action( 'init', array( User_Interface::class, 'init' ) );
|
||||
|
||||
// Register post meta to the event plugins post types needed for easier handling of this feature.
|
||||
\add_action( 'init', array( self::class, 'register_post_meta' ) );
|
||||
|
||||
// Register filters that prevent cached remote events from being federated again.
|
||||
\add_filter( 'activitypub_is_post_disabled', array( self::class, 'is_cached_external_post' ), 10, 2 );
|
||||
\add_filter( 'template_include', array( self::class, 'redirect_activitypub_requests_for_cached_external_events' ), 100 );
|
||||
|
||||
// Register daily schedule to cleanup cached remote events that have ended.
|
||||
if ( ! \wp_next_scheduled( 'event_bridge_for_activitypub_event_sources_clear_cache' ) ) {
|
||||
\wp_schedule_event( time(), 'daily', 'event_bridge_for_activitypub_event_sources_clear_cache' );
|
||||
}
|
||||
\add_action( 'event_bridge_for_activitypub_event_sources_clear_cache', array( self::class, 'clear_cache' ) );
|
||||
|
||||
// Add the actors followed by the event sources feature to the `follow` collection of the used ActivityPub actor.
|
||||
\add_filter( 'activitypub_rest_following', array( self::class, 'add_event_sources_to_following_collection' ), 10, 2 );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register post meta.
|
||||
*/
|
||||
public static function register_post_meta() {
|
||||
$setup = Setup::get_instance();
|
||||
|
||||
foreach ( $setup->get_active_event_plugins() as $event_plugin_integration ) {
|
||||
if ( ! $event_plugin_integration instanceof Feature_Event_Sources && $event_plugin_integration instanceof Event_Plugin_Integration ) {
|
||||
continue;
|
||||
}
|
||||
\register_post_meta(
|
||||
$event_plugin_integration::get_post_type(),
|
||||
'event_bridge_for_activitypub_is_cached',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => false,
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
return esc_sql( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter that cached external posts are not scheduled via the ActivityPub plugin.
|
||||
*
|
||||
* Posts that are actually just external events are treated as cache. They are displayed in
|
||||
* the frontend HTML view and redirected via ActivityPub request, but we do not own them.
|
||||
*
|
||||
* @param bool $disabled If it is disabled already by others (the upstream ActivityPub plugin).
|
||||
* @param WP_Post $post The WordPress post object.
|
||||
* @return bool True if the post can be federated via ActivityPub.
|
||||
*/
|
||||
public static function is_cached_external_post( $disabled, $post = null ): bool {
|
||||
if ( $disabled || ! $post ) {
|
||||
return $disabled;
|
||||
}
|
||||
return ! self::is_cached_external_event_post( $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a WP post is a cached external event.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_cached_external_event_post( $post ): bool {
|
||||
if ( get_post_meta( $post->ID, 'event_bridge_for_activitypub_is_cached', true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the ActivityPub template for EventPrime.
|
||||
*
|
||||
* @param string $template The path to the template object.
|
||||
* @return string The new path to the JSON template.
|
||||
*/
|
||||
public static function redirect_activitypub_requests_for_cached_external_events( $template ) {
|
||||
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
if ( ! is_activitypub_request() ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
if ( ! \is_singular() ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
global $post;
|
||||
|
||||
if ( self::is_cached_external_event_post( $post ) ) {
|
||||
\wp_safe_redirect( $post->guid, 301 );
|
||||
exit;
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete old cached events that took place in the past.
|
||||
*/
|
||||
public static function clear_cache() {
|
||||
// Get the event plugin integration that is used.
|
||||
$event_plugin_integration = Setup::get_event_plugin_integration_used_for_event_sources_feature();
|
||||
|
||||
if ( ! $event_plugin_integration ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache_retention_period = get_option( 'event_bridge_for_activitypub_event_source_cache_retention', WEEK_IN_SECONDS );
|
||||
|
||||
$ended_before_time = gmdate( 'Y-m-d H:i:s', time() - $cache_retention_period );
|
||||
|
||||
$past_event_ids = $event_plugin_integration::get_cached_remote_events( $ended_before_time );
|
||||
|
||||
foreach ( $past_event_ids as $post_id ) {
|
||||
if ( has_post_thumbnail( $post_id ) ) {
|
||||
$attachment_id = get_post_thumbnail_id( $post_id );
|
||||
wp_delete_attachment( $attachment_id, true );
|
||||
}
|
||||
wp_delete_post( $post_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Blog Authors to the following list of the Blog Actor
|
||||
* if Blog not in single mode.
|
||||
*
|
||||
* @param array $follow_list The array of following urls.
|
||||
* @param \Activitypub\Model\User $user The user object.
|
||||
*
|
||||
* @return array The array of following urls.
|
||||
*/
|
||||
public static function add_event_sources_to_following_collection( $follow_list, $user ) {
|
||||
if ( ! $user instanceof Blog ) {
|
||||
return $follow_list;
|
||||
}
|
||||
|
||||
$event_sources = Event_Sources_Collection::get_event_sources_ids();
|
||||
|
||||
if ( ! is_array( $event_sources ) ) {
|
||||
return $follow_list;
|
||||
}
|
||||
|
||||
return array_merge( $follow_list, $event_sources );
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ namespace Event_Bridge_For_ActivityPub;
|
|||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Feature_Event_Sources;
|
||||
|
||||
/**
|
||||
* Class responsible for the ActivityPui Event Extension related Settings.
|
||||
|
@ -93,6 +94,76 @@ class Settings {
|
|||
'default' => EVENT_BRIDGE_FOR_ACTIVITYPUB_CUSTOM_SUMMARY,
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_event_sources_active',
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'show_in_rest' => true,
|
||||
'description' => \__( 'Whether the event sources feature is activated.', 'event-bridge-for-activitypub' ),
|
||||
'default' => 0,
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_event_source_cache_retention',
|
||||
array(
|
||||
'type' => 'int',
|
||||
'show_in_rest' => true,
|
||||
'description' => \__( 'The cache retention period for external event sources.', 'event-bridge-for-activitypub' ),
|
||||
'default' => WEEK_IN_SECONDS,
|
||||
'sanitize_callback' => 'absint',
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
array(
|
||||
'type' => 'array',
|
||||
'description' => \__( 'Define which plugin/integration is used for the event sources feature', 'event-bridge-for-activitypub' ),
|
||||
'default' => array(),
|
||||
'sanitize_callback' => array( self::class, 'sanitize_event_plugin_integration_used_for_event_sources' ),
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub-event-sources',
|
||||
'event_bridge_for_activitypub_event_sources',
|
||||
array(
|
||||
'type' => 'array',
|
||||
'description' => \__( 'Dummy setting', 'event-bridge-for-activitypub' ),
|
||||
'default' => array(),
|
||||
'sanitize_callback' => 'is_array',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the option which event plugin.
|
||||
*
|
||||
* @param string $event_plugin_integration The setting.
|
||||
* @return string
|
||||
*/
|
||||
public static function sanitize_event_plugin_integration_used_for_event_sources( $event_plugin_integration ) {
|
||||
if ( ! is_string( $event_plugin_integration ) ) {
|
||||
return '';
|
||||
}
|
||||
$setup = Setup::get_instance();
|
||||
$active_event_plugins = $setup->get_active_event_plugins();
|
||||
|
||||
$valid_options = array();
|
||||
foreach ( $active_event_plugins as $active_event_plugin ) {
|
||||
if ( $active_event_plugin instanceof Feature_Event_Sources ) {
|
||||
$valid_options[] = $active_event_plugin::class;
|
||||
}
|
||||
}
|
||||
if ( in_array( $event_plugin_integration, $valid_options, true ) ) {
|
||||
return $event_plugin_integration;
|
||||
}
|
||||
return Setup::get_default_integration_class_name_used_for_event_sources_feature();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,17 +15,21 @@ namespace Event_Bridge_For_ActivityPub;
|
|||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Base as Transmogrifier;
|
||||
use Event_Bridge_For_ActivityPub\Admin\Event_Plugin_Admin_Notices;
|
||||
use Event_Bridge_For_ActivityPub\Admin\General_Admin_Notices;
|
||||
use Event_Bridge_For_ActivityPub\Admin\Health_Check;
|
||||
use Event_Bridge_For_ActivityPub\Admin\Settings_Page;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Feature_Event_Sources;
|
||||
|
||||
use function Activitypub\is_user_type_disabled;
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
/**
|
||||
* Class Setup.
|
||||
*
|
||||
|
||||
* This class is responsible for initializing Event Bridge for ActivityPub.
|
||||
*
|
||||
* @since 1.0.0
|
||||
|
@ -60,14 +64,6 @@ class Setup {
|
|||
* @since 1.0.0
|
||||
*/
|
||||
protected function __construct() {
|
||||
$this->activitypub_plugin_is_active = defined( 'ACTIVITYPUB_PLUGIN_VERSION' ) ||
|
||||
is_plugin_active( 'activitypub/activitypub.php' );
|
||||
// BeforeFirstRelease: decide whether we want to do anything at all when ActivityPub plugin is note active.
|
||||
// if ( ! $this->activitypub_plugin_is_active ) {
|
||||
// deactivate_plugins( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE );
|
||||
// return;
|
||||
// }.
|
||||
$this->activitypub_plugin_version = self::get_activitypub_plugin_version();
|
||||
add_action( 'plugins_loaded', array( $this, 'setup_hooks' ) );
|
||||
}
|
||||
|
||||
|
@ -111,7 +107,7 @@ class Setup {
|
|||
/**
|
||||
* Getter function for the active event plugins.
|
||||
*
|
||||
* @return Event_Plugin[]
|
||||
* @return \Event_Bridge_For_ActivityPub\Integrations\Event_Plugin_Integration[]
|
||||
*/
|
||||
public function get_active_event_plugins() {
|
||||
return $this->active_event_plugins;
|
||||
|
@ -120,18 +116,18 @@ class Setup {
|
|||
/**
|
||||
* Holds all the classes for the supported event plugins.
|
||||
*
|
||||
* @var array
|
||||
* @var \Event_Bridge_For_ActivityPub\Integrations\Event_Plugin_Integration[]
|
||||
*/
|
||||
private const EVENT_PLUGIN_CLASSES = array(
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\Events_Manager',
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\GatherPress',
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\The_Events_Calendar',
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\VS_Event_List',
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\WP_Event_Manager',
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\Eventin',
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\Modern_Events_Calendar_Lite',
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\EventPrime',
|
||||
'\Event_Bridge_For_ActivityPub\Integrations\Event_Organiser',
|
||||
private const EVENT_PLUGIN_INTEGRATIONS = array(
|
||||
\Event_Bridge_For_ActivityPub\Integrations\Events_Manager::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\The_Events_Calendar::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\WP_Event_Manager::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\Eventin::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\Modern_Events_Calendar_Lite::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\EventPrime::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\Event_Organiser::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -164,13 +160,13 @@ class Setup {
|
|||
$all_plugins = array_merge( get_plugins(), get_mu_plugins() );
|
||||
|
||||
$active_event_plugins = array();
|
||||
foreach ( self::EVENT_PLUGIN_CLASSES as $event_plugin_class ) {
|
||||
$event_plugin_file = call_user_func( array( $event_plugin_class, 'get_relative_plugin_file' ) );
|
||||
if ( ! $event_plugin_file ) {
|
||||
continue;
|
||||
}
|
||||
foreach ( self::EVENT_PLUGIN_INTEGRATIONS as $event_plugin_integration ) {
|
||||
// Get the filename of the main plugin file of the event plugin (relative to the plugin dir).
|
||||
$event_plugin_file = $event_plugin_integration::get_relative_plugin_file();
|
||||
|
||||
// Check if plugin is present on disk and is activated.
|
||||
if ( array_key_exists( $event_plugin_file, $all_plugins ) && \is_plugin_active( $event_plugin_file ) ) {
|
||||
$active_event_plugins[ $event_plugin_file ] = new $event_plugin_class();
|
||||
$active_event_plugins[ $event_plugin_file ] = new $event_plugin_integration();
|
||||
}
|
||||
}
|
||||
set_transient( 'event_bridge_for_activitypub_active_event_plugins', $active_event_plugins );
|
||||
|
@ -179,7 +175,23 @@ class Setup {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set up hooks for various purposes.
|
||||
* Function that checks which event plugins support the event sources feature.
|
||||
*
|
||||
* @return array List of supported event plugins as keys from the SUPPORTED_EVENT_PLUGINS const.
|
||||
*/
|
||||
public static function detect_event_plugins_supporting_event_sources(): array {
|
||||
$plugins_supporting_event_sources = array();
|
||||
|
||||
foreach ( self::EVENT_PLUGIN_INTEGRATIONS as $event_plugin_integration ) {
|
||||
if ( $event_plugin_integration instanceof Feature_Event_Sources ) {
|
||||
$plugins_supporting_event_sources[] = new $event_plugin_integration();
|
||||
}
|
||||
}
|
||||
return $plugins_supporting_event_sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main setup function of the plugin "Event Bridge For ActivityPub".
|
||||
*
|
||||
* This method adds hooks for different purposes as needed.
|
||||
*
|
||||
|
@ -188,16 +200,30 @@ class Setup {
|
|||
* @return void
|
||||
*/
|
||||
public function setup_hooks(): void {
|
||||
// Detect the presence of the ActivityPub plugin.
|
||||
$this->activitypub_plugin_is_active = defined( 'ACTIVITYPUB_PLUGIN_VERSION' ) || \is_plugin_active( 'activitypub/activitypub.php' );
|
||||
$this->activitypub_plugin_version = self::get_activitypub_plugin_version();
|
||||
|
||||
// Detect active supported event plugins.
|
||||
$this->detect_active_event_plugins();
|
||||
|
||||
// Register hook that runs when this plugin gets activated.
|
||||
register_activation_hook( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE, array( $this, 'activate' ) );
|
||||
|
||||
// Register listeners whenever any plugin gets activated or deactivated.
|
||||
add_action( 'activated_plugin', array( $this, 'redetect_active_event_plugins' ) );
|
||||
add_action( 'deactivated_plugin', array( $this, 'redetect_active_event_plugins' ) );
|
||||
|
||||
// Add hook that takes care of all notices in the Admin UI.
|
||||
add_action( 'admin_init', array( $this, 'do_admin_notices' ) );
|
||||
|
||||
// Add hook that registers all settings of this plugin to WordPress.
|
||||
add_action( 'admin_init', array( Settings::class, 'register_settings' ) );
|
||||
|
||||
// Add hook that loads CSS and JavaScript files for the Admin UI.
|
||||
add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_styles' ) );
|
||||
|
||||
// Register the settings page(s) of this plugin to the WordPress admin menu.
|
||||
add_action( 'admin_menu', array( Settings_Page::class, 'admin_menu' ) );
|
||||
add_filter(
|
||||
'plugin_action_links_' . EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_BASENAME,
|
||||
|
@ -209,13 +235,20 @@ class Setup {
|
|||
return;
|
||||
}
|
||||
|
||||
// Register health checks and status reports to the WordPress status report site.
|
||||
add_action( 'init', array( Health_Check::class, 'init' ) );
|
||||
|
||||
// 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 not abort.
|
||||
if ( ! version_compare( $this->activitypub_plugin_version, EVENT_BRIDGE_FOR_ACTIVITYPUB_ACTIVITYPUB_PLUGIN_MIN_VERSION ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the Event-Sources feature is enabled and all requirements are met, initialize it.
|
||||
if ( ! is_user_type_disabled( 'blog' ) && get_option( 'event_bridge_for_activitypub_event_sources_active' ) ) {
|
||||
Event_Sources::init();
|
||||
}
|
||||
|
||||
// Lastly but most importantly: register the ActivityPub transformers for events to the ActivityPub plugin.
|
||||
add_filter( 'activitypub_transformer', array( $this, 'register_activitypub_event_transformer' ), 10, 3 );
|
||||
}
|
||||
|
||||
|
@ -290,10 +323,7 @@ class Setup {
|
|||
// Get the transformer for a specific event plugins event-post type.
|
||||
foreach ( $this->active_event_plugins as $event_plugin ) {
|
||||
if ( $wp_object->post_type === $event_plugin->get_post_type() ) {
|
||||
$transformer_class = $event_plugin::get_activitypub_event_transformer_class();
|
||||
if ( class_exists( $transformer_class ) ) {
|
||||
return new $transformer_class( $wp_object, $event_plugin::get_event_category_taxonomy() );
|
||||
}
|
||||
return $event_plugin::get_activitypub_event_transformer( $wp_object, $event_plugin::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,4 +384,67 @@ class Setup {
|
|||
|
||||
self::activate_activitypub_support_for_active_event_plugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event plugin integration class name used for the event sources feature.
|
||||
*
|
||||
* @return string The class name of the event plugin integration class.
|
||||
*/
|
||||
public static function get_event_plugin_integration_used_for_event_sources_feature() {
|
||||
// Get plugin option.
|
||||
$event_plugin_integration = get_option( 'event_bridge_for_activitypub_integration_used_for_event_sources_feature', '' );
|
||||
|
||||
// Exit if event sources are not active or no plugin is specified.
|
||||
if ( empty( $event_plugin_integration ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate if setting is actual existing class.
|
||||
if ( ! class_exists( $event_plugin_integration ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $event_plugin_integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transmogrifier class.
|
||||
*
|
||||
* Retrieves the appropriate transmogrifier class based on the active event plugins and settings.
|
||||
*
|
||||
* @return ?Transmogrifier The transmogrifier class name or null if not available.
|
||||
*/
|
||||
public static function get_transmogrifier(): ?Transmogrifier {
|
||||
$event_plugin_integration = self::get_event_plugin_integration_used_for_event_sources_feature();
|
||||
|
||||
if ( ! $event_plugin_integration ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate if get_transformer method exists in event plugin integration.
|
||||
if ( ! method_exists( $event_plugin_integration, 'get_transmogrifier' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$transmogrifier = $event_plugin_integration::get_transmogrifier();
|
||||
|
||||
return $transmogrifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full class name of the first event plugin integration that is active and supports the event source feature.
|
||||
*
|
||||
* @return string The full class name of the event plugin integration.
|
||||
*/
|
||||
public static function get_default_integration_class_name_used_for_event_sources_feature(): string {
|
||||
$setup = self::get_instance();
|
||||
|
||||
$event_plugin_integrations = $setup->get_active_event_plugins();
|
||||
foreach ( $event_plugin_integrations as $event_plugin_integration ) {
|
||||
if ( $event_plugin_integration instanceof Feature_Event_Sources ) {
|
||||
get_class( $event_plugin_integration );
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
/**
|
||||
* Event Organiser.
|
||||
*
|
||||
* Defines all the necessary meta information for the Event Organiser plugin.
|
||||
* Defines all the necessary meta information and methods for the integration
|
||||
* of the WordPress "Event Organiser" plugin.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/event-organiser/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
|
@ -11,17 +12,20 @@
|
|||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event_Organiser as Event_Organiser_Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
* Event Organiser.
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
* Defines all the necessary meta information and methods for the integration
|
||||
* of the WordPress "Event Organiser" plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Event_Organiser extends Event_Plugin {
|
||||
final class Event_Organiser extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
|
@ -49,15 +53,6 @@ final class Event_Organiser extends Event_Plugin {
|
|||
return array( 'event-organiser' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_activitypub_transformer_class_name(): string {
|
||||
return 'Event_Organiser';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
|
@ -66,4 +61,14 @@ final class Event_Organiser extends Event_Plugin {
|
|||
public static function get_event_category_taxonomy(): string {
|
||||
return 'event-category';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Event_Organiser event post.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return Event_Organiser_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): Event_Organiser_Transformer {
|
||||
return new Event_Organiser_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,31 @@
|
|||
<?php
|
||||
/**
|
||||
* Interface for defining supported Event Plugins.
|
||||
* Abstract base class for a basic integration of a WordPress event plugin.
|
||||
*
|
||||
* Basic information that each supported event needs for this plugin to work.
|
||||
* Basic information and methods that each supported event needs for this plugin to work.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event as Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event as ActivityPub_Event_Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
require_once EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . 'includes/integrations/interface-feature-event-sources.php';
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
* Abstract base class for a basic integration of a WordPress event plugin.
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
* Basic information and methods that each supported event needs for this plugin to work.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class Event_Plugin {
|
||||
abstract class Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the plugin file relative to the plugins dir.
|
||||
*
|
||||
|
@ -44,6 +47,14 @@ abstract class Event_Plugin {
|
|||
*/
|
||||
abstract public static function get_event_category_taxonomy(): string;
|
||||
|
||||
/**
|
||||
* Returns the Activitypub transformer for the event plugins event post type.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return ActivityPub_Event_Transformer
|
||||
*/
|
||||
abstract public static function get_activitypub_event_transformer( $post ): ActivityPub_Event_Transformer;
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
|
@ -56,7 +67,7 @@ abstract class Event_Plugin {
|
|||
/**
|
||||
* Get the plugins name from the main plugin-file's top-level-file-comment.
|
||||
*/
|
||||
final public static function get_plugin_name(): string {
|
||||
public static function get_plugin_name(): string {
|
||||
$all_plugins = array_merge( get_plugins(), get_mu_plugins() );
|
||||
if ( isset( $all_plugins[ static::get_relative_plugin_file() ]['Name'] ) ) {
|
||||
return $all_plugins[ static::get_relative_plugin_file() ]['Name'];
|
||||
|
@ -78,11 +89,4 @@ abstract class Event_Plugin {
|
|||
|
||||
return $is_event_plugins_edit_page || $is_event_plugins_settings_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Activitypub transformer for the event plugins event post type.
|
||||
*/
|
||||
public static function get_activitypub_event_transformer_class(): string {
|
||||
return str_replace( 'Integrations', 'Activitypub\Transformer', static::class );
|
||||
}
|
||||
}
|
|
@ -1,27 +1,31 @@
|
|||
<?php
|
||||
/**
|
||||
* The Events Calendar.
|
||||
* Eventin.
|
||||
*
|
||||
* Defines all the necessary meta information for the events calendar.
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "Eventin".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/the-events-calendar/
|
||||
* @link https://wordpress.org/plugins/eventin/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Eventin as Eventin_Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
* Eventin.
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "Eventin".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Eventin extends Event_plugin {
|
||||
final class Eventin extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
|
@ -57,4 +61,14 @@ final class Eventin extends Event_plugin {
|
|||
public static function get_event_category_taxonomy(): string {
|
||||
return 'etn_category';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Eventin event post.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return Eventin_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): Eventin_Transformer {
|
||||
return new Eventin_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
/**
|
||||
* EventPrime – Events Calendar, Bookings and Tickets
|
||||
* EventPrime – Events Calendar, Bookings and Tickets.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "EventPrime – Events Calendar, Bookings and Tickets".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/eventprime-event-calendar-management/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
|
@ -9,6 +12,8 @@
|
|||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\EventPrime as EventPrime_Transformer;
|
||||
|
||||
use Activitypub\Signature;
|
||||
use Eventprime_Basic_Functions;
|
||||
|
||||
|
@ -16,11 +21,14 @@ use Eventprime_Basic_Functions;
|
|||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* This class defines which information is necessary for the EventPrime event plugin.
|
||||
* EventPrime – Events Calendar, Bookings and Tickets.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "EventPrime – Events Calendar, Bookings and Tickets".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class EventPrime extends Event_Plugin {
|
||||
final class EventPrime extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Add filter for the template inclusion.
|
||||
*/
|
||||
|
@ -56,12 +64,13 @@ final class EventPrime extends Event_Plugin {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer class.
|
||||
* Returns the ActivityPub transformer for a EventPrime event post.
|
||||
*
|
||||
* @return string
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return EventPrime_Transformer
|
||||
*/
|
||||
public static function get_activitypub_transformer_class_name(): string {
|
||||
return 'EventPrime';
|
||||
public static function get_activitypub_event_transformer( $post ): EventPrime_Transformer {
|
||||
return new EventPrime_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
/**
|
||||
* Events Manager.
|
||||
*
|
||||
* Defines all the necessary meta information for the Events Manager WordPress Plugin.
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "Events Manager".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/events-manager/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
|
@ -11,17 +12,20 @@
|
|||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Events_Manager as Events_Manager_Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
* Events Manager.
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "Events Manager".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Events_Manager extends Event_Plugin {
|
||||
final class Events_Manager extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
|
@ -57,4 +61,14 @@ final class Events_Manager extends Event_Plugin {
|
|||
public static function get_event_category_taxonomy(): string {
|
||||
return defined( 'EM_TAXONOMY_CATEGORY' ) ? constant( 'EM_TAXONOMY_CATEGORY' ) : 'event-categories';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Events_Manager event post.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return Events_Manager_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): Events_Manager_Transformer {
|
||||
return new Events_Manager_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
/**
|
||||
* GatherPress.
|
||||
*
|
||||
* Defines all the necessary meta information for the GatherPress plugin.
|
||||
* Defines all the necessary meta information and methods for the integration
|
||||
* of the WordPress event plugin "GatherPress".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/gatherpress/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
|
@ -11,17 +12,21 @@
|
|||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\GatherPress as GatherPress_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\GatherPress as GatherPress_Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
* GatherPress.
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
* Defines all the necessary meta information and methods for the integration
|
||||
* of the WordPress event plugin "GatherPress".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class GatherPress extends Event_Plugin {
|
||||
final class GatherPress extends Event_Plugin_Integration implements Feature_Event_Sources {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
|
@ -49,15 +54,6 @@ final class GatherPress extends Event_Plugin {
|
|||
return array( class_exists( '\GatherPress\Core\Utility' ) ? \GatherPress\Core\Utility::prefix_key( 'general' ) : 'gatherpress_general' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_activitypub_transformer_class_name(): string {
|
||||
return 'GatherPress';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
|
@ -66,4 +62,69 @@ final class GatherPress extends Event_Plugin {
|
|||
public static function get_event_category_taxonomy(): string {
|
||||
return class_exists( '\GatherPress\Core\Topic' ) ? \GatherPress\Core\Topic::TAXONOMY : 'gatherpress_topic';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a GatherPress event post.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return GatherPress_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): GatherPress_Transformer {
|
||||
return new GatherPress_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Transmogrifier for GatherPress.
|
||||
*/
|
||||
public static function get_transmogrifier(): GatherPress_Transmogrifier {
|
||||
return new GatherPress_Transmogrifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of Post IDs of events that have ended.
|
||||
*
|
||||
* @param int $ended_before_time Filter: only get events that ended before that datetime as unix-time.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_cached_remote_events( $ended_before_time ): array {
|
||||
global $wpdb;
|
||||
|
||||
$ended_before_time_string = gmdate( 'Y-m-d H:i:s', $ended_before_time );
|
||||
|
||||
$results = $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_id FROM {$wpdb->prefix}gatherpress_events WHERE datetime_end < %s",
|
||||
$ended_before_time_string
|
||||
)
|
||||
);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter(
|
||||
'gatherpress_force_online_event_link',
|
||||
function ( $force_online_event_link ) {
|
||||
// Get the current post object.
|
||||
$post = get_post();
|
||||
|
||||
// Check if we are in a valid context and the post type is 'gatherpress'.
|
||||
if ( $post && 'gatherpress_event' === $post->post_type ) {
|
||||
// Add your custom logic here to decide whether to force the link.
|
||||
// For example, force it only if a specific meta field exists.
|
||||
if ( get_post_meta( $post->ID, 'event_bridge_for_activitypub_is_cached', true ) ) {
|
||||
return true; // Force the online event link.
|
||||
}
|
||||
}
|
||||
|
||||
return $force_online_event_link; // Default behavior.
|
||||
},
|
||||
10,
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
/**
|
||||
* Modern Events Calendar (Lite)
|
||||
*
|
||||
* Defines all the necessary meta information for the Modern Events Calendar (Lite).
|
||||
* Defines all the necessary meta information for the integration of the
|
||||
* WordPress plugin "Modern Events Calendar (Lite)".
|
||||
*
|
||||
* @link https://webnus.net/modern-events-calendar/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
|
@ -11,17 +12,20 @@
|
|||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Modern_Events_Calendar_Lite as Modern_Events_Calendar_Lite_Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
* Modern Events Calendar (Lite)
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
* Defines all the necessary meta information for the integration of the
|
||||
* WordPress plugin "Modern Events Calendar (Lite)".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Modern_Events_Calendar_Lite extends Event_plugin {
|
||||
final class Modern_Events_Calendar_Lite extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
|
@ -58,4 +62,14 @@ final class Modern_Events_Calendar_Lite extends Event_plugin {
|
|||
public static function get_event_category_taxonomy(): string {
|
||||
return 'mec_category';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Modern_Events_Calendar_Lite event post.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return Modern_Events_Calendar_Lite_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): Modern_Events_Calendar_Lite_Transformer {
|
||||
return new Modern_Events_Calendar_Lite_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
/**
|
||||
* The Events Calendar.
|
||||
*
|
||||
* Defines all the necessary meta information for the events calendar.
|
||||
* Defines all the necessary meta information for the integration of the
|
||||
* WordPress plugin "The Events Calendar".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/the-events-calendar/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
|
@ -11,17 +12,21 @@
|
|||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\The_Events_Calendar as The_Events_Calendar_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\The_Events_Calendar as The_Events_Calendar_Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
* The Events Calendar.
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
* Defines all the necessary meta information for the integration of the
|
||||
* WordPress plugin "The Events Calendar".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class The_Events_Calendar extends Event_plugin {
|
||||
final class The_Events_Calendar extends Event_plugin_Integration implements Feature_Event_Sources {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
|
@ -40,6 +45,25 @@ final class The_Events_Calendar extends Event_plugin {
|
|||
return class_exists( '\Tribe__Events__Main' ) ? \Tribe__Events__Main::POSTTYPE : 'tribe_event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return class_exists( '\Tribe__Events__Main' ) ? \Tribe__Events__Main::TAXONOMY : 'tribe_events_cat';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a The_Events_Calendar event post.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return The_Events_Calendar_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): The_Events_Calendar_Transformer {
|
||||
return new The_Events_Calendar_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
|
@ -55,11 +79,20 @@ final class The_Events_Calendar extends Event_plugin {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
* Returns the Transmogrifier for The_Events_Calendar.
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return class_exists( '\Tribe__Events__Main' ) ? \Tribe__Events__Main::TAXONOMY : 'tribe_events_cat';
|
||||
public static function get_transmogrifier(): The_Events_Calendar_Transmogrifier {
|
||||
return new The_Events_Calendar_Transmogrifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of Post IDs of events that have ended.
|
||||
*
|
||||
* @param int $ended_before_time Filter: only get events that ended before that datetime as unix-time.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_cached_remote_events( $ended_before_time ): array {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/**
|
||||
* VS Events LIst.
|
||||
*
|
||||
* Defines all the necessary meta information for the WordPress event plugin
|
||||
* Defines all the necessary meta information for the integration of the WordPress event plugin
|
||||
* "Very Simple Events List".
|
||||
*
|
||||
* @link https://de.wordpress.org/plugins/very-simple-event-list/
|
||||
|
@ -12,19 +12,20 @@
|
|||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Event_Plugins;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\VS_Event_List as VS_Event_List_Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
* VS Events LIst.
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
* Defines all the necessary meta information for the integration of the WordPress event plugin
|
||||
* "Very Simple Events List".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class VS_Event_List extends Event_Plugin {
|
||||
final class VS_Event_List extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
|
@ -52,15 +53,6 @@ final class VS_Event_List extends Event_Plugin {
|
|||
return array( 'settings_page_vsel' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_activitypub_transformer_class_name(): string {
|
||||
return 'VS_Event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
|
@ -69,4 +61,14 @@ final class VS_Event_List extends Event_Plugin {
|
|||
public static function get_event_category_taxonomy(): string {
|
||||
return 'event_cat';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a VS_Event_List event post.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return VS_Event_List_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): VS_Event_List_Transformer {
|
||||
return new VS_Event_List_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
/**
|
||||
* WP Event Manager.
|
||||
*
|
||||
* Defines all the necessary meta information for the WordPress event plugin
|
||||
* "WP Event Manager"
|
||||
* Defines all the necessary meta information for the Integration of the
|
||||
* WordPress event plugin "WP Event Manager".
|
||||
*
|
||||
* @link https://de.wordpress.org/plugins/wp-event-manager
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
|
@ -12,7 +12,7 @@
|
|||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\WP_Event_Manager as WP_Event_Manager_Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
@ -24,7 +24,7 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
|||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class WP_Event_Manager extends Event_Plugin {
|
||||
final class WP_Event_Manager extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
|
@ -52,15 +52,6 @@ final class WP_Event_Manager extends Event_Plugin {
|
|||
return array( 'event-manager-settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_activitypub_transformer_class_name(): string {
|
||||
return 'WP_Event_Manager';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
|
@ -69,4 +60,14 @@ final class WP_Event_Manager extends Event_Plugin {
|
|||
public static function get_event_category_taxonomy(): string {
|
||||
return 'event_listing_category';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a WP_Event_Manager event post.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return WP_Event_Manager_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): WP_Event_Manager_Transformer {
|
||||
return new WP_Event_Manager_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
||||
|
|
44
includes/integrations/interface-feature-event-sources.php
Normal file
44
includes/integrations/interface-feature-event-sources.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
/**
|
||||
* Interface for defining Methods needed for the Event Sources feature.
|
||||
*
|
||||
* The Event Sources feature is about following other ActivityPub actors and
|
||||
* importing their events. That means treating them as cache and listing them.
|
||||
* Events should be deleted some time after they have ended.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Base as Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for an event plugin integration that supports the Event Sources feature.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
interface Feature_Event_Sources {
|
||||
/**
|
||||
* Returns the plugin file relative to the plugins dir.
|
||||
*
|
||||
* @return Transmogrifier
|
||||
*/
|
||||
public static function get_transmogrifier(): Transmogrifier;
|
||||
|
||||
/**
|
||||
* Retrieves a list of post IDs for cached remote events that have ended.
|
||||
*
|
||||
* Filters the events to include only those that ended before the specified timestamp.
|
||||
*
|
||||
* @param int $ended_before_time Unix timestamp. Only events ending before this time will be included.
|
||||
*
|
||||
* @return int[] List of post IDs for events that match the criteria.
|
||||
*/
|
||||
public static function get_cached_remote_events( $ended_before_time ): array;
|
||||
}
|
238
includes/table/class-event-sources.php
Normal file
238
includes/table/class-event-sources.php
Normal file
|
@ -0,0 +1,238 @@
|
|||
<?php
|
||||
/**
|
||||
* Event Sources Table-Class file.
|
||||
*
|
||||
* This table display the event sources (=followed ActivityPub actors) that are used for
|
||||
* importing (caching and displaying) remote events to the WordPress site.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Table;
|
||||
|
||||
use WP_List_Table;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
if ( ! \class_exists( '\WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Sources Table-Class.
|
||||
*/
|
||||
class Event_Sources extends WP_List_Table {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
array(
|
||||
'singular' => \__( 'Event Source', 'event-bridge-for-activitypub' ),
|
||||
'plural' => \__( 'Event Sources', 'event-bridge-for-activitypub' ),
|
||||
'ajax' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_columns() {
|
||||
return array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'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' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sortable columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sortable_columns() {
|
||||
return array(
|
||||
'name' => array( 'name', 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
|
||||
|
||||
$event_sources = Event_Sources_Collection::get_event_sources_with_count( $per_page, $page_num, $args );
|
||||
$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() ),
|
||||
'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() ),
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bulk actions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_bulk_actions() {
|
||||
return array(
|
||||
'delete' => __( 'Delete', 'event-bridge-for-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', 'event-bridge-for-activitypub' );
|
||||
}
|
||||
return $item[ $column_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Column avatar.
|
||||
*
|
||||
* @param array $item Item.
|
||||
* @return string
|
||||
*/
|
||||
public function column_icon( $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="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['event_sources'] ) || ! 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( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event_sources = $_REQUEST['event_sources']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
|
||||
if ( 'delete' === $this->current_action() ) {
|
||||
if ( ! is_array( $event_sources ) ) {
|
||||
$event_sources = array( $event_sources );
|
||||
}
|
||||
foreach ( $event_sources as $event_source ) {
|
||||
Event_Sources_Collection::remove_event_source( $event_source );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
* Template for the header and navigation of the admin pages.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
|
@ -12,8 +14,9 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
|||
$args = wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'welcome' => '',
|
||||
'settings' => '',
|
||||
'welcome' => '',
|
||||
'settings' => '',
|
||||
'event-sources' => '',
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
@ -31,6 +34,10 @@ $args = wp_parse_args(
|
|||
<a href="<?php echo \esc_url( admin_url( 'options-general.php?page=event-bridge-for-activitypub&tab=settings' ) ); ?>" class="event-bridge-for-activitypub-settings-tab <?php echo \esc_attr( $args['settings'] ); ?>">
|
||||
<?php \esc_html_e( 'Settings', 'event-bridge-for-activitypub' ); ?>
|
||||
</a>
|
||||
|
||||
<a href="<?php echo \esc_url( admin_url( 'options-general.php?page=event-bridge-for-activitypub&tab=event-sources' ) ); ?>" class="event-bridge-for-activitypub-settings-tab <?php echo \esc_attr( $args['event-sources'] ); ?>">
|
||||
<?php \esc_html_e( 'Event Sources', 'event-bridge-for-activitypub' ); ?>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
<hr class="wp-header-end">
|
||||
|
|
55
templates/event-sources.php
Normal file
55
templates/event-sources.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
/**
|
||||
* Event Sources management page for the ActivityPub Event Bridge.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
\load_template(
|
||||
__DIR__ . '/admin-header.php',
|
||||
true,
|
||||
array(
|
||||
'event-sources' => 'active',
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<div class="wrap event_bridge_for_activitypub-admin-table-container">
|
||||
<!-- Table title with add new button like on post edit pages -->
|
||||
<div class="event_bridge_for_activitypub-admin-table-top">
|
||||
<h2 class="wp-heading-inline"> <?php esc_html_e( 'List of Event Sources', 'event-bridge-for-activitypub' ); ?> </h2>
|
||||
<!-- Button that triggers ThickBox -->
|
||||
<a href="#TB_inline?width=600&height=400&inlineId=Event_Bridge_For_ActivityPub_add_new_source" class="thickbox page-title-action">
|
||||
<?php esc_html_e( 'Add Event Source', 'event-bridge-for-activitypub' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 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 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="">
|
||||
<?php \submit_button( __( 'Add Event Source', 'event-bridge-for-activitypub' ) ); ?>
|
||||
</form>
|
||||
</div>
|
||||
<div class="wrap activitypub-followers-page">
|
||||
<form method="get">
|
||||
<input type="hidden" name="page" value="event-bridge-for-activitypub" />
|
||||
<input type="hidden" name="tab" value="event-sources" />
|
||||
<?php
|
||||
$table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources();
|
||||
$table->prepare_items();
|
||||
$table->search_box( 'Search', 'search' );
|
||||
$table->display();
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* @param array $args An array of arguments for the settings page.
|
||||
*/
|
||||
|
@ -30,6 +31,16 @@ if ( ! isset( $args ) || ! array_key_exists( 'event_terms', $args ) ) {
|
|||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
\get_option( 'event_bridge_for_activitypub_event_sources_active', false );
|
||||
if ( ! isset( $args ) || ! array_key_exists( 'supports_event_sources', $args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event_plugins_supporting_event_sources = $args['supports_event_sources'];
|
||||
|
||||
$selected_plugin = \get_option( 'event_bridge_for_activitypub_integration_used_for_event_sources_feature', '' );
|
||||
$event_sources_active = \get_option( 'event_bridge_for_activitypub_event_sources_active', false );
|
||||
$cache_retention_period = \get_option( 'event_bridge_for_activitypub_event_source_cache_retention', DAY_IN_SECONDS );
|
||||
|
||||
$event_terms = $args['event_terms'];
|
||||
|
||||
|
@ -89,6 +100,122 @@ $current_category_mapping = \get_option( 'event_bridge_for_activitypub_ev
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h2><?php \esc_html_e( 'Event Sources', 'event-bridge-for-activitypub' ); ?></h2>
|
||||
<?php
|
||||
if ( ! \Activitypub\is_user_type_disabled( 'blog' ) && count( $event_plugins_supporting_event_sources ) ) {
|
||||
?>
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="event_bridge_for_activitypub_event_sources_active"><?php \esc_html_e( 'Enable External Event Sources', 'event-bridge-for-activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="event_bridge_for_activitypub_event_sources_active"
|
||||
id="event_bridge_for_activitypub_event_sources_active"
|
||||
aria-describedby="event-sources-description"
|
||||
value="1"
|
||||
<?php echo \checked( $event_sources_active ); ?>
|
||||
>
|
||||
<p id="event-sources-description"><?php esc_html_e( 'Activate this feature to allow your WordPress site to fetch events from external sources via ActivityPub. Once enabled, you can add any ActivityPub account as a source of events. These events will be cached on your site and seamlessly integrated into your existing event calendar, creating a unified view of events from both internal and external sources.', 'event-bridge-for-activitypub' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
if ( $event_sources_active ) {
|
||||
?>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="event_bridge_for_activitypub_integration_used_for_event_sources_feature"><?php \esc_html_e( 'Event Plugin', 'event-bridge-for-activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select
|
||||
name="event_bridge_for_activitypub_integration_used_for_event_sources_feature"
|
||||
id="event_bridge_for_activitypub_integration_used_for_event_sources_feature"
|
||||
value="gatherpress"
|
||||
aria-describedby="event-sources-used-plugin-description"
|
||||
>
|
||||
<?php
|
||||
foreach ( $event_plugins_supporting_event_sources as $event_plugin_class_name => $event_plugin_name ) {
|
||||
echo '<option value="' . esc_attr( $event_plugin_class_name ) . '" ' . selected( $event_plugin_class_name, $event_plugin, true ) . '>' . esc_attr( $event_plugin_name ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<p id="event-sources-used-plugin-description"><?php esc_html_e( 'In case you have multiple event plugins installed you might choose which event plugin is utilized.', 'event-bridge-for-activitypub' ); ?></p>
|
||||
</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="event_bridge_for_activitypub_event_source_cache"><?php \esc_html_e( 'Retention Period for External Events', 'event-bridge-for-activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select
|
||||
name="event_bridge_for_activitypub_event_source_cache_retention"
|
||||
id="event_bridge_for_activitypub_event_source_cache_retention"
|
||||
value="0"
|
||||
aria-describedby="event_bridge_for_activitypub_event-sources-cache-clear-time-frame"
|
||||
>
|
||||
<?php
|
||||
$choices = array(
|
||||
0 => __( 'Immediately', 'event-bridge-for-activitypub' ),
|
||||
DAY_IN_SECONDS => __( 'One Day', 'event-bridge-for-activitypub' ),
|
||||
WEEK_IN_SECONDS => __( 'One Week', 'event-bridge-for-activitypub' ),
|
||||
MONTH_IN_SECONDS => __( 'One Month', 'event-bridge-for-activitypub' ),
|
||||
YEAR_IN_SECONDS => __( 'One Year', 'event-bridge-for-activitypub' ),
|
||||
);
|
||||
foreach ( $choices as $time => $string ) {
|
||||
echo '<option value="' . esc_attr( $time ) . '" ' . selected( $cache_retention_period, $time, true ) . '>' . esc_attr( $string ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<p id="event_bridge_for_activitypub_event-sources-cache-clear-time-frame"><?php esc_html_e( 'External events from your event sources will be automatically removed from your site after the selected time period has passed since the event ended. Choose a time frame that works best for your needs.', 'event-bridge-for-activitypub' ); ?></p>
|
||||
</td>
|
||||
<tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<tbody>
|
||||
</table>
|
||||
<?php
|
||||
} elseif ( ! \Activitypub\is_user_type_disabled( 'blog' ) ) {
|
||||
?>
|
||||
<p><?php esc_html_e( 'You do not have an Event Plugin installed that supports this feature', 'event-bridge-for-activitypub' ); ?></p>
|
||||
<p><?php esc_html_e( 'The following Event Plugins are supported:', 'event-bridge-for-activitypub' ); ?></p>
|
||||
<?php
|
||||
$plugins_supporting_event_sources = \Event_Bridge_For_ActivityPub\Setup::detect_event_plugins_supporting_event_sources();
|
||||
echo '<ul class="event_bridge_for_activitypub-list">';
|
||||
foreach ( $plugins_supporting_event_sources as $event_plugin ) {
|
||||
echo '<li>' . esc_attr( $event_plugin->get_plugin_name() ) . '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
return;
|
||||
} else {
|
||||
$activitypub_plugin_data = get_plugin_data( ACTIVITYPUB_PLUGIN_FILE );
|
||||
|
||||
$notice = sprintf(
|
||||
/* translators: 1: The name of the ActivityPub plugin. */
|
||||
_x(
|
||||
'In order to use this feature your have to enable the Blog-Actor in the the <a href="%1$s">%2$s settings</a>.',
|
||||
'admin notice',
|
||||
'event-bridge-for-activitypub'
|
||||
),
|
||||
admin_url( 'options-general.php?page=activitypub&tab=settings' ),
|
||||
esc_html( $activitypub_plugin_data['Name'] )
|
||||
);
|
||||
|
||||
$allowed_html = array(
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
'title' => true,
|
||||
),
|
||||
);
|
||||
echo '<div class="notice-warning"><p>' . \wp_kses( $notice, $allowed_html ) . '</p></div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h2> <?php esc_html_e( 'ActivityPub Event Category', 'event-bridge-for-activitypub' ); ?> </h2>
|
||||
<p> <?php esc_html_e( 'To help visitors find events more easily, the community created a set of basic event categories. Please select the category that best matches the majority of the events you organize.', 'event-bridge-for-activitypub' ); ?> </p>
|
||||
|
@ -144,7 +271,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(); ?>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* Status page for the Event Bridge for ActivityPub.
|
||||
* Status/Welcome page for the Event Bridge for ActivityPub admin interface.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
|
|
|
@ -96,11 +96,13 @@ function _manually_load_plugin() {
|
|||
|
||||
if ( $plugin_file ) {
|
||||
_manually_load_event_plugin( $plugin_file );
|
||||
} elseif ( 'event_bridge_for_activitypub_event_sources' === $event_bridge_for_activitypub_integration_filter ) {
|
||||
// For the Event Sources feature we currently only test with GatherPress.
|
||||
_manually_load_event_plugin( 'gatherpress/gatherpress.php' );
|
||||
} else {
|
||||
// For all other tests we mainly use the Events Calendar as a reference.
|
||||
_manually_load_event_plugin( 'the-events-calendar/the-events-calendar.php' );
|
||||
_manually_load_event_plugin( 'very-simple-event-list/vsel.php' );
|
||||
|
||||
}
|
||||
|
||||
// Hot fix that allows using Events Manager within unit tests, because the em_init() is later not run as admin.
|
||||
|
|
|
@ -44,7 +44,7 @@ class Test_Activitypub_Event_Bridge_Shortcodes extends WP_UnitTestCase {
|
|||
// Call the transformer Factory.
|
||||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ class Test_Activitypub_Event_Bridge_Shortcodes extends WP_UnitTestCase {
|
|||
// Call the transformer Factory.
|
||||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ class Test_Activitypub_Event_Bridge_Shortcodes extends WP_UnitTestCase {
|
|||
// Call the transformer Factory.
|
||||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ class Test_Activitypub_Event_Bridge_Shortcodes extends WP_UnitTestCase {
|
|||
// Call the transformer Factory.
|
||||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,7 @@ class Test_Activitypub_Event_Bridge_Shortcodes extends WP_UnitTestCase {
|
|||
// Call the transformer Factory.
|
||||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event ) {
|
||||
if ( ! $transformer instanceof \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
356
tests/test-class-event-bridge-for-activitypub-event-sources.php
Normal file
356
tests/test-class-event-bridge-for-activitypub-event-sources.php
Normal file
|
@ -0,0 +1,356 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Event Sources feature.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test class for Activitypub Rest Inbox.
|
||||
*
|
||||
* @coversDefaultClass \Activitypub\Rest\Inbox
|
||||
*/
|
||||
class Test_Event_Bridge_For_ActivityPub_Event_Sources extends WP_UnitTestCase {
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
\delete_option( 'permalink_structure' );
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_false' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the inbox signature issue.
|
||||
*/
|
||||
public function test_inbox_signature_issue() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_false' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@id',
|
||||
'type' => 'Follow',
|
||||
'actor' => 'https://remote.example/@test',
|
||||
'object' => 'https://local.example/@test',
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
$response = \rest_do_request( $request );
|
||||
|
||||
$this->assertEquals( 401, $response->get_status() );
|
||||
$this->assertEquals( 'activitypub_signature_verification', $response->get_data()['code'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test missing attribute.
|
||||
*/
|
||||
public function test_missing_attribute() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@id',
|
||||
'type' => 'Follow',
|
||||
'actor' => 'https://remote.example/@test',
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
$response = \rest_do_request( $request );
|
||||
|
||||
$this->assertEquals( 400, $response->get_status() );
|
||||
$this->assertEquals( 'rest_missing_callback_param', $response->get_data()['code'] );
|
||||
$this->assertEquals( 'object', $response->get_data()['data']['params'][0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test follow request.
|
||||
*/
|
||||
public function test_follow_request() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@id',
|
||||
'type' => 'Follow',
|
||||
'actor' => 'https://remote.example/@test',
|
||||
'object' => 'https://local.example/@test',
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
// Dispatch the request.
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test follow request global inbox.
|
||||
*/
|
||||
public function test_follow_request_global_inbox() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@id',
|
||||
'type' => 'Follow',
|
||||
'actor' => 'https://remote.example/@test',
|
||||
'object' => 'https://local.example/@test',
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
// Dispatch the request.
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create request with a remote actor.
|
||||
*/
|
||||
public function test_create_request() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
// Invalid request, because of an invalid object.
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@id',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@test',
|
||||
'object' => 'https://local.example/@test',
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
// Dispatch the request.
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 400, $response->get_status() );
|
||||
$this->assertEquals( 'rest_invalid_param', $response->get_data()['code'] );
|
||||
|
||||
// Valid request, because of a valid object.
|
||||
$json['object'] = array(
|
||||
'id' => 'https://remote.example/post/test',
|
||||
'type' => 'Note',
|
||||
'content' => 'Hello, World!',
|
||||
'inReplyTo' => 'https://local.example/post/test',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
);
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
// Dispatch the request.
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create request global inbox.
|
||||
*/
|
||||
public function test_create_request_global_inbox() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
// Invalid request, because of an invalid object.
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@id',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@test',
|
||||
'object' => 'https://local.example/@test',
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
// Dispatch the request.
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 400, $response->get_status() );
|
||||
$this->assertEquals( 'rest_invalid_param', $response->get_data()['code'] );
|
||||
|
||||
// Valid request, because of a valid object.
|
||||
$json['object'] = array(
|
||||
'id' => 'https://remote.example/post/test',
|
||||
'type' => 'Note',
|
||||
'content' => 'Hello, World!',
|
||||
'inReplyTo' => 'https://local.example/post/test',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
);
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
// Dispatch the request.
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update request.
|
||||
*/
|
||||
public function test_update_request() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@id',
|
||||
'type' => 'Update',
|
||||
'actor' => 'https://remote.example/@test',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/post/test',
|
||||
'type' => 'Note',
|
||||
'content' => 'Hello, World!',
|
||||
'inReplyTo' => 'https://local.example/post/test',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
),
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
// Dispatch the request.
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test like request.
|
||||
*/
|
||||
public function test_like_request() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@id',
|
||||
'type' => 'Like',
|
||||
'actor' => 'https://remote.example/@test',
|
||||
'object' => 'https://local.example/post/test',
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
// Dispatch the request.
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test announce request.
|
||||
*/
|
||||
public function test_announce_request() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@id',
|
||||
'type' => 'Announce',
|
||||
'actor' => 'https://remote.example/@test',
|
||||
'object' => 'https://local.example/post/test',
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/1/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
|
||||
// Dispatch the request.
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether an activity is public.
|
||||
*
|
||||
* @dataProvider the_data_provider
|
||||
*
|
||||
* @param array $data The data.
|
||||
* @param bool $check The check.
|
||||
*/
|
||||
public function test_is_activity_public( $data, $check ) {
|
||||
$this->assertEquals( $check, Activitypub\is_activity_public( $data ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function the_data_provider() {
|
||||
return array(
|
||||
array(
|
||||
array(
|
||||
'cc' => array(
|
||||
'https://example.org/@test',
|
||||
'https://example.com/@test2',
|
||||
),
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'object' => array(),
|
||||
),
|
||||
true,
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'cc' => array(
|
||||
'https://example.org/@test',
|
||||
'https://example.com/@test2',
|
||||
),
|
||||
'to' => array(
|
||||
'https://www.w3.org/ns/activitystreams#Public',
|
||||
),
|
||||
'object' => array(),
|
||||
),
|
||||
true,
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'cc' => array(
|
||||
'https://example.org/@test',
|
||||
'https://example.com/@test2',
|
||||
),
|
||||
'object' => array(),
|
||||
),
|
||||
false,
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'cc' => array(
|
||||
'https://example.org/@test',
|
||||
'https://example.com/@test2',
|
||||
),
|
||||
'object' => array(
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
),
|
||||
),
|
||||
true,
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'cc' => array(
|
||||
'https://example.org/@test',
|
||||
'https://example.com/@test2',
|
||||
),
|
||||
'object' => array(
|
||||
'to' => array(
|
||||
'https://www.w3.org/ns/activitystreams#Public',
|
||||
),
|
||||
),
|
||||
),
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -62,7 +62,7 @@ class Test_Event_Organiser extends WP_UnitTestCase {
|
|||
$transformer = \Activitypub\Transformer\Factory::get_transformer( get_post( $post_id ) );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Event_Organiser::class, $transformer );
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event_Organiser::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -64,7 +64,7 @@ class Test_Eventin extends WP_UnitTestCase {
|
|||
$transformer = \Activitypub\Transformer\Factory::get_transformer( get_post( $event->id ) );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Eventin::class, $transformer );
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Eventin::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -93,7 +93,7 @@ class Test_EventPrime extends WP_UnitTestCase {
|
|||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\Activitypub\Transformer\EventPrime::class, $transformer );
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\EventPrime::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -61,7 +61,7 @@ class Test_Events_Manager extends WP_UnitTestCase {
|
|||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Events_Manager::class, $transformer );
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Events_Manager::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -65,7 +65,7 @@ class Test_GatherPress extends WP_UnitTestCase {
|
|||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $event->event );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\Activitypub\Transformer\GatherPress::class, $transformer );
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\GatherPress::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -74,7 +74,7 @@ class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
|
|||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\Activitypub\Transformer\Modern_Events_Calendar_Lite::class, $transformer );
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Modern_Events_Calendar_Lite::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -91,7 +91,7 @@ class Test_The_Events_Calendar extends WP_UnitTestCase {
|
|||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\Activitypub\Transformer\The_Events_Calendar::class, $transformer );
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\The_Events_Calendar::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -58,7 +58,7 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\Activitypub\Transformer\VS_Event_List::class, $transformer );
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\VS_Event_List::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -58,7 +58,7 @@ class Test_WP_Event_Manager extends WP_UnitTestCase {
|
|||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\Activitypub\Transformer\WP_Event_Manager::class, $transformer );
|
||||
$this->assertInstanceOf( \Event_Bridge_For_ActivityPub\ActivityPub\Transformer\WP_Event_Manager::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue