Compare commits
95 commits
main
...
event_sour
Author | SHA1 | Date | |
---|---|---|---|
e40b6cbb14 | |||
9e6705983f | |||
70e53fc721 | |||
a926b8c605 | |||
f1572798a8 | |||
963ae48784 | |||
1c5f428289 | |||
f225db0f1e | |||
f9bd26cf09 | |||
60ebd72e41 | |||
c291c81ecc | |||
ddeb5c6cca | |||
8026ea69f4 | |||
21d1ee9915 | |||
1eec93d342 | |||
ee7f0a932d | |||
e4937146e1 | |||
e6a0af1146 | |||
7e75e61b70 | |||
6950d52264 | |||
9a2dda0388 | |||
b8bb698f60 | |||
c55ce36499 | |||
b7747de5ac | |||
f683893284 | |||
d603cea442 | |||
5f41ccdc42 | |||
674552513b | |||
b42fe590a1 | |||
38074d66e4 | |||
d8b3c27ead | |||
0be07c050c | |||
c7bd884f57 | |||
454f489815 | |||
8ee1e2cad4 | |||
94b7812ecf | |||
aca96ae712 | |||
6ec1079c80 | |||
b31b9e22fb | |||
e895687a76 | |||
750fd04d1f | |||
fbba8c283c | |||
aee19e9606 | |||
633a5332d1 | |||
460b223920 | |||
355dfb8991 | |||
a1c776641f | |||
6732a0a5ca | |||
1b3e505d7b | |||
a027c72b34 | |||
cb8c4eac5e | |||
ba9698f36d | |||
26a2a6fce8 | |||
d5c14ae03a | |||
ba222a4a9a | |||
f413bb38d3 | |||
e494aba31e | |||
51eb8e2b51 | |||
be0a10dc22 | |||
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 |
74 changed files with 6368 additions and 567 deletions
|
@ -76,7 +76,7 @@ jobs:
|
|||
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wordpress-version }} false true true true
|
||||
|
||||
- name: Run General Tests
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=event_bridge_for_activitypub
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ composer.lock
|
|||
.phpunit.result.cache
|
||||
node_modules/
|
||||
package-lock.json
|
||||
.phpdoc
|
||||
|
|
11
README.md
11
README.md
|
@ -21,15 +21,20 @@ This plugin is not an event managing plugin but an add-on to popular event plugi
|
|||
With the ActivityPub plugin people can follow your website directly and engage with your events just as they would on social media: liking, boosting and even commenting if you enable it.
|
||||
You retain full ownership of your content. By integrating into your existing setup, it ensures no extra work is needed while enhancing your events' visibility across the web.
|
||||
|
||||
### Supported Event Plugins
|
||||
### Supported Event Plugins ###
|
||||
|
||||
Full support (including importing events from the Fediverse):
|
||||
|
||||
* [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/)
|
||||
* [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/)
|
||||
* [GatherPress](https://gatherpress.org/)
|
||||
|
||||
Basic support (outgoing events):
|
||||
|
||||
* [Events Manager](https://de.wordpress.org/plugins/events-manager/)
|
||||
* [WP Event Manager](https://de.wordpress.org/plugins/wp-event-manager/)
|
||||
* [Eventin](https://de.wordpress.org/plugins/wp-event-solution/)
|
||||
* [Modern Events Calendar Lite](https://webnus.net/modern-events-calendar/)
|
||||
* [GatherPress](https://gatherpress.org/)
|
||||
* [Event Organiser](https://wordpress.org/plugins/event-organiser/)
|
||||
|
||||
### How It Works ###
|
||||
|
@ -61,6 +66,8 @@ The Event Federation plugin ensures that users from those platforms are provided
|
|||
|
||||
**Event Reminders for Your Followers:** Often, events are planned well in advance. To keep your followers informed right in time, you can set up reminders that are supposed to trigger the events showing up in their timelines right before the event starts. At the moment this reminder is implemented as a self-boost of your original event post. While this feature may behave differently across various platforms, we are working on a more robust solution that will let you schedule dedicated reminder notes that appear in all followers' timelines.
|
||||
|
||||
**External Event Sources:** This functionality is only available for a subset of the supported event plugins. It enables your WordPress site to act as a hub for displaying events from other ActivityPub profiles, aggregating them into a cohesive calendar view.
|
||||
|
||||
## Installation ##
|
||||
|
||||
This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/). Additionally, you need to use one of the supported event Plugins.
|
||||
|
|
|
@ -190,3 +190,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.
|
||||
});
|
||||
});
|
||||
|
||||
} );
|
||||
|
|
|
@ -214,7 +214,7 @@ install_wp_plugin() {
|
|||
else
|
||||
PLUGIN_VERSION=$2
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "$PLUGIN_VERSION" ]; then
|
||||
PLUGIN_FILE="$PLUGIN_NAME.$PLUGIN_VERSION.zip"
|
||||
else
|
||||
|
|
|
@ -48,19 +48,20 @@
|
|||
],
|
||||
"test": [
|
||||
"@prepare-test",
|
||||
"@test-vs-event-list",
|
||||
"@test-the-events-calendar",
|
||||
"@test-gatherpress",
|
||||
"@test-events-manager",
|
||||
"@test-wp-event-manager",
|
||||
"@test-eventin",
|
||||
"@test-modern-events-calendar-lite",
|
||||
"@test-eventprime",
|
||||
"@test-event-organiser"
|
||||
"@test-integration-vs-event-list",
|
||||
"@test-integration-the-events-calendar",
|
||||
"@test-integration-gatherpress",
|
||||
"@test-integration- events-manager",
|
||||
"@test-integration-wp-event-manager",
|
||||
"@test-integration-eventin",
|
||||
"@test-integration-modern-events-calendar-lite",
|
||||
"@test-integration-eventprime",
|
||||
"@test-integration-event-organiser",
|
||||
"@test-event-sources"
|
||||
],
|
||||
"test-debug": [
|
||||
"@prepare-test",
|
||||
"@test-event-bridge-for-activitypub-shortcodes"
|
||||
"@test-event-sources"
|
||||
],
|
||||
"test-vs-event-list": "phpunit --filter=vs_event_list",
|
||||
"test-the-events-calendar": "phpunit --filter=the_events_calendar",
|
||||
|
@ -71,7 +72,8 @@
|
|||
"test-modern-events-calendar-lite": "phpunit --filter=modern_events_calendar_lite",
|
||||
"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-all": "phpunit"
|
||||
"test-event-bridge-for-activitypub-shortcodes": "phpunit --filter=shortcodes",
|
||||
"test-event-sources": "phpunit --filter=event_sources",
|
||||
"test-general": "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}/"
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
35
includes/activitypub/class-handler.php
Normal file
35
includes/activitypub/class-handler.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?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 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;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Undo;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
Undo::init();
|
||||
}
|
||||
}
|
503
includes/activitypub/collection/class-event-sources.php
Normal file
503
includes/activitypub/collection/class-event-sources.php
Normal file
|
@ -0,0 +1,503 @@
|
|||
<?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 = 'ap_event_source';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public static function init() {
|
||||
self::register_post_type();
|
||||
\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,
|
||||
'activitypub_inbox',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_event_bridge_for_activitypub_utilize_announces',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
if ( 'same_origin' === $value ) {
|
||||
return 'same_origin';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_event_bridge_for_activitypub_accept_of_follow',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_event_bridge_for_activitypub_event_count',
|
||||
array(
|
||||
'type' => 'integer',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
'default' => '0',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
self::queue_follow_actor( $actor );
|
||||
|
||||
self::delete_event_source_transients();
|
||||
|
||||
return $event_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the ActivityPub ID of a follow request.
|
||||
*
|
||||
* @param string $follower_id The ActivityPub ID of the actor that followers the other one.
|
||||
* @param string $followed_id The ActivityPub ID of the followed actor.
|
||||
* @return string The `Follow` ID.
|
||||
*/
|
||||
public static function compose_follow_id( $follower_id, $followed_id ) {
|
||||
return $follower_id . '#follow-' . \preg_replace( '~^https?://~', '', $followed_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an attachment is set as a featured image of any post.
|
||||
*
|
||||
* @param string|int $attachment_id The numeric post ID of the attachment.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_attachment_featured_image( $attachment_id ) {
|
||||
if ( ! is_numeric( $attachment_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Query posts with the given attachment ID as their featured image.
|
||||
$args = array(
|
||||
'post_type' => 'any',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_thumbnail_id',
|
||||
'value' => $attachment_id,
|
||||
'compare' => '=',
|
||||
),
|
||||
),
|
||||
'fields' => 'ids', // Only retrieve post IDs for performance.
|
||||
'numberposts' => 1, // We only need one match to confirm.
|
||||
);
|
||||
|
||||
$posts = \get_posts( $args );
|
||||
|
||||
return ! empty( $posts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all posts of an event source.
|
||||
*
|
||||
* @param int $event_source_post_id The WordPress Post ID of the event source.
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_events_by_event_source( $event_source_post_id ) {
|
||||
global $wpdb;
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s",
|
||||
'_event_bridge_for_activitypub_event_source',
|
||||
absint( $event_source_post_id )
|
||||
)
|
||||
);
|
||||
|
||||
// If no matching posts are found, return early.
|
||||
if ( empty( $results ) || ! $results ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through the posts and delete them permanently.
|
||||
foreach ( $results as $result ) {
|
||||
// Check if the post has a thumbnail.
|
||||
$thumbnail_id = get_post_thumbnail_id( $result->post_id );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
// Remove the thumbnail from the post.
|
||||
\delete_post_thumbnail( $result->post_id );
|
||||
|
||||
// Delete the attachment (and its files) from the media library.
|
||||
if ( self::is_attachment_featured_image( $thumbnail_id ) ) {
|
||||
\wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
\wp_delete_post( $result->post_id, true );
|
||||
}
|
||||
|
||||
// Clean up the query.
|
||||
\wp_reset_postdata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an Event Source (=Followed ActivityPub actor).
|
||||
*
|
||||
* @param string $activitypub_id The Events Sources ActivityPub Actor ID/URL.
|
||||
*
|
||||
* @return WP_Post|false|null Post data on success, false or null on failure.
|
||||
*/
|
||||
public static function remove_event_source( $activitypub_id ) {
|
||||
self::delete_event_source_transients();
|
||||
|
||||
$event_source = Event_Source::get_by_id( $activitypub_id );
|
||||
|
||||
if ( ! $event_source ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = $event_source->get__id();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::delete_events_by_event_source( $post_id );
|
||||
|
||||
$deleted = $event_source->delete();
|
||||
|
||||
if ( $deleted ) {
|
||||
self::queue_unfollow_actor( $activitypub_id );
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Event-Sources.
|
||||
*
|
||||
* @return Event_Source[] A List of all Event Sources (follows).
|
||||
*/
|
||||
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',
|
||||
'post_status' => array( 'publish', 'pending', 'draft', 'auto-draft', 'future', 'private', 'inherit' ),
|
||||
);
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
$query = new WP_Query( $args );
|
||||
$total = $query->found_posts;
|
||||
$actors = array();
|
||||
|
||||
foreach ( $query->get_posts() as $post ) {
|
||||
$actors[ $post->guid ] = Event_Source::init_from_cpt( $post );
|
||||
}
|
||||
|
||||
$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( self::compose_follow_id( $application->get_id(), $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 Event_Source $actor The ActivityPub actor model.
|
||||
*/
|
||||
public static function activitypub_unfollow_actor( $actor ) {
|
||||
if ( ! $actor instanceof Event_Source ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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( $application->get_id() . '#unfollow-' . \preg_replace( '~^https?://~', '', $to ) );
|
||||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::BLOG_USER_ID );
|
||||
}
|
||||
}
|
80
includes/activitypub/handler/class-accept.php
Normal file
80
includes/activitypub/handler/class-accept.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?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;
|
||||
use Activitypub\Model\Blog;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* 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' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Accept" activities.
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_accept( $activity, $user_id ) {
|
||||
// We only process activities that are target to the blog actor.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we are actually following/or have a pending follow request this actor.
|
||||
$event_source = Event_Source::get_by_id( $activity['actor'] );
|
||||
if ( ! $event_source ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is what the ID of the follow request would look like.
|
||||
$application = new Blog();
|
||||
$follow_id = Event_Sources_Collection::compose_follow_id( $application->get_id(), $activity['actor'] );
|
||||
|
||||
// Check if the object of the `Accept` is indeed the `Follow` request we sent to that actor.
|
||||
if ( object_to_uri( $activity['object'] ) !== $follow_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the WordPress post ID of the Event Source. This should not be able to fail here.
|
||||
$post_id = $event_source->get__id();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the accept status of the follow request to the event source post.
|
||||
\update_post_meta( $post_id, '_event_bridge_for_activitypub_accept_of_follow', $activity['id'] );
|
||||
\wp_update_post(
|
||||
array(
|
||||
'ID' => $post_id,
|
||||
'post_status' => 'publish',
|
||||
)
|
||||
);
|
||||
|
||||
// Trigger the backfilling of events from this actor.
|
||||
\do_action( 'event_bridge_for_activitypub_backfill_events', $event_source );
|
||||
}
|
||||
}
|
77
includes/activitypub/handler/class-create.php
Normal file
77
includes/activitypub/handler/class-create.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?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\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
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 blog actor.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Activity is public or not.
|
||||
if ( ! is_activity_public( $activity ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an object is set and it is an object of type `Event`.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we are actually following/or have a pending follow request this actor.
|
||||
$event_source = Event_Source::get_by_id( $activity['actor'] );
|
||||
if ( ! $event_source ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Event_Sources::is_time_passed( $activity['object']['startTime'] ) ) {
|
||||
return new \WP_Error(
|
||||
'event_bridge_for_activitypub_not_accepting_events_from_the_past',
|
||||
__( 'We do not accept this event because it took place in the past.', 'event-bridge-for-activitypub' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier->save( $activity['object'], $event_source );
|
||||
}
|
||||
}
|
59
includes/activitypub/handler/class-delete.php
Normal file
59
includes/activitypub/handler/class-delete.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?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\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// Check that we are actually following this actor.
|
||||
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$id = object_to_uri( $activity['object'] );
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier->delete( $id );
|
||||
}
|
||||
}
|
71
includes/activitypub/handler/class-undo.php
Normal file
71
includes/activitypub/handler/class-undo.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
/**
|
||||
* Undo 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;
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* Handle Uno requests.
|
||||
*/
|
||||
class Undo {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Uno` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_inbox_undo',
|
||||
array( self::class, 'handle_undo' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Undo" activities.
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_undo( $activity, $user_id ) {
|
||||
// We only process activities that are target to the blog actor.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we are actually following/or have a pending follow request for this actor.
|
||||
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$accept_id = object_to_uri( $activity['object'] );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s",
|
||||
'_event_bridge_for_activitypub_accept_of_follow',
|
||||
esc_sql( $accept_id )
|
||||
)
|
||||
);
|
||||
|
||||
// If no event source with that accept ID is found return.
|
||||
if ( empty( $results ) || ! $results ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = reset( $results )->post_id;
|
||||
|
||||
\delete_post_meta( $post_id, '_event_bridge_for_activitypub_accept_of_follow' );
|
||||
}
|
||||
}
|
73
includes/activitypub/handler/class-update.php
Normal file
73
includes/activitypub/handler/class-update.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?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\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Check if Activity is public or not.
|
||||
if ( ! is_activity_public( $activity ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an object is set and it is an object of type `Event`.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we are actually following/or have a pending follow request this actor.
|
||||
$event_source = Event_Source::get_by_id( $activity['actor'] );
|
||||
if ( ! $event_source ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Event_Sources::is_time_passed( $activity['object']['startTime'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier->save( $activity['object'], $event_source );
|
||||
}
|
||||
}
|
300
includes/activitypub/model/class-event-source.php
Normal file
300
includes/activitypub/model/class-event-source.php
Normal file
|
@ -0,0 +1,300 @@
|
|||
<?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 WP_Post;
|
||||
|
||||
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 WordPress Post ID which stores 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Post-IDs of all events cached by this event source.
|
||||
*/
|
||||
public static function get_cached_events(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for URL attribute.
|
||||
*
|
||||
* @return string The URL.
|
||||
*/
|
||||
public function get_url() {
|
||||
if ( $this->url ) {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the outbox.
|
||||
*
|
||||
* @return ?string The outbox URL.
|
||||
*/
|
||||
public function get_outbox() {
|
||||
if ( $this->outbox ) {
|
||||
return $this->outbox;
|
||||
}
|
||||
|
||||
$actor_json = \get_post_meta( $this->get__id(), 'activitypub_actor_json', true );
|
||||
|
||||
if ( ! $actor_json ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$actor = \json_decode( $actor_json, true );
|
||||
|
||||
if ( ! isset( $actor['outbox'] ) ) {
|
||||
\do_action( 'event_bridge_for_activitypub_write_log', array( "[ACTIVITYPUB] Did not find outbox URL for actor {$actor}" ) );
|
||||
return null;
|
||||
}
|
||||
|
||||
return $actor['outbox'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Event Source by the ActivityPub ID.
|
||||
*
|
||||
* @param string $activitypub_actor_id The ActivityPub actor ID.
|
||||
* @return Event_Source|false The Event Source Actor, if a WordPress Post representing it is found, false otherwise.
|
||||
*/
|
||||
public static function get_by_id( $activitypub_actor_id ) {
|
||||
$event_sources = Event_Sources::get_event_sources();
|
||||
|
||||
if ( ! array_key_exists( $activitypub_actor_id, $event_sources ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $event_sources[ $activitypub_actor_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => 'pending',
|
||||
'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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an Event Source and it's profile image.
|
||||
*/
|
||||
public function delete() {
|
||||
$post_id = $this->get__id();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
|
||||
return wp_delete_post( $post_id, false ) ?? false;
|
||||
}
|
||||
}
|
|
@ -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 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;
|
||||
|
||||
/**
|
||||
|
|
361
includes/activitypub/transmogrifier/class-base.php
Normal file
361
includes/activitypub/transmogrifier/class-base.php
Normal file
|
@ -0,0 +1,361 @@
|
|||
<?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 Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
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 Event object.
|
||||
*
|
||||
* @var Event
|
||||
*/
|
||||
protected $activitypub_event;
|
||||
|
||||
/**
|
||||
* The current Event object.
|
||||
*
|
||||
* @var Event_Source
|
||||
*/
|
||||
protected $event_source;
|
||||
|
||||
/**
|
||||
* 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 within WordPress.
|
||||
*
|
||||
* @param array $activitypub_event The ActivityPub event as associative array.
|
||||
* @param Event_Source $event_source The Event Source we received the event from.
|
||||
*/
|
||||
public function save( $activitypub_event, $event_source ) {
|
||||
$activitypub_event = Event::init_from_array( $activitypub_event );
|
||||
|
||||
if ( is_wp_error( $activitypub_event ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activitypub_event = $activitypub_event;
|
||||
$this->event_source = $event_source;
|
||||
|
||||
$post_id = $this->save_event();
|
||||
|
||||
$event_id = $activitypub_event->get_id();
|
||||
|
||||
$event_source_activitypub_id = $event_source->get_id();
|
||||
$event_source_post_id = $event_source->get__id();
|
||||
|
||||
if ( $post_id ) {
|
||||
\do_action(
|
||||
'event_bridge_for_activitypub_write_log',
|
||||
array( "[ACTIVITYPUB] Processed incoming event {$event_id} from {$event_source_activitypub_id}" )
|
||||
);
|
||||
update_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', absint( $event_source_post_id ) );
|
||||
update_post_meta( $post_id, 'activitypub_content_visibility', constant( 'ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL' ) ?? '' );
|
||||
} else {
|
||||
\do_action(
|
||||
'event_bridge_for_activitypub_write_log',
|
||||
array( "[ACTIVITYPUB] Failed processing incoming event {$event_id} from {$event_source_activitypub_id}" )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a local event in WordPress that is a cached remote one.
|
||||
*
|
||||
* @param int $activitypub_event_id The ActivityPub events ID.
|
||||
*/
|
||||
public function delete( $activitypub_event_id ) {
|
||||
$post_id = self::get_post_id_from_activitypub_id( $activitypub_event_id );
|
||||
|
||||
if ( ! $post_id ) {
|
||||
\do_action(
|
||||
'event_bridge_for_activitypub_write_log',
|
||||
array( "[ACTIVITYPUB] Received delete for event that is not cached locally {$activitypub_event_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 && ! Event_Sources::is_attachment_featured_image( $thumbnail_id ) ) {
|
||||
wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
|
||||
$result = wp_delete_post( $post_id, true );
|
||||
|
||||
if ( $result ) {
|
||||
\do_action( 'event_bridge_for_activitypub_write_log', array( "[ACTIVITYPUB] Deleted cached event {$activitypub_event_id}" ) );
|
||||
} else {
|
||||
\do_action( 'event_bridge_for_activitypub_write_log', array( "[ACTIVITYPUB] Failed deleting cached event {$activitypub_event_id}" ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WordPress post by ActivityPub object ID.
|
||||
*
|
||||
* @param int $activitypub_id The ActivityPub object ID.
|
||||
* @return int The WordPress Post ID.
|
||||
*/
|
||||
protected static function get_post_id_from_activitypub_id( $activitypub_id ) {
|
||||
global $wpdb;
|
||||
return $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
|
||||
esc_sql( $activitypub_id ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
185
includes/activitypub/transmogrifier/class-gatherpress.php
Normal file
185
includes/activitypub/transmogrifier/class-gatherpress.php
Normal file
|
@ -0,0 +1,185 @@
|
|||
<?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 Event_Bridge_For_ActivityPub\Integrations\GatherPress as IntegrationsGatherPress;
|
||||
|
||||
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, IntegrationsGatherPress::get_event_category_taxonomy(), 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 = self::get_post_id_from_activitypub_id( $this->activitypub_event->get_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,240 @@
|
|||
<?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 Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Tribe__Date_Utils;
|
||||
|
||||
use function Activitypub\sanitize_url;
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
// 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 {
|
||||
/**
|
||||
* Map an ActivityStreams Place to the Events Calendar venue.
|
||||
*
|
||||
* @param array $location An ActivityPub location as an associative array.
|
||||
* @link https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place
|
||||
* @return array
|
||||
*/
|
||||
private function get_venue_args( $location ) {
|
||||
$args = array(
|
||||
'venue' => $location['name'],
|
||||
'status' => 'publish',
|
||||
);
|
||||
|
||||
if ( is_array( $location['address'] ) && isset( $location['address']['type'] ) && 'PostalAddress' === $location['address']['type'] ) {
|
||||
$mapping = array(
|
||||
'streetAddress' => 'address',
|
||||
'postalCode' => 'zip',
|
||||
'addressLocality' => 'city',
|
||||
'addressState' => 'state',
|
||||
'addressCountry' => 'country',
|
||||
'url' => 'website',
|
||||
);
|
||||
|
||||
foreach ( $mapping as $postal_address_key => $venue_key ) {
|
||||
if ( isset( $location['address'][ $postal_address_key ] ) ) {
|
||||
$args[ $venue_key ] = $location['address'][ $postal_address_key ];
|
||||
}
|
||||
}
|
||||
} elseif ( is_string( $location['address'] ) ) {
|
||||
// Use the address field for a solely text address.
|
||||
$args['address'] = $location['address'];
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add venue.
|
||||
*
|
||||
* @return int|bool $post_id The venues post ID.
|
||||
*/
|
||||
private function add_venue() {
|
||||
$location = $this->activitypub_event->get_location();
|
||||
|
||||
if ( ! $location ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['name'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback for Gancio instances.
|
||||
if ( 'online' === $location['name'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_ids = tribe_venues()->search( $location['name'] )->all();
|
||||
|
||||
$post_id = false;
|
||||
|
||||
if ( count( $post_ids ) ) {
|
||||
$post_id = reset( $post_ids );
|
||||
if ( $post_id instanceof \WP_Post ) {
|
||||
$post_id = $post_id->ID;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $post_id && get_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', true ) ) {
|
||||
$result = tribe_venues()->where( 'id', $post_id )->set_args( $this->get_venue_args( $location ) )->save();
|
||||
if ( array_key_exists( $post_id, $result ) && $result[ $post_id ] ) {
|
||||
return $post_id;
|
||||
}
|
||||
} else {
|
||||
$post = tribe_venues()->set_args( $this->get_venue_args( $location ) )->create();
|
||||
if ( $post ) {
|
||||
$post_id = $post->ID;
|
||||
update_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', $this->event_source->get__id() );
|
||||
}
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add organizer.
|
||||
*
|
||||
* @return int|bool $post_id The organizers post ID.
|
||||
*/
|
||||
private function add_organizer() {
|
||||
// This might likely change, because of FEP-8a8e.
|
||||
$actor = $this->activitypub_event->get_attributed_to();
|
||||
if ( is_null( $actor ) ) {
|
||||
return false;
|
||||
}
|
||||
$actor_id = object_to_uri( $actor );
|
||||
|
||||
$event_source = Event_Source::get_by_id( $actor_id );
|
||||
|
||||
// As long as we do not support announces, we expect the attributedTo to be an existing event source.
|
||||
if ( ! $event_source ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tribe_organizer = tribe_organizers()
|
||||
->set_args(
|
||||
array(
|
||||
'organizer' => $event_source->get_name(),
|
||||
'description' => $event_source->get_summary(),
|
||||
'post_date_gmt' => $event_source->get_published(),
|
||||
'website' => $event_source->get_id(),
|
||||
'excerpt' => $event_source->get_summary(),
|
||||
),
|
||||
'publish',
|
||||
true // This enables avoid_duplicates which includes exact matches of title, content, excerpt, and website.
|
||||
)->create();
|
||||
|
||||
if ( ! $tribe_organizer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a relationship between the event source WP_Post and the organizer WP_Post.
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $tribe_organizer->ID,
|
||||
'post_parent' => $event_source->get__id(),
|
||||
)
|
||||
);
|
||||
|
||||
// Add the thumbnail of the event source to the organizer.
|
||||
if ( get_post_thumbnail_id( $event_source ) ) {
|
||||
set_post_thumbnail( $tribe_organizer, get_post_thumbnail_id( $event_source ) );
|
||||
}
|
||||
|
||||
return $tribe_organizer->ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = self::get_post_id_from_activitypub_id( $this->activitypub_event->get_id() );
|
||||
|
||||
$duration = $this->get_duration();
|
||||
|
||||
$venue_id = $this->add_venue();
|
||||
|
||||
$organizer_id = $this->add_organizer();
|
||||
|
||||
$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() ),
|
||||
);
|
||||
|
||||
if ( $venue_id ) {
|
||||
$args['venue'] = $venue_id;
|
||||
$args['VenueID'] = $venue_id;
|
||||
}
|
||||
|
||||
if ( $organizer_id ) {
|
||||
$args['organizer'] = $organizer_id;
|
||||
$args['OrganizerID'] = $organizer_id;
|
||||
}
|
||||
|
||||
$tribe_event = new The_Events_Calendar_Event_Repository();
|
||||
|
||||
if ( $post_id ) {
|
||||
$args['post_title'] = $args['title'];
|
||||
$args['post_content'] = $args['content'];
|
||||
// Update existing The Events Calendar event post.
|
||||
$post_id = \Tribe__Events__API::updateEvent( $post_id, $args );
|
||||
} else {
|
||||
$post = $tribe_event->set_args( $args )->create();
|
||||
if ( $post instanceof \WP_Post ) {
|
||||
$post_id = $post->ID;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $post_id || is_wp_error( $post_id ) ) {
|
||||
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() ) );
|
||||
}
|
||||
}
|
160
includes/activitypub/transmogrifier/class-vs-event-list.php
Normal file
160
includes/activitypub/transmogrifier/class-vs-event-list.php
Normal file
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the VS Event List event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to events of VS Event List.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/very-simple-event-list/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Integrations\VS_Event_List as IntegrationsVS_Event_List;
|
||||
|
||||
use function Activitypub\sanitize_url;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the VS Event List event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to events of VS Event List.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/very-simple-event-list/
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class VS_Event_List extends Base {
|
||||
/**
|
||||
* Extract location and address as string.
|
||||
*
|
||||
* @param ?array $location The ActivitySTreams location as an associative array.
|
||||
* @return string The location and address formatted as a single string.
|
||||
*/
|
||||
private function get_location_as_string( $location ): string {
|
||||
$location_string = '';
|
||||
|
||||
// Return empty string when location is not an associative array.
|
||||
if ( is_null( $location ) || ! is_array( $location ) ) {
|
||||
return $location_string;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['type'] ) || 'Place' !== $location['type'] ) {
|
||||
return $location_string;
|
||||
}
|
||||
|
||||
// Add name of the location.
|
||||
if ( isset( $location['name'] ) ) {
|
||||
$location_string .= $location['name'];
|
||||
}
|
||||
|
||||
// Add delimiter between name and address if both are set.
|
||||
if ( isset( $location['name'] ) && isset( $location['address'] ) ) {
|
||||
$location_string .= ' – ';
|
||||
}
|
||||
|
||||
// Add address.
|
||||
if ( isset( $location['address'] ) ) {
|
||||
$location_string .= $this->address_to_string( $location['address'] );
|
||||
}
|
||||
return $location_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, IntegrationsVS_Event_List::get_event_category_taxonomy(), true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = self::get_post_id_from_activitypub_id( $this->activitypub_event->get_id() );
|
||||
|
||||
$args = array(
|
||||
'post_title' => \sanitize_text_field( $this->activitypub_event->get_name() ),
|
||||
'post_type' => \Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::get_post_type(),
|
||||
'post_content' => \wp_kses_post( $this->activitypub_event->get_content() ?? '' ),
|
||||
'post_excerpt' => \wp_kses_post( $this->activitypub_event->get_summary() ?? '' ),
|
||||
'post_status' => 'publish',
|
||||
'guid' => \sanitize_url( $this->activitypub_event->get_id() ),
|
||||
'meta_input' => array(
|
||||
'event-start-date' => \strtotime( $this->activitypub_event->get_start_time() ),
|
||||
'event-link' => \sanitize_url( $this->activitypub_event->get_url() ?? $this->activitypub_event->get_id() ),
|
||||
'event-link-label' => \sanitize_text_field( __( 'Original Website', 'event-bridge-for-activitypub' ) ),
|
||||
'event-link-target' => 'yes', // Open in new window.
|
||||
'event-link-title' => 'no', // Whether to redirect event title to original source.
|
||||
'event-link-image' => 'no', // Whether to redirect events featured image to original source.
|
||||
),
|
||||
);
|
||||
|
||||
// Add end time.
|
||||
$end_time = $this->activitypub_event->get_end_time();
|
||||
if ( $end_time ) {
|
||||
$args['meta_input']['event-date'] = \strtotime( $end_time );
|
||||
}
|
||||
|
||||
// Maybe add location.
|
||||
$location = $this->get_location_as_string( $this->activitypub_event->get_location() );
|
||||
if ( $location ) {
|
||||
$args['meta_input']['event-location'] = $location;
|
||||
}
|
||||
|
||||
if ( $post_id ) {
|
||||
// Update existing event post.
|
||||
$args['ID'] = $post_id;
|
||||
$post_id = \wp_update_post( $args );
|
||||
} else {
|
||||
// Insert new event post.
|
||||
$post_id = \wp_insert_post( $args );
|
||||
}
|
||||
|
||||
if ( ! $post_id || \is_wp_error( $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 );
|
||||
|
||||
// Limit this as a safety measure.
|
||||
\remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
/**
|
||||
* Health_Check class.
|
||||
*
|
||||
* @package Activitypub_Event_Bridge
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Admin;
|
||||
|
@ -86,6 +86,10 @@ class Health_Check {
|
|||
* @return bool True if the check passed.
|
||||
*/
|
||||
public static function test_if_event_transformer_is_used( $event_plugin ) {
|
||||
if ( ! Setup::get_instance()->is_activitypub_plugin_active() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a (random) event post.
|
||||
$event_posts = self::get_most_recent_event_posts( $event_plugin->get_post_type(), 1 );
|
||||
|
||||
|
@ -97,7 +101,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;
|
||||
}
|
||||
|
@ -113,6 +117,10 @@ class Health_Check {
|
|||
* @return WP_Post[]|false Array of event posts, or false if none are found.
|
||||
*/
|
||||
public static function get_most_recent_event_posts( $event_post_type = null, $number_of_posts = 5 ) {
|
||||
if ( ! Setup::get_instance()->is_activitypub_plugin_active() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $event_post_type ) {
|
||||
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
|
||||
$active_event_plugin = reset( $active_event_plugins );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
38
includes/class-debug.php
Normal file
38
includes/class-debug.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/**
|
||||
* Class file for Debug Class.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
/**
|
||||
* Debug Class.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
class Debug {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
if ( defined( 'WP_DEBUG_LOG' ) && constant( 'WP_DEBUG_LOG' ) ) {
|
||||
\add_action( 'event_bridge_for_activitypub_write_log', array( self::class, 'write_log' ), 10, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a log entry.
|
||||
*
|
||||
* @param mixed $log The log entry.
|
||||
*/
|
||||
public static function write_log( $log ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
|
||||
\error_log( \print_r( $log, true ) );
|
||||
}
|
||||
}
|
483
includes/class-event-sources.php
Normal file
483
includes/class-event-sources.php
Normal file
|
@ -0,0 +1,483 @@
|
|||
<?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 DateTime;
|
||||
use DateTimeZone;
|
||||
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 Exception;
|
||||
use WP_Error;
|
||||
|
||||
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' ) );
|
||||
|
||||
// Allow wp_safe_redirect to all followed event sources hosts.
|
||||
\add_filter( 'allowed_redirect_hosts', array( self::class, 'add_event_sources_hosts_to_allowed_redirect_hosts' ) );
|
||||
|
||||
// Register handlers for incoming activities to the ActivityPub plugin, e.g. incoming `Event` objects.
|
||||
\add_action( 'activitypub_register_handlers', array( Handler::class, 'register_handlers' ) );
|
||||
|
||||
// Add validation filter, so that only plausible activities reach the handlers above.
|
||||
\add_filter(
|
||||
'activitypub_validate_object',
|
||||
array( self::class, 'validate_event_object' ),
|
||||
12,
|
||||
3
|
||||
);
|
||||
\add_filter(
|
||||
'activitypub_validate_object',
|
||||
array( self::class, 'validate_activity' ),
|
||||
13,
|
||||
3
|
||||
);
|
||||
|
||||
// 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_follow_collection' ), 10, 2 );
|
||||
|
||||
// Add action for backfilling the events.
|
||||
Outbox_Parser::init();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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_event_source',
|
||||
array(
|
||||
'type' => 'integer',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
)
|
||||
);
|
||||
|
||||
$location_post_type = $event_plugin_integration::get_location_post_type();
|
||||
if ( $location_post_type ) {
|
||||
\register_post_meta(
|
||||
$location_post_type,
|
||||
'_event_bridge_for_activitypub_event_source',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => false,
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
return sanitize_url( $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_event_source', 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_follow_collection( $follow_list, $user ) {
|
||||
if ( ! $user instanceof Blog ) {
|
||||
return $follow_list;
|
||||
}
|
||||
|
||||
$event_sources = array_keys( Event_Sources_Collection::get_event_sources() );
|
||||
|
||||
if ( ! is_array( $event_sources ) ) {
|
||||
return $follow_list;
|
||||
}
|
||||
|
||||
return array_merge( $follow_list, $event_sources );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array will all unique hosts of all Event-Sources.
|
||||
*
|
||||
* @return array A list with all unique hosts of all Event Sources' ActivityPub IDs.
|
||||
*/
|
||||
public static function get_event_sources_hosts() {
|
||||
$hosts = get_transient( 'event_bridge_for_activitypub_event_sources_hosts' );
|
||||
|
||||
if ( $hosts ) {
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
$event_sources = Event_Sources_Collection::get_event_sources();
|
||||
|
||||
$hosts = array();
|
||||
foreach ( array_keys( $event_sources ) as $actor ) {
|
||||
$url = wp_parse_url( $actor );
|
||||
if ( isset( $url['host'] ) ) {
|
||||
$hosts[] = $url['host'];
|
||||
}
|
||||
}
|
||||
|
||||
$hosts = array_unique( $hosts );
|
||||
|
||||
set_transient( 'event_bridge_for_activitypub_event_sources_hosts', $hosts );
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the event object.
|
||||
*
|
||||
* @param bool $valid The validation state.
|
||||
* @param string $param The object parameter.
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return bool|WP_Error The validation state: true if valid, false if not.
|
||||
*/
|
||||
public static function validate_activity( $valid, $param, $request ) {
|
||||
if ( $valid ) {
|
||||
return $valid;
|
||||
}
|
||||
$json_params = $request->get_json_params();
|
||||
|
||||
if ( isset( $json_params['object']['type'] ) && in_array( $json_params['object']['type'], array( 'Accept', 'Undo' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the event object.
|
||||
*
|
||||
* @param bool $valid The validation state.
|
||||
* @param string $param The object parameter.
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return bool|WP_Error The validation state: true if valid, false if not.
|
||||
*/
|
||||
public static function validate_event_object( $valid, $param, $request ) {
|
||||
$json_params = $request->get_json_params();
|
||||
|
||||
// Check if we should continue with the validation.
|
||||
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;
|
||||
}
|
||||
|
||||
return self::is_valid_activitypub_event_object( $json_params['object'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the object is a valid ActivityPub event.
|
||||
*
|
||||
* @param array $event_object The (event) object as an associative array.
|
||||
* @return bool|WP_Error True if the object is an valid ActivityPub Event, false or WP_Error if not.
|
||||
*/
|
||||
public static function is_valid_activitypub_event_object( $event_object ) {
|
||||
if ( ! is_array( $event_object ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$required = array(
|
||||
'id',
|
||||
'startTime',
|
||||
'name',
|
||||
);
|
||||
|
||||
if ( array_intersect( $required, array_keys( $event_object ) ) !== $required ) {
|
||||
return new WP_Error(
|
||||
'event_bridge_for_activitypub_invalid_event_object',
|
||||
__( 'The Event object is missing a required attribute.', 'event-bridge-for-activitypub' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! self::is_valid_activitypub_time_string( $event_object['startTime'] ) ) {
|
||||
return new WP_Error(
|
||||
'event_bridge_for_activitypub_event_object_is_not_in_the_future',
|
||||
__( 'Ignoring event that has already started.', 'event-bridge-for-activitypub' )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given DateTime is already passed.
|
||||
*
|
||||
* @param string|DateTime $time The ActivityPub like time string or DateTime object.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_time_passed( $time ) {
|
||||
if ( ! $time instanceof DateTime ) {
|
||||
// Create a DateTime object from the ActivityPub time string.
|
||||
$time = new DateTime( $time, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an Event is an ongoing or future event.
|
||||
*
|
||||
* @param array $event_object The ActivityPub Event as an associative array.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_ongoing_or_future_event( $event_object ) {
|
||||
if ( isset( $event_object['endTime'] ) ) {
|
||||
$time = $event_object['endTime'];
|
||||
} else {
|
||||
$time = new DateTime( $event_object['startTime'], new DateTimeZone( 'UTC' ) ) + 3 * HOUR_IN_SECONDS;
|
||||
}
|
||||
return ! self::is_time_passed( $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_ids = array_keys( Event_Sources_Collection::get_event_sources() );
|
||||
if ( in_array( $actor_id, $event_sources_ids, true ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
263
includes/class-outbox-parser.php
Normal file
263
includes/class-outbox-parser.php
Normal file
|
@ -0,0 +1,263 @@
|
|||
<?php
|
||||
/**
|
||||
* Class file for parsing an ActivityPub outbox for Events.
|
||||
*
|
||||
* The main external entry function is `backfill_events`.
|
||||
* The function `import_events_from_outbox` is used for delaying the parsing via schedules.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
use Activitypub\Http;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
|
||||
/**
|
||||
* Class for parsing an ActivityPub outbox for Events.
|
||||
*
|
||||
* The main external entry function is `backfill_events`.
|
||||
* The function `import_events_from_outbox` is used for delaying the parsing via schedules.
|
||||
*/
|
||||
class Outbox_Parser {
|
||||
/**
|
||||
* Maximum number of events to backfill per actor.
|
||||
*/
|
||||
const MAX_EVENTS_TO_IMPORT = 20;
|
||||
|
||||
/**
|
||||
* Init actions.
|
||||
*/
|
||||
public static function init() {
|
||||
// Add action for backfilling the events.
|
||||
\add_action( 'event_bridge_for_activitypub_backfill_events', array( self::class, 'backfill_events' ), 10, 1 );
|
||||
\add_action( 'event_bridge_for_activitypub_import_events_from_outbox', array( self::class, 'import_events_from_outbox' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the backfilling of events via the outbox of an ActivityPub actor.
|
||||
*
|
||||
* @param Event_Source $event_source The Event Source we want to backfill the events for.
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public static function backfill_events( $event_source ) {
|
||||
$outbox_url = $event_source->get_outbox();
|
||||
|
||||
if ( ! $outbox_url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule the import of events via the outbox.
|
||||
return self::queue_importing_from_outbox( $outbox_url, $event_source, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import events from an outbox: OrderedCollection or OrderedCollectionPage.
|
||||
*
|
||||
* @param string $url The url of the current page or outbox.
|
||||
* @param Event_Source $event_source The event source that the outbox belongs to.
|
||||
* @return void
|
||||
*/
|
||||
public static function import_events_from_outbox( $url, $event_source ) {
|
||||
$outbox = self::fetch_outbox( $url );
|
||||
|
||||
if ( ! $outbox ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_count = self::get_import_count( $event_source );
|
||||
|
||||
if ( $current_count >= self::MAX_EVENTS_TO_IMPORT ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process orderedItems if they exist (non-paginated outbox).
|
||||
if ( isset( $outbox['orderedItems'] ) && is_array( $outbox['orderedItems'] ) ) {
|
||||
$current_count += self::import_events_from_items(
|
||||
$outbox['orderedItems'],
|
||||
$event_source,
|
||||
self::MAX_EVENTS_TO_IMPORT - $current_count
|
||||
);
|
||||
}
|
||||
|
||||
self::update_import_count( $event_source, $current_count );
|
||||
|
||||
// If the count is already exceeded abort here.
|
||||
if ( $current_count >= self::MAX_EVENTS_TO_IMPORT ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get next page and if it exists schedule the import of next page.
|
||||
$pagination_url = self::get_pagination_url( $outbox );
|
||||
|
||||
if ( $pagination_url ) {
|
||||
self::queue_importing_from_outbox( $pagination_url, $event_source );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an Activity is of type Update or Create.
|
||||
*
|
||||
* @param array $activity The Activity as associative array.
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_create_or_update_activity( $activity ) {
|
||||
if ( ! isset( $activity['type'] ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( in_array( $activity['type'], array( 'Update', 'Create' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses items from an Collection, OrderedCollection, CollectionPage or OrderedCollectionPage.
|
||||
*
|
||||
* @param array $items The items as an associative array.
|
||||
* @param int $max_items The maximum number of items to parse.
|
||||
* @return array Parsed events from the collection.
|
||||
*/
|
||||
private static function parse_outbox_items_for_events( $items, $max_items ) {
|
||||
$parsed_events = array();
|
||||
|
||||
foreach ( $items as $activity ) {
|
||||
// Abort if we have exceeded the maximal events to return.
|
||||
if ( $max_items > 0 && count( $parsed_events ) >= $max_items ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if it is a create or update Activity.
|
||||
if ( ! self::is_create_or_update_activity( $activity ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If no object is set we cannot process anything.
|
||||
if ( ! isset( $activity['object'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the Event object meets the minimum requirements and is valid.
|
||||
$is_valid = Event_Sources::is_valid_activitypub_event_object( $activity['object'] );
|
||||
if ( ! $is_valid || \is_wp_error( $is_valid ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the event is in the future or ongoing.
|
||||
if ( Event_Sources::is_ongoing_or_future_event( $activity['object'] ) ) {
|
||||
$parsed_events[] = $activity['object'];
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed_events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import events from the items of an outbox.
|
||||
*
|
||||
* @param array $items The items/orderedItems as an associative array.
|
||||
* @param Event_Source $event_source The Event Source the items belong to.
|
||||
* @param int $limit The limit of how many events to save locally.
|
||||
* @return int The number of saved events (at least attempted).
|
||||
*/
|
||||
private static function import_events_from_items( $items, $event_source, $limit = -1 ) {
|
||||
$events = self::parse_outbox_items_for_events( $items, $limit );
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$imported_count = 0;
|
||||
|
||||
foreach ( $events as $event ) {
|
||||
$transmogrifier->save( $event, $event_source );
|
||||
++$imported_count;
|
||||
if ( $limit > 0 && $imported_count >= $limit ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $imported_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the import of events from an outbox OrderedCollection or OrderedCollectionPage.
|
||||
*
|
||||
* @param string $url The url of the current page or outbox.
|
||||
* @param Event_Source $event_source The Event Source that owns the outbox.
|
||||
* @param int $delay The delay of the current time in seconds.
|
||||
* @return void
|
||||
*/
|
||||
private static function queue_importing_from_outbox( $url, $event_source, $delay = 10 ) {
|
||||
$hook = 'event_bridge_for_activitypub_import_events_from_outbox';
|
||||
$args = array( $url, $event_source );
|
||||
|
||||
if ( \wp_next_scheduled( $hook, $args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return \wp_schedule_single_event( \time() + $delay, $hook, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current import count for the actor.
|
||||
*
|
||||
* @param Event_Source $event_source The event source.
|
||||
* @return int The current count of imported events.
|
||||
*/
|
||||
private static function get_import_count( $event_source ) {
|
||||
return (int) \get_post_meta( $event_source->get__id(), '_event_bridge_for_activitypub_event_count', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the import count for an event source..
|
||||
*
|
||||
* @param Event_Source $event_source The event source.
|
||||
* @param int $count The new count of imported events.
|
||||
* @return void
|
||||
*/
|
||||
private static function update_import_count( $event_source, $count ) {
|
||||
\update_post_meta( $event_source->get__id(), '_event_bridge_for_activitypub_event_count', $count );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the outbox from the given URL.
|
||||
*
|
||||
* @param string $url The URL of the outbox.
|
||||
* @return array|null The decoded outbox data, or null if fetching fails.
|
||||
*/
|
||||
private static function fetch_outbox( $url ) {
|
||||
$response = Http::get( $url );
|
||||
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$outbox = \wp_remote_retrieve_body( $response );
|
||||
$outbox = \json_decode( $outbox, true );
|
||||
|
||||
return ( is_array( $outbox ) && isset( $outbox['type'] ) && isset( $outbox['id'] ) ) ? $outbox : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pagination URL from the outbox.
|
||||
*
|
||||
* @param array $outbox The outbox data.
|
||||
* @return string|null The pagination URL, or null if not found.
|
||||
*/
|
||||
private static function get_pagination_url( $outbox ) {
|
||||
if ( 'OrderedCollection' === $outbox['type'] && ! empty( $outbox['first'] ) && is_string( $outbox['first'] ) ) {
|
||||
return $outbox['first'];
|
||||
}
|
||||
|
||||
if ( 'OrderedCollectionPage' === $outbox['type'] && ! empty( $outbox['next'] ) && is_string( $outbox['next'] ) ) {
|
||||
return $outbox['next'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ namespace Event_Bridge_For_ActivityPub;
|
|||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Feature_Event_Sources;
|
||||
|
||||
/**
|
||||
* Class responsible for the ActivityPui Event Extension related Settings.
|
||||
*
|
||||
|
@ -91,6 +93,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' => 'integer',
|
||||
'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' => 'string',
|
||||
'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' ) );
|
||||
}
|
||||
|
||||
|
@ -96,6 +92,15 @@ class Setup {
|
|||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter function for whether the ActivityPub plugin is active.
|
||||
*
|
||||
* @return bool True when the ActivityPub plugin is active.
|
||||
*/
|
||||
public function is_activitypub_plugin_active(): bool {
|
||||
return $this->activitypub_plugin_is_active;
|
||||
}
|
||||
|
||||
/**
|
||||
* LooksUp the current version of the ActivityPub.
|
||||
*
|
||||
|
@ -111,7 +116,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,17 +125,17 @@ 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\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\Event_Organiser::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -139,6 +144,9 @@ class Setup {
|
|||
* @return void
|
||||
*/
|
||||
public function redetect_active_event_plugins(): void {
|
||||
if ( ! $this->activitypub_plugin_is_active ) {
|
||||
return;
|
||||
}
|
||||
delete_transient( 'event_bridge_for_activitypub_active_event_plugins' );
|
||||
$this->detect_active_event_plugins();
|
||||
}
|
||||
|
@ -156,6 +164,11 @@ class Setup {
|
|||
return $active_event_plugins;
|
||||
}
|
||||
|
||||
// Detection will fail in case the ActivityPub plugin is not active.
|
||||
if ( ! $this->activitypub_plugin_is_active ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
@ -163,13 +176,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 );
|
||||
|
@ -178,7 +191,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.
|
||||
*
|
||||
|
@ -187,16 +216,27 @@ class Setup {
|
|||
* @return void
|
||||
*/
|
||||
public function setup_hooks(): void {
|
||||
$this->detect_active_event_plugins();
|
||||
// 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();
|
||||
|
||||
// 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 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_action( 'admin_init', array( $this, 'do_admin_notices' ) );
|
||||
add_action( 'admin_init', array( Settings::class, 'register_settings' ) );
|
||||
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,
|
||||
|
@ -204,17 +244,35 @@ class Setup {
|
|||
);
|
||||
|
||||
// If we don't have any active event plugins, or the ActivityPub plugin is not enabled, abort here.
|
||||
if ( empty( $this->active_event_plugins ) || ! $this->activitypub_plugin_is_active ) {
|
||||
if ( ! $this->activitypub_plugin_is_active ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect active supported event plugins.
|
||||
$this->detect_active_event_plugins();
|
||||
|
||||
// If we don't have any active event plugins, or the ActivityPub plugin is not enabled, abort here.
|
||||
if ( empty( $this->active_event_plugins ) ) {
|
||||
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();
|
||||
}
|
||||
|
||||
// Initialize writing of debug logs.
|
||||
Debug::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 );
|
||||
}
|
||||
|
||||
|
@ -259,15 +317,17 @@ class Setup {
|
|||
// Check if any general admin notices are needed and add actions to insert the needed admin notices.
|
||||
if ( ! $this->activitypub_plugin_is_active ) {
|
||||
// The ActivityPub plugin is not active.
|
||||
add_action( 'admin_notices', array( 'Event_Bridge_For_ActivityPub\Admin\General_Admin_Notices', 'activitypub_plugin_not_enabled' ), 10, 1 );
|
||||
add_action( 'admin_notices', array( General_Admin_Notices::class, 'activitypub_plugin_not_enabled' ), 10, 1 );
|
||||
return;
|
||||
}
|
||||
if ( ! version_compare( $this->activitypub_plugin_version, EVENT_BRIDGE_FOR_ACTIVITYPUB_ACTIVITYPUB_PLUGIN_MIN_VERSION ) ) {
|
||||
// The ActivityPub plugin is too old.
|
||||
add_action( 'admin_notices', array( 'Event_Bridge_For_ActivityPub\Admin\General_Admin_Notices', 'activitypub_plugin_version_too_old' ), 10, 1 );
|
||||
add_action( 'admin_notices', array( General_Admin_Notices::class, 'activitypub_plugin_version_too_old' ), 10, 1 );
|
||||
return;
|
||||
}
|
||||
if ( empty( $this->active_event_plugins ) ) {
|
||||
// No supported Event Plugin is active.
|
||||
add_action( 'admin_notices', array( 'Event_Bridge_For_ActivityPub\Admin\General_Admin_Notices', 'no_supported_event_plugin_active' ), 10, 1 );
|
||||
add_action( 'admin_notices', array( General_Admin_Notices::class, 'no_supported_event_plugin_active' ), 10, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,10 +349,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() );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,4 +410,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,23 @@ 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;
|
||||
|
||||
/**
|
||||
* In case an event plugin used a custom post type for the locations/venues return it here.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_location_post_type() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
|
@ -56,7 +76,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 +98,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() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,82 @@ 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 $ends_before_time Filter: only get events that ended before that datetime as unix-time.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_cached_remote_events( $ends_before_time ): array {
|
||||
global $wpdb;
|
||||
|
||||
$ends_before_time_string = gmdate( 'Y-m-d H:i:s', $ends_before_time );
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT DISTINCT {$wpdb->prefix}posts.ID
|
||||
FROM {$wpdb->prefix}posts
|
||||
LEFT JOIN {$wpdb->prefix}gatherpress_events
|
||||
ON {$wpdb->prefix}posts.ID = {$wpdb->prefix}gatherpress_events.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta
|
||||
ON {$wpdb->prefix}posts.ID = {$wpdb->prefix}postmeta.post_id
|
||||
WHERE {$wpdb->prefix}posts.post_type = 'gatherpress_event'
|
||||
AND {$wpdb->prefix}posts.post_status = 'publish'
|
||||
AND {$wpdb->prefix}gatherpress_events.datetime_end_gmt <= %s
|
||||
AND {$wpdb->prefix}postmeta.meta_key = '_event_bridge_for_activitypub_event_source'
|
||||
",
|
||||
$ends_before_time_string
|
||||
),
|
||||
ARRAY_N
|
||||
);
|
||||
|
||||
$post_ids = array_column( $results, 0 );
|
||||
|
||||
return $post_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init function: force displaying online event link for federated events.
|
||||
*/
|
||||
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_event_source', 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,34 @@ 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() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location/venue post type used by tribe.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_location_post_type() {
|
||||
return class_exists( '\Tribe__Events__Venue' ) ? \Tribe__Events__Venue::POSTTYPE : 'tribe_venue';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
|
@ -55,11 +88,48 @@ 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 $ends_before_time Filter to only get events that ended before that datetime as unix-time.
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
public static function get_cached_remote_events( $ends_before_time ): array {
|
||||
add_filter(
|
||||
'tribe_repository_events_apply_modifier_schema_entry',
|
||||
array( self::class, 'add_is_activitypub_remote_cached_to_query' ),
|
||||
10,
|
||||
1
|
||||
);
|
||||
|
||||
$events = tribe_events()->where( 'ends_before', $ends_before_time )->get_ids();
|
||||
|
||||
remove_filter(
|
||||
'tribe_repository_events_apply_modifier_schema_entry',
|
||||
array( self::class, 'add_is_activitypub_remote_cached_to_query' )
|
||||
);
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only show remote cached ActivityPub events in Tribe query.
|
||||
*
|
||||
* @param array $schema_entry The current schema entry.
|
||||
* @return array The modified schema entry.
|
||||
*/
|
||||
public static function add_is_activitypub_remote_cached_to_query( $schema_entry ) {
|
||||
$schema_entry['meta_query']['is-remote-cached'] = array(
|
||||
'key' => '_event_bridge_for_activitypub_event_source',
|
||||
'compare' => 'EXISTS',
|
||||
);
|
||||
return $schema_entry;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,22 @@
|
|||
|
||||
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;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\VS_Event_List as VS_Event_List_Transmogrifier;
|
||||
use WP_Query;
|
||||
|
||||
// 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 implements Feature_Event_Sources {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
|
@ -52,15 +55,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 +63,55 @@ 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() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Transmogrifier for The_Events_Calendar.
|
||||
*/
|
||||
public static function get_transmogrifier(): VS_Event_List_Transmogrifier {
|
||||
return new VS_Event_List_Transmogrifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of Post IDs of events that have ended.
|
||||
*
|
||||
* @param int $ends_before_time Filter to only get events that ended before that datetime as unix-time.
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
public static function get_cached_remote_events( $ends_before_time ): array {
|
||||
$args = array(
|
||||
'post_type' => 'event',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => '_event_bridge_for_activitypub_event_source',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
array(
|
||||
'key' => 'event-date',
|
||||
'value' => $ends_before_time,
|
||||
'type' => 'NUMERIC',
|
||||
'compare' => '<',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$query = new WP_Query( $args );
|
||||
|
||||
$post_ids = $query->posts;
|
||||
|
||||
return $post_ids;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 $ends_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( $ends_before_time ): array;
|
||||
}
|
233
includes/table/class-event-sources.php
Normal file
233
includes/table/class-event-sources.php
Normal file
|
@ -0,0 +1,233 @@
|
|||
<?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' ),
|
||||
'accepted' => \__( 'Follow', '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 );
|
||||
$total_count = $event_sources['total'];
|
||||
|
||||
$this->items = array();
|
||||
$this->set_pagination_args(
|
||||
array(
|
||||
'total_items' => $total_count,
|
||||
'total_pages' => ceil( $total_count / $per_page ),
|
||||
'per_page' => $per_page,
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $event_sources['actors'] as $event_source ) {
|
||||
$item = array(
|
||||
'icon' => esc_attr( $event_source->get_icon_url() ),
|
||||
'name' => esc_attr( $event_source->get_name() ),
|
||||
'url' => esc_attr( object_to_uri( $event_source->get_id() ) ),
|
||||
'accepted' => esc_attr( get_post_meta( $event_source->get__id(), '_event_bridge_for_activitypub_accept_of_follow', true ) ),
|
||||
'identifier' => esc_attr( $event_source->get_id() ),
|
||||
'published' => esc_attr( $event_source->get_published() ),
|
||||
'modified' => esc_attr( $event_source->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_accepted( $item ) {
|
||||
if ( $item['accepted'] ) {
|
||||
return esc_html__( 'Accepted', 'event-bridge-for-activitypub' );
|
||||
} else {
|
||||
return esc_html__( 'Pending', 'event-bridge-for-activitypub' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
>
|
||||
<testsuites>
|
||||
<testsuite name="testing">
|
||||
<directory prefix="test-" suffix=".php">./tests/</directory>
|
||||
<directory prefix="class-test-" suffix=".php">./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
|
|
18
readme.txt
18
readme.txt
|
@ -17,13 +17,18 @@ This plugin is not an event managing plugin but an add-on to popular event plugi
|
|||
|
||||
= Supported Event Plugins =
|
||||
|
||||
Full support (including importing events from the Fediverse):
|
||||
|
||||
* [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/)
|
||||
* [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/)
|
||||
* [GatherPress](https://gatherpress.org/)
|
||||
|
||||
Basic support (outgoing events):
|
||||
|
||||
* [Events Manager](https://de.wordpress.org/plugins/events-manager/)
|
||||
* [WP Event Manager](https://de.wordpress.org/plugins/wp-event-manager/)
|
||||
* [Eventin](https://de.wordpress.org/plugins/wp-event-solution/)
|
||||
* [Modern Events Calendar Lite](https://webnus.net/modern-events-calendar/)
|
||||
* [GatherPress](https://gatherpress.org/)
|
||||
* [Event Organiser](https://wordpress.org/plugins/event-organiser/)
|
||||
|
||||
= How It Works =
|
||||
|
@ -48,19 +53,26 @@ Even platforms that don't yet fully support events, like [Mastodon](https://join
|
|||
|
||||
**Event Reminders for Your Followers:** Often, events are planned well in advance. To keep your followers informed right in time, you can set up reminders that are supposed to trigger the events showing up in their timelines right before the event starts. At the moment this reminder is implemented as a self-boost of your original event post. While this feature may behave differently across various platforms, we are working on a more robust solution that will let you schedule dedicated reminder notes that appear in all followers' timelines.
|
||||
|
||||
**External Event Sources:** This functionality is only available for a subset of the supported event plugins. It enables your WordPress site to act as a hub for displaying events from other ActivityPub profiles, aggregating them into a cohesive calendar view.
|
||||
|
||||
== Installation ==
|
||||
|
||||
This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/). Additionally, you need to use one of the supported event Plugins.
|
||||
|
||||
= Supported Event Plugins =
|
||||
|
||||
Full support (including importing events from the Fediverse):
|
||||
|
||||
* [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/)
|
||||
* [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/)
|
||||
* [GatherPress](https://gatherpress.org/)
|
||||
|
||||
Basic support (outgoing events):
|
||||
|
||||
* [Events Manager](https://de.wordpress.org/plugins/events-manager/)
|
||||
* [WP Event Manager](https://de.wordpress.org/plugins/wp-event-manager/)
|
||||
* [Eventin](https://de.wordpress.org/plugins/wp-event-solution/)
|
||||
* [Modern Events Calendar Lite](https://webnus.net/modern-events-calendar/)
|
||||
* [GatherPress](https://gatherpress.org/)
|
||||
* [Event Organiser](https://wordpress.org/plugins/event-organiser/)
|
||||
|
||||
= Configuration =
|
||||
|
@ -107,6 +119,6 @@ The development of this WordPress plugin was funded through the [NGI0 Entrust](h
|
|||
|
||||
* Fixed: Images of Acknowledgements in Admin UI
|
||||
|
||||
= [0.3.4] 2024-12-21 =
|
||||
= [0.3.4] - 2024-12-21 =
|
||||
|
||||
* Initial release on https://wordpress.org/
|
||||
|
|
|
@ -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">
|
||||
|
|
57
templates/event-sources.php
Normal file
57
templates/event-sources.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?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">
|
||||
<?php if ( defined( 'ACTIVITYPUB_PLUGIN_VERSION' ) ) { ?>
|
||||
<!-- 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>
|
||||
<?php } ?>
|
||||
</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.
|
||||
*/
|
||||
|
@ -22,6 +23,9 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
|||
);
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
$activitypub_plugin_is_active = Setup::get_instance()->is_activitypub_plugin_active();
|
||||
|
||||
if ( ! isset( $args ) || ! array_key_exists( 'event_terms', $args ) ) {
|
||||
return;
|
||||
|
@ -30,6 +34,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 +103,124 @@ $current_category_mapping = \get_option( 'event_bridge_for_activitypub_ev
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ( $activitypub_plugin_is_active ) { ?>
|
||||
<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, get_option( 'event_bridge_for_activitypub_integration_used_for_event_sources_feature', $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>
|
||||
<?php } ?>
|
||||
|
||||
<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 +276,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.
|
||||
|
@ -22,6 +24,7 @@ use Event_Bridge_For_ActivityPub\Admin\Health_Check;
|
|||
);
|
||||
|
||||
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
|
||||
$activitypub_plugin_is_active = Setup::get_instance()->is_activitypub_plugin_active();
|
||||
$event_bridge_for_activitypub_status_ok = true;
|
||||
$example_event_post = Health_Check::get_most_recent_event_posts();
|
||||
|
||||
|
@ -43,7 +46,10 @@ WP_Filesystem();
|
|||
<h2><?php \esc_html_e( 'Status', 'event-bridge-for-activitypub' ); ?></h2>
|
||||
<p><?php \esc_html_e( 'The Event Bridge for ActivityPub detected the following (activated) event plugins:', 'event-bridge-for-activitypub' ); ?></p>
|
||||
<?php
|
||||
if ( empty( $active_event_plugins ) ) {
|
||||
if ( ! $activitypub_plugin_is_active ) {
|
||||
$notice = General_Admin_Notices::get_admin_notice_activitypub_plugin_not_enabled();
|
||||
echo '<p>⚠' . \wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ) . '</p>';
|
||||
} elseif ( empty( $active_event_plugins ) ) {
|
||||
$notice = General_Admin_Notices::get_admin_notice_no_supported_event_plugin_active();
|
||||
echo '<p>⚠' . \wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ) . '</p>';
|
||||
}
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
// Defined here because setting them in .wp-env.json doesn't work for some reason.
|
||||
\defined( 'WP_TESTS_DOMAIN' ) ?? \define( 'WP_TESTS_DOMAIN', 'example.org' );
|
||||
\defined( 'WP_SITEURL' ) ?? \define( 'WP_SITEURL', 'http://example.org' );
|
||||
\defined( 'WP_HOME' ) ?? \define( 'WP_HOME', 'http://example.org' );
|
||||
|
||||
$_tests_dir = getenv( 'WP_TESTS_DIR' );
|
||||
|
||||
if ( ! $_tests_dir ) {
|
||||
|
@ -67,9 +72,21 @@ function _manually_load_plugin() {
|
|||
switch ( $event_bridge_for_activitypub_integration_filter ) {
|
||||
case 'the_events_calendar':
|
||||
$plugin_file = 'the-events-calendar/the-events-calendar.php';
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\The_Events_Calendar::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
break;
|
||||
case 'vs_event_list':
|
||||
$plugin_file = 'very-simple-event-list/vsel.php';
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
break;
|
||||
case 'events_manager':
|
||||
$plugin_file = 'events-manager/events-manager.php';
|
||||
|
@ -82,6 +99,12 @@ function _manually_load_plugin() {
|
|||
break;
|
||||
case 'gatherpress':
|
||||
$plugin_file = 'gatherpress/gatherpress.php';
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
break;
|
||||
case 'wp_event_manager':
|
||||
$plugin_file = 'wp-event-manager/wp-event-manager.php';
|
||||
|
@ -97,10 +120,15 @@ function _manually_load_plugin() {
|
|||
if ( $plugin_file ) {
|
||||
_manually_load_event_plugin( $plugin_file );
|
||||
} else {
|
||||
// For all other tests we mainly use the Events Calendar as a reference.
|
||||
// For all other tests we mainly use the Events Calendar and GatherPress as reference.
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
_manually_load_event_plugin( 'the-events-calendar/the-events-calendar.php' );
|
||||
_manually_load_event_plugin( 'very-simple-event-list/vsel.php' );
|
||||
|
||||
_manually_load_event_plugin( 'gatherpress/gatherpress.php' );
|
||||
}
|
||||
|
||||
// Hot fix that allows using Events Manager within unit tests, because the em_init() is later not run as admin.
|
||||
|
@ -119,6 +147,9 @@ function _manually_load_plugin() {
|
|||
|
||||
// At last manually load our WordPress plugin.
|
||||
require dirname( __DIR__ ) . '/event-bridge-for-activitypub.php';
|
||||
|
||||
// Always manually load the ActivityPub plugin.
|
||||
require_once $plugin_dir . 'activitypub/activitypub.php';
|
||||
}
|
||||
|
||||
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Event Sources collection.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Collection;
|
||||
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Test class for the Event Sources collection.
|
||||
*
|
||||
* @coversDefaultClass \Event_Bridge_For_ActivityPub\ActivityPub\Collections\Event_Sources
|
||||
*/
|
||||
class Test_Event_Sources extends \WP_UnitTestCase {
|
||||
const FOLLOWED_ACTOR_1 = array(
|
||||
'id' => 'https://remote.example/@organizer',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer/outbox',
|
||||
'name' => 'The Organizer',
|
||||
'summary' => 'Just a random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
const FOLLOWED_ACTOR_2 = array(
|
||||
'id' => 'https://remote.example/@organizer2',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer2/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer2/outbox',
|
||||
'name' => 'The Second Organizer',
|
||||
'summary' => 'Just a second random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
/**
|
||||
* REST Server.
|
||||
*
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( ! defined( 'GATHERPRESS_CORE_FILE' ) ) {
|
||||
self::markTestSkipped( 'GatherPress plugin is not active.' );
|
||||
}
|
||||
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new WP_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
|
||||
// Mock the plugin activation.
|
||||
\GatherPress\Core\Setup::get_instance()->activate_gatherpress_plugin( false );
|
||||
|
||||
// Make sure that ActivityPub support is enabled for GatherPress.
|
||||
$aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
|
||||
$aec->activate_activitypub_support_for_active_event_plugins();
|
||||
|
||||
// Add event sources (ActivityPub followers).
|
||||
_delete_all_posts();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR_1 )->save();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR_2 )->save();
|
||||
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing the fetching of event sources from the database.
|
||||
*
|
||||
* @covers \Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources::get_event_sources_with_count
|
||||
* @covers \Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources::get_event_sources
|
||||
*/
|
||||
public function test_get_event_sources_with_count() {
|
||||
\delete_transient( 'event_bridge_for_activitypub_event_sources' );
|
||||
$event_sources = \Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources::get_event_sources();
|
||||
|
||||
$this->assertCount( 2, $event_sources );
|
||||
|
||||
$this->assertArrayHasKey( self::FOLLOWED_ACTOR_1['id'], $event_sources );
|
||||
$this->assertArrayHasKey( self::FOLLOWED_ACTOR_2['id'], $event_sources );
|
||||
}
|
||||
}
|
|
@ -5,10 +5,14 @@
|
|||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transformer;
|
||||
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Sample test case.
|
||||
*/
|
||||
class Test_Event_Organiser extends WP_UnitTestCase {
|
||||
class Test_Event_Organiser extends \WP_UnitTestCase {
|
||||
/**
|
||||
* Override the setup function, so that tests don't run if the Events Calendar is not active.
|
||||
*/
|
||||
|
@ -24,7 +28,7 @@ class Test_Event_Organiser extends WP_UnitTestCase {
|
|||
$aec->activate_activitypub_support_for_active_event_plugins();
|
||||
|
||||
// Run the install script just in time which makes sure the custom tables exist and more.
|
||||
eventorganiser_install();
|
||||
\eventorganiser_install();
|
||||
|
||||
// Delete all posts afterwards.
|
||||
_delete_all_posts();
|
||||
|
@ -44,8 +48,8 @@ class Test_Event_Organiser extends WP_UnitTestCase {
|
|||
$this->assertContains( 'event', get_option( 'activitypub_support_post_types' ) );
|
||||
|
||||
$event_data = array(
|
||||
'start' => new DateTime( '+10 days 15:00:00', eo_get_blog_timezone() ),
|
||||
'end' => new DateTime( '+10 days 16:00:00', eo_get_blog_timezone() ),
|
||||
'start' => new DateTime( '+10 days 15:00:00', \eo_get_blog_timezone() ),
|
||||
'end' => new DateTime( '+10 days 16:00:00', \eo_get_blog_timezone() ),
|
||||
'all_day' => 0,
|
||||
'schedule' => 'once',
|
||||
);
|
||||
|
@ -56,13 +60,13 @@ class Test_Event_Organiser extends WP_UnitTestCase {
|
|||
'post_status' => 'publish',
|
||||
);
|
||||
|
||||
$post_id = eo_insert_event( $post_data, $event_data );
|
||||
$post_id = \eo_insert_event( $post_data, $event_data );
|
||||
|
||||
// Call the transformer Factory.
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,7 +87,7 @@ class Test_Event_Organiser extends WP_UnitTestCase {
|
|||
'post_status' => 'publish',
|
||||
);
|
||||
|
||||
$post_id = eo_insert_event( $post_data, $event_data );
|
||||
$post_id = \eo_insert_event( $post_data, $event_data );
|
||||
|
||||
// Call the transformer Factory.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $post_id ) )->to_object()->to_array();
|
||||
|
@ -91,9 +95,9 @@ class Test_Event_Organiser extends WP_UnitTestCase {
|
|||
// Check that the event ActivityStreams representation contains everything as expected.
|
||||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'Unit Test Event', $event_array['name'] );
|
||||
$this->assertEquals( 'Unit Test description.', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( 'Unit Test description.', \wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertArrayNotHasKey( 'location', $event_array );
|
||||
}
|
||||
|
@ -114,7 +118,7 @@ class Test_Event_Organiser extends WP_UnitTestCase {
|
|||
'longitude' => 15.421371,
|
||||
);
|
||||
$venue_name = 'Fediverse Venue';
|
||||
$venue = eo_insert_venue( $venue_name, $venue_args );
|
||||
$venue = \eo_insert_venue( $venue_name, $venue_args );
|
||||
|
||||
// Mock Event.
|
||||
$event_data = array(
|
||||
|
@ -128,8 +132,8 @@ class Test_Event_Organiser extends WP_UnitTestCase {
|
|||
'post_content' => 'Unit Test description.',
|
||||
'post_status' => 'publish',
|
||||
);
|
||||
$post_id = eo_insert_event( $post_data, $event_data );
|
||||
wp_set_object_terms( $post_id, $venue['term_id'], 'event-venue' );
|
||||
$post_id = \eo_insert_event( $post_data, $event_data );
|
||||
\wp_set_object_terms( $post_id, $venue['term_id'], 'event-venue' );
|
||||
|
||||
// Call the transformer Factory.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $post_id ) )->to_object()->to_array();
|
||||
|
@ -137,12 +141,12 @@ class Test_Event_Organiser extends WP_UnitTestCase {
|
|||
// Check that the event ActivityStreams representation contains everything as expected.
|
||||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'Unit Test Event', $event_array['name'] );
|
||||
$this->assertEquals( 'Unit Test description.', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( 'Unit Test description.', \wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertArrayHasKey( 'location', $event_array );
|
||||
$this->assertEquals( $venue_args['description'], wp_strip_all_tags( $event_array['location']['content'] ) );
|
||||
$this->assertEquals( $venue_args['description'], \wp_strip_all_tags( $event_array['location']['content'] ) );
|
||||
$this->assertEquals( $venue_args['address'], $event_array['location']['address']['streetAddress'] );
|
||||
$this->assertEquals( $venue_args['city'], $event_array['location']['address']['addressLocality'] );
|
||||
$this->assertEquals( $venue_args['state'], $event_array['location']['address']['addressRegion'] );
|
|
@ -6,14 +6,14 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
use Activitypub\Shortcodes;
|
||||
namespace Event_Bridge_For_ActivityPup\Tests\ActivityPub\Transformer;
|
||||
|
||||
use Event_Bridge_For_ActivityPup\Tests\ActivityPub\Transformer\Test_The_Events_Calendar;
|
||||
|
||||
/**
|
||||
* Test class for Activitypub Shortcodes.
|
||||
*
|
||||
* @coversDefaultClass \Activitypub\Shortcodes
|
||||
* Test class for Shortcodes.
|
||||
*/
|
||||
class Test_Activitypub_Event_Bridge_Shortcodes extends WP_UnitTestCase {
|
||||
class Test_Event extends \WP_UnitTestCase {
|
||||
/**
|
||||
* Override the setup function, so that tests don't run if the Events Calendar is not active.
|
||||
*/
|
||||
|
@ -36,31 +36,36 @@ class Test_Activitypub_Event_Bridge_Shortcodes extends WP_UnitTestCase {
|
|||
* Test the shortcode for rendering the events start time.
|
||||
*/
|
||||
public function test_start_time() {
|
||||
$event = Test_The_Events_Calendar::MOCKUP_EVENTS['minimal_event'];
|
||||
|
||||
// Create a The Events Calendar Event without content.
|
||||
$wp_object = tribe_events()
|
||||
->set_args( Test_The_Events_Calendar::MOCKUP_EVENTS['minimal_event'] )
|
||||
->set_args( $event )
|
||||
->create();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
$datetime_format = \get_option( 'date_format' ) . ' ' . \get_option( 'time_format' );
|
||||
$time_string = \wp_date( $datetime_format, \strtotime( $event['start_date'] ) );
|
||||
|
||||
$transformer->register_shortcodes();
|
||||
|
||||
$summary = '[ap_start_time]';
|
||||
$summary = do_shortcode( $summary );
|
||||
$this->assertEquals( '🗓️ Start: December 1, 2024 3:00 pm', $summary );
|
||||
$summary = \do_shortcode( $summary );
|
||||
$this->assertEquals( "🗓️ Start: {$time_string}", $summary );
|
||||
|
||||
$summary = '[ap_start_time icon="false"]';
|
||||
$summary = do_shortcode( $summary );
|
||||
$this->assertEquals( 'Start: December 1, 2024 3:00 pm', $summary );
|
||||
$summary = \do_shortcode( $summary );
|
||||
$this->assertEquals( "Start: {$time_string}", $summary );
|
||||
|
||||
$summary = '[ap_start_time icon="false" label="false"]';
|
||||
$summary = do_shortcode( $summary );
|
||||
$this->assertEquals( 'December 1, 2024 3:00 pm', $summary );
|
||||
$summary = \do_shortcode( $summary );
|
||||
$this->assertEquals( $time_string, $summary );
|
||||
|
||||
$transformer->unregister_shortcodes();
|
||||
}
|
||||
|
@ -69,31 +74,36 @@ class Test_Activitypub_Event_Bridge_Shortcodes extends WP_UnitTestCase {
|
|||
* Test the shortcode for rendering the events end time.
|
||||
*/
|
||||
public function test_end_time() {
|
||||
$event = Test_The_Events_Calendar::MOCKUP_EVENTS['minimal_event'];
|
||||
|
||||
// Create a The Events Calendar Event without content.
|
||||
$wp_object = tribe_events()
|
||||
->set_args( Test_The_Events_Calendar::MOCKUP_EVENTS['minimal_event'] )
|
||||
->set_args( $event )
|
||||
->create();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
$datetime_format = \get_option( 'date_format' ) . ' ' . \get_option( 'time_format' );
|
||||
$time_string = \wp_date( $datetime_format, \strtotime( $event['start_date'] ) + $event['duration'] );
|
||||
|
||||
$transformer->register_shortcodes();
|
||||
|
||||
$summary = '[ap_end_time]';
|
||||
$summary = do_shortcode( $summary );
|
||||
$this->assertEquals( '⏳ End: December 1, 2024 4:00 pm', $summary );
|
||||
$summary = \do_shortcode( $summary );
|
||||
$this->assertEquals( "⏳ End: {$time_string}", $summary );
|
||||
|
||||
$summary = '[ap_end_time icon="false"]';
|
||||
$summary = do_shortcode( $summary );
|
||||
$this->assertEquals( 'End: December 1, 2024 4:00 pm', $summary );
|
||||
$summary = \do_shortcode( $summary );
|
||||
$this->assertEquals( "End: {$time_string}", $summary );
|
||||
|
||||
$summary = '[ap_end_time icon="false" label="false"]';
|
||||
$summary = do_shortcode( $summary );
|
||||
$this->assertEquals( 'December 1, 2024 4:00 pm', $summary );
|
||||
$summary = \do_shortcode( $summary );
|
||||
$this->assertEquals( $time_string, $summary );
|
||||
|
||||
$transformer->unregister_shortcodes();
|
||||
}
|
||||
|
@ -110,7 +120,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 +148,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 +184,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;
|
||||
}
|
||||
|
|
@ -5,10 +5,12 @@
|
|||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transformer;
|
||||
|
||||
/**
|
||||
* Test cases for WP Event Solution.
|
||||
*/
|
||||
class Test_Eventin extends WP_UnitTestCase {
|
||||
class Test_Eventin extends \WP_UnitTestCase {
|
||||
/**
|
||||
* Basic Mock-up event.
|
||||
*/
|
||||
|
@ -54,7 +56,7 @@ class Test_Eventin extends WP_UnitTestCase {
|
|||
$this->assertEquals( 1, count( $active_event_plugins ) );
|
||||
|
||||
// Enable ActivityPub support for the event plugin.
|
||||
$this->assertContains( 'etn', get_option( 'activitypub_support_post_types' ) );
|
||||
$this->assertContains( 'etn', \get_option( 'activitypub_support_post_types' ) );
|
||||
|
||||
// Create a Eventin Event without content.
|
||||
$event = new \Etn\Core\Event\Event_Model();
|
||||
|
@ -64,7 +66,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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,12 +161,12 @@ class Test_Eventin extends WP_UnitTestCase {
|
|||
|
||||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'Eventin Test Event Title', $event_array['name'] );
|
||||
$this->assertEquals( 'Eventin Test Event Description', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '+10 days 15:00:00' ) ), $event_array['startTime'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '+10 days 16:00:00' ) ), $event_array['endTime'] );
|
||||
$this->assertEquals( 'Eventin Test Event Description', \wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( '+10 days 15:00:00' ) ), $event_array['startTime'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( '+10 days 16:00:00' ) ), $event_array['endTime'] );
|
||||
$this->assertEquals( 'Europe/Vienna', $event_array['timezone'] );
|
||||
$this->assertEquals( comments_open( $event->id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( comments_open( $event->id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( \comments_open( $event->id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( \comments_open( $event->id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertArrayHasKey( 'location', $event_array );
|
||||
$this->assertEquals( 'MEETING', $event_array['category'] );
|
|
@ -5,10 +5,12 @@
|
|||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transformer;
|
||||
|
||||
/**
|
||||
* Sample test case.
|
||||
*/
|
||||
class Test_Events_Manager extends WP_UnitTestCase {
|
||||
class Test_Events_Manager extends \WP_UnitTestCase {
|
||||
/**
|
||||
* Override the setup function, so that tests don't run if the Events Calendar is not active.
|
||||
*/
|
||||
|
@ -44,7 +46,7 @@ class Test_Events_Manager extends WP_UnitTestCase {
|
|||
$this->assertContains( EM_POST_TYPE_EVENT, get_option( 'activitypub_support_post_types' ) );
|
||||
|
||||
// Insert a new Event.
|
||||
$wp_post_id = wp_insert_post(
|
||||
$wp_post_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'Events Manager Test event',
|
||||
'post_status' => 'publish',
|
||||
|
@ -55,13 +57,13 @@ class Test_Events_Manager extends WP_UnitTestCase {
|
|||
)
|
||||
);
|
||||
|
||||
$wp_object = get_post( $wp_post_id );
|
||||
$wp_object = \get_post( $wp_post_id );
|
||||
|
||||
// Call the transformer Factory.
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,7 +71,7 @@ class Test_Events_Manager extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_of_minimal_event() {
|
||||
// Create mockup event.
|
||||
$event = new EM_Event();
|
||||
$event = new \EM_Event();
|
||||
$event->event_name = 'Events Manager Test event';
|
||||
$event->post_content = 'Event description';
|
||||
$event->event_start_date = gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) );
|
||||
|
@ -100,7 +102,7 @@ class Test_Events_Manager extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_of__full_event_with_location() {
|
||||
// Create a mockup location.
|
||||
$location = new EM_Location();
|
||||
$location = new \EM_Location();
|
||||
$location->location_name = 'Test location';
|
||||
$location->location_address = 'Test Address';
|
||||
$location->location_town = 'Test Town';
|
||||
|
@ -111,7 +113,7 @@ class Test_Events_Manager extends WP_UnitTestCase {
|
|||
$this->assertTrue( $location->save() );
|
||||
|
||||
// Create mockup event.
|
||||
$event = new EM_Event();
|
||||
$event = new \EM_Event();
|
||||
$event->event_name = 'Events Manager Test event';
|
||||
$event->post_content = 'Event description';
|
||||
$event->location_id = $location->location_id;
|
||||
|
@ -149,21 +151,21 @@ class Test_Events_Manager extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_of_event_with_name_only_location() {
|
||||
// Create a mockup location.
|
||||
$location = new EM_Location();
|
||||
$location = new \EM_Location();
|
||||
$location->location_name = 'Name only location';
|
||||
$this->assertTrue( $location->save() );
|
||||
|
||||
// Create mockup event.
|
||||
$event = new EM_Event();
|
||||
$event = new \EM_Event();
|
||||
$event->event_name = 'Events Manager Test event';
|
||||
$event->post_content = 'Event description';
|
||||
$event->location_id = $location->location_id;
|
||||
$event->event_start_date = gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) );
|
||||
$event->event_end_date = gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) );
|
||||
$event->event_start_date = \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) );
|
||||
$event->event_end_date = \gmdate( 'Y-m-d', \strtotime( '+10 days 16:00:00' ) );
|
||||
$event->event_start_time = '15:00:00';
|
||||
$event->event_end_time = '16:00:00';
|
||||
$event->start = strtotime( $event->event_start_date . ' ' . $event->event_start_time );
|
||||
$event->end = strtotime( $event->event_end_date . ' ' . $event->event_end_time );
|
||||
$event->start = \strtotime( $event->event_start_date . ' ' . $event->event_start_time );
|
||||
$event->end = \strtotime( $event->event_end_date . ' ' . $event->event_end_time );
|
||||
$event->force_status = 'publish';
|
||||
$event->event_rsvp = false;
|
||||
$this->assertTrue( $event->save() );
|
||||
|
@ -175,7 +177,7 @@ class Test_Events_Manager extends WP_UnitTestCase {
|
|||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'Events Manager Test event', $event_array['name'] );
|
||||
$this->assertEquals( 'Event description', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertEquals( 'MEETING', $event_array['category'] );
|
||||
$this->assertArrayHasKey( 'location', $event_array );
|
|
@ -5,10 +5,12 @@
|
|||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transformer;
|
||||
|
||||
/**
|
||||
* Sample test case.
|
||||
*/
|
||||
class Test_GatherPress extends WP_UnitTestCase {
|
||||
class Test_GatherPress extends \WP_UnitTestCase {
|
||||
/**
|
||||
* Override the setup function, so that tests don't run if the Events Calendar is not active.
|
||||
*/
|
||||
|
@ -20,9 +22,9 @@ class Test_GatherPress extends WP_UnitTestCase {
|
|||
}
|
||||
|
||||
// Mock the plugin activation.
|
||||
GatherPress\Core\Setup::get_instance()->activate_gatherpress_plugin( false );
|
||||
\GatherPress\Core\Setup::get_instance()->activate_gatherpress_plugin( false );
|
||||
|
||||
// Make sure that ActivityPub support is enabled for The Events Calendar.
|
||||
// Make sure that ActivityPub support is enabled for GatherPress.
|
||||
$aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
|
||||
$aec->activate_activitypub_support_for_active_event_plugins();
|
||||
|
||||
|
@ -30,6 +32,13 @@ class Test_GatherPress extends WP_UnitTestCase {
|
|||
_delete_all_posts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
_delete_all_posts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the right transformer gets applied.
|
||||
*/
|
||||
|
@ -38,7 +47,7 @@ class Test_GatherPress extends WP_UnitTestCase {
|
|||
// even though we support multiple onces in theory.
|
||||
// But testing all combinations is beyond scope.
|
||||
$active_event_plugins = \Event_Bridge_For_ActivityPub\Setup::get_instance()->get_active_event_plugins();
|
||||
$this->assertEquals( 1, count( $active_event_plugins ) );
|
||||
$this->assertArrayHasKey( 'gatherpress/gatherpress.php', $active_event_plugins );
|
||||
|
||||
// Enable ActivityPub support for the event plugin.
|
||||
$this->assertContains( 'gatherpress_event', get_option( 'activitypub_support_post_types' ) );
|
||||
|
@ -46,7 +55,7 @@ class Test_GatherPress extends WP_UnitTestCase {
|
|||
// Mock GatherPress Event.
|
||||
$post_id = wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'Unit Test Event',
|
||||
'post_title' => 'Test Event for transformer class.',
|
||||
'post_type' => 'gatherpress_event',
|
||||
'post_content' => 'Unit Test description.',
|
||||
'post_status' => 'publish',
|
||||
|
@ -65,7 +74,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 );
|
||||
}
|
||||
|
||||
/**
|
|
@ -5,10 +5,12 @@
|
|||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transformer;
|
||||
|
||||
/**
|
||||
* Sample test case.
|
||||
*/
|
||||
class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
|
||||
class Test_Modern_Events_Calendar_Lite extends \WP_UnitTestCase {
|
||||
/**
|
||||
* The MEC main instance.
|
||||
*
|
||||
|
@ -47,7 +49,7 @@ class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
|
|||
$this->assertEquals( 1, count( $active_event_plugins ) );
|
||||
|
||||
// Enable ActivityPub support for the event plugin.
|
||||
$this->assertContains( 'mec-events', get_option( 'activitypub_support_post_types' ) );
|
||||
$this->assertContains( 'mec-events', \get_option( 'activitypub_support_post_types' ) );
|
||||
|
||||
// Insert a new Event.
|
||||
$event = array(
|
||||
|
@ -68,35 +70,35 @@ class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
|
|||
|
||||
$post_id = $this->mec_main->save_event( $event );
|
||||
|
||||
$wp_object = get_post( $post_id );
|
||||
$wp_object = \get_post( $post_id );
|
||||
|
||||
// Call the transformer Factory.
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the transformation of minimal event.
|
||||
*/
|
||||
public function test_modern_events_calendar_lite_minimal_event() {
|
||||
$start_timestamp = strtotime( '+10 days 15:00:00' );
|
||||
$end_timestamp = strtotime( '+10 days 16:00:00' );
|
||||
$start_timestamp = \strtotime( '+10 days 15:00:00' );
|
||||
$end_timestamp = \strtotime( '+10 days 16:00:00' );
|
||||
|
||||
// Insert a new Event.
|
||||
$event = array(
|
||||
'title' => 'MEC Test Event',
|
||||
'status' => 'publish',
|
||||
'content' => 'This is the content of the MEC!',
|
||||
'start_time_hour' => gmdate( 'h', $start_timestamp ),
|
||||
'start_time_minutes' => gmdate( 'i', $start_timestamp ),
|
||||
'start_time_ampm' => gmdate( 'A', $start_timestamp ),
|
||||
'start' => gmdate( 'Y-m-d', $start_timestamp ),
|
||||
'end' => gmdate( 'Y-m-d', $end_timestamp ),
|
||||
'end_time_hour' => gmdate( 'h', $end_timestamp ),
|
||||
'end_time_minutes' => gmdate( 'i', $end_timestamp ),
|
||||
'end_time_ampm' => gmdate( 'A', $end_timestamp ),
|
||||
'start_time_hour' => \gmdate( 'h', $start_timestamp ),
|
||||
'start_time_minutes' => \gmdate( 'i', $start_timestamp ),
|
||||
'start_time_ampm' => \gmdate( 'A', $start_timestamp ),
|
||||
'start' => \gmdate( 'Y-m-d', $start_timestamp ),
|
||||
'end' => \gmdate( 'Y-m-d', $end_timestamp ),
|
||||
'end_time_hour' => \gmdate( 'h', $end_timestamp ),
|
||||
'end_time_minutes' => \gmdate( 'i', $end_timestamp ),
|
||||
'end_time_ampm' => \gmdate( 'A', $end_timestamp ),
|
||||
'repeat_status' => 0,
|
||||
'repeat_type' => 'daily',
|
||||
'interval' => 1,
|
||||
|
@ -104,16 +106,16 @@ class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
|
|||
|
||||
$post_id = $this->mec_main->save_event( $event );
|
||||
|
||||
$wp_object = get_post( $post_id );
|
||||
$wp_object = \get_post( $post_id );
|
||||
|
||||
// Call the transformer to make the ActivityStreams representation of the event.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( $wp_object )->to_object()->to_array();
|
||||
|
||||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'MEC Test Event', $event_array['name'] );
|
||||
$this->assertEquals( 'This is the content of the MEC!', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( 'This is the content of the MEC!', \wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertTrue( $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( 'allow_all', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
|
@ -126,8 +128,8 @@ class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
|
|||
* Test that the transformation of minimal event.
|
||||
*/
|
||||
public function test_modern_events_calendar_lite_event_with_location() {
|
||||
$start_timestamp = strtotime( '+10 days 15:00:00' );
|
||||
$end_timestamp = strtotime( '+10 days 16:00:00' );
|
||||
$start_timestamp = \strtotime( '+10 days 15:00:00' );
|
||||
$end_timestamp = \strtotime( '+10 days 16:00:00' );
|
||||
|
||||
// Add new location.
|
||||
$location = array(
|
||||
|
@ -145,14 +147,14 @@ class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
|
|||
'title' => 'MEC Test Event',
|
||||
'status' => 'publish',
|
||||
'content' => 'This is the content of the MEC!',
|
||||
'start_time_hour' => gmdate( 'h', $start_timestamp ),
|
||||
'start_time_minutes' => gmdate( 'i', $start_timestamp ),
|
||||
'start_time_ampm' => gmdate( 'A', $start_timestamp ),
|
||||
'start' => gmdate( 'Y-m-d', $start_timestamp ),
|
||||
'end' => gmdate( 'Y-m-d', $end_timestamp ),
|
||||
'end_time_hour' => gmdate( 'h', $end_timestamp ),
|
||||
'end_time_minutes' => gmdate( 'i', $end_timestamp ),
|
||||
'end_time_ampm' => gmdate( 'A', $end_timestamp ),
|
||||
'start_time_hour' => \gmdate( 'h', $start_timestamp ),
|
||||
'start_time_minutes' => \gmdate( 'i', $start_timestamp ),
|
||||
'start_time_ampm' => \gmdate( 'A', $start_timestamp ),
|
||||
'start' => \gmdate( 'Y-m-d', $start_timestamp ),
|
||||
'end' => \gmdate( 'Y-m-d', $end_timestamp ),
|
||||
'end_time_hour' => \gmdate( 'h', $end_timestamp ),
|
||||
'end_time_minutes' => \gmdate( 'i', $end_timestamp ),
|
||||
'end_time_ampm' => \gmdate( 'A', $end_timestamp ),
|
||||
'repeat_status' => 0,
|
||||
'repeat_type' => 'daily',
|
||||
'interval' => 1,
|
||||
|
@ -161,20 +163,20 @@ class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
|
|||
|
||||
$post_id = $this->mec_main->save_event( $event );
|
||||
|
||||
$wp_object = get_post( $post_id );
|
||||
$wp_object = \get_post( $post_id );
|
||||
|
||||
// Call the transformer to make the ActivityStreams representation of the event.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( $wp_object )->to_object()->to_array();
|
||||
|
||||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'MEC Test Event', $event_array['name'] );
|
||||
$this->assertEquals( 'This is the content of the MEC!', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( 'This is the content of the MEC!', \wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertTrue( $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( 'allow_all', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertEquals( get_permalink( $wp_object ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertEquals( \get_permalink( $wp_object ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertArrayHasKey( 'location', $event_array );
|
||||
$this->assertEquals( 'MEETING', $event_array['category'] );
|
||||
$this->assertEquals( $location['address'], $event_array['location']['address'] );
|
|
@ -1,14 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* Class SampleTest
|
||||
* Class file containing tests for the ActivityPub transformer of the WordPress plugin The Events Calendar.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPup\Tests\ActivityPub\Transformer;
|
||||
|
||||
/**
|
||||
* Sample test case.
|
||||
* Class containing tests for the ActivityPub transformer of the WordPress plugin The Events Calendar.
|
||||
*/
|
||||
class Test_The_Events_Calendar extends WP_UnitTestCase {
|
||||
class Test_The_Events_Calendar extends \WP_UnitTestCase {
|
||||
/**
|
||||
* Mockup events of certain complexity.
|
||||
*/
|
||||
|
@ -77,7 +79,7 @@ class Test_The_Events_Calendar extends WP_UnitTestCase {
|
|||
// even though we support multiple onces in theory.
|
||||
// But testing all combinations is beyond scope.
|
||||
$active_event_plugins = \Event_Bridge_For_ActivityPub\Setup::get_instance()->get_active_event_plugins();
|
||||
$this->assertEquals( 1, count( $active_event_plugins ) );
|
||||
$this->assertArrayHasKey( 'the-events-calendar/the-events-calendar.php', $active_event_plugins );
|
||||
|
||||
// Enable ActivityPub support for the event plugin.
|
||||
$this->assertContains( 'tribe_events', get_option( 'activitypub_support_post_types' ) );
|
||||
|
@ -91,7 +93,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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,28 +126,28 @@ class Test_The_Events_Calendar extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_event_with_mapped_categories() {
|
||||
// Create category.
|
||||
$category_id_music = wp_insert_term( 'Music', Tribe__Events__Main::TAXONOMY, array( 'slug' => 'music' ) );
|
||||
$category_id_theatre = wp_insert_term( 'Theatre', Tribe__Events__Main::TAXONOMY, array( 'slug' => 'theatre' ) );
|
||||
$category_id_music = wp_insert_term( 'Music', \Tribe__Events__Main::TAXONOMY, array( 'slug' => 'music' ) );
|
||||
$category_id_theatre = wp_insert_term( 'Theatre', \Tribe__Events__Main::TAXONOMY, array( 'slug' => 'theatre' ) );
|
||||
|
||||
// Set default mapping for event categories.
|
||||
update_option( 'event_bridge_for_activitypub_default_event_category', 'MUSIC' );
|
||||
\update_option( 'event_bridge_for_activitypub_default_event_category', 'MUSIC' );
|
||||
|
||||
// Set an override for the category with the slug theatre.
|
||||
update_option( 'event_bridge_for_activitypub_event_category_mappings', array( 'theatre' => 'THEATRE' ) );
|
||||
\update_option( 'event_bridge_for_activitypub_event_category_mappings', array( 'theatre' => 'THEATRE' ) );
|
||||
|
||||
// Create a The Events Calendar event with the music category.
|
||||
$wp_object = tribe_events()
|
||||
->set_args( self::MOCKUP_EVENTS['minimal_event'] )
|
||||
->create();
|
||||
// Set the post term music to the event.
|
||||
wp_set_post_terms( $wp_object->ID, $category_id_music['term_id'], Tribe__Events__Main::TAXONOMY );
|
||||
wp_set_post_terms( $wp_object->ID, $category_id_music['term_id'], \Tribe__Events__Main::TAXONOMY );
|
||||
// Call the transformer.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( $wp_object )->to_object()->to_array();
|
||||
// See if the default category mapping is applied.
|
||||
$this->assertEquals( 'MUSIC', $event_array['category'] );
|
||||
|
||||
// Set the post term theatre to the event.
|
||||
wp_set_post_terms( $wp_object->ID, $category_id_theatre['term_id'], Tribe__Events__Main::TAXONOMY, false );
|
||||
wp_set_post_terms( $wp_object->ID, $category_id_theatre['term_id'], \Tribe__Events__Main::TAXONOMY, false );
|
||||
// Call the transformer.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( $wp_object )->to_object()->to_array();
|
||||
// See if the default category mapping is applied.
|
|
@ -5,10 +5,12 @@
|
|||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transformer;
|
||||
|
||||
/**
|
||||
* Sample test case.
|
||||
*/
|
||||
class Test_VS_Event_List extends WP_UnitTestCase {
|
||||
class Test_VS_Event_List extends \WP_UnitTestCase {
|
||||
/**
|
||||
* Override the setup function, so that tests don't run if the Events Calendar is not active.
|
||||
*/
|
||||
|
@ -38,27 +40,27 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
$this->assertEquals( 1, count( $active_event_plugins ) );
|
||||
|
||||
// Enable ActivityPub support for the event plugin.
|
||||
$this->assertContains( 'event', get_option( 'activitypub_support_post_types' ) );
|
||||
$this->assertContains( 'event', \get_option( 'activitypub_support_post_types' ) );
|
||||
|
||||
// Insert a new Event.
|
||||
$wp_post_id = wp_insert_post(
|
||||
$wp_post_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'VSEL Test Event',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'event',
|
||||
'meta_input' => array(
|
||||
'event-start-date' => strtotime( '+10 days 15:00:00' ),
|
||||
'event-start-date' => \strtotime( '+10 days 15:00:00' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$wp_object = get_post( $wp_post_id );
|
||||
$wp_object = \get_post( $wp_post_id );
|
||||
|
||||
// Call the transformer Factory.
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,7 +68,7 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_of_minimal_event() {
|
||||
// Insert a new Event.
|
||||
$wp_post_id = wp_insert_post(
|
||||
$wp_post_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'VSEL Test Event',
|
||||
'post_status' => 'publish',
|
||||
|
@ -84,12 +86,12 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'VSEL Test Event', $event_array['name'] );
|
||||
$this->assertEquals( '', $event_array['content'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertArrayNotHasKey( 'endTime', $event_array );
|
||||
$this->assertEquals( comments_open( $wp_post_id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( \comments_open( $wp_post_id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( \comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertEquals( esc_url( get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertEquals( \esc_url( \get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertArrayNotHasKey( 'location', $event_array );
|
||||
$this->assertEquals( 'MEETING', $event_array['category'] );
|
||||
}
|
||||
|
@ -99,14 +101,14 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_of_full_event() {
|
||||
// Insert a new Event.
|
||||
$wp_post_id = wp_insert_post(
|
||||
$wp_post_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'VSEL Test Event',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'event',
|
||||
'meta_input' => array(
|
||||
'event-start-date' => strtotime( '+10 days 15:00:00' ),
|
||||
'event-date' => strtotime( '+10 days 16:00:00' ),
|
||||
'event-start-date' => \strtotime( '+10 days 15:00:00' ),
|
||||
'event-date' => \strtotime( '+10 days 16:00:00' ),
|
||||
'event-link' => 'https://event-federation.eu/vsel-test-event',
|
||||
'event-link-label' => 'Website',
|
||||
),
|
||||
|
@ -120,12 +122,12 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'VSEL Test Event', $event_array['name'] );
|
||||
$this->assertEquals( '', $event_array['content'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( comments_open( $wp_post_id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( \comments_open( $wp_post_id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( \comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertEquals( esc_url( get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertEquals( \esc_url( \get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertArrayNotHasKey( 'location', $event_array );
|
||||
$this->assertEquals( 'MEETING', $event_array['category'] );
|
||||
$this->assertContains(
|
||||
|
@ -144,7 +146,7 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_of_event_with_hidden_end_time() {
|
||||
// Insert a new Event.
|
||||
$wp_post_id = wp_insert_post(
|
||||
$wp_post_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'VSEL Test Event',
|
||||
'post_status' => 'publish',
|
||||
|
@ -169,8 +171,8 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_event_with_mapped_categories() {
|
||||
// Create category.
|
||||
$category_id_music = wp_insert_term( 'Music', 'event_cat', array( 'slug' => 'music' ) );
|
||||
$category_id_theatre = wp_insert_term( 'Theatre', 'event_cat', array( 'slug' => 'theatre' ) );
|
||||
$category_id_music = \wp_insert_term( 'Music', 'event_cat', array( 'slug' => 'music' ) );
|
||||
$category_id_theatre = \wp_insert_term( 'Theatre', 'event_cat', array( 'slug' => 'theatre' ) );
|
||||
|
||||
// Set default mapping for event categories.
|
||||
update_option( 'event_bridge_for_activitypub_default_event_category', 'MUSIC' );
|
||||
|
@ -185,13 +187,13 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
'post_status' => 'publish',
|
||||
'post_type' => 'event',
|
||||
'meta_input' => array(
|
||||
'event-start-date' => strtotime( '+10 days 15:00:00' ),
|
||||
'event-date' => strtotime( '+10 days 16:00:00' ),
|
||||
'event-start-date' => \strtotime( '+10 days 15:00:00' ),
|
||||
'event-date' => \strtotime( '+10 days 16:00:00' ),
|
||||
'event-hide-end-time' => 'yes',
|
||||
),
|
||||
)
|
||||
);
|
||||
wp_set_post_terms( $wp_post_id, $category_id_music['term_id'], 'event_cat' );
|
||||
\wp_set_post_terms( $wp_post_id, $category_id_music['term_id'], 'event_cat' );
|
||||
|
||||
// Call the transformer.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $wp_post_id ) )->to_object()->to_array();
|
||||
|
@ -200,7 +202,7 @@ class Test_VS_Event_List extends WP_UnitTestCase {
|
|||
$this->assertEquals( 'MUSIC', $event_array['category'] );
|
||||
|
||||
// Change the event category to theatre.
|
||||
wp_set_post_terms( $wp_post_id, $category_id_theatre['term_id'], 'event_cat', false );
|
||||
\wp_set_post_terms( $wp_post_id, $category_id_theatre['term_id'], 'event_cat', false );
|
||||
|
||||
// Call the transformer.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( get_post( $wp_post_id ) )->to_object()->to_array();
|
|
@ -5,10 +5,12 @@
|
|||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transformer;
|
||||
|
||||
/**
|
||||
* Sample test case.
|
||||
*/
|
||||
class Test_WP_Event_Manager extends WP_UnitTestCase {
|
||||
class Test_WP_Event_Manager extends \WP_UnitTestCase {
|
||||
/**
|
||||
* Override the setup function, so that tests don't run if the Events Calendar is not active.
|
||||
*/
|
||||
|
@ -52,13 +54,13 @@ class Test_WP_Event_Manager extends WP_UnitTestCase {
|
|||
)
|
||||
);
|
||||
|
||||
$wp_object = get_post( $wp_post_id );
|
||||
$wp_object = \get_post( $wp_post_id );
|
||||
|
||||
// Call the transformer Factory.
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,7 +68,7 @@ class Test_WP_Event_Manager extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_of_minimal_event() {
|
||||
// Insert a new Event.
|
||||
$wp_post_id = wp_insert_post(
|
||||
$wp_post_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'WP Event Manager TestEvent',
|
||||
'post_status' => 'publish',
|
||||
|
@ -84,13 +86,13 @@ class Test_WP_Event_Manager extends WP_UnitTestCase {
|
|||
// Check that the event ActivityStreams representation contains everything as expected.
|
||||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'WP Event Manager TestEvent', $event_array['name'] );
|
||||
$this->assertEquals( 'Come to my WP Event Manager event!', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( 'Come to my WP Event Manager event!', \wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertArrayNotHasKey( 'endTime', $event_array );
|
||||
$this->assertEquals( comments_open( $wp_post_id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( \comments_open( $wp_post_id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( \comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertEquals( esc_url( get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertEquals( \esc_url( \get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertArrayNotHasKey( 'location', $event_array );
|
||||
$this->assertEquals( 'MEETING', $event_array['category'] );
|
||||
}
|
||||
|
@ -100,7 +102,7 @@ class Test_WP_Event_Manager extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_of_full_online_event() {
|
||||
// Insert a new Event.
|
||||
$wp_post_id = wp_insert_post(
|
||||
$wp_post_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'WP Event Manager TestEvent',
|
||||
'post_status' => 'publish',
|
||||
|
@ -147,15 +149,15 @@ class Test_WP_Event_Manager extends WP_UnitTestCase {
|
|||
*/
|
||||
public function test_transform_of_event_with_location() {
|
||||
// Insert a new Event.
|
||||
$wp_post_id = wp_insert_post(
|
||||
$wp_post_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'WP Event Manager TestEvent',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'event_listing',
|
||||
'post_content' => 'Come to my WP Event Manager event!',
|
||||
'meta_input' => array(
|
||||
'_event_start_date' => \gmdate( 'Y-m-d H:i:s', strtotime( '+10 days 15:00:00' ) ),
|
||||
'_event_end_date' => \gmdate( 'Y-m-d H:i:s', strtotime( '+10 days 16:00:00' ) ),
|
||||
'_event_start_date' => \gmdate( 'Y-m-d H:i:s', \strtotime( '+10 days 15:00:00' ) ),
|
||||
'_event_end_date' => \gmdate( 'Y-m-d H:i:s', \strtotime( '+10 days 16:00:00' ) ),
|
||||
'_event_location' => 'Some text location',
|
||||
'_event_online' => 'no',
|
||||
),
|
||||
|
@ -168,14 +170,14 @@ class Test_WP_Event_Manager extends WP_UnitTestCase {
|
|||
// Check that the event ActivityStreams representation contains everything as expected.
|
||||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'WP Event Manager TestEvent', $event_array['name'] );
|
||||
$this->assertEquals( 'Come to my WP Event Manager event!', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( comments_open( $wp_post_id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'Come to my WP Event Manager event!', \wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( \gmdate( 'Y-m-d', \strtotime( '+10 days 15:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertEquals( \comments_open( $wp_post_id ), $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( \comments_open( $wp_post_id ) ? 'allow_all' : 'closed', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertEquals( false, $event_array['isOnline'] );
|
||||
$this->assertEquals( esc_url( get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertEquals( \esc_url( \get_permalink( $wp_post_id ) ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertArrayHasKey( 'location', $event_array );
|
||||
$this->assertEquals( 'Some text location', $event_array['location']['address'] );
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Transmogrifier (import of ActivityPub Event objects) of GatherPress.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transmogrifier;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use GatherPress\Core\Event;
|
||||
use GatherPress\Core\Event_Query;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Test class for the Transmogrifier (import of ActivityPub Event objects) of GatherPress.
|
||||
*
|
||||
* @coversDefaultClass \Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\GatherPress
|
||||
*/
|
||||
class Test_GatherPress extends \WP_UnitTestCase {
|
||||
const FOLLOWED_ACTOR = array(
|
||||
'id' => 'https://remote.example/@organizer',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer/outbox',
|
||||
'name' => 'The Organizer',
|
||||
'summary' => 'Just a random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
/**
|
||||
* Post ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $event_source_post_id;
|
||||
|
||||
/**
|
||||
* REST Server.
|
||||
*
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( ! defined( 'GATHERPRESS_CORE_FILE' ) ) {
|
||||
self::markTestSkipped( 'GatherPress plugin is not active.' );
|
||||
}
|
||||
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new WP_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
|
||||
// Mock the plugin activation.
|
||||
\GatherPress\Core\Setup::get_instance()->activate_gatherpress_plugin( false );
|
||||
|
||||
// Make sure that ActivityPub support is enabled for GatherPress.
|
||||
$aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
|
||||
$aec->activate_activitypub_support_for_active_event_plugins();
|
||||
|
||||
// Add event source (ActivityPub follower).
|
||||
_delete_all_posts();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
|
||||
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge gatherpress custom events table.
|
||||
*/
|
||||
public static function delete_all_gatherpress_events() {
|
||||
global $wpdb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
\delete_option( 'permalink_structure' );
|
||||
_delete_all_posts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor.
|
||||
*/
|
||||
public function test_incoming_event() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Fediverse Party',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
// Check if post has been created.
|
||||
$event_query = Event_Query::get_instance();
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
|
||||
$this->assertEquals( true, $the_query->have_posts() );
|
||||
$this->assertEquals( 1, $the_query->post_count );
|
||||
|
||||
// Initialize new GatherPress Event object.
|
||||
$event = new Event( $the_query->get_posts()[0] );
|
||||
|
||||
$this->assertEquals( $json['object']['name'], $event->event->post_title );
|
||||
$this->assertEquals( $json['object']['startTime'], $event->get_datetime_start( 'Y-m-d\TH:i:s\Z' ) );
|
||||
$this->assertEquals( $json['object']['endTime'], $event->get_datetime_end( 'Y-m-d\TH:i:s\Z' ) );
|
||||
$this->assertEquals( $json['object']['location']['address'], $event->get_venue_information()['full_address'] );
|
||||
$this->assertEquals( $json['object']['location']['name'], $event->get_venue_information()['name'] );
|
||||
$this->assertEquals( false, $event->get_venue_information()['is_online_event'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor.
|
||||
*/
|
||||
public function test_incoming_event_update_and_delete() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Fediverse Party',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
// Check if post has been created.
|
||||
$event_query = Event_Query::get_instance();
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
|
||||
$this->assertEquals( true, $the_query->have_posts() );
|
||||
$this->assertEquals( 1, $the_query->post_count );
|
||||
|
||||
// Initialize new GatherPress Event object.
|
||||
$event = new Event( $the_query->get_posts()[0] );
|
||||
|
||||
$this->assertEquals( $json['object']['name'], $event->event->post_title );
|
||||
$this->assertEquals( $json['object']['startTime'], $event->get_datetime_start( 'Y-m-d\TH:i:s\Z' ) );
|
||||
$this->assertEquals( $json['object']['endTime'], $event->get_datetime_end( 'Y-m-d\TH:i:s\Z' ) );
|
||||
$this->assertEquals( $json['object']['location']['address'], $event->get_venue_information()['full_address'] );
|
||||
$this->assertEquals( $json['object']['location']['name'], $event->get_venue_information()['name'] );
|
||||
$this->assertEquals( false, $event->get_venue_information()['is_online_event'] );
|
||||
|
||||
// Now we receive an update of that event.
|
||||
$json['type'] = 'Update';
|
||||
$json['object']['name'] = 'Updated name';
|
||||
$json['object']['location']['address'] = 'Updated address';
|
||||
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
|
||||
// We do not except duplicated.
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertEquals( 1, $the_query->post_count );
|
||||
|
||||
// Check the updated representation of the event within The Events Calendar.
|
||||
$event = new Event( $the_query->get_posts()[0] );
|
||||
|
||||
$this->assertEquals( 'Updated name', $event->event->post_title );
|
||||
$this->assertEquals( 'Updated address', $event->get_venue_information()['full_address'] );
|
||||
|
||||
// Test delete.
|
||||
$json['type'] = 'Delete';
|
||||
$json['object'] = $json['object']['id'];
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
// We do expect the event to be removed.
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertFalse( $the_query->have_posts() );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Transmogrifier (import of ActivityPub Event objects) of "The Events Calendar".
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transmogrifier;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Test class for the Transmogrifier (import of ActivityPub Event objects) of "The Events Calendar".
|
||||
*
|
||||
* @coversDefaultClass \Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\The_Events_Calendar
|
||||
*/
|
||||
class Test_The_Events_Calendar extends \WP_UnitTestCase {
|
||||
const FOLLOWED_ACTOR = array(
|
||||
'id' => 'https://remote.example/@organizer',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer/outbox',
|
||||
'name' => 'The Organizer',
|
||||
'summary' => 'Just a random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
/**
|
||||
* REST Server.
|
||||
*
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( ! class_exists( '\Tribe__Events__Main' ) ) {
|
||||
self::markTestSkipped( 'The Events Calendar plugin is not active.' );
|
||||
}
|
||||
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new WP_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
|
||||
// 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();
|
||||
|
||||
// Add event source (ActivityPub follower).
|
||||
_delete_all_posts();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
|
||||
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\The_Events_Calendar::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
\delete_option( 'permalink_structure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first venue of an Event of The Events Calendar.
|
||||
*
|
||||
* @param WP_Post $event The Event Post.
|
||||
* @return ?WP_Post
|
||||
*/
|
||||
private static function get_first_tribe_venue_of_tribe_event( $event ) {
|
||||
// Get first venue. We currently only support a single venue.
|
||||
if ( $event->venues instanceof \Tribe\Events\Collections\Lazy_Post_Collection ) {
|
||||
return $event->venues->first();
|
||||
} elseif ( empty( $event->venues ) || ! empty( $event->venues[0] ) ) {
|
||||
return null;
|
||||
} else {
|
||||
return $event->venues[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor.
|
||||
*/
|
||||
public function test_incoming_event() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Fediverse Party for The Events Calendar',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
// Check if post has been created.
|
||||
$events = tribe_get_events();
|
||||
|
||||
$this->assertEquals( 1, count( $events ) );
|
||||
|
||||
// Initialize new Tribe Event object.
|
||||
$event = tribe_get_event( $events[0] );
|
||||
|
||||
$this->assertEquals( $json['object']['name'], $event->post_title );
|
||||
$this->assertEquals( $json['object']['startTime'], $event->dates->start->format( 'Y-m-d\TH:i:s\Z' ) );
|
||||
$this->assertEquals( $json['object']['endTime'], $event->dates->end->format( 'Y-m-d\TH:i:s\Z' ) );
|
||||
|
||||
$venue = self::get_first_tribe_venue_of_tribe_event( $event );
|
||||
|
||||
$this->assertEquals( $json['object']['location']['address'], $venue->address );
|
||||
$this->assertEquals( $json['object']['location']['name'], $venue->post_title );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor.
|
||||
*/
|
||||
public function test_incoming_event_with_postal_address() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Fediverse Party for The Events Calendar',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => array(
|
||||
'type' => 'PostalAddress',
|
||||
'streetAddress' => 'FediStreet 13',
|
||||
'postalCode' => '1337',
|
||||
'addressLocality' => 'Feditown',
|
||||
'addressState' => 'Fediverse State',
|
||||
'addressCountry' => 'Fediverse World',
|
||||
'url' => 'https://fedidevs.org/',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
// Check if post has been created.
|
||||
$events = tribe_get_events();
|
||||
|
||||
$this->assertEquals( 1, count( $events ) );
|
||||
|
||||
// Initialize new Tribe Event object.
|
||||
$event = tribe_get_event( $events[0] );
|
||||
|
||||
$this->assertEquals( $json['object']['name'], $event->post_title );
|
||||
$this->assertEquals( $json['object']['startTime'], $event->dates->start->format( 'Y-m-d\TH:i:s\Z' ) );
|
||||
$this->assertEquals( $json['object']['endTime'], $event->dates->end->format( 'Y-m-d\TH:i:s\Z' ) );
|
||||
|
||||
$venue = self::get_first_tribe_venue_of_tribe_event( $event );
|
||||
|
||||
$this->assertEquals( $json['object']['location']['name'], $venue->post_title );
|
||||
$this->assertEquals( $json['object']['location']['address']['streetAddress'], $venue->address );
|
||||
$this->assertEquals( $json['object']['location']['address']['postalCode'], $venue->zip );
|
||||
$this->assertEquals( $json['object']['location']['address']['addressLocality'], $venue->city );
|
||||
$this->assertEquals( $json['object']['location']['address']['addressState'], $venue->state );
|
||||
$this->assertEquals( $json['object']['location']['address']['addressCountry'], $venue->country );
|
||||
$this->assertEquals( $json['object']['location']['address']['url'], $venue->website );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test handling updates and deletes.
|
||||
*/
|
||||
public function test_incoming_event_updates() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Fediverse Party for The Events Calendar',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
// Check if post has been created.
|
||||
$events = tribe_get_events();
|
||||
|
||||
$this->assertEquals( 1, count( $events ) );
|
||||
|
||||
// Now we receive an update of that event.
|
||||
$json['type'] = 'Update';
|
||||
$json['object']['name'] = 'Updated name';
|
||||
$json['object']['location']['address'] = 'Updated address';
|
||||
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
|
||||
// We do not except duplicated.
|
||||
$events = tribe_get_events();
|
||||
$this->assertEquals( 1, count( $events ) );
|
||||
|
||||
// Check the updated representation of the event within The Events Calendar.
|
||||
$event = tribe_get_event( $events[0] );
|
||||
$venue = self::get_first_tribe_venue_of_tribe_event( $event );
|
||||
|
||||
$this->assertEquals( 'Updated name', $event->post_title );
|
||||
$this->assertEquals( 'Updated address', $venue->address );
|
||||
|
||||
// Test delete.
|
||||
$json['type'] = 'Delete';
|
||||
$json['object'] = $json['object']['id'];
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
// We do expect the event to be removed.
|
||||
$events = tribe_get_events();
|
||||
$this->assertEmpty( $events );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Transmogrifier (import of ActivityPub Event objects) in VS Event List.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Transmogrifier;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\VS_Event_List as TransformerVS_Event_List;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\VS_Event_List;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\VS_Event_List as IntegrationsVS_Event_List;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Test class for the Transmogrifier (import of ActivityPub Event objects) in VS Event List.
|
||||
*
|
||||
* @coversDefaultClass \Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\VS_Event_List
|
||||
*/
|
||||
class Test_VS_Event_List extends \WP_UnitTestCase {
|
||||
const FOLLOWED_ACTOR = array(
|
||||
'id' => 'https://remote.example/@organizer',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer/outbox',
|
||||
'name' => 'The Organizer',
|
||||
'summary' => 'Just a random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
/**
|
||||
* REST Server.
|
||||
*
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( ! function_exists( 'vsel_custom_post_type' ) ) {
|
||||
self::markTestSkipped( 'VS Event List plugin is not active.' );
|
||||
}
|
||||
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new WP_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
|
||||
// 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();
|
||||
|
||||
// Add event source (ActivityPub follower).
|
||||
_delete_all_posts();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
|
||||
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
\delete_option( 'permalink_structure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor.
|
||||
*/
|
||||
public function test_incoming_event() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Fediverse Party Test Event',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() ),
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
$events = get_posts( array( 'post_type' => IntegrationsVS_Event_List::get_post_type() ) );
|
||||
$this->assertCount( 1, $events );
|
||||
$event = $events[0];
|
||||
|
||||
$this->assertEquals( $json['object']['name'], $event->post_title );
|
||||
$this->assertEquals( $json['object']['startTime'], \gmdate( 'Y-m-d\TH:i:s\Z', get_post_meta( $event->ID, 'event-start-date', true ) ) );
|
||||
$this->assertEquals( $json['object']['endTime'], \gmdate( 'Y-m-d\TH:i:s\Z', get_post_meta( $event->ID, 'event-date', true ) ) );
|
||||
$this->assertStringStartsWith( $json['object']['location']['name'], get_post_meta( $event->ID, 'event-location', true ) );
|
||||
$this->assertStringContainsString( $json['object']['location']['address'], get_post_meta( $event->ID, 'event-location', true ) );
|
||||
|
||||
// Now we receive an update of that event.
|
||||
$json['type'] = 'Update';
|
||||
$json['object']['name'] = 'Updated name';
|
||||
$json['object']['location']['address'] = 'Updated address';
|
||||
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
|
||||
// We do not except duplicated.
|
||||
$events = get_posts( array( 'post_type' => IntegrationsVS_Event_List::get_post_type() ) );
|
||||
$this->assertCount( 1, $events );
|
||||
$event = $events[0];
|
||||
$this->assertStringContainsString( 'Updated address', get_post_meta( $event->ID, 'event-location', true ) );
|
||||
|
||||
// We should see the updates.
|
||||
$this->assertEquals( 'Updated name', $event->post_title );
|
||||
|
||||
// Test delete.
|
||||
$json['type'] = 'Delete';
|
||||
$json['object'] = $json['object']['id'];
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
// We do expect the event to be removed.
|
||||
$events = get_posts( array( 'post_type' => IntegrationsVS_Event_List::get_post_type() ) );
|
||||
$this->assertCount( 0, $events );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
}
|
416
tests/includes/class-test-event-sources.php
Normal file
416
tests/includes/class-test-event-sources.php
Normal file
|
@ -0,0 +1,416 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Event Sources feature.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests;
|
||||
|
||||
use Activitypub\Model\Blog;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
use GatherPress\Core\Event_Query;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Test class for the Event Sources Feature.
|
||||
*
|
||||
* @coversDefaultClass \Event_Bridge_For_ActivityPub\Event_Sources
|
||||
*/
|
||||
class Test_Event_Sources extends \WP_UnitTestCase {
|
||||
const FOLLOWED_ACTOR = array(
|
||||
'id' => 'https://remote.example/@organizer',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer/outbox',
|
||||
'name' => 'The Organizer',
|
||||
'summary' => 'Just a random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
const FOLLOWED_ACTOR_2 = array(
|
||||
'id' => 'https://remote.example/@organizer2',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer2/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer2/outbox',
|
||||
'name' => 'The Second Organizer',
|
||||
'summary' => 'Just a second random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
/**
|
||||
* REST Server.
|
||||
*
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( ! defined( 'GATHERPRESS_CORE_FILE' ) ) {
|
||||
self::markTestSkipped( 'GatherPress plugin is not active.' );
|
||||
}
|
||||
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new WP_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
|
||||
// Mock the plugin activation.
|
||||
\GatherPress\Core\Setup::get_instance()->activate_gatherpress_plugin( false );
|
||||
|
||||
// Make sure that ActivityPub support is enabled for GatherPress.
|
||||
$aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
|
||||
$aec->activate_activitypub_support_for_active_event_plugins();
|
||||
|
||||
// Add event source (ActivityPub follower).
|
||||
_delete_all_posts();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
|
||||
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
\delete_option( 'permalink_structure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor.
|
||||
*/
|
||||
public function test_incoming_valid_event_returns_202() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'name' => 'Fediverse Party [valid]',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor with missing start time.
|
||||
*/
|
||||
public function test_incoming_create_with_missing_start_time() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'name' => 'Fediverse Party [missing start time]',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor with wrongly formatted start time.
|
||||
*/
|
||||
public function test_incoming_event_with_faulty_start_time() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'name' => 'Fediverse Party [faulty start time]',
|
||||
'startTime' => \gmdate( 'Y-m-d H:i:s', time() + WEEK_IN_SECONDS ),
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* We do understand, but do not care about incoming events that happened in the past.
|
||||
*/
|
||||
public function test_incoming_event_which_took_place_in_the_past() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'name' => 'Fediverse Event [took place in past]',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() - WEEK_IN_SECONDS ),
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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 should be 403 but it is not possible without lots of hacks at the moment.
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
// Verify that event did not get cached and added.
|
||||
$event_query = Event_Query::get_instance();
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertEquals( false, $the_query->have_posts() );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from actor we do not follow.
|
||||
*/
|
||||
public function test_incoming_create_from_non_followed_actor() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@another_organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@another_organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@another_organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => '2050-12-31T18:00:00Z',
|
||||
'name' => 'Fediverse Party [from non-follower actor]',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
// Verify that event did not get cached and added.
|
||||
$event_query = Event_Query::get_instance();
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertEquals( false, $the_query->have_posts() );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving "Accept" of "Follow".
|
||||
*/
|
||||
public function test_incoming_accept_of_follow() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
$blog = new Blog();
|
||||
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/random-accept-id',
|
||||
'type' => 'Accept',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => Event_Sources::compose_follow_id( $blog->get_id(), 'https://remote.example/@organizer' ),
|
||||
'type' => 'Follow',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'object' => 'https://remote.example/@organizer',
|
||||
),
|
||||
);
|
||||
|
||||
$event_source = \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::get_by_id( 'https://remote.example/@organizer' );
|
||||
$accepted = get_post_meta( $event_source->get__id(), '_event_bridge_for_activitypub_accept_of_follow', true );
|
||||
$this->assertEmpty( $accepted );
|
||||
|
||||
// Receive Accept.
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/0/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
$accepted = get_post_meta( $event_source->get__id(), '_event_bridge_for_activitypub_accept_of_follow', true );
|
||||
|
||||
$this->assertEquals( 'https://remote.example/random-accept-id', $accepted );
|
||||
|
||||
// Receive Undo of the Accept.
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/random-undo-id',
|
||||
'type' => 'Undo',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/random-accept-id',
|
||||
'type' => 'Accept',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => Event_Sources::compose_follow_id( $blog->get_id(), 'https://remote.example/@organizer' ),
|
||||
'type' => 'Follow',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'object' => 'https://remote.example/@organizer',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/0/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
$accepted = get_post_meta( $event_source->get__id(), '_event_bridge_for_activitypub_accept_of_follow', true );
|
||||
|
||||
$this->assertEmpty( $accepted );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving "Accept" of "Follow".
|
||||
*/
|
||||
public function test_delete_cached_events_of_removed_event_sources() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
// Check that we have three event posts.
|
||||
$event_query = Event_Query::get_instance();
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertEquals( 0, $the_query->post_count );
|
||||
|
||||
// Add second event source.
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR_2 )->save();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources::delete_event_source_transients();
|
||||
|
||||
// Receive first event of first Organizer.
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'name' => 'Fediverse Party',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/0/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertEquals( 1, $the_query->post_count );
|
||||
|
||||
// Second event of first organizer.
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/birthday-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/birthday-party',
|
||||
'type' => 'Event',
|
||||
'name' => 'Fediverse Party',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
),
|
||||
);
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertEquals( 2, $the_query->post_count );
|
||||
|
||||
// Receive event of other organizer.
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer2/events/concert#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer2',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer2/events/concert',
|
||||
'type' => 'Event',
|
||||
'name' => 'Concert',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 3 * WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 3 * WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
),
|
||||
);
|
||||
$request->set_body( \wp_json_encode( $json ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertEquals( 3, $the_query->post_count );
|
||||
|
||||
// Remove first event source.
|
||||
$result = \Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources::remove_event_source( 'https://remote.example/@organizer' );
|
||||
|
||||
// Verify that event posts got deleted.
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertEquals( 1, $the_query->post_count );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
}
|
156
tests/includes/class-test-outbox-parser.php
Normal file
156
tests/includes/class-test-outbox-parser.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Outbox Parser Library.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests;
|
||||
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Test class for the Outbox Parser Library.
|
||||
*
|
||||
* @coversDefaultClass \Event_Bridge_For_ActivityPub\Outbox_Parser
|
||||
*/
|
||||
class Test_Outbox_Parser extends \WP_UnitTestCase {
|
||||
const FOLLOWED_ACTOR = array(
|
||||
'id' => 'https://remote.example/@organizer',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer/outbox',
|
||||
'name' => 'The Organizer',
|
||||
'summary' => 'Just a random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
/**
|
||||
* REST Server.
|
||||
*
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( ! defined( 'GATHERPRESS_CORE_FILE' ) ) {
|
||||
self::markTestSkipped( 'GatherPress plugin is not active.' );
|
||||
}
|
||||
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new WP_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
|
||||
// Mock the plugin activation.
|
||||
\GatherPress\Core\Setup::get_instance()->activate_gatherpress_plugin( false );
|
||||
|
||||
// Make sure that ActivityPub support is enabled for GatherPress.
|
||||
$aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
|
||||
$aec->activate_activitypub_support_for_active_event_plugins();
|
||||
|
||||
// Add event source (ActivityPub follower).
|
||||
_delete_all_posts();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
|
||||
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
\delete_option( 'permalink_structure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the import of events from an items array of an outbox.
|
||||
*/
|
||||
public function test_import_events_from_items() {
|
||||
$items = array(
|
||||
array(
|
||||
'id' => 'https://remote.example/@organizer/events/concert#create',
|
||||
'type' => 'Create',
|
||||
'actor' => self::FOLLOWED_ACTOR['id'],
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/concert',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Concert',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() - WEEK_IN_SECONDS ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'https://remote.example/@organizer/events/birthday-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => self::FOLLOWED_ACTOR['id'],
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/birthday-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Birthday Party',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() - WEEK_IN_SECONDS ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'https://remote.example/@organizer/events/status/1#create',
|
||||
'type' => 'Create',
|
||||
'actor' => self::FOLLOWED_ACTOR['id'],
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/status/1',
|
||||
'type' => 'Note',
|
||||
'content' => 'This is a note',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', time() - WEEK_IN_SECONDS ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'https://remote.example/@organizer/likes/1',
|
||||
'type' => 'Like',
|
||||
'actor' => self::FOLLOWED_ACTOR['id'],
|
||||
'object' => 'https://remote2.example/@actor/status/1',
|
||||
),
|
||||
array(
|
||||
'id' => 'https://remote.example/@organizer/shares/1',
|
||||
'type' => 'Announce',
|
||||
'actor' => self::FOLLOWED_ACTOR['id'],
|
||||
'object' => 'https://remote2.example/@actor/status/2',
|
||||
),
|
||||
);
|
||||
|
||||
// The function we want to test is private, so we need a Reflection class.
|
||||
$reflection = new \ReflectionClass( \Event_Bridge_For_ActivityPub\Outbox_Parser::class );
|
||||
|
||||
$method = $reflection->getMethod( 'import_events_from_items' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$event_source = \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::get_by_id( self::FOLLOWED_ACTOR['id'] );
|
||||
|
||||
$count = $method->invoke( null, $items, $event_source );
|
||||
|
||||
$this->assertEquals( 2, $count );
|
||||
|
||||
// Check that we have two event posts.
|
||||
$event_query = \GatherPress\Core\Event_Query::get_instance();
|
||||
$the_query = $event_query->get_upcoming_events();
|
||||
$this->assertEquals( 2, $the_query->post_count );
|
||||
}
|
||||
}
|
173
tests/includes/integrations/class-test-gatherpress.php
Normal file
173
tests/includes/integrations/class-test-gatherpress.php
Normal file
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Transmogrifier (import of ActivityPub Event objects) of GatherPress.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Integrations\GatherPress;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Test class for the Transmogrifier (import of ActivityPub Event objects) of GatherPress.
|
||||
*
|
||||
* @coversDefaultClass \Event_Bridge_For_ActivityPub\Integrations\GatherPress
|
||||
*/
|
||||
class Test_GatherPress extends \WP_UnitTestCase {
|
||||
const FOLLOWED_ACTOR = array(
|
||||
'id' => 'https://remote.example/@organizer',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer/outbox',
|
||||
'name' => 'The Organizer',
|
||||
'summary' => 'Just a random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
/**
|
||||
* REST Server.
|
||||
*
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( ! defined( 'GATHERPRESS_CORE_FILE' ) ) {
|
||||
self::markTestSkipped( 'GatherPress plugin is not active.' );
|
||||
}
|
||||
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new WP_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
|
||||
// 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();
|
||||
|
||||
// Add event source (ActivityPub follower).
|
||||
_delete_all_posts();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
|
||||
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
\delete_option( 'permalink_structure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor.
|
||||
*/
|
||||
public function test_getting_past_remote_events() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
// Federated event 1: starts in one week.
|
||||
$event_in_one_week = array(
|
||||
'id' => 'https://remote.example/@organizer/events/in-one-week#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/in-one-week',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Remote Event in One Week',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Federated event 1: starts in two months.
|
||||
$event_in_two_months = array(
|
||||
'id' => 'https://remote.example/@organizer/events/in-two-months#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/in-two-months',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * MONTH_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * MONTH_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Remote Event in Two Months',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/0/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
|
||||
// Receive both events.
|
||||
$request->set_body( \wp_json_encode( $event_in_one_week ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
$request->set_body( \wp_json_encode( $event_in_two_months ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
// Mock local GatherPress Event.
|
||||
$post_id = wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'Locally created GatherPress event',
|
||||
'post_type' => 'gatherpress_event',
|
||||
'post_content' => 'Unit Test description.',
|
||||
'post_status' => 'publish',
|
||||
)
|
||||
);
|
||||
$event = new \GatherPress\Core\Event( $post_id );
|
||||
$params = array(
|
||||
'datetime_start' => '+10 days 15:00:00',
|
||||
'datetime_end' => '+10 days 16:00:00',
|
||||
'timezone' => \wp_timezone_string(),
|
||||
);
|
||||
$event->save_datetimes( $params );
|
||||
// Assure that adding the local third GatherPress event worked.
|
||||
$this->assertNotEquals( false, $post_id );
|
||||
|
||||
// Only one event should show up in the remote events query.
|
||||
$events = GatherPress::get_cached_remote_events( time() + MONTH_IN_SECONDS );
|
||||
$this->assertEquals( 1, count( $events ) );
|
||||
$this->assertEquals( $event_in_one_week['object']['id'], get_post( $events[0] )->guid );
|
||||
|
||||
// Include the even in two months in the time_span.
|
||||
$events = GatherPress::get_cached_remote_events( time() + 3 * MONTH_IN_SECONDS );
|
||||
$this->assertEquals( 2, count( $events ) );
|
||||
|
||||
// All events are in the future, so no events should be in past.
|
||||
$events = GatherPress::get_cached_remote_events( time() );
|
||||
$this->assertEquals( 0, count( $events ) );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
}
|
139
tests/includes/integrations/class-test-the-events-calendar.php
Normal file
139
tests/includes/integrations/class-test-the-events-calendar.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Transmogrifier (import of ActivityPub Event objects) of GatherPress.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPup\Tests\ActivityPub\Transformer\Test_The_Events_Calendar as TRIBE_Transformer_Test;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\The_Events_Calendar;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Test class for the Transmogrifier (import of ActivityPub Event objects) of GatherPress.
|
||||
*
|
||||
* @coversDefaultClass \Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\The_Events_Calendar
|
||||
*/
|
||||
class Test_The_Events_Calendar extends \WP_UnitTestCase {
|
||||
const FOLLOWED_ACTOR = array(
|
||||
'id' => 'https://remote.example/@organizer',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer/outbox',
|
||||
'name' => 'The Organizer',
|
||||
'summary' => 'Just a random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
/**
|
||||
* REST Server.
|
||||
*
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( ! class_exists( '\Tribe__Events__Main' ) ) {
|
||||
self::markTestSkipped( 'The Events Calendar plugin is not active.' );
|
||||
}
|
||||
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new WP_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
|
||||
// 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();
|
||||
|
||||
// Add event source (ActivityPub follower).
|
||||
_delete_all_posts();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
|
||||
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\The_Events_Calendar::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
\delete_option( 'permalink_structure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor.
|
||||
*/
|
||||
public function test_getting_past_remote_events() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
// Receive an federated event.
|
||||
$json = array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/new-year-party',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Fediverse Party for The Events Calendar',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/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() );
|
||||
|
||||
// Check if post has been created.
|
||||
$events = tribe_get_events();
|
||||
$this->assertEquals( 1, count( $events ) );
|
||||
|
||||
// Create a The Events Calendar Event without content.
|
||||
$wp_object = tribe_events()
|
||||
->set_args( TRIBE_Transformer_Test::MOCKUP_EVENTS['minimal_event'] )
|
||||
->create();
|
||||
|
||||
$this->assertNotEquals( false, $wp_object );
|
||||
// Check if we now have two tribe events.
|
||||
$events = tribe_get_events();
|
||||
$this->assertEquals( 2, count( $events ) );
|
||||
|
||||
$events = The_Events_Calendar::get_cached_remote_events( time() + MONTH_IN_SECONDS );
|
||||
$this->assertEquals( 1, count( $events ) );
|
||||
$this->assertEquals( $json['object']['id'], get_post( $events[0] )->guid );
|
||||
|
||||
$events = The_Events_Calendar::get_cached_remote_events( time() - WEEK_IN_SECONDS );
|
||||
$this->assertEquals( 0, count( $events ) );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
}
|
171
tests/includes/integrations/class-test-vs-event-list.php
Normal file
171
tests/includes/integrations/class-test-vs-event-list.php
Normal file
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for the Transmogrifier (import of ActivityPub Event objects) of GatherPress.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Tests\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Integrations\VS_Event_List;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Test class for the Transmogrifier (import of ActivityPub Event objects) of GatherPress.
|
||||
*
|
||||
* @coversDefaultClass \Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\The_Events_Calendar
|
||||
*/
|
||||
class Test_VS_Event_List extends \WP_UnitTestCase {
|
||||
const FOLLOWED_ACTOR = array(
|
||||
'id' => 'https://remote.example/@organizer',
|
||||
'type' => 'Person',
|
||||
'inbox' => 'https://remote.example/@organizer/inbox',
|
||||
'outbox' => 'https://remote.example/@organizer/outbox',
|
||||
'name' => 'The Organizer',
|
||||
'summary' => 'Just a random organizer of events in the Fediverse',
|
||||
);
|
||||
|
||||
/**
|
||||
* REST Server.
|
||||
*
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Set up the test.
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( ! function_exists( 'vsel_custom_post_type' ) ) {
|
||||
self::markTestSkipped( 'VS Event List plugin is not active.' );
|
||||
}
|
||||
|
||||
\add_option( 'permalink_structure', '/%postname%/' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new WP_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
\Activitypub\Rest\Server::add_hooks();
|
||||
|
||||
// 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();
|
||||
|
||||
// Add event source (ActivityPub follower).
|
||||
_delete_all_posts();
|
||||
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR )->save();
|
||||
|
||||
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
|
||||
\update_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
\Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::class
|
||||
);
|
||||
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the test.
|
||||
*/
|
||||
public function tear_down() {
|
||||
\delete_option( 'permalink_structure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test receiving event from followed actor.
|
||||
*/
|
||||
public function test_getting_past_remote_events() {
|
||||
\add_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
|
||||
// Federated event 1: starts in one week.
|
||||
$event_in_one_week = array(
|
||||
'id' => 'https://remote.example/@organizer/events/in-one-week#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/in-one-week',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + WEEK_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Remote Event in One Week',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Federated event 1: starts in two months.
|
||||
$event_in_two_months = array(
|
||||
'id' => 'https://remote.example/@organizer/events/in-two-months#create',
|
||||
'type' => 'Create',
|
||||
'actor' => 'https://remote.example/@organizer',
|
||||
'object' => array(
|
||||
'id' => 'https://remote.example/@organizer/events/in-two-months',
|
||||
'type' => 'Event',
|
||||
'startTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * MONTH_IN_SECONDS ),
|
||||
'endTime' => \gmdate( 'Y-m-d\TH:i:s\Z', time() + 2 * MONTH_IN_SECONDS + HOUR_IN_SECONDS ),
|
||||
'name' => 'Remote Event in Two Months',
|
||||
'to' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'published' => '2020-01-01T00:00:00Z',
|
||||
'location' => array(
|
||||
'type' => 'Place',
|
||||
'name' => 'Fediverse Concert Hall',
|
||||
'address' => 'Fedistreet 13, Feditown 1337',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/activitypub/1.0/users/0/inbox' );
|
||||
$request->set_header( 'Content-Type', 'application/activity+json' );
|
||||
|
||||
// Receive both events.
|
||||
$request->set_body( \wp_json_encode( $event_in_one_week ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
$request->set_body( \wp_json_encode( $event_in_two_months ) );
|
||||
$response = \rest_do_request( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
// Create a local event in VS Event List.
|
||||
$post_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => 'VSEL Local Test Event',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'event',
|
||||
'meta_input' => array(
|
||||
'event-start-date' => \strtotime( '+10 days 15:00:00' ),
|
||||
'event-date' => \strtotime( '+10 days 16:00:00' ),
|
||||
'event-link' => 'https://event-federation.eu/vsel-test-event',
|
||||
'event-link-label' => 'Website',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertNotEquals( false, $post_id );
|
||||
|
||||
// Only one event should show up in the remote events query.
|
||||
$events = VS_Event_List::get_cached_remote_events( time() + MONTH_IN_SECONDS );
|
||||
$this->assertEquals( 1, count( $events ) );
|
||||
$this->assertEquals( $event_in_one_week['object']['id'], get_post( $events[0] )->guid );
|
||||
|
||||
// Include the even in two months in the time_span.
|
||||
$events = VS_Event_List::get_cached_remote_events( time() + 3 * MONTH_IN_SECONDS );
|
||||
$this->assertEquals( 2, count( $events ) );
|
||||
|
||||
// All events are in the future, so no events should be in past.
|
||||
$events = VS_Event_List::get_cached_remote_events( time() );
|
||||
$this->assertEquals( 0, count( $events ) );
|
||||
|
||||
\remove_filter( 'activitypub_defer_signature_verification', '__return_true' );
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue