updated plugin `ActivityPub` version 2.2.0

This commit is contained in:
KawaiiPunk 2024-03-28 09:39:50 +00:00 committed by Gitium
parent fd53533f59
commit 00c5db12cc
50 changed files with 1726 additions and 374 deletions

View File

@ -3,7 +3,7 @@
* Plugin Name: ActivityPub * Plugin Name: ActivityPub
* Plugin URI: https://github.com/pfefferle/wordpress-activitypub/ * Plugin URI: https://github.com/pfefferle/wordpress-activitypub/
* Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format. * Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.
* Version: 2.0.1 * Version: 2.2.0
* Author: Matthias Pfefferle & Automattic * Author: Matthias Pfefferle & Automattic
* Author URI: https://automattic.com/ * Author URI: https://automattic.com/
* License: MIT * License: MIT
@ -33,7 +33,10 @@ require_once __DIR__ . '/includes/functions.php';
\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "<strong>[ap_title]</strong>\n\n[ap_content]\n\n[ap_hashtags]\n\n[ap_shortlink]" ); \defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "<strong>[ap_title]</strong>\n\n[ap_content]\n\n[ap_hashtags]\n\n[ap_shortlink]" );
\defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) || \define( 'ACTIVITYPUB_AUTHORIZED_FETCH', false ); \defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) || \define( 'ACTIVITYPUB_AUTHORIZED_FETCH', false );
\defined( 'ACTIVITYPUB_DISABLE_REWRITES' ) || \define( 'ACTIVITYPUB_DISABLE_REWRITES', false ); \defined( 'ACTIVITYPUB_DISABLE_REWRITES' ) || \define( 'ACTIVITYPUB_DISABLE_REWRITES', false );
\defined( 'ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS' ) || \define( 'ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS', false );
\defined( 'ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS' ) || \define( 'ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS', false );
\defined( 'ACTIVITYPUB_SHARED_INBOX_FEATURE' ) || \define( 'ACTIVITYPUB_SHARED_INBOX_FEATURE', false ); \defined( 'ACTIVITYPUB_SHARED_INBOX_FEATURE' ) || \define( 'ACTIVITYPUB_SHARED_INBOX_FEATURE', false );
\defined( 'ACTIVITYPUB_SEND_VARY_HEADER' ) || \define( 'ACTIVITYPUB_SEND_VARY_HEADER', false );
\define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); \define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
\define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); \define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
@ -50,6 +53,7 @@ function rest_init() {
Rest\Followers::init(); Rest\Followers::init();
Rest\Following::init(); Rest\Following::init();
Rest\Webfinger::init(); Rest\Webfinger::init();
Rest\Comment::init();
Rest\Server::init(); Rest\Server::init();
Rest\Collection::init(); Rest\Collection::init();
@ -73,6 +77,7 @@ function plugin_init() {
\add_action( 'init', array( __NAMESPACE__ . '\Mention', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Mention', 'init' ) );
\add_action( 'init', array( __NAMESPACE__ . '\Health_Check', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Health_Check', 'init' ) );
\add_action( 'init', array( __NAMESPACE__ . '\Scheduler', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Scheduler', 'init' ) );
\add_action( 'init', array( __NAMESPACE__ . '\Comment', 'init' ) );
if ( site_supports_blocks() ) { if ( site_supports_blocks() ) {
\add_action( 'init', array( __NAMESPACE__ . '\Blocks', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Blocks', 'init' ) );
@ -89,6 +94,9 @@ function plugin_init() {
require_once __DIR__ . '/integration/class-nodeinfo.php'; require_once __DIR__ . '/integration/class-nodeinfo.php';
Integration\Nodeinfo::init(); Integration\Nodeinfo::init();
require_once __DIR__ . '/integration/class-enable-mastodon-apps.php';
Integration\Enable_Mastodon_Apps::init();
} }
\add_action( 'plugins_loaded', __NAMESPACE__ . '\plugin_init' ); \add_action( 'plugins_loaded', __NAMESPACE__ . '\plugin_init' );
@ -111,7 +119,7 @@ function plugin_init() {
if ( false !== strpos( $class, '\\' ) ) { if ( false !== strpos( $class, '\\' ) ) {
$parts = explode( '\\', $class ); $parts = explode( '\\', $class );
$class = array_pop( $parts ); $class = array_pop( $parts );
$sub_dir = implode( '/', $parts ); $sub_dir = strtr( implode( '/', $parts ), '_', '-' );
$base_dir = $base_dir . $sub_dir . '/'; $base_dir = $base_dir . $sub_dir . '/';
} }

View File

@ -1 +1 @@
<?php return array( 'dependencies' => array( 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives' ), 'version' => '3ffce3edc6fed284bfbc' ); <?php return array('dependencies' => array('wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'c7c2319cdfac52a18445');

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
.activitypub-follow-me-block-wrapper{width:100%}.activitypub-follow-me-block-wrapper.has-background .activitypub-profile,.activitypub-follow-me-block-wrapper.has-border-color .activitypub-profile{padding-left:1rem;padding-right:1rem}.activitypub-follow-me-block-wrapper .activitypub-profile{align-items:center;display:flex;padding:1rem 0}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__avatar{border-radius:50%;height:75px;margin-right:1rem;width:75px}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__content{flex:1;min-width:0}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__handle,.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__name{line-height:1.2;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__name{font-size:1.25em}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__follow{align-self:center;background-color:var(--wp--preset--color--black);color:var(--wp--preset--color--white);margin-left:1rem}.activitypub-profile__confirm.components-modal__frame{background-color:#f7f7f7;color:#333}.activitypub-profile__confirm.components-modal__frame .components-modal__header-heading,.activitypub-profile__confirm.components-modal__frame h4{color:#333;letter-spacing:inherit;word-spacing:inherit}.activitypub-follow-me__dialog{max-width:30em}.activitypub-follow-me__dialog h4{line-height:1;margin:0}.activitypub-follow-me__dialog .apmfd__section{margin-bottom:2em}.activitypub-follow-me__dialog .apfmd-description{font-size:var(--wp--preset--font-size--normal,.75rem);margin:.33em 0 1em}.activitypub-follow-me__dialog .apfmd__button-group{align-items:flex-end;display:flex;justify-content:flex-end}.activitypub-follow-me__dialog .apfmd__button-group svg{height:21px;margin-right:.5em;width:21px}.activitypub-follow-me__dialog .apfmd__button-group input{background-color:var(--wp--preset--color--white);border:1px solid var(--wp--preset--color--black);color:var(--wp--preset--color--black);flex:1;padding:6px 12px} .activitypub-follow-me-block-wrapper{width:100%}.activitypub-follow-me-block-wrapper.has-background .activitypub-profile,.activitypub-follow-me-block-wrapper.has-border-color .activitypub-profile{padding-left:1rem;padding-right:1rem}.activitypub-follow-me-block-wrapper .activitypub-profile{align-items:center;display:flex;padding:1rem 0}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__avatar{border-radius:50%;height:75px;margin-right:1rem;width:75px}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__content{flex:1;min-width:0}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__handle,.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__name{line-height:1.2;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__name{font-size:1.25em}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__follow{align-self:center;background-color:var(--wp--preset--color--black);color:var(--wp--preset--color--white);margin-left:1rem}.activitypub__modal.components-modal__frame{background-color:#f7f7f7;color:#333}.activitypub__modal.components-modal__frame .components-modal__header-heading,.activitypub__modal.components-modal__frame h4{color:#333;letter-spacing:inherit;word-spacing:inherit}.activitypub__modal.components-modal__frame .components-modal__header .components-button:hover{color:var(--wp--preset--color--white)}.activitypub__dialog{max-width:40em}.activitypub__dialog h4{line-height:1;margin:0}.activitypub__dialog .activitypub-dialog__section{margin-bottom:2em}.activitypub__dialog .activitypub-dialog__description{font-size:var(--wp--preset--font-size--normal,.75rem);margin:.33em 0 1em}.activitypub__dialog .activitypub-dialog__button-group{align-items:flex-end;display:flex;justify-content:flex-end}.activitypub__dialog .activitypub-dialog__button-group svg{height:21px;margin-right:.5em;width:21px}.activitypub__dialog .activitypub-dialog__button-group input{background-color:var(--wp--preset--color--white);border:1px solid var(--wp--preset--color--black);border-radius:inherit 0;color:var(--wp--preset--color--black);flex:1;padding:6px 12px}.activitypub__dialog .activitypub-dialog__button-group button{align-self:center;background-color:var(--wp--preset--color--black);color:var(--wp--preset--color--white);margin-left:0;text-decoration:none}

View File

@ -1 +1 @@
<?php return array( 'dependencies' => array( 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-primitives' ), 'version' => '40f3434fe6f953826373' ); <?php return array('dependencies' => array('wp-api-fetch', 'wp-components', 'wp-compose', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '1349fc90a3f33dde3595');

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<?php return array( 'dependencies' => array( 'react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url' ), 'version' => 'c338a0364a63e21934ae' ); <?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => 'c338a0364a63e21934ae');

View File

@ -1 +1 @@
<?php return array( 'dependencies' => array( 'react', 'wp-api-fetch', 'wp-components', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-url' ), 'version' => 'ed5a13e66f8b10323435' ); <?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => 'ed5a13e66f8b10323435');

View File

@ -0,0 +1,11 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"name": "activitypub/remote-reply",
"apiVersion": 3,
"version": "1.0.0",
"title": "Reply on the Fediverse",
"category": "widgets",
"description": "",
"textdomain": "activitypub",
"viewScript": "file:./index.js"
}

View File

@ -0,0 +1 @@
<?php return array('dependencies' => array('wp-api-fetch', 'wp-components', 'wp-compose', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '0235743f089d22122d70');

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.activitypub__modal.components-modal__frame{background-color:#f7f7f7;color:#333}.activitypub__modal.components-modal__frame .components-modal__header-heading,.activitypub__modal.components-modal__frame h4{color:#333;letter-spacing:inherit;word-spacing:inherit}.activitypub__modal.components-modal__frame .components-modal__header .components-button:hover{color:var(--wp--preset--color--white)}.activitypub__dialog{max-width:40em}.activitypub__dialog h4{line-height:1;margin:0}.activitypub__dialog .activitypub-dialog__section{margin-bottom:2em}.activitypub__dialog .activitypub-dialog__description{font-size:var(--wp--preset--font-size--normal,.75rem);margin:.33em 0 1em}.activitypub__dialog .activitypub-dialog__button-group{align-items:flex-end;display:flex;justify-content:flex-end}.activitypub__dialog .activitypub-dialog__button-group svg{height:21px;margin-right:.5em;width:21px}.activitypub__dialog .activitypub-dialog__button-group input{background-color:var(--wp--preset--color--white);border:1px solid var(--wp--preset--color--black);border-radius:inherit 0;color:var(--wp--preset--color--black);flex:1;padding:6px 12px}.activitypub__dialog .activitypub-dialog__button-group button{align-self:center;background-color:var(--wp--preset--color--black);color:var(--wp--preset--color--white);margin-left:0;text-decoration:none}

View File

@ -17,75 +17,15 @@ use Activitypub\Activity\Base_Object;
* @see https://www.w3.org/TR/activitystreams-core/#intransitiveactivities * @see https://www.w3.org/TR/activitystreams-core/#intransitiveactivities
*/ */
class Activity extends Base_Object { class Activity extends Base_Object {
const CONTEXT = array( const JSON_LD_CONTEXT = array(
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
'https://purl.archive.org/socialweb/webfinger',
array(
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'PropertyValue' => 'schema:PropertyValue',
'schema' => 'http://schema.org#',
'pt' => 'https://joinpeertube.org/ns#',
'toot' => 'http://joinmastodon.org/ns#',
'litepub' => 'http://litepub.social/ns#',
'lemmy' => 'https://join-lemmy.org/ns#',
'value' => 'schema:value',
'Hashtag' => 'as:Hashtag',
'featured' => array(
'@id' => 'toot:featured',
'@type' => '@id',
),
'featuredTags' => array(
'@id' => 'toot:featuredTags',
'@type' => '@id',
),
'alsoKnownAs' => array(
'@id' => 'as:alsoKnownAs',
'@type' => '@id',
),
'moderators' => array(
'@id' => 'lemmy:moderators',
'@type' => '@id',
),
'postingRestrictedToMods' => 'lemmy:postingRestrictedToMods',
'discoverable' => 'toot:discoverable',
'indexable' => 'toot:indexable',
'sensitive' => 'as:sensitive',
),
); );
/**
* The object's unique global identifier
*
* @see https://www.w3.org/TR/activitypub/#obj-id
*
* @var string
*/
protected $id;
/** /**
* @var string * @var string
*/ */
protected $type = 'Activity'; protected $type = 'Activity';
/**
* The context within which the object exists or an activity was
* performed.
* The notion of "context" used is intentionally vague.
* The intended function is to serve as a means of grouping objects
* and activities that share a common originating context or
* purpose. An example could be all activities relating to a common
* project or event.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-context
*
* @var string
* | ObjectType
* | Link
* | null
*/
protected $context = self::CONTEXT;
/** /**
* Describes the direct object of the activity. * Describes the direct object of the activity.
* For instance, in the activity "John added a movie to his * For instance, in the activity "John added a movie to his
@ -94,7 +34,7 @@ class Activity extends Base_Object {
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object-term * @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object-term
* *
* @var string * @var string
* | Base_Objectr * | Base_Object
* | Link * | Link
* | null * | null
*/ */
@ -225,4 +165,21 @@ class Activity extends Base_Object {
$this->set( 'id', $object->get_id() . '#activity' ); $this->set( 'id', $object->get_id() . '#activity' );
} }
} }
/**
* The context of an Activity is usually just the context of the object it contains.
*
* @return array $context A compacted JSON-LD context.
*/
public function get_json_ld_context() {
if ( $this->object instanceof Base_Object ) {
$class = get_class( $this->object );
if ( $class && $class::JSON_LD_CONTEXT ) {
// Without php 5.6 support this could be just: 'return $this->object::JSON_LD_CONTEXT;'
return $class::JSON_LD_CONTEXT;
}
}
return static::JSON_LD_CONTEXT;
}
} }

View File

@ -16,6 +16,39 @@ namespace Activitypub\Activity;
* @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types * @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
*/ */
class Actor extends Base_Object { class Actor extends Base_Object {
// Reduced context for actors. TODO: still unused.
const JSON_LD_CONTEXT = array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
'https://purl.archive.org/socialweb/webfinger',
array(
'schema' => 'http://schema.org#',
'toot' => 'http://joinmastodon.org/ns#',
'webfinger' => 'https://webfinger.net/#',
'lemmy' => 'https://join-lemmy.org/ns#',
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'PropertyValue' => 'schema:PropertyValue',
'value' => 'schema:value',
'Hashtag' => 'as:Hashtag',
'featured' => array(
'@id' => 'toot:featured',
'@type' => '@id',
),
'featuredTags' => array(
'@id' => 'toot:featuredTags',
'@type' => '@id',
),
'moderators' => array(
'@id' => 'lemmy:moderators',
'@type' => '@id',
),
'postingRestrictedToMods' => 'lemmy:postingRestrictedToMods',
'discoverable' => 'toot:discoverable',
'indexable' => 'toot:indexable',
'resource' => 'webfinger:resource',
),
);
/** /**
* @var string * @var string
*/ */
@ -133,6 +166,8 @@ class Actor extends Base_Object {
* *
* @see https://docs.joinmastodon.org/spec/activitypub/#as * @see https://docs.joinmastodon.org/spec/activitypub/#as
* *
* @context as:manuallyApprovesFollowers
*
* @var boolean * @var boolean
*/ */
protected $manually_approves_followers = false; protected $manually_approves_followers = false;

View File

@ -9,6 +9,7 @@ namespace Activitypub\Activity;
use WP_Error; use WP_Error;
use ReflectionClass; use ReflectionClass;
use DateTime;
use function Activitypub\camel_to_snake_case; use function Activitypub\camel_to_snake_case;
use function Activitypub\snake_to_camel_case; use function Activitypub\snake_to_camel_case;
@ -26,6 +27,13 @@ use function Activitypub\snake_to_camel_case;
* @see https://www.w3.org/TR/activitystreams-core/#object * @see https://www.w3.org/TR/activitystreams-core/#object
*/ */
class Base_Object { class Base_Object {
const JSON_LD_CONTEXT = array(
'https://www.w3.org/ns/activitystreams',
array(
'Hashtag' => 'as:Hashtag',
),
);
/** /**
* The object's unique global identifier * The object's unique global identifier
* *
@ -457,7 +465,7 @@ class Base_Object {
} }
if ( \strncasecmp( $method, 'set', 3 ) === 0 ) { if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
$this->set( $var, $params[0] ); return $this->set( $var, $params[0] );
} }
if ( \strncasecmp( $method, 'add', 3 ) === 0 ) { if ( \strncasecmp( $method, 'add', 3 ) === 0 ) {
@ -524,7 +532,7 @@ class Base_Object {
$this->$key = $value; $this->$key = $value;
return $this->$key; return $this;
} }
/** /**
@ -622,9 +630,11 @@ class Base_Object {
* It tries to get the object attributes if they exist * It tries to get the object attributes if they exist
* and falls back to the getters. Empty values are ignored. * and falls back to the getters. Empty values are ignored.
* *
* @param bool $include_json_ld_context Whether to include the JSON-LD context. Default true.
*
* @return array An array built from the Object. * @return array An array built from the Object.
*/ */
public function to_array() { public function to_array( $include_json_ld_context = true ) {
$array = array(); $array = array();
$vars = get_object_vars( $this ); $vars = get_object_vars( $this );
@ -640,7 +650,7 @@ class Base_Object {
} }
if ( is_object( $value ) ) { if ( is_object( $value ) ) {
$value = $value->to_array(); $value = $value->to_array( false );
} }
// if value is still empty, ignore it for the array and continue. // if value is still empty, ignore it for the array and continue.
@ -649,11 +659,9 @@ class Base_Object {
} }
} }
// replace 'context' key with '@context' and move it to the top. if ( $include_json_ld_context ) {
if ( array_key_exists( 'context', $array ) ) { // Get JsonLD context and move it to '@context' at the top.
$context = $array['context']; $array = array_merge( array( '@context' => $this->get_json_ld_context() ), $array );
unset( $array['context'] );
$array = array_merge( array( '@context' => $context ), $array );
} }
$class = new ReflectionClass( $this ); $class = new ReflectionClass( $this );
@ -668,10 +676,12 @@ class Base_Object {
/** /**
* Convert Object to JSON. * Convert Object to JSON.
* *
* @param bool $include_json_ld_context Whether to include the JSON-LD context. Default true.
*
* @return string The JSON string. * @return string The JSON string.
*/ */
public function to_json() { public function to_json( $include_json_ld_context = true ) {
$array = $this->to_array(); $array = $this->to_array( $include_json_ld_context );
$options = \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT; $options = \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT;
/* /*
@ -692,4 +702,13 @@ class Base_Object {
public function get_object_var_keys() { public function get_object_var_keys() {
return \array_keys( \get_object_vars( $this ) ); return \array_keys( \get_object_vars( $this ) );
} }
/**
* Returns the JSON-LD context of this object.
*
* @return array $context A compacted JSON-LD context for the ActivityPub object.
*/
public function get_json_ld_context() {
return static::JSON_LD_CONTEXT;
}
} }

View File

@ -0,0 +1,340 @@
<?php
/**
* ActivityPub Object of type Event.
*
* @package activity-event-transformers
*/
namespace Activitypub\Activity\Extended_Object;
use Activitypub\Activity\Base_Object;
/**
* Event is an implementation of one of the Activity Streams Event object type.
*
* This class contains extra keys as used by Mobilizon to ensure compatibility.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event
*/
class Event extends Base_Object {
// Human friendly minimal context for full Mobilizon compatible ActivityPub events.
const JSON_LD_CONTEXT = array(
'https://schema.org/', // The base context is schema.org, cause it is used a lot.
'https://www.w3.org/ns/activitystreams', // The ActivityStreams context overrides everyting also defined in schema.org.
array( // The keys here override/extend the context even more.
'pt' => 'https://joinpeertube.org/ns#',
'mz' => 'https://joinmobilizon.org/ns#',
'status' => 'http://www.w3.org/2002/12/cal/ical#status',
'commentsEnabled' => 'pt:commentsEnabled',
'isOnline' => 'mz:isOnline',
'timezone' => 'mz:timezone',
'participantCount' => 'mz:participantCount',
'anonymousParticipationEnabled' => 'mz:anonymousParticipationEnabled',
'joinMode' => array(
'@id' => 'mz:joinMode',
'@type' => 'mz:joinModeType',
),
'externalParticipationUrl' => array(
'@id' => 'mz:externalParticipationUrl',
'@type' => 'schema:URL',
),
'repliesModerationOption' => array(
'@id' => 'mz:repliesModerationOption',
'@type' => '@vocab',
),
'contacts' => array(
'@id' => 'mz:contacts',
'@type' => '@id',
),
),
);
/**
* Mobilizon compatible values for repliesModertaionOption.
* @var array
*/
const REPLIES_MODERATION_OPTION_TYPES = array( 'allow_all', 'closed' );
/**
* Mobilizon compatible values for joinModeTypes.
*/
const JOIN_MODE_TYPES = array( 'free', 'restricted', 'external' ); // and 'invite', but not used by mobilizon atm
/**
* Allowed values for ical VEVENT STATUS.
* @var array
*/
const ICAL_EVENT_STATUS_TYPES = array( 'TENTATIVE', 'CONFIRMED', 'CANCELLED' );
/**
* Default event categories.
*
* These values currently reflect the default set as proposed by Mobilizon to maximize interoperability.
* @var array
*/
const DEFAULT_EVENT_CATEGORIES = array(
'ARTS',
'BOOK_CLUBS',
'BUSINESS',
'CAUSES',
'COMEDY',
'CRAFTS',
'FOOD_DRINK',
'HEALTH',
'MUSIC',
'AUTO_BOAT_AIR',
'COMMUNITY',
'FAMILY_EDUCATION',
'FASHION_BEAUTY',
'FILM_MEDIA',
'GAMES',
'LANGUAGE_CULTURE',
'LEARNING',
'LGBTQ',
'MOVEMENTS_POLITICS',
'NETWORKING',
'PARTY',
'PERFORMING_VISUAL_ARTS',
'PETS',
'PHOTOGRAPHY',
'OUTDOORS_ADVENTURE',
'SPIRITUALITY_RELIGION_BELIEFS',
'SCIENCE_TECH',
'SPORTS',
'THEATRE',
'MEETING', // Default value.
);
/**
* Event is an implementation of one of the
* Activity Streams
*
* @var string
*/
protected $type = 'Event';
/**
* The Title of the event.
*/
protected $name;
/**
* The events contacts
*
* @context {
* '@id' => 'mz:contacts',
* '@type' => '@id',
* }
*
* @var array Array of contacts (ActivityPub actor IDs).
*/
protected $contacts;
/**
* Extension invented by PeerTube whether comments/replies are <enabled>
* Mobilizon also implemented this as a fallback to their own
* repliesModerationOption.
*
* @see https://docs.joinpeertube.org/api/activitypub#video
* @see https://docs.joinmobilizon.org/contribute/activity_pub/
* @var bool|null
*/
protected $comments_enabled;
/**
* @context https://joinmobilizon.org/ns#timezone
* @var string
*/
protected $timezone;
/**
* @context https://joinmobilizon.org/ns#repliesModerationOption
* @see https://docs.joinmobilizon.org/contribute/activity_pub/#repliesmoderation
* @var string
*/
protected $replies_moderation_option;
/**
* @context https://joinmobilizon.org/ns#anonymousParticipationEnabled
* @see https://docs.joinmobilizon.org/contribute/activity_pub/#anonymousparticipationenabled
* @var bool
*/
protected $anonymous_participation_enabled;
/**
* @context https://schema.org/category
* @var enum
*/
protected $category;
/**
* @context https://schema.org/inLanguage
* @var
*/
protected $in_language;
/**
* @context https://joinmobilizon.org/ns#isOnline
* @var bool
*/
protected $is_online;
/**
* @context https://www.w3.org/2002/12/cal/ical#status
* @var enum
*/
protected $status;
/**
* Which actor created the event.
*
* This field is needed due to the current group structure of Mobilizon.
*
* @todo this seems to not be a default property of an Object but needed by mobilizon.
* @var string
*/
protected $actor;
/**
* @context https://joinmobilizon.org/ns#externalParticipationUrl
* @var string
*/
protected $external_participation_url;
/**
* @context https://joinmobilizon.org/ns#joinMode
* @see https://docs.joinmobilizon.org/contribute/activity_pub/#joinmode
* @var
*/
protected $join_mode;
/**
* @context https://joinmobilizon.org/ns#participantCount
* @var int
*/
protected $participant_count;
/**
* @context https://schema.org/maximumAttendeeCapacity
* @see https://docs.joinmobilizon.org/contribute/activity_pub/#maximumattendeecapacity
* @var int
*/
protected $maximum_attendee_capacity;
/**
* @context https://schema.org/remainingAttendeeCapacity
* @see https://docs.joinmobilizon.org/contribute/activity_pub/#remainignattendeecapacity
* @var int
*/
protected $remaining_attendee_capacity;
/**
* Setter for the timezone.
*
* The passed timezone is only set when it is a valid one, otherwise the site's timezone is used.
*
* @param string $timezone The timezone string to be set, e.g. 'Europe/Berlin'.
*/
public function set_timezone( $timezone ) {
if ( in_array( $timezone, timezone_identifiers_list(), true ) ) {
$this->timezone = $timezone;
} else {
$this->timezone = wp_timezone_string();
}
return $this;
}
/**
* Custom setter for repliesModerationOption which also directy sets commentsEnabled accordingly.
*
* @param string $type
*/
public function set_replies_moderation_option( $type ) {
if ( in_array( $type, self::REPLIES_MODERATION_OPTION_TYPES, true ) ) {
$this->replies_moderation_option = $type;
$this->comments_enabled = ( 'allow_all' === $type ) ? true : false;
} else {
_doing_it_wrong(
__METHOD__,
'The replies moderation option must be either allow_all or closed.',
'<version_placeholder>'
);
}
return $this;
}
/**
* Custom setter for commentsEnabled which also directly sets repliesModerationOption accordingly.
*
* @param bool $comments_enabled
*/
public function set_comments_enabled( $comments_enabled ) {
if ( is_bool( $comments_enabled ) ) {
$this->comments_enabled = $comments_enabled;
$this->replies_moderation_option = $comments_enabled ? 'allow_all' : 'closed';
} else {
_doing_it_wrong(
__METHOD__,
'The commentsEnabled must be boolean.',
'<version_placeholder>'
);
}
return $this;
}
/**
* Custom setter for the ical status that checks whether the status is an ical event status.
*
* @param string $status
*/
public function set_status( $status ) {
if ( in_array( $status, self::ICAL_EVENT_STATUS_TYPES, true ) ) {
$this->status = $status;
} else {
_doing_it_wrong(
__METHOD__,
'The status of the event must be a VEVENT iCal status.',
'<version_placeholder>'
);
}
return $this;
}
/**
* Custom setter for the event category.
*
* Falls back to Mobilizons default category.
*
* @param string $category
* @param bool $mobilizon_compatibilty Whether the category must be compatibly with Mobilizon.
*/
public function set_category( $category, $mobilizon_compatibilty = true ) {
if ( $mobilizon_compatibilty ) {
$this->category = in_array( $category, self::DEFAULT_EVENT_CATEGORIES, true ) ? $category : 'MEETING';
} else {
$this->category = $category;
}
return $this;
}
/**
* Custom setter for an external participation url.
*
* Automatically sets the joinMode to true if called.
*
* @param string $url
*/
public function set_external_participation_url( $url ) {
if ( preg_match( '/^https?:\/\/.*/i', $url ) ) {
$this->external_participation_url = $url;
$this->join_mode = 'external';
}
return $this;
}
}

View File

@ -0,0 +1,93 @@
<?php
/**
* Event is an implementation of one of the
* Activity Streams Event object type
*
* @package activity-event-transformers
*/
namespace Activitypub\Activity\Extended_Object;
use Activitypub\Activity\Base_Object;
/**
* Event is an implementation of one of the
* Activity Streams Event object type
*
* The Object is the primary base type for the Activity Streams
* vocabulary.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event
*/
class Place extends Base_Object {
/**
* Place is an implementation of one of the
* Activity Streams
*
* @var string
*/
protected $type = 'Place';
/**
* Indicates the accuracy of position coordinates on a Place objects.
* Expressed in properties of percentage. e.g. "94.0" means "94.0% accurate".
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accuracy
* @var float xsd:float [>= 0.0f, <= 100.0f]
*/
protected $accuracy;
/**
* Indicates the altitude of a place. The measurement units is indicated using the units property.
* If units is not specified, the default is assumed to be "m" indicating meters.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-altitude
* @var float xsd:float
*/
protected $altitude;
/**
* The latitude of a place.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-latitude
* @var float xsd:float
*/
protected $latitude;
/**
* The longitude of a place.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-longitude
* @var float xsd:float
*/
protected $longitude;
/**
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-radius
* @var float
*/
protected $radius;
/**
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-units
* @var string
*/
protected $units;
/**
* @var Postal_Address|string
*/
protected $address;
public function set_address( $address ) {
if ( is_string( $address ) || is_array( $address ) ) {
$this->address = $address;
} else {
_doing_it_wrong(
__METHOD__,
'The address must be either a string or an array like schema.org/PostalAddress.',
'<version_placeholder>'
);
}
}
}

View File

@ -13,6 +13,7 @@ use Activitypub\Transformer\Comment;
use function Activitypub\is_single_user; use function Activitypub\is_single_user;
use function Activitypub\is_user_disabled; use function Activitypub\is_user_disabled;
use function Activitypub\safe_remote_post; use function Activitypub\safe_remote_post;
use function Activitypub\set_wp_object_state;
/** /**
* ActivityPub Activity_Dispatcher Class * ActivityPub Activity_Dispatcher Class
@ -26,6 +27,9 @@ class Activity_Dispatcher {
* Initialize the class, registering WordPress hooks. * Initialize the class, registering WordPress hooks.
*/ */
public static function init() { public static function init() {
\add_action( 'activitypub_send_post', array( self::class, 'send_post' ), 10, 2 );
\add_action( 'activitypub_send_comment', array( self::class, 'send_comment' ), 10, 2 );
\add_action( 'activitypub_send_activity', array( self::class, 'send_activity' ), 10, 2 ); \add_action( 'activitypub_send_activity', array( self::class, 'send_activity' ), 10, 2 );
\add_action( 'activitypub_send_activity', array( self::class, 'send_activity_or_announce' ), 10, 2 ); \add_action( 'activitypub_send_activity', array( self::class, 'send_activity_or_announce' ), 10, 2 );
\add_action( 'activitypub_send_update_profile_activity', array( self::class, 'send_profile_update' ), 10, 1 ); \add_action( 'activitypub_send_update_profile_activity', array( self::class, 'send_profile_update' ), 10, 1 );
@ -77,7 +81,7 @@ class Activity_Dispatcher {
$activity = $transformer->to_activity( $type ); $activity = $transformer->to_activity( $type );
self::send_activity_to_inboxes( $activity, $user_id ); self::send_activity_to_followers( $activity, $user_id, $wp_object );
} }
/** /**
@ -103,7 +107,7 @@ class Activity_Dispatcher {
$user_id = $transformer->get_wp_user_id(); $user_id = $transformer->get_wp_user_id();
$activity = $transformer->to_activity( 'Announce' ); $activity = $transformer->to_activity( 'Announce' );
self::send_activity_to_inboxes( $activity, $user_id ); self::send_activity_to_followers( $activity, $user_id, $wp_object );
} }
/** /**
@ -130,18 +134,24 @@ class Activity_Dispatcher {
$activity->set_to( 'https://www.w3.org/ns/activitystreams#Public' ); $activity->set_to( 'https://www.w3.org/ns/activitystreams#Public' );
// send the update // send the update
self::send_activity_to_inboxes( $activity, $user_id ); self::send_activity_to_followers( $activity, $user_id, $user );
} }
/** /**
* Send an Activity to all followers and mentioned users. * Send an Activity to all followers and mentioned users.
* *
* @param Activity $activity The ActivityPub Activity. * @param Activity $activity The ActivityPub Activity.
* @param int $user_id The user ID. * @param int $user_id The user ID.
* @param WP_User|WP_Post|WP_Comment $wp_object The WordPress object.
* *
* @return void * @return void
*/ */
private static function send_activity_to_inboxes( $activity, $user_id ) { private static function send_activity_to_followers( $activity, $user_id, $wp_object ) {
// check if the Activity should be send to the followers
if ( ! apply_filters( 'activitypub_send_activity_to_followers', true, $activity, $user_id, $wp_object ) ) {
return;
}
$follower_inboxes = Followers::get_inboxes( $user_id ); $follower_inboxes = Followers::get_inboxes( $user_id );
$mentioned_inboxes = array(); $mentioned_inboxes = array();
@ -162,5 +172,57 @@ class Activity_Dispatcher {
foreach ( $inboxes as $inbox ) { foreach ( $inboxes as $inbox ) {
safe_remote_post( $inbox, $json, $user_id ); safe_remote_post( $inbox, $json, $user_id );
} }
set_wp_object_state( $wp_object, 'federated' );
}
/**
* Send a "Create" or "Update" Activity for a WordPress Post.
*
* @param int $id The WordPress Post ID.
* @param string $type The Activity-Type.
*
* @return void
*/
public static function send_post( $id, $type ) {
$post = get_post( $id );
if ( ! $post ) {
return;
}
do_action( 'activitypub_send_activity', $post, $type );
do_action(
sprintf(
'activitypub_send_%s_activity',
\strtolower( $type )
),
$post
);
}
/**
* Send a "Create" or "Update" Activity for a WordPress Comment.
*
* @param int $id The WordPress Comment ID.
* @param string $type The Activity-Type.
*
* @return void
*/
public static function send_comment( $id, $type ) {
$comment = get_comment( $id );
if ( ! $comment ) {
return;
}
do_action( 'activitypub_send_activity', $comment, $type );
do_action(
sprintf(
'activitypub_send_%s_activity',
\strtolower( $type )
),
$comment
);
} }
} }

View File

@ -6,9 +6,11 @@ use Activitypub\Signature;
use Activitypub\Collection\Users; use Activitypub\Collection\Users;
use Activitypub\Collection\Followers; use Activitypub\Collection\Followers;
use function Activitypub\sanitize_url;
use function Activitypub\is_comment; use function Activitypub\is_comment;
use function Activitypub\sanitize_url;
use function Activitypub\is_local_comment;
use function Activitypub\is_activitypub_request; use function Activitypub\is_activitypub_request;
use function Activitypub\should_comment_be_federated;
/** /**
* ActivityPub Class * ActivityPub Class
@ -24,10 +26,9 @@ class Activitypub {
\add_action( 'template_redirect', array( self::class, 'template_redirect' ) ); \add_action( 'template_redirect', array( self::class, 'template_redirect' ) );
\add_filter( 'query_vars', array( self::class, 'add_query_vars' ) ); \add_filter( 'query_vars', array( self::class, 'add_query_vars' ) );
\add_filter( 'pre_get_avatar_data', array( self::class, 'pre_get_avatar_data' ), 11, 2 ); \add_filter( 'pre_get_avatar_data', array( self::class, 'pre_get_avatar_data' ), 11, 2 );
\add_filter( 'get_comment_link', array( self::class, 'remote_comment_link' ), 11, 3 );
// Add support for ActivityPub to custom post types // Add support for ActivityPub to custom post types
$post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) : array(); $post_types = \get_option( 'activitypub_support_post_types', array( 'post' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post' ) ) : array();
foreach ( $post_types as $post_type ) { foreach ( $post_types as $post_type ) {
\add_post_type_support( $post_type, 'activitypub' ); \add_post_type_support( $post_type, 'activitypub' );
@ -42,8 +43,6 @@ class Activitypub {
\add_action( 'in_plugin_update_message-' . ACTIVITYPUB_PLUGIN_BASENAME, array( self::class, 'plugin_update_message' ) ); \add_action( 'in_plugin_update_message-' . ACTIVITYPUB_PLUGIN_BASENAME, array( self::class, 'plugin_update_message' ) );
\add_filter( 'comment_class', array( self::class, 'comment_class' ), 10, 3 );
// register several post_types // register several post_types
self::register_post_types(); self::register_post_types();
} }
@ -100,6 +99,11 @@ class Activitypub {
return $template; return $template;
} }
// check if blog-user is enabled
if ( \is_home() && is_wp_error( Users::get_by_id( Users::BLOG_USER_ID ) ) ) {
return $template;
}
if ( \is_author() ) { if ( \is_author() ) {
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/author-json.php'; $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/author-json.php';
} elseif ( is_comment() ) { } elseif ( is_comment() ) {
@ -144,7 +148,7 @@ class Activitypub {
} }
// stop if it's not an ActivityPub comment // stop if it's not an ActivityPub comment
if ( is_activitypub_request() && $comment->user_id ) { if ( is_activitypub_request() && ! is_local_comment( $comment ) ) {
return; return;
} }
@ -225,30 +229,6 @@ class Activitypub {
return \get_comment_meta( $comment->comment_ID, 'avatar_url', true ); return \get_comment_meta( $comment->comment_ID, 'avatar_url', true );
} }
/**
* Link remote comments to source url.
*
* @param string $comment_link
* @param object|WP_Comment $comment
*
* @return string $url
*/
public static function remote_comment_link( $comment_link, $comment ) {
if ( ! $comment || is_admin() ) {
return $comment_link;
}
$comment_meta = \get_comment_meta( $comment->comment_ID );
if ( ! empty( $comment_meta['source_url'][0] ) ) {
return $comment_meta['source_url'][0];
} elseif ( ! empty( $comment_meta['source_id'][0] ) ) {
return $comment_meta['source_id'][0];
}
return $comment_link;
}
/** /**
* Store permalink in meta, to send delete Activity. * Store permalink in meta, to send delete Activity.
* *
@ -457,22 +437,4 @@ class Activitypub {
\do_action( 'activitypub_after_register_post_type' ); \do_action( 'activitypub_after_register_post_type' );
} }
/**
* Filters the CSS classes to add an ActivityPub class.
*
* @param string[] $classes An array of comment classes.
* @param string[] $css_class An array of additional classes added to the list.
* @param string $comment_id The comment ID as a numeric string.
*
* @return string[] An array of classes.
*/
public static function comment_class( $classes, $css_class, $comment_id ) {
// check if ActivityPub comment
if ( 'activitypub' === get_comment_meta( $comment_id, 'protocol', true ) ) {
$classes[] = 'activitypub-comment';
}
return $classes;
}
} }

View File

@ -4,6 +4,10 @@ namespace Activitypub;
use WP_User_Query; use WP_User_Query;
use Activitypub\Model\Blog_User; use Activitypub\Model\Blog_User;
use function Activitypub\is_user_disabled;
use function Activitypub\was_comment_received;
use function Activitypub\is_comment_federatable;
/** /**
* ActivityPub Admin Class * ActivityPub Admin Class
* *
@ -16,9 +20,11 @@ class Admin {
public static function init() { public static function init() {
\add_action( 'admin_menu', array( self::class, 'admin_menu' ) ); \add_action( 'admin_menu', array( self::class, 'admin_menu' ) );
\add_action( 'admin_init', array( self::class, 'register_settings' ) ); \add_action( 'admin_init', array( self::class, 'register_settings' ) );
\add_action( 'load-comment.php', array( self::class, 'edit_comment' ) );
\add_action( 'personal_options_update', array( self::class, 'save_user_description' ) ); \add_action( 'personal_options_update', array( self::class, 'save_user_description' ) );
\add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_scripts' ) ); \add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_scripts' ) );
\add_action( 'admin_notices', array( self::class, 'admin_notices' ) ); \add_action( 'admin_notices', array( self::class, 'admin_notices' ) );
\add_filter( 'comment_row_actions', array( self::class, 'comment_row_actions' ), 10, 2 );
if ( ! is_user_disabled( get_current_user_id() ) ) { if ( ! is_user_disabled( get_current_user_id() ) ) {
\add_action( 'show_user_profile', array( self::class, 'add_profile' ) ); \add_action( 'show_user_profile', array( self::class, 'add_profile' ) );
@ -193,7 +199,7 @@ class Admin {
'type' => 'string', 'type' => 'string',
'description' => \esc_html__( 'Enable ActivityPub support for post types', 'activitypub' ), 'description' => \esc_html__( 'Enable ActivityPub support for post types', 'activitypub' ),
'show_in_rest' => true, 'show_in_rest' => true,
'default' => array( 'post', 'pages' ), 'default' => array( 'post' ),
) )
); );
\register_setting( \register_setting(
@ -304,4 +310,40 @@ class Admin {
wp_enqueue_script( 'activitypub-admin-styles', plugins_url( 'assets/js/activitypub-admin.js', ACTIVITYPUB_PLUGIN_FILE ), array( 'jquery' ), '1.0.0', false ); wp_enqueue_script( 'activitypub-admin-styles', plugins_url( 'assets/js/activitypub-admin.js', ACTIVITYPUB_PLUGIN_FILE ), array( 'jquery' ), '1.0.0', false );
} }
} }
/**
* Hook into the edit_comment functionality
*
* * Disable the edit_comment capability for federated comments.
*
* @return void
*/
public static function edit_comment() {
// Disable the edit_comment capability for federated comments.
\add_filter(
'user_has_cap',
function ( $allcaps, $caps, $arg ) {
if ( 'edit_comment' !== $arg[0] ) {
return $allcaps;
}
if ( was_comment_received( $arg[2] ) ) {
return false;
}
return $allcaps;
},
1,
3
);
}
public static function comment_row_actions( $actions, $comment ) {
if ( was_comment_received( $comment ) ) {
unset( $actions['edit'] );
unset( $actions['quickedit'] );
}
return $actions;
}
} }

View File

@ -78,6 +78,12 @@ class Blocks {
array( 'icon', 'name', 'webfinger' ) array( 'icon', 'name', 'webfinger' )
); );
} }
// add `@` prefix if it's missing
if ( '@' !== substr( $attrs['profileData']['webfinger'], 0, 1 ) ) {
$attrs['profileData']['webfinger'] = '@' . $attrs['profileData']['webfinger'];
}
$wrapper_attributes = get_block_wrapper_attributes( $wrapper_attributes = get_block_wrapper_attributes(
array( array(
'aria-label' => __( 'Follow me on the Fediverse', 'activitypub' ), 'aria-label' => __( 'Follow me on the Fediverse', 'activitypub' ),

View File

@ -0,0 +1,384 @@
<?php
namespace Activitypub;
use WP_Comment_Query;
use function Activitypub\is_user_disabled;
/**
* ActivityPub Comment Class
*
* This class is a helper/utils class that provides a collection of static
* methods that are used to handle comments.
*/
class Comment {
/**
* Initialize the class, registering WordPress hooks
*/
public static function init() {
\add_filter( 'comment_reply_link', array( self::class, 'comment_reply_link' ), 10, 3 );
\add_filter( 'comment_class', array( self::class, 'comment_class' ), 10, 3 );
\add_filter( 'get_comment_link', array( self::class, 'remote_comment_link' ), 11, 3 );
\add_action( 'wp_enqueue_scripts', array( self::class, 'enqueue_scripts' ) );
}
/**
* Filter the comment reply link.
*
* We don't want to show the comment reply link for federated comments
* if the user is disabled for federation.
*
* @param string $link The HTML markup for the comment reply link.
* @param array $args An array of arguments overriding the defaults.
* @param WP_Comment $comment The object of the comment being replied.
*
* @return string The filtered HTML markup for the comment reply link.
*/
public static function comment_reply_link( $link, $args, $comment ) {
if ( self::are_comments_allowed( $comment ) ) {
return $link;
}
$attrs = array(
'selectedComment' => self::generate_id( $comment ),
'commentId' => $comment->comment_ID,
);
$div = sprintf(
'<div class="activitypub-remote-reply" data-attrs="%s"></div>',
esc_attr( wp_json_encode( $attrs ) )
);
return apply_filters( 'activitypub_comment_reply_link', $div );
}
/**
* Check if it is allowed to comment to a comment.
*
* Checks if the comment is local only or if the user can comment federated comments.
*
* @param mixed $comment Comment object or ID.
*
* @return boolean True if the user can comment, false otherwise.
*/
public static function are_comments_allowed( $comment ) {
$comment = \get_comment( $comment );
if ( ! self::was_received( $comment ) ) {
return true;
}
$current_user = get_current_user_id();
if ( ! $current_user ) {
return false;
}
$is_user_disabled = is_user_disabled( $current_user );
if ( $is_user_disabled ) {
return false;
}
return true;
}
/**
* Check if a comment is federated.
*
* We consider a comment federated if comment was received via ActivityPub.
*
* Use this function to check if it is comment that was received via ActivityPub.
*
* @param mixed $comment Comment object or ID.
*
* @return boolean True if the comment is federated, false otherwise.
*/
public static function was_received( $comment ) {
$comment = \get_comment( $comment );
if ( ! $comment ) {
return false;
}
$protocol = \get_comment_meta( $comment->comment_ID, 'protocol', true );
if ( 'activitypub' === $protocol ) {
return true;
}
return false;
}
/**
* Check if a comment was federated.
*
* This function checks if a comment was federated via ActivityPub.
*
* @param mixed $comment Comment object or ID.
*
* @return boolean True if the comment was federated, false otherwise.
*/
public static function was_sent( $comment ) {
$comment = \get_comment( $comment );
if ( ! $comment ) {
return false;
}
$status = \get_comment_meta( $comment->comment_ID, 'activitypub_status', true );
if ( $status ) {
return true;
}
return false;
}
/**
* Check if a comment is local only.
*
* This function checks if a comment is local only and was not sent or received via ActivityPub.
*
* @param mixed $comment Comment object or ID.
*
* @return boolean True if the comment is local only, false otherwise.
*/
public static function is_local( $comment ) {
if ( self::was_sent( $comment ) || self::was_received( $comment ) ) {
return false;
}
return true;
}
/**
* Check if a comment should be federated.
*
* We consider a comment should be federated if it is authored by a user that is
* not disabled for federation and if it is a reply directly to the post or to a
* federated comment.
*
* Use this function to check if a comment should be federated.
*
* @param mixed $comment Comment object or ID.
*
* @return boolean True if the comment should be federated, false otherwise.
*/
public static function should_be_federated( $comment ) {
// we should not federate federated comments
if ( self::was_received( $comment ) ) {
return false;
}
$comment = \get_comment( $comment );
$user_id = $comment->user_id;
// comments without user can't be federated
if ( ! $user_id ) {
return false;
}
$is_user_disabled = is_user_disabled( $user_id );
// user is disabled for federation
if ( $is_user_disabled ) {
return false;
}
// it is a comment to the post and can be federated
if ( empty( $comment->comment_parent ) ) {
return true;
}
// check if parent comment is federated
$parent_comment = \get_comment( $comment->comment_parent );
return ! self::is_local( $parent_comment );
}
/**
* Examine a comment ID and look up an existing comment it represents.
*
* @param string $id ActivityPub object ID (usually a URL) to check.
*
* @return int|boolean Comment ID, or false on failure.
*/
public static function object_id_to_comment( $id ) {
$comment_query = new WP_Comment_Query(
array(
'meta_key' => 'source_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_value' => $id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
)
);
if ( ! $comment_query->comments ) {
return false;
}
if ( count( $comment_query->comments ) > 1 ) {
return false;
}
return $comment_query->comments[0];
}
/**
* Verify if URL is a local comment, or if it is a previously received
* remote comment (For threading comments locally)
*
* @param string $url The URL to check.
*
* @return int comment_ID or null if not found
*/
public static function url_to_commentid( $url ) {
if ( ! $url || ! filter_var( $url, \FILTER_VALIDATE_URL ) ) {
return null;
}
// check for local comment
if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) === \wp_parse_url( $url, \PHP_URL_HOST ) ) {
$query = \wp_parse_url( $url, \PHP_URL_QUERY );
if ( $query ) {
parse_str( $query, $params );
if ( ! empty( $params['c'] ) ) {
$comment = \get_comment( $params['c'] );
if ( $comment ) {
return $comment->comment_ID;
}
}
}
}
$args = array(
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'source_url',
'value' => $url,
),
array(
'key' => 'source_id',
'value' => $url,
),
),
);
$query = new WP_Comment_Query();
$comments = $query->query( $args );
if ( $comments && is_array( $comments ) ) {
return $comments[0]->comment_ID;
}
return null;
}
/**
* Filters the CSS classes to add an ActivityPub class.
*
* @param string[] $classes An array of comment classes.
* @param string[] $css_class An array of additional classes added to the list.
* @param string $comment_id The comment ID as a numeric string.
*
* @return string[] An array of classes.
*/
public static function comment_class( $classes, $css_class, $comment_id ) {
// check if ActivityPub comment
if ( 'activitypub' === get_comment_meta( $comment_id, 'protocol', true ) ) {
$classes[] = 'activitypub-comment';
}
return $classes;
}
/**
* Link remote comments to source url.
*
* @param string $comment_link
* @param object|WP_Comment $comment
*
* @return string $url
*/
public static function remote_comment_link( $comment_link, $comment ) {
if ( ! $comment || is_admin() ) {
return $comment_link;
}
$comment_meta = \get_comment_meta( $comment->comment_ID );
if ( ! empty( $comment_meta['source_url'][0] ) ) {
return $comment_meta['source_url'][0];
} elseif ( ! empty( $comment_meta['source_id'][0] ) ) {
return $comment_meta['source_id'][0];
}
return $comment_link;
}
/**
* Generates an ActivityPub URI for a comment
*
* @param WP_Comment|int $comment A comment object or comment ID
*
* @return string ActivityPub URI for comment
*/
public static function generate_id( $comment ) {
$comment = get_comment( $comment );
// show external comment ID if it exists
$source_id = get_comment_meta( $comment->comment_ID, 'source_id', true );
if ( ! empty( $source_id ) ) {
return $source_id;
}
// generate URI based on comment ID
return \add_query_arg(
array(
'c' => $comment->comment_ID,
),
\trailingslashit( site_url() )
);
}
/**
* Enqueue scripts for remote comments
*/
public static function enqueue_scripts() {
if ( ! is_singular() ) {
return;
}
$handle = 'activitypub-remote-reply';
$data = array(
'namespace' => ACTIVITYPUB_REST_NAMESPACE,
);
$js = sprintf( 'var _activityPubOptions = %s;', wp_json_encode( $data ) );
$asset_file = ACTIVITYPUB_PLUGIN_DIR . 'build/remote-reply/index.asset.php';
if ( \file_exists( $asset_file ) ) {
$assets = require_once $asset_file;
\wp_enqueue_script(
$handle,
\plugins_url( 'build/remote-reply/index.js', __DIR__ ),
$assets['dependencies'],
$assets['version'],
true
);
\wp_add_inline_script( $handle, $js, 'before' );
\wp_enqueue_style(
$handle,
\plugins_url( 'build/remote-reply/style-index.css', __DIR__ ),
[ 'wp-components' ],
$assets['version']
);
}
}
}

View File

@ -14,11 +14,12 @@ class Debug {
* Initialize the class, registering WordPress hooks * Initialize the class, registering WordPress hooks
*/ */
public static function init() { public static function init() {
if ( WP_DEBUG && WP_DEBUG_LOG ) { if ( WP_DEBUG_LOG ) {
\add_action( 'activitypub_safe_remote_post_response', array( self::class, 'log_remote_post_responses' ), 10, 4 ); \add_action( 'activitypub_safe_remote_post_response', array( self::class, 'log_remote_post_responses' ), 10, 4 );
} }
} }
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
public static function log_remote_post_responses( $response, $url, $body, $user_id ) { public static function log_remote_post_responses( $response, $url, $body, $user_id ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r
\error_log( "Request to: {$url} with response: " . \print_r( $response, true ) ); \error_log( "Request to: {$url} with response: " . \print_r( $response, true ) );

View File

@ -102,7 +102,7 @@ class Mention {
$username = $metadata['preferredUsername']; $username = $metadata['preferredUsername'];
} }
$url = isset( $metadata['url'] ) ? $metadata['url'] : $metadata['url']; $url = isset( $metadata['url'] ) ? $metadata['url'] : $metadata['id'];
if ( \is_array( $url ) ) { if ( \is_array( $url ) ) {
$url = $url[0]; $url = $url[0];

View File

@ -117,6 +117,9 @@ class Migration {
if ( version_compare( $version_from_db, '1.3.0', '<' ) ) { if ( version_compare( $version_from_db, '1.3.0', '<' ) ) {
self::migrate_from_1_2_0(); self::migrate_from_1_2_0();
} }
if ( version_compare( $version_from_db, '2.1.0', '<' ) ) {
self::migrate_from_2_0_0();
}
update_option( 'activitypub_db_version', self::get_target_version() ); update_option( 'activitypub_db_version', self::get_target_version() );
@ -197,4 +200,19 @@ class Migration {
wp_cache_delete( sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); wp_cache_delete( sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
} }
} }
/**
* Unschedule Hooks after updating to 2.0.0
*
* @return void
*/
private static function migrate_from_2_0_0() {
wp_clear_scheduled_hook( 'activitypub_send_post_activity' );
wp_clear_scheduled_hook( 'activitypub_send_update_activity' );
wp_clear_scheduled_hook( 'activitypub_send_delete_activity' );
wp_unschedule_hook( 'activitypub_send_post_activity' );
wp_unschedule_hook( 'activitypub_send_update_activity' );
wp_unschedule_hook( 'activitypub_send_delete_activity' );
}
} }

View File

@ -2,11 +2,14 @@
namespace Activitypub; namespace Activitypub;
use Activitypub\Transformer\Post;
use Activitypub\Collection\Users; use Activitypub\Collection\Users;
use Activitypub\Collection\Followers; use Activitypub\Collection\Followers;
use Activitypub\Transformer\Post;
use function Activitypub\was_comment_sent;
use function Activitypub\is_user_type_disabled; use function Activitypub\is_user_type_disabled;
use function Activitypub\should_comment_be_federated;
use function Activitypub\get_remote_metadata_by_actor;
/** /**
* ActivityPub Scheduler Class * ActivityPub Scheduler Class
@ -40,20 +43,22 @@ class Scheduler {
} }
); );
// Comment transitions if ( ! ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS ) {
\add_action( 'transition_comment_status', array( self::class, 'schedule_comment_activity' ), 20, 3 ); // Comment transitions
\add_action( \add_action( 'transition_comment_status', array( self::class, 'schedule_comment_activity' ), 20, 3 );
'edit_comment', \add_action(
function ( $comment_id ) { 'edit_comment',
self::schedule_comment_activity( 'approved', 'approved', $comment_id ); function ( $comment_id ) {
} self::schedule_comment_activity( 'approved', 'approved', $comment_id );
); }
\add_action( );
'wp_insert_comment', \add_action(
function ( $comment_id ) { 'wp_insert_comment',
self::schedule_comment_activity( 'approved', '', $comment_id ); function ( $comment_id ) {
} self::schedule_comment_activity( 'approved', '', $comment_id );
); }
);
}
// Follower Cleanups // Follower Cleanups
\add_action( 'activitypub_update_followers', array( self::class, 'update_followers' ) ); \add_action( 'activitypub_update_followers', array( self::class, 'update_followers' ) );
@ -136,24 +141,17 @@ class Scheduler {
$type = 'Delete'; $type = 'Delete';
} }
if ( ! $type ) { if ( empty( $type ) ) {
return; return;
} }
\wp_schedule_single_event( $hook = 'activitypub_send_post';
\time(), $args = array( $post->ID, $type );
'activitypub_send_activity',
array( $post, $type )
);
\wp_schedule_single_event( if ( false === wp_next_scheduled( $hook, $args ) ) {
\time(), set_wp_object_state( $post, 'federate' );
sprintf( \wp_schedule_single_event( \time(), $hook, $args );
'activitypub_send_%s_activity', }
\strtolower( $type )
),
array( $post )
);
} }
/** /**
@ -168,11 +166,13 @@ class Scheduler {
public static function schedule_comment_activity( $new_status, $old_status, $comment ) { public static function schedule_comment_activity( $new_status, $old_status, $comment ) {
$comment = get_comment( $comment ); $comment = get_comment( $comment );
// Federate only approved comments. // federate only comments that are written by a registered user.
if ( ! $comment->user_id ) { if ( ! $comment->user_id ) {
return; return;
} }
$type = false;
if ( if (
'approved' === $new_status && 'approved' === $new_status &&
'approved' !== $old_status 'approved' !== $old_status
@ -188,24 +188,22 @@ class Scheduler {
$type = 'Delete'; $type = 'Delete';
} }
if ( ! $type ) { if ( empty( $type ) ) {
return; return;
} }
\wp_schedule_single_event( // check if comment should be federated or not
\time(), if ( ! should_comment_be_federated( $comment ) ) {
'activitypub_send_activity', return;
array( $comment, $type ) }
);
\wp_schedule_single_event( $hook = 'activitypub_send_comment';
\time(), $args = array( $comment->comment_ID, $type );
sprintf(
'activitypub_send_%s_activity', if ( false === wp_next_scheduled( $hook, $args ) ) {
\strtolower( $type ) set_wp_object_state( $comment, 'federate' );
), \wp_schedule_single_event( \time(), $hook, $args );
array( $comment ) }
);
} }
/** /**
@ -220,6 +218,7 @@ class Scheduler {
$number = 50; $number = 50;
} }
$number = apply_filters( 'activitypub_update_followers_number', $number );
$followers = Followers::get_outdated_followers( $number ); $followers = Followers::get_outdated_followers( $number );
foreach ( $followers as $follower ) { foreach ( $followers as $follower ) {
@ -246,6 +245,7 @@ class Scheduler {
$number = 50; $number = 50;
} }
$number = apply_filters( 'activitypub_update_followers_number', $number );
$followers = Followers::get_faulty_followers( $number ); $followers = Followers::get_faulty_followers( $number );
foreach ( $followers as $follower ) { foreach ( $followers as $follower ) {

View File

@ -42,16 +42,31 @@ class Webfinger {
return $data; return $data;
} }
if ( ! is_array( $data ) || empty( $data['links'] ) ) {
return new WP_Error(
'webfinger_missing_links',
__( 'No valid Link elements found.', 'activitypub' ),
$data
);
}
foreach ( $data['links'] as $link ) { foreach ( $data['links'] as $link ) {
if ( if (
'self' === $link['rel'] && 'self' === $link['rel'] &&
'application/activity+json' === $link['type'] (
'application/activity+json' === $link['type'] ||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' === $link['type']
)
) { ) {
return $link['href']; return $link['href'];
} }
} }
return new WP_Error( 'webfinger_url_no_activitypub', null, $data ); return new WP_Error(
'webfinger_url_no_activitypub',
__( 'The Site supports WebFinger but not ActivityPub', 'activitypub' ),
$data
);
} }
/** /**

View File

@ -2,8 +2,8 @@
namespace Activitypub; namespace Activitypub;
use WP_Error; use WP_Error;
use WP_Comment_Query;
use Activitypub\Http; use Activitypub\Http;
use Activitypub\Comment;
use Activitypub\Webfinger; use Activitypub\Webfinger;
use Activitypub\Activity\Activity; use Activitypub\Activity\Activity;
use Activitypub\Collection\Followers; use Activitypub\Collection\Followers;
@ -15,7 +15,7 @@ use Activitypub\Collection\Users;
* @return array the activitypub context * @return array the activitypub context
*/ */
function get_context() { function get_context() {
$context = Activity::CONTEXT; $context = Activity::JSON_LD_CONTEXT;
return \apply_filters( 'activitypub_json_context', $context ); return \apply_filters( 'activitypub_json_context', $context );
} }
@ -312,6 +312,12 @@ function is_activitypub_request() {
} }
} }
// Check if header already sent.
if ( ! \headers_sent() && ACTIVITYPUB_SEND_VARY_HEADER ) {
// Send Vary header for Accept header.
\header( 'Vary: Accept' );
}
// One can trigger an ActivityPub request by adding ?activitypub to the URL. // One can trigger an ActivityPub request by adding ?activitypub to the URL.
// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.VariableRedeclaration // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.VariableRedeclaration
global $wp_query; global $wp_query;
@ -664,22 +670,7 @@ function get_total_users() {
* @return int|boolean Comment ID, or false on failure. * @return int|boolean Comment ID, or false on failure.
*/ */
function object_id_to_comment( $id ) { function object_id_to_comment( $id ) {
$comment_query = new WP_Comment_Query( return Comment::object_id_to_comment( $id );
array(
'meta_key' => 'source_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_value' => $id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
)
);
if ( ! $comment_query->comments ) {
return false;
}
if ( count( $comment_query->comments ) > 1 ) {
return false;
}
return $comment_query->comments[0];
} }
/** /**
@ -692,50 +683,7 @@ function object_id_to_comment( $id ) {
* @return int comment_ID or null if not found * @return int comment_ID or null if not found
*/ */
function url_to_commentid( $url ) { function url_to_commentid( $url ) {
if ( ! $url || ! filter_var( $url, FILTER_VALIDATE_URL ) ) { return Comment::url_to_commentid( $url );
return null;
}
// check for local comment
if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) === \wp_parse_url( $url, \PHP_URL_HOST ) ) {
$query = \wp_parse_url( $url, PHP_URL_QUERY );
if ( $query ) {
parse_str( $query, $params );
if ( ! empty( $params['c'] ) ) {
$comment = \get_comment( $params['c'] );
if ( $comment ) {
return $comment->comment_ID;
}
}
}
}
$args = array(
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'source_url',
'value' => $url,
),
array(
'key' => 'source_id',
'value' => $url,
),
),
);
$query = new \WP_Comment_Query();
$comments = $query->query( $args );
if ( $comments && is_array( $comments ) ) {
return $comments[0]->comment_ID;
}
return null;
} }
/** /**
@ -774,3 +722,108 @@ function object_to_uri( $object ) {
return $object; return $object;
} }
/**
* Check if a comment should be federated.
*
* We consider a comment should be federated if it is authored by a user that is
* not disabled for federation and if it is a reply directly to the post or to a
* federated comment.
*
* @param mixed $comment Comment object or ID.
*
* @return boolean True if the comment should be federated, false otherwise.
*/
function should_comment_be_federated( $comment ) {
return Comment::should_be_federated( $comment );
}
/**
* Check if a comment was federated.
*
* This function checks if a comment was federated via ActivityPub.
*
* @param mixed $comment Comment object or ID.
*
* @return boolean True if the comment was federated, false otherwise.
*/
function was_comment_sent( $comment ) {
return Comment::was_sent( $comment );
}
/**
* Check if a comment is federated.
*
* We consider a comment federated if comment was received via ActivityPub.
*
* Use this function to check if it is comment that was received via ActivityPub.
*
* @param mixed $comment Comment object or ID.
*
* @return boolean True if the comment is federated, false otherwise.
*/
function was_comment_received( $comment ) {
return Comment::was_received( $comment );
}
/**
* Check if a comment is local only.
*
* This function checks if a comment is local only and was not sent or received via ActivityPub.
*
* @param mixed $comment Comment object or ID.
*
* @return boolean True if the comment is local only, false otherwise.
*/
function is_local_comment( $comment ) {
return Comment::is_local( $comment );
}
/**
* Mark a WordPress object as federated.
*
* @param WP_Comment|WP_Post|mixed $wp_object
* @return void
*/
function set_wp_object_state( $wp_object, $state ) {
$meta_key = 'activitypub_status';
if ( $wp_object instanceof \WP_Post ) {
\update_post_meta( $wp_object->ID, $meta_key, $state );
} elseif ( $wp_object instanceof \WP_Comment ) {
\update_comment_meta( $wp_object->comment_ID, $meta_key, $state );
} else {
\apply_filters( 'activitypub_mark_wp_object_as_federated', $wp_object );
}
}
/**
* Get the description of a post type.
*
* Set some default descriptions for the default post types.
*
* @param WP_Post_Type $post_type The post type object.
*
* @return string The description of the post type.
*/
function get_post_type_description( $post_type ) {
$description = '';
switch ( $post_type->name ) {
case 'post':
$description = '';
break;
case 'page':
$description = '';
break;
case 'attachment':
$description = ' - ' . __( 'The attachments that you have uploaded to a post (images, videos, documents or other files).', 'activitypub' );
break;
default:
if ( ! empty( $post_type->description ) ) {
$description = ' - ' . $post_type->description;
}
}
return apply_filters( 'activitypub_post_type_description', $description, $post_type->name, $post_type );
}

View File

@ -33,6 +33,10 @@ class Create {
* @return void * @return void
*/ */
public static function handle_create( $array, $user_id, $object = null ) { public static function handle_create( $array, $user_id, $object = null ) {
if ( ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
return;
}
if ( if (
! isset( $array['object'] ) || ! isset( $array['object'] ) ||
! isset( $array['object']['id'] ) ! isset( $array['object']['id'] )

View File

@ -81,7 +81,6 @@ class Delete {
// check if Object is an Actor. // check if Object is an Actor.
if ( $activity['actor'] === $activity['object'] ) { if ( $activity['actor'] === $activity['object'] ) {
self::maybe_delete_follower( $activity ); self::maybe_delete_follower( $activity );
self::maybe_delete_interactions( $activity );
} else { // assume a interaction otherwise. } else { // assume a interaction otherwise.
self::maybe_delete_interaction( $activity ); self::maybe_delete_interaction( $activity );
} }
@ -101,6 +100,7 @@ class Delete {
// verify if Actor is deleted. // verify if Actor is deleted.
if ( $follower && Http::is_tombstone( $activity['actor'] ) ) { if ( $follower && Http::is_tombstone( $activity['actor'] ) ) {
$follower->delete(); $follower->delete();
self::maybe_delete_interactions( $activity );
} }
} }

View File

@ -15,19 +15,17 @@ class Application_User extends Blog_User {
*/ */
protected $_id = Users::APPLICATION_USER_ID; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore protected $_id = Users::APPLICATION_USER_ID; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/** public function get_type() {
* The User-Type return 'Application';
* }
* @var string
*/
protected $type = 'Application';
/** public function get_discoverable() {
* If the User is discoverable. return false;
* }
* @var boolean
*/ public function get_manually_approves_followers() {
protected $discoverable = false; return true;
}
/** /**
* Get the User-Url. * Get the User-Url.
@ -52,7 +50,7 @@ class Application_User extends Blog_User {
} }
public function get_preferred_username() { public function get_preferred_username() {
return $this::get_name(); return $this->get_name();
} }
public function get_followers() { public function get_followers() {
@ -78,8 +76,4 @@ class Application_User extends Blog_User {
public function get_indexable() { public function get_indexable() {
return false; return false;
} }
public function get_type() {
return $this->type;
}
} }

View File

@ -2,7 +2,8 @@
namespace Activitypub\Model; namespace Activitypub\Model;
use WP_Query; use WP_Query;
use Activitypub\Signature; use WP_Error;
use Activitypub\Collection\Users; use Activitypub\Collection\Users;
use function Activitypub\is_single_user; use function Activitypub\is_single_user;
@ -17,19 +18,13 @@ class Blog_User extends User {
*/ */
protected $_id = Users::BLOG_USER_ID; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore protected $_id = Users::BLOG_USER_ID; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/** public function get_manually_approves_followers() {
* The User-Type return false;
* }
* @var string
*/
protected $type = null;
/** public function get_discoverable() {
* Is Account discoverable? return true;
* }
* @var boolean
*/
protected $discoverable = true;
public static function from_wp_user( $user_id ) { public static function from_wp_user( $user_id ) {
if ( is_user_disabled( $user_id ) ) { if ( is_user_disabled( $user_id ) ) {

View File

@ -162,8 +162,10 @@ class Follower extends Actor {
} }
} }
$post_id = $this->get__id();
$args = array( $args = array(
'ID' => $this->get__id(), 'ID' => $post_id,
'guid' => esc_url_raw( $this->get_id() ), 'guid' => esc_url_raw( $this->get_id() ),
'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ), 'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ),
'post_author' => 0, 'post_author' => 0,
@ -174,6 +176,14 @@ class Follower extends Actor {
'meta_input' => $this->get_post_meta_input(), 'meta_input' => $this->get_post_meta_input(),
); );
if ( ! empty( $post_id ) ) {
// If this is an update, prevent the "followed" 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 ); $post_id = wp_insert_post( $args );
$this->_id = $post_id; $this->_id = $post_id;
@ -286,6 +296,25 @@ class Follower extends Actor {
return $icon; return $icon;
} }
/**
* Get the Icon URL (Avatar)
*
* @return string The URL to the Avatar.
*/
public function get_image_url() {
$image = $this->get_image();
if ( ! $image ) {
return '';
}
if ( is_array( $image ) ) {
return $image['url'];
}
return $image;
}
/** /**
* Get the shared inbox, with a fallback to the inbox. * Get the shared inbox, with a fallback to the inbox.
* *

View File

@ -23,6 +23,11 @@ class User extends Actor {
* *
* @see https://docs.joinmastodon.org/spec/activitypub/#featured * @see https://docs.joinmastodon.org/spec/activitypub/#featured
* *
* @context {
* "@id": "http://joinmastodon.org/ns#featured",
* "@type": "@id"
* }
*
* @var string * @var string
*/ */
protected $featured; protected $featured;
@ -36,18 +41,17 @@ class User extends Actor {
*/ */
protected $moderators; protected $moderators;
/** public function get_type() {
* The User-Type return 'Person';
* }
* @var string
*/
protected $type = 'Person';
/** /**
* If the User is discoverable. * If the User is discoverable.
* *
* @see https://docs.joinmastodon.org/spec/activitypub/#discoverable * @see https://docs.joinmastodon.org/spec/activitypub/#discoverable
* *
* @context http://joinmastodon.org/ns#discoverable
*
* @var boolean * @var boolean
*/ */
protected $discoverable = true; protected $discoverable = true;
@ -55,6 +59,8 @@ class User extends Actor {
/** /**
* If the User is indexable. * If the User is indexable.
* *
* @context http://joinmastodon.org/ns#indexable
*
* @var boolean * @var boolean
*/ */
protected $indexable; protected $indexable;

View File

@ -5,7 +5,8 @@ use WP_Error;
use WP_REST_Server; use WP_REST_Server;
use WP_REST_Response; use WP_REST_Response;
use Activitypub\Transformer\Post; use Activitypub\Transformer\Post;
use Activitypub\Activity\Activity; use Activitypub\Activity\Actor;
use Activitypub\Activity\Base_Object;
use Activitypub\Collection\Users as User_Collection; use Activitypub\Collection\Users as User_Collection;
use function Activitypub\esc_hashtag; use function Activitypub\esc_hashtag;
@ -102,7 +103,7 @@ class Collection {
} }
$response = array( $response = array(
'@context' => Activity::CONTEXT, '@context' => Base_Object::JSON_LD_CONTEXT,
'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/tags', $user->get__id() ) ), 'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/tags', $user->get__id() ) ),
'type' => 'Collection', 'type' => 'Collection',
'totalItems' => is_countable( $tags ) ? count( $tags ) : 0, 'totalItems' => is_countable( $tags ) ? count( $tags ) : 0,
@ -160,7 +161,7 @@ class Collection {
} }
$response = array( $response = array(
'@context' => Activity::CONTEXT, '@context' => Base_Object::JSON_LD_CONTEXT,
'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/featured', $user_id ) ), 'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/featured', $user_id ) ),
'type' => 'OrderedCollection', 'type' => 'OrderedCollection',
'totalItems' => is_countable( $posts ) ? count( $posts ) : 0, 'totalItems' => is_countable( $posts ) ? count( $posts ) : 0,
@ -168,7 +169,7 @@ class Collection {
); );
foreach ( $posts as $post ) { foreach ( $posts as $post ) {
$response['orderedItems'][] = Post::transform( $post )->to_object()->to_array(); $response['orderedItems'][] = Post::transform( $post )->to_object()->to_array( false );
} }
$rest_response = new WP_REST_Response( $response, 200 ); $rest_response = new WP_REST_Response( $response, 200 );
@ -186,7 +187,7 @@ class Collection {
*/ */
public static function moderators_get( $request ) { public static function moderators_get( $request ) {
$response = array( $response = array(
'@context' => Activity::CONTEXT, '@context' => Actor::JSON_LD_CONTEXT,
'id' => get_rest_url_by_path( 'collections/moderators' ), 'id' => get_rest_url_by_path( 'collections/moderators' ),
'type' => 'OrderedCollection', 'type' => 'OrderedCollection',
'orderedItems' => array(), 'orderedItems' => array(),

View File

@ -0,0 +1,91 @@
<?php
namespace Activitypub\Rest;
use WP_Error;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use Activitypub\Comment as Comment_Utils;
use Activitypub\Webfinger as Webfinger_Utils;
/**
* ActivityPub Followers REST-Class
*
* @author Matthias Pfefferle
*
* @see https://www.w3.org/TR/activitypub/#followers
*/
class Comment {
/**
* Initialize the class, registering WordPress hooks
*/
public static function init() {
self::register_routes();
}
/**
* Register routes
*/
public static function register_routes() {
\register_rest_route(
ACTIVITYPUB_REST_NAMESPACE,
'/comments/(?P<comment_id>\d+)/remote-reply',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( self::class, 'remote_reply_get' ),
'permission_callback' => '__return_true',
'args' => array(
'resource' => array(
'required' => true,
'sanitize_callback' => 'sanitize_text_field',
),
),
),
)
);
}
/**
* Endpoint for remote follow UI/Block
*
* @param WP_REST_Request $request The request object.
*
* @return void|string The URL to the remote follow page
*/
public static function remote_reply_get( WP_REST_Request $request ) {
$resource = $request->get_param( 'resource' );
$comment_id = $request->get_param( 'comment_id' );
$comment = get_comment( $comment_id );
if ( ! $comment ) {
return new WP_Error( 'activitypub_comment_not_found', __( 'Comment not found', 'activitypub' ), array( 'status' => 404 ) );
}
$is_local = Comment_Utils::is_local( $comment );
if ( $is_local ) {
return new WP_Error( 'activitypub_local_only_comment', __( 'Comment is local only', 'activitypub' ), array( 'status' => 403 ) );
}
$template = Webfinger_Utils::get_remote_follow_endpoint( $resource );
if ( is_wp_error( $template ) ) {
return $template;
}
$resource = \get_comment_meta( $comment_id, 'source_id', true );
if ( ! $resource ) {
$resource = Comment_Utils::generate_id( $comment );
}
$url = str_replace( '{uri}', $resource, $template );
return new WP_REST_Response(
array( 'url' => $url ),
200
);
}
}

View File

@ -96,7 +96,7 @@ class Followers {
$json->orderedItems = array_map( $json->orderedItems = array_map(
function ( $item ) use ( $context ) { function ( $item ) use ( $context ) {
if ( 'full' === $context ) { if ( 'full' === $context ) {
return $item->to_array(); return $item->to_array( false );
} }
return $item->get_url(); return $item->get_url();
}, },

View File

@ -11,7 +11,6 @@ use function Activitypub\get_context;
use function Activitypub\object_to_uri; use function Activitypub\object_to_uri;
use function Activitypub\url_to_authorid; use function Activitypub\url_to_authorid;
use function Activitypub\get_rest_url_by_path; use function Activitypub\get_rest_url_by_path;
use function Activitypub\get_remote_metadata_by_actor;
use function Activitypub\extract_recipients_from_activity; use function Activitypub\extract_recipients_from_activity;
/** /**

View File

@ -129,7 +129,7 @@ class Nodeinfo {
$nodeinfo = array(); $nodeinfo = array();
$nodeinfo['version'] = '1.0'; $nodeinfo['version'] = '2.0';
$nodeinfo['server'] = array( $nodeinfo['server'] = array(
'baseUrl' => \home_url( '/' ), 'baseUrl' => \home_url( '/' ),
'name' => \get_bloginfo( 'name' ), 'name' => \get_bloginfo( 'name' ),

View File

@ -59,7 +59,7 @@ class Outbox {
return $user; return $user;
} }
$post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ); $post_types = \get_option( 'activitypub_support_post_types', array( 'post' ) );
$page = $request->get_param( 'page', 1 ); $page = $request->get_param( 'page', 1 );
@ -108,10 +108,8 @@ class Outbox {
$post = Post::transform( $post )->to_object(); $post = Post::transform( $post )->to_object();
$activity = new Activity(); $activity = new Activity();
$activity->set_type( 'Create' ); $activity->set_type( 'Create' );
$activity->set_context( null );
$activity->set_object( $post ); $activity->set_object( $post );
$json->orderedItems[] = $activity->to_array( false ); // phpcs:ignore
$json->orderedItems[] = $activity->to_array(); // phpcs:ignore
} }
} }

View File

@ -49,10 +49,6 @@ class Server {
public static function application_actor() { public static function application_actor() {
$user = new Application_User(); $user = new Application_User();
$user->set_context(
\Activitypub\Activity\Activity::CONTEXT
);
$json = $user->to_array(); $json = $user->to_array();
$rest_response = new WP_REST_Response( $json, 200 ); $rest_response = new WP_REST_Response( $json, 200 );

View File

@ -88,10 +88,6 @@ class Users {
*/ */
\do_action( 'activitypub_rest_users_pre' ); \do_action( 'activitypub_rest_users_pre' );
$user->set_context(
Activity::CONTEXT
);
$json = $user->to_array(); $json = $user->to_array();
$rest_response = new WP_REST_Response( $json, 200 ); $rest_response = new WP_REST_Response( $json, 200 );

View File

@ -30,7 +30,7 @@ abstract class Base {
* *
* @param WP_Post|WP_Comment $wp_object The WordPress object * @param WP_Post|WP_Comment $wp_object The WordPress object
* *
* @return void * @return Base_Object
*/ */
public static function transform( $object ) { public static function transform( $object ) {
return new static( $object ); return new static( $object );
@ -46,13 +46,13 @@ abstract class Base {
} }
/** /**
* Transform the WordPress Object into an ActivityPub Object. * Transform all properties with available get(ter) functions.
* *
* @return Activitypub\Activity\Base_Object * @param Base_Object|object $object
*
* @return Base_Object|object $object
*/ */
public function to_object() { protected function transform_object_properties( $activitypub_object ) {
$activitypub_object = new Base_Object();
$vars = $activitypub_object->get_object_var_keys(); $vars = $activitypub_object->get_object_var_keys();
foreach ( $vars as $var ) { foreach ( $vars as $var ) {
@ -68,6 +68,17 @@ abstract class Base {
} }
} }
} }
return $activitypub_object;
}
/**
* Transform the WordPress Object into an ActivityPub Object.
*
* @return Activitypub\Activity\Base_Object
*/
public function to_object() {
$activitypub_object = new Base_Object();
$activitypub_object = $this->transform_object_properties( $activitypub_object );
return $activitypub_object; return $activitypub_object;
} }
@ -84,6 +95,8 @@ abstract class Base {
$activity = new Activity(); $activity = new Activity();
$activity->set_type( $type ); $activity->set_type( $type );
// Pre-fill the Activity with data (for example cc and to).
$activity->set_object( $object ); $activity->set_object( $object );
// Use simple Object (only ID-URI) for Like and Announce // Use simple Object (only ID-URI) for Like and Announce

View File

@ -4,6 +4,8 @@ namespace Activitypub\Transformer;
use WP_Comment; use WP_Comment;
use WP_Comment_Query; use WP_Comment_Query;
use Activitypub\Webfinger;
use Activitypub\Comment as Comment_Utils;
use Activitypub\Model\Blog_User; use Activitypub\Model\Blog_User;
use Activitypub\Collection\Users; use Activitypub\Collection\Users;
use Activitypub\Transformer\Base; use Activitypub\Transformer\Base;
@ -51,7 +53,7 @@ class Comment extends Base {
$comment = $this->wp_object; $comment = $this->wp_object;
$object = parent::to_object(); $object = parent::to_object();
$object->set_url( \get_comment_link( $comment->comment_ID ) ); $object->set_url( $this->get_id() );
$object->set_type( 'Note' ); $object->set_type( 'Note' );
$published = \strtotime( $comment->comment_date_gmt ); $published = \strtotime( $comment->comment_date_gmt );
@ -107,10 +109,10 @@ class Comment extends Base {
$comment = $this->wp_object; $comment = $this->wp_object;
$content = $comment->comment_content; $content = $comment->comment_content;
$content = \wpautop( $content ); $content = \apply_filters( 'comment_text', $content, $comment, array() );
$content = \preg_replace( '/[\n\r\t]/', '', $content ); $content = \preg_replace( '/[\n\r\t]/', '', $content );
$content = \trim( $content ); $content = \trim( $content );
$content = \apply_filters( 'the_content', $content, $comment ); $content = \apply_filters( 'activitypub_the_content', $content, $comment );
return $content; return $content;
} }
@ -123,7 +125,12 @@ class Comment extends Base {
protected function get_in_reply_to() { protected function get_in_reply_to() {
$comment = $this->wp_object; $comment = $this->wp_object;
$parent_comment = \get_comment( $comment->comment_parent ); $parent_comment = null;
$in_reply_to = null;
if ( $comment->comment_parent ) {
$parent_comment = \get_comment( $comment->comment_parent );
}
if ( $parent_comment ) { if ( $parent_comment ) {
$comment_meta = \get_comment_meta( $parent_comment->comment_ID ); $comment_meta = \get_comment_meta( $parent_comment->comment_ID );
@ -132,8 +139,8 @@ class Comment extends Base {
$in_reply_to = $comment_meta['source_id'][0]; $in_reply_to = $comment_meta['source_id'][0];
} elseif ( ! empty( $comment_meta['source_url'][0] ) ) { } elseif ( ! empty( $comment_meta['source_url'][0] ) ) {
$in_reply_to = $comment_meta['source_url'][0]; $in_reply_to = $comment_meta['source_url'][0];
} else { } elseif ( ! empty( $parent_comment->user_id ) ) {
$in_reply_to = $this->generate_id( $parent_comment ); $in_reply_to = Comment_Utils::generate_id( $parent_comment );
} }
} else { } else {
$in_reply_to = \get_permalink( $comment->comment_post_ID ); $in_reply_to = \get_permalink( $comment->comment_post_ID );
@ -152,25 +159,7 @@ class Comment extends Base {
*/ */
protected function get_id() { protected function get_id() {
$comment = $this->wp_object; $comment = $this->wp_object;
return $this->generate_id( $comment ); return Comment_Utils::generate_id( $comment );
}
/**
* Generates an ActivityPub URI for a comment
*
* @param WP_Comment|int $comment A comment object or comment ID
*
* @return string ActivityPub URI for comment
*/
protected function generate_id( $comment ) {
$comment = get_comment( $comment );
return \add_query_arg(
array(
'c' => $comment->comment_ID,
),
\trailingslashit( site_url() )
);
} }
/** /**
@ -190,31 +179,7 @@ class Comment extends Base {
} }
} }
$comment_query = new WP_Comment_Query( return array_unique( $cc );
array(
'post_id' => $this->wp_object->comment_post_ID,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
array(
'key' => 'source_id',
'compare' => 'EXISTS',
),
),
)
);
if ( $comment_query->comments ) {
foreach ( $comment_query->comments as $comment ) {
if ( empty( $comment->comment_author_url ) ) {
continue;
}
$cc[] = \esc_url( $comment->comment_author_url );
}
}
$cc = \array_unique( $cc );
return $cc;
} }
/** /**
@ -248,9 +213,53 @@ class Comment extends Base {
* @return array The list of @-Mentions. * @return array The list of @-Mentions.
*/ */
protected function get_mentions() { protected function get_mentions() {
\add_filter( 'activitypub_extract_mentions', array( $this, 'extract_reply_context' ) );
return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_object->comment_content, $this->wp_object ); return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_object->comment_content, $this->wp_object );
} }
/**
* Collect all other Users that participated in this comment-thread
* to send them a notification about the new reply.
*
* @param array $mentions The already mentioned ActivityPub users
*
* @return array The list of all Repliers.
*/
public function extract_reply_context( $mentions ) {
// Check if `$this->wp_object` is a WP_Comment
if ( 'WP_Comment' !== get_class( $this->wp_object ) ) {
return $mentions;
}
$comment_query = new WP_Comment_Query(
array(
'post_id' => $this->wp_object->comment_post_ID,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
array(
'key' => 'protocol',
'value' => 'activitypub',
),
),
)
);
if ( $comment_query->comments ) {
foreach ( $comment_query->comments as $comment ) {
if ( ! empty( $comment->comment_author_url ) ) {
$acct = Webfinger::uri_to_acct( $comment->comment_author_url );
if ( $acct && ! is_wp_error( $acct ) ) {
$acct = str_replace( 'acct:', '@', $acct );
$mentions[ $acct ] = $comment->comment_author_url;
}
}
}
}
return $mentions;
}
/** /**
* Returns the locale of the post. * Returns the locale of the post.
* *

View File

@ -0,0 +1,172 @@
<?php
namespace Activitypub\Integration;
use DateTime;
use Activitypub\Webfinger as Webfinger_Util;
use Activitypub\Collection\Users;
use Activitypub\Collection\Followers;
use Activitypub\Integration\Nodeinfo;
use Enable_Mastodon_Apps\Entity\Account;
use function Activitypub\get_remote_metadata_by_actor;
/**
* Class Enable_Mastodon_Apps
*
* This class is used to enable Mastodon Apps to work with ActivityPub
*
* @see https://github.com/akirk/enable-mastodon-apps
*/
class Enable_Mastodon_Apps {
/**
* Initialize the class, registering WordPress hooks
*/
public static function init() {
\add_filter( 'mastodon_api_account_followers', array( self::class, 'api_account_followers' ), 10, 2 );
\add_filter( 'mastodon_api_account', array( self::class, 'api_account_add_followers' ), 20, 2 );
\add_filter( 'mastodon_api_account', array( self::class, 'api_account_external' ), 10, 2 );
}
/**
* Add followers to Mastodon API
*
* @param array $followers An array of followers
* @param string $user_id The user id
* @param WP_REST_Request $request The request object
*
* @return array The filtered followers
*/
public static function api_account_followers( $followers, $user_id ) {
$activitypub_followers = Followers::get_followers( $user_id, 40 );
$mastodon_followers = array_map(
function ( $item ) {
$acct = Webfinger_Util::uri_to_acct( $item->get_id() );
if ( $acct && ! is_wp_error( $acct ) ) {
$acct = \str_replace( 'acct:', '', $acct );
} else {
$acct = $item->get_url();
}
$activitypub_follower = array(
'id' => \strval( $item->get__id() ),
'username' => $item->get_preferred_username(),
'acct' => $acct,
'display_name' => $item->get_name(),
'url' => $item->get_url(),
'uri' => $item->get_id(),
'avatar' => $item->get_icon_url(),
'avatar_static' => $item->get_icon_url(),
'created_at' => gmdate( DATE_W3C, strtotime( $item->get_published() ) ),
'last_status_at' => gmdate( DATE_W3C, strtotime( $item->get_published() ) ),
'note' => $item->get_summary(),
'header' => $item->get_image_url(),
'header_static' => $item->get_image_url(),
'followers_count' => 0,
'following_count' => 0,
'statuses_count' => 0,
'bot' => false,
'locked' => false,
'group' => false,
'discoversable' => false,
'indexable' => false,
'hide_collections' => false,
'noindex' => false,
'fields' => array(),
'emojis' => array(),
'roles' => array(),
);
return $activitypub_follower;
},
$activitypub_followers
);
$followers = array_merge( $mastodon_followers, $followers );
return $followers;
}
/**
* Add followers count to Mastodon API
*
* @param Enable_Mastodon_Apps\Entity\Account $account The account
* @param int $user_id The user id
*
* @return Enable_Mastodon_Apps\Entity\Account The filtered Account
*/
public static function api_account_add_followers( $account, $user_id ) {
if ( ! $account instanceof Account ) {
return $account;
}
$user = Users::get_by_id( $user_id );
if ( ! $user || is_wp_error( $user ) ) {
return $account;
}
$account->followers_count = Followers::count_followers( $user_id );
return $account;
}
/**
* Resolve external accounts for Mastodon API
*
* @param Enable_Mastodon_Apps\Entity\Account $user_data The user data
* @param string $user_id The user id
*
* @return Enable_Mastodon_Apps\Entity\Account The filtered Account
*/
public static function api_account_external( $user_data, $user_id ) {
if ( ! preg_match( '/^' . ACTIVITYPUB_USERNAME_REGEXP . '$/', $user_id ) ) {
return $user_data;
}
$uri = Webfinger_Util::resolve( $user_id );
if ( ! $uri ) {
return $user_data;
}
$acct = Webfinger_Util::uri_to_acct( $uri );
$data = get_remote_metadata_by_actor( $uri );
if ( ! $data || is_wp_error( $data ) ) {
return $user_data;
}
if ( $user_data instanceof Account ) {
$account = $user_data;
} else {
$account = new Account();
}
$account->id = strval( $user_id );
$account->username = $acct;
$account->acct = $acct;
$account->display_name = $data['name'];
$account->url = $uri;
if ( ! empty( $data['summary'] ) ) {
$account->note = $data['summary'];
}
if (
isset( $data['icon']['type'] ) &&
isset( $data['icon']['url'] ) &&
'Image' === $data['icon']['type']
) {
$account->avatar = $data['icon']['url'];
$account->avatar_static = $data['icon']['url'];
}
if ( isset( $data['image'] ) ) {
$account->header = $data['image'];
$account->header_static = $data['image'];
}
$account->created_at = new DateTime( $data['published'] );
return $account;
}
}

View File

@ -3,7 +3,7 @@ Contributors: automattic, pfefferle, mediaformat, mattwiebe, akirk, jeherve, nur
Tags: OStatus, fediverse, activitypub, activitystream Tags: OStatus, fediverse, activitypub, activitystream
Requires at least: 5.5 Requires at least: 5.5
Tested up to: 6.4 Tested up to: 6.4
Stable tag: 2.0.1 Stable tag: 2.2.0
Requires PHP: 5.6 Requires PHP: 5.6
License: MIT License: MIT
License URI: http://opensource.org/licenses/MIT License URI: http://opensource.org/licenses/MIT
@ -105,10 +105,56 @@ Where 'blog' is the path to the subdirectory at which your blog resides.
In that case you don't need the redirect, because the index.php will take care of that. In that case you don't need the redirect, because the index.php will take care of that.
= Constants =
The plugin uses PHP Constants to enable, disable or change its default behaviour. Please use them with caution and only if you know what you are doing.
* `ACTIVITYPUB_REST_NAMESPACE` - Change the default Namespace of the REST endpoint. Default: `activitypub/1.0`.
* `ACTIVITYPUB_EXCERPT_LENGTH` - Change the length of the Excerpt. Default: `400`.
* `ACTIVITYPUB_SHOW_PLUGIN_RECOMMENDATIONS` - show plugin recommendations in the ActivityPub settings. Default: `true`.
* `ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS` - Change the number of attachments, that should be federated. Default: `3`.
* `ACTIVITYPUB_HASHTAGS_REGEXP` - Change the default regex to detect hashtext in a text. Default: `(?:(?<=\s)|(?<=<p>)|(?<=<br>)|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))`.
* `ACTIVITYPUB_USERNAME_REGEXP` - Change the default regex to detect @-replies in a text. Default: `(?:([A-Za-z0-9\._-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))`.
* `ACTIVITYPUB_CUSTOM_POST_CONTENT` - Change the default template for Activities. Default: `<strong>[ap_title]</strong>\n\n[ap_content]\n\n[ap_hashtags]\n\n[ap_shortlink]`.
* `ACTIVITYPUB_AUTHORIZED_FETCH` - Enable AUTHORIZED_FETCH. Default: `false`.
* `ACTIVITYPUB_DISABLE_REWRITES` - Disable auto generation of `mod_rewrite` rules. Default: `false`.
* `ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS` - Block incoming replies/comments/likes. Default: `false`.
* `ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS` - Disable outgoing replies/comments/likes. Default: `false`.
* `ACTIVITYPUB_SHARED_INBOX_FEATURE` - Enable the shared inbox. Default: `false`.
* `ACTIVITYPUB_SEND_VARY_HEADER` - Enable to send the `Vary: Accept` header. Default: `false`.
= Where can you manage your followers? =
If you have activated the blog user, you will find the list of his followers in the settings under `/wp-admin/options-general.php?page=activitypub&tab=followers`.
The followers of a user can be found in the menu under "Users" -> "Followers" or under `wp-admin/users.php?page=activitypub-followers-list`.
For reasons of data protection, it is not possible to see the followers of other users.
== Changelog == == Changelog ==
Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub). Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub).
= 2.2.0 =
* Added: Remote-Reply lightbox
* Added: Support `application/ld+json` mime-type with AP profile in WebFinger
* Fixed: Prevent scheduler overload
= 2.1.1 =
* Added: Add `@` prefix to Follow-Block
* Added: Apply `comment_text` filter to Activity
= 2.1.0 =
* Fixed: Some Federated Comment improvements
* Fixed: Remove old/abandoned Crons
* Added: Various endpoints for the "Enable Mastodon Apps" plugin
* Added: Event Objects
* Added: Send notification to all Repliers if a new Comment is added
* Added: Vary-Header support behind feature flag
= 2.0.1 = = 2.0.1 =
* Fixed: Comment `Update` Federation * Fixed: Comment `Update` Federation

View File

@ -1,10 +1,6 @@
<?php <?php
$user = \Activitypub\Collection\Users::get_by_id( \get_the_author_meta( 'ID' ) ); $user = \Activitypub\Collection\Users::get_by_id( \get_the_author_meta( 'ID' ) );
$user->set_context(
\Activitypub\Activity\Activity::CONTEXT
);
/* /*
* Action triggerd prior to the ActivityPub profile being created and sent to the client * Action triggerd prior to the ActivityPub profile being created and sent to the client
*/ */

View File

@ -1,10 +1,6 @@
<?php <?php
$user = new \Activitypub\Model\Blog_User(); $user = new \Activitypub\Model\Blog_User();
$user->set_context(
\Activitypub\Activity\Activity::CONTEXT
);
/* /*
* Action triggerd prior to the ActivityPub profile being created and sent to the client * Action triggerd prior to the ActivityPub profile being created and sent to the client
*/ */

View File

@ -3,7 +3,6 @@
$post = \get_post(); $post = \get_post();
$post_object = \Activitypub\Transformer\Factory::get_transformer( $post )->to_object(); $post_object = \Activitypub\Transformer\Factory::get_transformer( $post )->to_object();
$post_object->set_context( \Activitypub\get_context() );
/* /*
* Action triggerd prior to the ActivityPub profile being created and sent to the client * Action triggerd prior to the ActivityPub profile being created and sent to the client

View File

@ -118,7 +118,7 @@
</label> </label>
</p> </p>
<p> <p>
<textarea name="activitypub_custom_post_content" id="activitypub_custom_post_content" rows="10" cols="50" class="large-text" placeholder="<?php echo wp_kses( ACTIVITYPUB_CUSTOM_POST_CONTENT, 'post' ); ?>"><?php echo wp_kses( \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT ), 'post' ); ?></textarea> <textarea name="activitypub_custom_post_content" id="activitypub_custom_post_content" rows="10" cols="50" class="large-text" placeholder="<?php echo wp_kses( ACTIVITYPUB_CUSTOM_POST_CONTENT, 'post' ); ?>"><?php echo esc_textarea( wp_kses( \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT ), 'post' ) ); ?></textarea>
<details> <details>
<summary><?php esc_html_e( 'See a list of ActivityPub Template Tags.', 'activitypub' ); ?></summary> <summary><?php esc_html_e( 'See a list of ActivityPub Template Tags.', 'activitypub' ); ?></summary>
<div class="description"> <div class="description">
@ -205,16 +205,19 @@
<th scope="row"><?php \esc_html_e( 'Supported post types', 'activitypub' ); ?></th> <th scope="row"><?php \esc_html_e( 'Supported post types', 'activitypub' ); ?></th>
<td> <td>
<fieldset> <fieldset>
<?php \esc_html_e( 'Enable ActivityPub support for the following post types:', 'activitypub' ); ?> <?php \esc_html_e( 'Automatically publish items of the selected post types to the fediverse:', 'activitypub' ); ?>
<?php $post_types = \get_post_types( array( 'public' => true ), 'objects' ); ?> <?php $post_types = \get_post_types( array( 'public' => true ), 'objects' ); ?>
<?php $support_post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) : array(); ?> <?php $support_post_types = \get_option( 'activitypub_support_post_types', array( 'post' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post' ) ) : array(); ?>
<ul> <ul>
<?php // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited ?> <?php // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited ?>
<?php foreach ( $post_types as $post_type ) { ?> <?php foreach ( $post_types as $post_type ) { ?>
<li> <li>
<input type="checkbox" id="activitypub_support_post_type_<?php echo \esc_attr( $post_type->name ); ?>" name="activitypub_support_post_types[]" value="<?php echo \esc_attr( $post_type->name ); ?>" <?php echo \checked( \in_array( $post_type->name, $support_post_types, true ) ); ?> /> <input type="checkbox" id="activitypub_support_post_type_<?php echo \esc_attr( $post_type->name ); ?>" name="activitypub_support_post_types[]" value="<?php echo \esc_attr( $post_type->name ); ?>" <?php echo \checked( \in_array( $post_type->name, $support_post_types, true ) ); ?> />
<label for="activitypub_support_post_type_<?php echo \esc_attr( $post_type->name ); ?>"><?php echo \esc_html( $post_type->label ); ?></label> <label for="activitypub_support_post_type_<?php echo \esc_attr( $post_type->name ); ?>"><?php echo \esc_html( $post_type->label ); ?></label>
<span class="description">
<?php echo \esc_html( \Activitypub\get_post_type_description( $post_type ) ); ?>
</span>
</li> </li>
<?php } ?> <?php } ?>
</ul> </ul>