Merge pull request #342 from Automattic/add/catchall
prepare pseudo users like a blog wide user. (Catch-All)
This commit is contained in:
commit
f9d1a6e4c5
46 changed files with 2425 additions and 1352 deletions
|
@ -29,6 +29,9 @@ function init() {
|
|||
\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_SECURE_MODE' ) || \define( 'ACTIVITYPUB_SECURE_MODE', apply_filters( 'activitypub_secure_mode', $value = false ) );
|
||||
|
||||
\defined( 'ACTIVITYPUB_DISABLE_USER' ) || \define( 'ACTIVITYPUB_DISABLE_USER', false );
|
||||
\defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) || \define( 'ACTIVITYPUB_DISABLE_BLOG_USER', false );
|
||||
|
||||
\define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
\define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
|
||||
\define( 'ACTIVITYPUB_PLUGIN_FILE', plugin_dir_path( __FILE__ ) . '/' . basename( __FILE__ ) );
|
||||
|
@ -39,6 +42,7 @@ function init() {
|
|||
Collection\Followers::init();
|
||||
|
||||
// Configure the REST API route
|
||||
Rest\Users::init();
|
||||
Rest\Outbox::init();
|
||||
Rest\Inbox::init();
|
||||
Rest\Followers::init();
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
.activitypub-settings-body {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.settings_page_activitypub .notice {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.settings_page_activitypub .wrap {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.activitypub-settings-header {
|
||||
text-align: center;
|
||||
margin: 0 0 1rem;
|
||||
|
@ -25,10 +34,10 @@
|
|||
|
||||
.activitypub-settings-tabs-wrapper {
|
||||
display: -ms-inline-grid;
|
||||
-ms-grid-columns: 1fr 1fr;
|
||||
-ms-grid-columns: auto auto auto;
|
||||
vertical-align: top;
|
||||
display: inline-grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: auto auto auto;
|
||||
}
|
||||
|
||||
.activitypub-settings-tab.active {
|
||||
|
@ -111,7 +120,8 @@ summary {
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.activitypub-settings-accordion-trigger .icon, .activitypub-settings-accordion-viewed .icon {
|
||||
.activitypub-settings-accordion-trigger .icon,
|
||||
.activitypub-settings-accordion-viewed .icon {
|
||||
border: solid #50575e medium;
|
||||
border-width: 0 2px 2px 0;
|
||||
height: .5rem;
|
||||
|
@ -127,7 +137,8 @@ summary {
|
|||
transform: translateY(-30%) rotate(-135deg);
|
||||
}
|
||||
|
||||
.activitypub-settings-accordion-trigger:active, .activitypub-settings-accordion-trigger:hover {
|
||||
.activitypub-settings-accordion-trigger:active,
|
||||
.activitypub-settings-accordion-trigger:hover {
|
||||
background: #f6f7f7;
|
||||
}
|
||||
|
||||
|
@ -139,3 +150,29 @@ summary {
|
|||
outline: 2px solid #2271b1;
|
||||
background-color: #f6f7f7;
|
||||
}
|
||||
|
||||
.activitypub-settings-body
|
||||
input.blog-user-identifier {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.activitypub-settings-body
|
||||
.header-image {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-bottom: 40px;
|
||||
background-image: rgb(168,165,175);
|
||||
background-image: linear-gradient(180deg, red, yellow);
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.activitypub-settings-body
|
||||
.logo {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
position: relative;
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
}
|
||||
|
|
207
includes/activity/class-activity.php
Normal file
207
includes/activity/class-activity.php
Normal file
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
/**
|
||||
* Inspired by the PHP ActivityPub Library by @Landrok
|
||||
*
|
||||
* @link https://github.com/landrok/activitypub
|
||||
*/
|
||||
|
||||
namespace Activitypub\Activity;
|
||||
|
||||
use Activitypub\Activity\Base_Object;
|
||||
|
||||
/**
|
||||
* \Activitypub\Activity\Activity implements the common
|
||||
* attributes of an Activity.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-core/#activities
|
||||
* @see https://www.w3.org/TR/activitystreams-core/#intransitiveactivities
|
||||
*/
|
||||
class Activity extends Base_Object {
|
||||
const CONTEXT = array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
array(
|
||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||
'PropertyValue' => 'schema:PropertyValue',
|
||||
'schema' => 'http://schema.org#',
|
||||
'pt' => 'https://joinpeertube.org/ns#',
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'value' => 'schema:value',
|
||||
'Hashtag' => 'as:Hashtag',
|
||||
'featured' => array(
|
||||
'@id' => 'toot:featured',
|
||||
'@type' => '@id',
|
||||
),
|
||||
'featuredTags' => array(
|
||||
'@id' => 'toot:featuredTags',
|
||||
'@type' => '@id',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* The object's unique global identifier
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/#obj-id
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
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.
|
||||
* For instance, in the activity "John added a movie to his
|
||||
* wishlist", the object of the activity is the movie added.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object-term
|
||||
*
|
||||
* @var string
|
||||
* | Base_Objectr
|
||||
* | Link
|
||||
* | null
|
||||
*/
|
||||
protected $object;
|
||||
|
||||
/**
|
||||
* Describes one or more entities that either performed or are
|
||||
* expected to perform the activity.
|
||||
* Any single activity can have multiple actors.
|
||||
* The actor MAY be specified using an indirect Link.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-actor
|
||||
*
|
||||
* @var string
|
||||
* | \ActivityPhp\Type\Extended\AbstractActor
|
||||
* | array<Actor>
|
||||
* | array<Link>
|
||||
* | Link
|
||||
*/
|
||||
protected $actor;
|
||||
|
||||
/**
|
||||
* The indirect object, or target, of the activity.
|
||||
* The precise meaning of the target is largely dependent on the
|
||||
* type of action being described but will often be the object of
|
||||
* the English preposition "to".
|
||||
* For instance, in the activity "John added a movie to his
|
||||
* wishlist", the target of the activity is John's wishlist.
|
||||
* An activity can have more than one target.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-target
|
||||
*
|
||||
* @var string
|
||||
* | ObjectType
|
||||
* | array<ObjectType>
|
||||
* | Link
|
||||
* | array<Link>
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* Describes the result of the activity.
|
||||
* For instance, if a particular action results in the creation of
|
||||
* a new resource, the result property can be used to describe
|
||||
* that new resource.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-result
|
||||
*
|
||||
* @var string
|
||||
* | ObjectType
|
||||
* | Link
|
||||
* | null
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* An indirect object of the activity from which the
|
||||
* activity is directed.
|
||||
* The precise meaning of the origin is the object of the English
|
||||
* preposition "from".
|
||||
* For instance, in the activity "John moved an item to List B
|
||||
* from List A", the origin of the activity is "List A".
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-origin
|
||||
*
|
||||
* @var string
|
||||
* | ObjectType
|
||||
* | Link
|
||||
* | null
|
||||
*/
|
||||
protected $origin;
|
||||
|
||||
/**
|
||||
* One or more objects used (or to be used) in the completion of an
|
||||
* Activity.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-instrument
|
||||
*
|
||||
* @var string
|
||||
* | ObjectType
|
||||
* | Link
|
||||
* | null
|
||||
*/
|
||||
protected $instrument;
|
||||
|
||||
/**
|
||||
* Set the object and copy Object properties to the Activity.
|
||||
*
|
||||
* Any to, bto, cc, bcc, and audience properties specified on the object
|
||||
* MUST be copied over to the new Create activity by the server.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/#object-without-create
|
||||
*
|
||||
* @param string|Base_Objectr|Link|null $object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_object( $object ) {
|
||||
$this->set( 'object', $object );
|
||||
|
||||
if ( ! is_object( $object ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
|
||||
$this->set( $i, $object->get( $i ) );
|
||||
}
|
||||
|
||||
if ( $object->get_published() && ! $this->get_published() ) {
|
||||
$this->set( 'published', $object->get_published() );
|
||||
}
|
||||
|
||||
if ( $object->get_updated() && ! $this->get_updated() ) {
|
||||
$this->set( 'updated', $object->get_updated() );
|
||||
}
|
||||
|
||||
if ( $object->get_attributed_to() && ! $this->get_actor() ) {
|
||||
$this->set( 'actor', $object->get_attributed_to() );
|
||||
}
|
||||
|
||||
if ( $object->get_id() && ! $this->get_id() ) {
|
||||
$this->set( 'id', $object->get_id() . '#activity' );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,14 @@
|
|||
|
||||
namespace Activitypub\Activity;
|
||||
|
||||
/**
|
||||
* \Activitypub\Activity\Actor is an implementation of
|
||||
* one an Activity Streams Actor.
|
||||
*
|
||||
* Represents an individual actor.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
|
||||
*/
|
||||
class Actor extends Base_Object {
|
||||
/**
|
||||
* @var string
|
||||
|
@ -114,4 +122,18 @@ class Actor extends Base_Object {
|
|||
* @var string|array|null
|
||||
*/
|
||||
protected $public_key;
|
||||
|
||||
/**
|
||||
* It's not part of the ActivityPub protocol but it's a quite common
|
||||
* practice to lock an account. If anabled, new followers will not be
|
||||
* automatically accepted, but will instead require you to manually
|
||||
* approve them.
|
||||
*
|
||||
* WordPress does only support 'false' at the moment.
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/spec/activitypub/#as
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $manually_approves_followers = false;
|
||||
}
|
||||
|
|
|
@ -11,15 +11,16 @@ use WP_Error;
|
|||
|
||||
use function Activitypub\camel_to_snake_case;
|
||||
use function Activitypub\snake_to_camel_case;
|
||||
|
||||
/**
|
||||
* ObjectType is an implementation of one of the
|
||||
* Base_Object is an implementation of one of the
|
||||
* Activity Streams Core Types.
|
||||
*
|
||||
* The Object is the primary base type for the Activity Streams
|
||||
* vocabulary.
|
||||
*
|
||||
* Note: Object is a reserved keyword in PHP. It has been suffixed with
|
||||
* 'Type' for this reason.
|
||||
* 'Base_' for this reason.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-core/#object
|
||||
*/
|
||||
|
@ -462,6 +463,24 @@ class Base_Object {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic function, to transform the object to string.
|
||||
*
|
||||
* @return string The object id.
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->to_string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to transform the object to string.
|
||||
*
|
||||
* @return string The object id.
|
||||
*/
|
||||
public function to_string() {
|
||||
return $this->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic getter.
|
||||
*
|
||||
|
@ -472,7 +491,6 @@ class Base_Object {
|
|||
public function get( $key ) {
|
||||
if ( ! $this->has( $key ) ) {
|
||||
return new WP_Error( 'invalid_key', 'Invalid key' );
|
||||
|
||||
}
|
||||
|
||||
return $this->$key;
|
||||
|
@ -537,12 +555,12 @@ class Base_Object {
|
|||
*
|
||||
* @return string The JSON string.
|
||||
*
|
||||
* @return array An Object built from the JSON string.
|
||||
* @return \Activitypub\Activity\Base_Object An Object built from the JSON string.
|
||||
*/
|
||||
public static function from_json( $json ) {
|
||||
$array = wp_json_decode( $json, true );
|
||||
public static function init_from_json( $json ) {
|
||||
$array = \json_decode( $json, true );
|
||||
|
||||
return self::from_array( $array );
|
||||
return self::init_from_array( $array );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -550,9 +568,9 @@ class Base_Object {
|
|||
*
|
||||
* @return string The object array.
|
||||
*
|
||||
* @return array An Object built from the JSON string.
|
||||
* @return \Activitypub\Activity\Base_Object An Object built from the JSON string.
|
||||
*/
|
||||
public static function from_array( $array ) {
|
||||
public static function init_from_array( $array ) {
|
||||
$object = new static();
|
||||
|
||||
foreach ( $array as $key => $value ) {
|
||||
|
@ -563,6 +581,29 @@ class Base_Object {
|
|||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert JSON input to an array and pre-fill the object.
|
||||
*
|
||||
* @param string $json The JSON string.
|
||||
*/
|
||||
public function from_json( $json ) {
|
||||
$array = \json_decode( $json, true );
|
||||
|
||||
$this->from_array( $array );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert JSON input to an array and pre-fill the object.
|
||||
*
|
||||
* @param array $array The array.
|
||||
*/
|
||||
public function from_array( $array ) {
|
||||
foreach ( $array as $key => $value ) {
|
||||
$key = camel_to_snake_case( $key );
|
||||
$this->set( $key, $value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Object to an array.
|
||||
*
|
||||
|
@ -576,17 +617,50 @@ class Base_Object {
|
|||
$vars = get_object_vars( $this );
|
||||
|
||||
foreach ( $vars as $key => $value ) {
|
||||
// ignotre all _prefixed keys.
|
||||
if ( '_' === substr( $key, 0, 1 ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if value is empty, try to get it from a getter.
|
||||
if ( ! $value ) {
|
||||
if ( ! isset( $value ) ) {
|
||||
$value = call_user_func( array( $this, 'get_' . $key ) );
|
||||
}
|
||||
|
||||
if ( is_object( $value ) ) {
|
||||
$value = $value->to_array();
|
||||
}
|
||||
|
||||
// if value is still empty, ignore it for the array and continue.
|
||||
if ( $value ) {
|
||||
if ( isset( $value ) ) {
|
||||
$array[ snake_to_camel_case( $key ) ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// replace 'context' key with '@context' and move it to the top.
|
||||
if ( array_key_exists( 'context', $array ) ) {
|
||||
$context = $array['context'];
|
||||
unset( $array['context'] );
|
||||
$array = array_merge( array( '@context' => $context ), $array );
|
||||
}
|
||||
|
||||
$class = new \ReflectionClass( $this );
|
||||
$class = strtolower( $class->getShortName() );
|
||||
|
||||
$array = \apply_filters( 'activitypub_activity_object_array', $array, $class, $this->id, $this );
|
||||
$array = \apply_filters( "activitypub_activity_{$class}_object_array", $array, $this->id, $this );
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Object to JSON.
|
||||
*
|
||||
* @return string The JSON string.
|
||||
*/
|
||||
public function to_json() {
|
||||
$array = $this->to_array();
|
||||
|
||||
return \wp_json_encode( $array, \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Inspired by the PHP ActivityPub Library by @Landrok
|
||||
*
|
||||
* @link https://github.com/landrok/activitypub
|
||||
*/
|
||||
|
||||
namespace Activitypub\Activity;
|
||||
|
||||
/**
|
||||
* Represents an individual person.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person
|
||||
*/
|
||||
class Person extends Base_Object {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Person';
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Model\Post;
|
||||
use Activitypub\Model\Activity;
|
||||
use WP_Post;
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Collection\Followers;
|
||||
use Activitypub\Transformer\Post;
|
||||
|
||||
use function Activitypub\is_single_user;
|
||||
use function Activitypub\is_user_disabled;
|
||||
use function Activitypub\safe_remote_post;
|
||||
|
||||
/**
|
||||
|
@ -19,69 +23,106 @@ class Activity_Dispatcher {
|
|||
* Initialize the class, registering WordPress hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
// legacy
|
||||
\add_action( 'activitypub_send_post_activity', array( self::class, 'send_create_activity' ) );
|
||||
// check if a migration is needed before sending new posts
|
||||
Migration::maybe_migrate();
|
||||
|
||||
\add_action( 'activitypub_send_create_activity', array( self::class, 'send_create_activity' ) );
|
||||
\add_action( 'activitypub_send_update_activity', array( self::class, 'send_update_activity' ) );
|
||||
\add_action( 'activitypub_send_delete_activity', array( self::class, 'send_delete_activity' ) );
|
||||
\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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "create" activities.
|
||||
* Send Activities to followers and mentioned users or `Announce` (boost) a blog post.
|
||||
*
|
||||
* @param Activitypub\Model\Post $activitypub_post
|
||||
* @param WP_Post $wp_post The ActivityPub Post.
|
||||
* @param string $type The Activity-Type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function send_create_activity( Post $activitypub_post ) {
|
||||
self::send_activity( $activitypub_post, 'Create' );
|
||||
public static function send_activity_or_announce( WP_Post $wp_post, $type ) {
|
||||
if ( is_user_disabled( Users::BLOG_USER_ID ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "update" activities.
|
||||
*
|
||||
* @param Activitypub\Model\Post $activitypub_post The ActivityPub Post.
|
||||
*/
|
||||
public static function send_update_activity( Post $activitypub_post ) {
|
||||
self::send_activity( $activitypub_post, 'Update' );
|
||||
}
|
||||
$wp_post->post_author = Users::BLOG_USER_ID;
|
||||
|
||||
/**
|
||||
* Send "delete" activities.
|
||||
*
|
||||
* @param Activitypub\Model\Post $activitypub_post The ActivityPub Post.
|
||||
*/
|
||||
public static function send_delete_activity( Post $activitypub_post ) {
|
||||
self::send_activity( $activitypub_post, 'Delete' );
|
||||
if ( is_single_user() ) {
|
||||
self::send_activity( $wp_post, $type );
|
||||
} else {
|
||||
self::send_announce( $wp_post, $type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Activities to followers and mentioned users.
|
||||
*
|
||||
* @param Activitypub\Model\Post $activitypub_post The ActivityPub Post.
|
||||
* @param string $activity_type The Activity-Type.
|
||||
* @param WP_Post $wp_post The ActivityPub Post.
|
||||
* @param string $type The Activity-Type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function send_activity( Post $activitypub_post, $activity_type ) {
|
||||
// check if a migration is needed before sending new posts
|
||||
Migration::maybe_migrate();
|
||||
public static function send_activity( WP_Post $wp_post, $type ) {
|
||||
if ( is_user_disabled( $wp_post->post_author ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get latest version of post
|
||||
$user_id = $activitypub_post->get_post_author();
|
||||
$object = Post::transform( $wp_post )->to_object();
|
||||
|
||||
$activitypub_activity = new Activity( $activity_type );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
$activity = new Activity();
|
||||
$activity->set_type( $type );
|
||||
$activity->set_object( $object );
|
||||
|
||||
$follower_inboxes = Followers::get_inboxes( $user_id );
|
||||
$mentioned_inboxes = Mention::get_inboxes( $activitypub_activity->get_cc() );
|
||||
$follower_inboxes = Followers::get_inboxes( $wp_post->post_author );
|
||||
$mentioned_inboxes = Mention::get_inboxes( $activity->get_cc() );
|
||||
|
||||
$inboxes = array_merge( $follower_inboxes, $mentioned_inboxes );
|
||||
$inboxes = array_unique( $inboxes );
|
||||
|
||||
foreach ( $inboxes as $inbox ) {
|
||||
$activity = $activitypub_activity->to_json();
|
||||
$json = $activity->to_json();
|
||||
|
||||
safe_remote_post( $inbox, $activity, $user_id );
|
||||
foreach ( $inboxes as $inbox ) {
|
||||
safe_remote_post( $inbox, $json, $wp_post->post_author );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Announces to followers and mentioned users.
|
||||
*
|
||||
* @param WP_Post $wp_post The ActivityPub Post.
|
||||
* @param string $type The Activity-Type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function send_announce( WP_Post $wp_post, $type ) {
|
||||
// check if a migration is needed before sending new posts
|
||||
Migration::maybe_migrate();
|
||||
|
||||
if ( ! in_array( $type, array( 'Create', 'Update' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_user_disabled( Users::BLOG_USER_ID ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$object = Post::transform( $wp_post )->to_object();
|
||||
|
||||
$activity = new Activity();
|
||||
$activity->set_type( 'Announce' );
|
||||
// to pre-fill attributes like "published" and "id"
|
||||
$activity->set_object( $object );
|
||||
// send only the id
|
||||
$activity->set_object( $object->get_id() );
|
||||
|
||||
$follower_inboxes = Followers::get_inboxes( $wp_post->post_author );
|
||||
$mentioned_inboxes = Mention::get_inboxes( $activity->get_cc() );
|
||||
|
||||
$inboxes = array_merge( $follower_inboxes, $mentioned_inboxes );
|
||||
$inboxes = array_unique( $inboxes );
|
||||
|
||||
$json = $activity->to_json();
|
||||
|
||||
foreach ( $inboxes as $inbox ) {
|
||||
safe_remote_post( $inbox, $json, $wp_post->post_author );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Signature;
|
||||
use Activitypub\Collection\Users;
|
||||
|
||||
/**
|
||||
* ActivityPub Class
|
||||
|
@ -28,6 +29,8 @@ class Activitypub {
|
|||
\add_action( 'untrash_post', array( self::class, 'untrash_post' ), 1 );
|
||||
|
||||
\add_action( 'init', array( self::class, 'add_rewrite_rules' ) );
|
||||
|
||||
\add_action( 'after_setup_theme', array( self::class, 'theme_compat' ), 99 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,15 +72,18 @@ class Activitypub {
|
|||
* @return string The new path to the JSON template.
|
||||
*/
|
||||
public static function render_json_template( $template ) {
|
||||
if ( ! \is_author() && ! \is_singular() && ! \is_home() ) {
|
||||
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
// Ensure that edge caches know that this page can deliver both HTML and JSON.
|
||||
header( 'Vary: Accept' );
|
||||
if ( ! is_activitypub_request() ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$json_template = false;
|
||||
|
||||
// check if user can publish posts
|
||||
if ( \is_author() && ! user_can( \get_the_author_meta( 'ID' ), 'publish_posts' ) ) {
|
||||
if ( \is_author() && ! Users::get_by_id( \get_the_author_meta( 'ID' ) ) ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
@ -89,7 +95,6 @@ class Activitypub {
|
|||
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/blog-json.php';
|
||||
}
|
||||
|
||||
if ( is_activitypub_request() ) {
|
||||
if ( ACTIVITYPUB_SECURE_MODE ) {
|
||||
$verification = Signature::verify_http_signature( $_SERVER );
|
||||
if ( \is_wp_error( $verification ) ) {
|
||||
|
@ -97,10 +102,8 @@ class Activitypub {
|
|||
return $template;
|
||||
}
|
||||
}
|
||||
return $json_template;
|
||||
}
|
||||
|
||||
return $template;
|
||||
return $json_template;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -224,6 +227,11 @@ class Activitypub {
|
|||
'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/nodeinfo2',
|
||||
'top'
|
||||
);
|
||||
\add_rewrite_rule(
|
||||
'^@([\w\-\.]+)',
|
||||
'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/users/$matches[1]',
|
||||
'top'
|
||||
);
|
||||
}
|
||||
|
||||
\add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
|
||||
|
@ -236,4 +244,36 @@ class Activitypub {
|
|||
self::add_rewrite_rules();
|
||||
\flush_rewrite_rules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme compatibility stuff
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function theme_compat() {
|
||||
$site_icon = get_theme_support( 'custom-logo' );
|
||||
|
||||
if ( ! $site_icon ) {
|
||||
// custom logo support
|
||||
add_theme_support(
|
||||
'custom-logo',
|
||||
array(
|
||||
'height' => 80,
|
||||
'width' => 80,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$custom_header = get_theme_support( 'custom-header' );
|
||||
|
||||
if ( ! $custom_header ) {
|
||||
// This theme supports a custom header
|
||||
$custom_header_args = array(
|
||||
'width' => 1250,
|
||||
'height' => 600,
|
||||
'header-text' => true,
|
||||
);
|
||||
add_theme_support( 'custom-header', $custom_header_args );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Model\Post;
|
||||
|
||||
/**
|
||||
* ActivityPub Admin Class
|
||||
*
|
||||
|
@ -34,10 +32,13 @@ class Admin {
|
|||
|
||||
\add_action( 'load-' . $settings_page, array( self::class, 'add_settings_help_tab' ) );
|
||||
|
||||
// user has to be able to publish posts
|
||||
if ( ! is_user_disabled( get_current_user_id() ) ) {
|
||||
$followers_list_page = \add_users_page( \__( 'Followers', 'activitypub' ), \__( 'Followers', 'activitypub' ), 'read', 'activitypub-followers-list', array( self::class, 'followers_list_page' ) );
|
||||
|
||||
\add_action( 'load-' . $followers_list_page, array( self::class, 'add_followers_list_help_tab' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings page
|
||||
|
@ -55,6 +56,9 @@ class Admin {
|
|||
case 'settings':
|
||||
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/settings.php' );
|
||||
break;
|
||||
case 'followers':
|
||||
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/blog-user-followers-list.php' );
|
||||
break;
|
||||
case 'welcome':
|
||||
default:
|
||||
wp_enqueue_script( 'plugin-install' );
|
||||
|
@ -70,7 +74,10 @@ class Admin {
|
|||
* Load user settings page
|
||||
*/
|
||||
public static function followers_list_page() {
|
||||
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/followers-list.php' );
|
||||
if ( ! current_user_can( 'publish_posts' ) ) {
|
||||
return;
|
||||
}
|
||||
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/user-followers-list.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,7 +92,11 @@ class Admin {
|
|||
'description' => \__( 'Use title and link, summary, full or custom content', 'activitypub' ),
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'enum' => array( 'title', 'excerpt', 'content' ),
|
||||
'enum' => array(
|
||||
'title',
|
||||
'excerpt',
|
||||
'content',
|
||||
),
|
||||
),
|
||||
),
|
||||
'default' => 'content',
|
||||
|
@ -118,7 +129,11 @@ class Admin {
|
|||
'description' => \__( 'The Activity-Object-Type', 'activitypub' ),
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'enum' => array( 'note', 'article', 'wordpress-post-format' ),
|
||||
'enum' => array(
|
||||
'note',
|
||||
'article',
|
||||
'wordpress-post-format',
|
||||
),
|
||||
),
|
||||
),
|
||||
'default' => 'note',
|
||||
|
@ -143,6 +158,27 @@ class Admin {
|
|||
'default' => array( 'post', 'pages' ),
|
||||
)
|
||||
);
|
||||
\register_setting(
|
||||
'activitypub',
|
||||
'activitypub_blog_user_identifier',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => \esc_html__( 'The Identifier of th Blog-User', 'activitypub' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => 'feed',
|
||||
'sanitize_callback' => function( $value ) {
|
||||
// hack to allow dots in the username
|
||||
$parts = explode( '.', $value );
|
||||
$sanitized = array();
|
||||
|
||||
foreach ( $parts as $part ) {
|
||||
$sanitized[] = \sanitize_title( $part );
|
||||
}
|
||||
|
||||
return implode( '.', $sanitized );
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function add_settings_help_tab() {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
namespace Activitypub;
|
||||
|
||||
use WP_Error;
|
||||
use Activitypub\Model\User;
|
||||
use Activitypub\Collection\Users;
|
||||
|
||||
/**
|
||||
* ActivityPub HTTP Class
|
||||
|
@ -25,6 +25,12 @@ class Http {
|
|||
$signature = Signature::generate_signature( $user_id, 'post', $url, $date, $digest );
|
||||
|
||||
$wp_version = \get_bloginfo( 'version' );
|
||||
|
||||
/**
|
||||
* Filter the HTTP headers user agent.
|
||||
*
|
||||
* @param string $user_agent The user agent string.
|
||||
*/
|
||||
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
|
||||
$args = array(
|
||||
'timeout' => 100,
|
||||
|
@ -63,10 +69,17 @@ class Http {
|
|||
*/
|
||||
public static function get( $url ) {
|
||||
$date = \gmdate( 'D, d M Y H:i:s T' );
|
||||
$signature = Signature::generate_signature( User::APPLICATION_USER_ID, 'get', $url, $date );
|
||||
$signature = Signature::generate_signature( Users::APPLICATION_USER_ID, 'get', $url, $date );
|
||||
|
||||
$wp_version = \get_bloginfo( 'version' );
|
||||
|
||||
/**
|
||||
* Filter the HTTP headers user agent.
|
||||
*
|
||||
* @param string $user_agent The user agent string.
|
||||
*/
|
||||
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
|
||||
|
||||
$args = array(
|
||||
'timeout' => apply_filters( 'activitypub_remote_get_timeout', 100 ),
|
||||
'limit_response_size' => 1048576,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Model\Follower;
|
||||
use Activitypub\Model\Blog_User;
|
||||
use Activitypub\Collection\Followers;
|
||||
|
||||
/**
|
||||
|
@ -17,10 +17,23 @@ class Migration {
|
|||
\add_action( 'activitypub_schedule_migration', array( self::class, 'maybe_migrate' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target version.
|
||||
*
|
||||
* This is the version that the database structure will be updated to.
|
||||
* It is the same as the plugin version.
|
||||
*
|
||||
* @return string The target version.
|
||||
*/
|
||||
public static function get_target_version() {
|
||||
return get_plugin_version();
|
||||
}
|
||||
|
||||
/**
|
||||
* The current version of the database structure.
|
||||
*
|
||||
* @return string The current version.
|
||||
*/
|
||||
public static function get_version() {
|
||||
return get_option( 'activitypub_db_version', 0 );
|
||||
}
|
||||
|
@ -28,7 +41,7 @@ class Migration {
|
|||
/**
|
||||
* Whether the database structure is up to date.
|
||||
*
|
||||
* @return bool
|
||||
* @return bool True if the database structure is up to date, false otherwise.
|
||||
*/
|
||||
public static function is_latest_version() {
|
||||
return (bool) version_compare(
|
||||
|
@ -64,29 +77,21 @@ class Migration {
|
|||
* @return void
|
||||
*/
|
||||
private static function migrate_from_0_17() {
|
||||
// migrate followers
|
||||
foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
|
||||
$followers = get_user_meta( $user_id, 'activitypub_followers', true );
|
||||
|
||||
if ( $followers ) {
|
||||
foreach ( $followers as $actor ) {
|
||||
$meta = get_remote_metadata_by_actor( $actor );
|
||||
|
||||
if ( is_tombstone( $meta ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$follower = Follower::from_array( $meta );
|
||||
|
||||
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
|
||||
$follower->set_error( $meta );
|
||||
}
|
||||
|
||||
$follower->upsert();
|
||||
|
||||
add_post_meta( $follower->get__id(), '_user_id', $user_id );
|
||||
Followers::add_follower( $user_id, $actor );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set the default username for the Blog User
|
||||
if ( ! \get_option( 'activitypub_blog_user_identifier' ) ) {
|
||||
\update_option( 'activitypub_blog_user_identifier', Blog_User::get_default_username() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Model\Post;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Collection\Followers;
|
||||
use Activitypub\Transformer\Post;
|
||||
|
||||
/**
|
||||
* ActivityPub Scheduler Class
|
||||
|
@ -68,27 +69,34 @@ class Scheduler {
|
|||
return;
|
||||
}
|
||||
|
||||
$activitypub_post = new Post( $post );
|
||||
$type = false;
|
||||
|
||||
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
|
||||
\wp_schedule_single_event(
|
||||
\time(),
|
||||
'activitypub_send_create_activity',
|
||||
array( $activitypub_post )
|
||||
);
|
||||
$type = 'Create';
|
||||
} elseif ( 'publish' === $new_status ) {
|
||||
\wp_schedule_single_event(
|
||||
\time(),
|
||||
'activitypub_send_update_activity',
|
||||
array( $activitypub_post )
|
||||
);
|
||||
$type = 'Update';
|
||||
} elseif ( 'trash' === $new_status ) {
|
||||
$type = 'Delete';
|
||||
}
|
||||
|
||||
if ( ! $type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\wp_schedule_single_event(
|
||||
\time(),
|
||||
'activitypub_send_delete_activity',
|
||||
array( $activitypub_post )
|
||||
'activitypub_send_activity',
|
||||
array( $post, $type )
|
||||
);
|
||||
|
||||
\wp_schedule_single_event(
|
||||
\time(),
|
||||
sprintf(
|
||||
'activitypub_send_%s_activity',
|
||||
\strtolower( $type )
|
||||
),
|
||||
array( $post )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,14 +111,13 @@ class Scheduler {
|
|||
$meta = get_remote_metadata_by_actor( $follower->get_url(), true );
|
||||
|
||||
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
|
||||
$follower->set_error( $meta );
|
||||
Followers::add_error( $follower->get__id(), $meta );
|
||||
} else {
|
||||
$follower->from_meta( $meta );
|
||||
}
|
||||
|
||||
$follower->from_array( $meta );
|
||||
$follower->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup followers
|
||||
|
@ -129,8 +136,7 @@ class Scheduler {
|
|||
if ( 5 <= $follower->count_errors() ) {
|
||||
$follower->delete();
|
||||
} else {
|
||||
$follower->set_error( $meta );
|
||||
$follower->update();
|
||||
Followers::add_error( $follower->get__id(), $meta );
|
||||
}
|
||||
} else {
|
||||
$follower->reset_errors();
|
||||
|
|
|
@ -5,6 +5,7 @@ use WP_Error;
|
|||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Activitypub\Model\User;
|
||||
use Activitypub\Collection\Users;
|
||||
|
||||
/**
|
||||
* ActivityPub Signature Class
|
||||
|
@ -73,7 +74,7 @@ class Signature {
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function generate_key_pair( $user_id ) {
|
||||
public static function generate_key_pair() {
|
||||
$config = array(
|
||||
'digest_alg' => 'sha512',
|
||||
'private_key_bits' => 2048,
|
||||
|
@ -84,22 +85,13 @@ class Signature {
|
|||
$priv_key = null;
|
||||
|
||||
\openssl_pkey_export( $key, $priv_key );
|
||||
|
||||
$detail = \openssl_pkey_get_details( $key );
|
||||
|
||||
if ( User::APPLICATION_USER_ID === $user_id ) {
|
||||
// private key
|
||||
\update_option( 'activitypub_magic_sig_private_key', $priv_key );
|
||||
|
||||
// public key
|
||||
\update_option( 'activitypub_magic_sig_public_key', $detail['key'] );
|
||||
|
||||
} else {
|
||||
// private key
|
||||
\update_user_meta( $user_id, 'magic_sig_private_key', $priv_key );
|
||||
|
||||
// public key
|
||||
\update_user_meta( $user_id, 'magic_sig_public_key', $detail['key'] );
|
||||
}
|
||||
return array(
|
||||
'private_key' => $priv_key,
|
||||
'public_key' => $detail['key'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,7 +106,8 @@ class Signature {
|
|||
* @return string The signature.
|
||||
*/
|
||||
public static function generate_signature( $user_id, $http_method, $url, $date, $digest = null ) {
|
||||
$key = self::get_private_key( $user_id );
|
||||
$user = Users::get_by_id( $user_id );
|
||||
$key = $user->get__private_key();
|
||||
|
||||
$url_parts = \wp_parse_url( $url );
|
||||
|
||||
|
@ -143,11 +136,8 @@ class Signature {
|
|||
\openssl_sign( $signed_string, $signature, $key, \OPENSSL_ALGO_SHA256 );
|
||||
$signature = \base64_encode( $signature ); // phpcs:ignore
|
||||
|
||||
if ( User::APPLICATION_USER_ID === $user_id ) {
|
||||
$key_id = \get_rest_url( null, 'activitypub/1.0/application#main-key' );
|
||||
} else {
|
||||
$key_id = \get_author_posts_url( $user_id ) . '#main-key';
|
||||
}
|
||||
$user = Users::get_by_id( $user_id );
|
||||
$key_id = $user->get_url() . '#main-key';
|
||||
|
||||
if ( ! empty( $digest ) ) {
|
||||
return \sprintf( 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="%s"', $key_id, $signature );
|
||||
|
@ -252,7 +242,7 @@ class Signature {
|
|||
* @return string The public key.
|
||||
*/
|
||||
public static function get_remote_key( $key_id ) { // phpcs:ignore
|
||||
$actor = \Activitypub\get_remote_metadata_by_actor( strtok( strip_fragment_from_url( $key_id ), '?' ) ); // phpcs:ignore
|
||||
$actor = get_remote_metadata_by_actor( strtok( strip_fragment_from_url( $key_id ), '?' ) ); // phpcs:ignore
|
||||
if ( \is_wp_error( $actor ) ) {
|
||||
return $actor;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Activitypub;
|
||||
|
||||
use WP_Error;
|
||||
use Activitypub\Collection\Users;
|
||||
|
||||
/**
|
||||
* ActivityPub WebFinger Class
|
||||
|
@ -24,26 +25,33 @@ class Webfinger {
|
|||
return \get_webfinger_resource( $user_id, false );
|
||||
}
|
||||
|
||||
$user = \get_user_by( 'id', $user_id );
|
||||
$user = Users::get_by_id( $user_id );
|
||||
if ( ! $user ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $user->user_login . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
|
||||
return $user->get_resource();
|
||||
}
|
||||
|
||||
public static function resolve( $account ) {
|
||||
if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $account, $m ) ) {
|
||||
/**
|
||||
* Resolve a WebFinger resource
|
||||
*
|
||||
* @param string $resource The WebFinger resource
|
||||
*
|
||||
* @return string|WP_Error The URL or WP_Error
|
||||
*/
|
||||
public static function resolve( $resource ) {
|
||||
if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $resource, $m ) ) {
|
||||
return null;
|
||||
}
|
||||
$transient_key = 'activitypub_resolve_' . ltrim( $account, '@' );
|
||||
$transient_key = 'activitypub_resolve_' . ltrim( $resource, '@' );
|
||||
|
||||
$link = \get_transient( $transient_key );
|
||||
if ( $link ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
$url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[2] . '/.well-known/webfinger' );
|
||||
$url = \add_query_arg( 'resource', 'acct:' . ltrim( $resource, '@' ), 'https://' . $m[2] . '/.well-known/webfinger' );
|
||||
if ( ! \wp_http_validate_url( $url ) ) {
|
||||
$response = new WP_Error( 'invalid_webfinger_url', null, $url );
|
||||
\set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
|
@ -82,7 +90,7 @@ class Webfinger {
|
|||
}
|
||||
}
|
||||
|
||||
$link = new WP_Error( 'webfinger_url_no_activity_pub', null, $body );
|
||||
$link = new WP_Error( 'webfinger_url_no_activitypub', null, $body );
|
||||
\set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $link;
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ use Exception;
|
|||
use WP_Query;
|
||||
use Activitypub\Http;
|
||||
use Activitypub\Webfinger;
|
||||
use Activitypub\Model\Activity;
|
||||
use Activitypub\Model\Follower;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Activity\Base_Object;
|
||||
|
||||
use function Activitypub\is_tombstone;
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
|
@ -62,37 +64,7 @@ class Followers {
|
|||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'preferred_username',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => function( $value ) {
|
||||
return sanitize_user( $value, true );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'icon',
|
||||
array(
|
||||
'single' => true,
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'url',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => false,
|
||||
'sanitize_callback' => array( self::class, 'sanitize_url' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'inbox',
|
||||
'activitypub_inbox',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
|
@ -102,17 +74,7 @@ class Followers {
|
|||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_shared_inbox',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => array( self::class, 'sanitize_url' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_errors',
|
||||
'activitypub_errors',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => false,
|
||||
|
@ -128,7 +90,7 @@ class Followers {
|
|||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_actor',
|
||||
'activitypub_user_id',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => false,
|
||||
|
@ -138,6 +100,18 @@ class Followers {
|
|||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'activitypub_actor_json',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => function( $value ) {
|
||||
return sanitize_text_field( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
do_action( 'activitypub_after_register_post_type' );
|
||||
}
|
||||
|
||||
|
@ -191,17 +165,33 @@ class Followers {
|
|||
public static function add_follower( $user_id, $actor ) {
|
||||
$meta = get_remote_metadata_by_actor( $actor );
|
||||
|
||||
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
|
||||
if ( is_tombstone( $meta ) ) {
|
||||
return $meta;
|
||||
}
|
||||
|
||||
$follower = Follower::from_array( $meta );
|
||||
$error = null;
|
||||
|
||||
$follower = new Follower();
|
||||
|
||||
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
|
||||
$follower->set_id( $actor );
|
||||
$follower->set_url( $actor );
|
||||
$error = $meta;
|
||||
} else {
|
||||
$follower->from_array( $meta );
|
||||
}
|
||||
|
||||
$follower->upsert();
|
||||
|
||||
$meta = get_post_meta( $follower->get__id(), '_user_id' );
|
||||
$meta = get_post_meta( $follower->get__id(), 'activitypub_user_id' );
|
||||
|
||||
if ( is_array( $meta ) && ! in_array( $user_id, $meta, true ) ) {
|
||||
add_post_meta( $follower->get__id(), '_user_id', $user_id );
|
||||
if ( $error ) {
|
||||
self::add_error( $follower->get__id(), $error );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
|
||||
if ( is_array( $meta ) && ! in_array( $user_id, $meta ) ) {
|
||||
add_post_meta( $follower->get__id(), 'activitypub_user_id', $user_id );
|
||||
wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
|
||||
}
|
||||
|
||||
|
@ -225,7 +215,7 @@ class Followers {
|
|||
return false;
|
||||
}
|
||||
|
||||
return delete_post_meta( $follower->get__id(), '_user_id', $user_id );
|
||||
return delete_post_meta( $follower->get__id(), 'activitypub_user_id', $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,7 +231,7 @@ class Followers {
|
|||
|
||||
$post_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT p.ID FROM $wpdb->posts p INNER JOIN $wpdb->postmeta pm ON p.ID = pm.post_id WHERE p.post_type = %s AND pm.meta_key = '_user_id' AND pm.meta_value = %d AND p.guid = %s",
|
||||
"SELECT DISTINCT p.ID FROM $wpdb->posts p INNER JOIN $wpdb->postmeta pm ON p.ID = pm.post_id WHERE p.post_type = %s AND pm.meta_key = 'activitypub_user_id' AND pm.meta_value = %d AND p.guid = %s",
|
||||
array(
|
||||
esc_sql( self::POST_TYPE ),
|
||||
esc_sql( $user_id ),
|
||||
|
@ -252,7 +242,7 @@ class Followers {
|
|||
|
||||
if ( $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
return Follower::from_custom_post_type( $post );
|
||||
return Follower::init_from_cpt( $post );
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -277,20 +267,25 @@ class Followers {
|
|||
|
||||
if ( isset( $object['user_id'] ) ) {
|
||||
unset( $object['user_id'] );
|
||||
unset( $object['@context'] );
|
||||
}
|
||||
|
||||
unset( $object['@context'] );
|
||||
|
||||
$user = Users::get_by_id( $user_id );
|
||||
|
||||
// get inbox
|
||||
$inbox = $follower->get_inbox();
|
||||
$inbox = $follower->get_shared_inbox();
|
||||
|
||||
// send "Accept" activity
|
||||
$activity = new Activity( 'Accept' );
|
||||
$activity->set_activity_object( $object );
|
||||
$activity->set_actor( \get_author_posts_url( $user_id ) );
|
||||
$activity = new Activity();
|
||||
$activity->set_type( 'Accept' );
|
||||
$activity->set_object( $object );
|
||||
$activity->set_actor( $user->get_id() );
|
||||
$activity->set_to( $actor );
|
||||
$activity->set_id( \get_author_posts_url( $user_id ) . '#follow-' . \preg_replace( '~^https?://~', '', $actor ) );
|
||||
$activity->set_id( $user->get_id() . '#follow-' . \preg_replace( '~^https?://~', '', $actor ) );
|
||||
|
||||
$activity = $activity->to_json();
|
||||
|
||||
$activity = $activity->to_simple_json();
|
||||
$response = Http::post( $inbox, $activity, $user_id );
|
||||
}
|
||||
|
||||
|
@ -304,16 +299,16 @@ class Followers {
|
|||
*
|
||||
* @return array The Term list of Followers, the format depends on $output
|
||||
*/
|
||||
public static function get_followers( $user_id, $number = null, $offset = null, $args = array() ) {
|
||||
public static function get_followers( $user_id, $number = -1, $page = null, $args = array() ) {
|
||||
$defaults = array(
|
||||
'post_type' => self::POST_TYPE,
|
||||
'posts_per_page' => $number,
|
||||
'offset' => $offset,
|
||||
'paged' => $page,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'DESC',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_user_id',
|
||||
'key' => 'activitypub_user_id',
|
||||
'value' => $user_id,
|
||||
),
|
||||
),
|
||||
|
@ -321,10 +316,11 @@ class Followers {
|
|||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
$query = new WP_Query( $args );
|
||||
$posts = $query->get_posts();
|
||||
$items = array();
|
||||
|
||||
foreach ( $query->get_posts() as $post ) {
|
||||
$items[] = Follower::from_custom_post_type( $post ); // phpcs:ignore
|
||||
foreach ( $posts as $post ) {
|
||||
$items[] = Follower::init_from_cpt( $post ); // phpcs:ignore
|
||||
}
|
||||
|
||||
return $items;
|
||||
|
@ -337,11 +333,11 @@ class Followers {
|
|||
*
|
||||
* @return array The Term list of Followers.
|
||||
*/
|
||||
public static function get_all_followers( $user_id = null ) {
|
||||
public static function get_all_followers() {
|
||||
$args = array(
|
||||
'meta_query' => array(),
|
||||
);
|
||||
return self::get_followers( $user_id, null, null, $args );
|
||||
return self::get_followers( null, null, null, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -358,7 +354,7 @@ class Followers {
|
|||
'fields' => 'ids',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_user_id',
|
||||
'key' => 'activitypub_user_id',
|
||||
'value' => $user_id,
|
||||
),
|
||||
),
|
||||
|
@ -390,11 +386,11 @@ class Followers {
|
|||
'fields' => 'ids',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_shared_inbox',
|
||||
'key' => 'activitypub_inbox',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
array(
|
||||
'key' => '_user_id',
|
||||
'key' => 'activitypub_user_id',
|
||||
'value' => $user_id,
|
||||
),
|
||||
),
|
||||
|
@ -412,7 +408,7 @@ class Followers {
|
|||
$wpdb->prepare(
|
||||
"SELECT DISTINCT meta_value FROM {$wpdb->postmeta}
|
||||
WHERE post_id IN (" . implode( ', ', array_fill( 0, count( $posts ), '%d' ) ) . ")
|
||||
AND meta_key = '_shared_inbox'
|
||||
AND meta_key = 'activitypub_inbox'
|
||||
AND meta_value IS NOT NULL",
|
||||
$posts
|
||||
)
|
||||
|
@ -452,7 +448,7 @@ class Followers {
|
|||
$items = array();
|
||||
|
||||
foreach ( $posts->get_posts() as $follower ) {
|
||||
$items[] = Follower::from_custom_post_type( $follower ); // phpcs:ignore
|
||||
$items[] = Follower::init_from_cpt( $follower ); // phpcs:ignore
|
||||
}
|
||||
|
||||
return $items;
|
||||
|
@ -472,7 +468,7 @@ class Followers {
|
|||
'posts_per_page' => $number,
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => 'errors',
|
||||
'key' => 'activitypub_errors',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
),
|
||||
|
@ -482,9 +478,40 @@ class Followers {
|
|||
$items = array();
|
||||
|
||||
foreach ( $posts->get_posts() as $follower ) {
|
||||
$items[] = Follower::from_custom_post_type( $follower ); // phpcs:ignore
|
||||
$items[] = Follower::init_from_cpt( $follower ); // phpcs:ignore
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to store errors that occur when
|
||||
* sending an ActivityPub message to a Follower.
|
||||
*
|
||||
* The error will be stored in the
|
||||
* post meta.
|
||||
*
|
||||
* @param int $post_id The ID of the WordPress Custom-Post-Type.
|
||||
* @param mixed $error The error message. Can be a string or a WP_Error.
|
||||
*
|
||||
* @return int|false The meta ID on success, false on failure.
|
||||
*/
|
||||
public static function add_error( $post_id, $error ) {
|
||||
if ( is_string( $error ) ) {
|
||||
$error_message = $error;
|
||||
} elseif ( is_wp_error( $error ) ) {
|
||||
$error_message = $error->get_error_message();
|
||||
} else {
|
||||
$error_message = __(
|
||||
'Unknown Error or misconfigured Error-Message',
|
||||
'activitypub'
|
||||
);
|
||||
}
|
||||
|
||||
return add_post_meta(
|
||||
$post_id,
|
||||
'activitypub_errors',
|
||||
$error_message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
187
includes/collection/class-users.php
Normal file
187
includes/collection/class-users.php
Normal file
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
namespace Activitypub\Collection;
|
||||
|
||||
use WP_Error;
|
||||
use WP_User_Query;
|
||||
use Activitypub\Model\User;
|
||||
use Activitypub\Model\Blog_User;
|
||||
use Activitypub\Model\Application_User;
|
||||
|
||||
use function Activitypub\is_user_disabled;
|
||||
|
||||
class Users {
|
||||
/**
|
||||
* The ID of the Blog User
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const BLOG_USER_ID = 0;
|
||||
|
||||
/**
|
||||
* The ID of the Application User
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const APPLICATION_USER_ID = -1;
|
||||
|
||||
/**
|
||||
* Get the User by ID
|
||||
*
|
||||
* @param int $user_id The User-ID.
|
||||
*
|
||||
* @return \Acitvitypub\Model\User The User.
|
||||
*/
|
||||
public static function get_by_id( $user_id ) {
|
||||
if ( is_string( $user_id ) || is_numeric( $user_id ) ) {
|
||||
$user_id = (int) $user_id;
|
||||
}
|
||||
|
||||
if ( is_user_disabled( $user_id ) ) {
|
||||
return new WP_Error(
|
||||
'activitypub_user_not_found',
|
||||
\__( 'User not found', 'activitypub' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( self::BLOG_USER_ID === $user_id ) {
|
||||
return Blog_User::from_wp_user( $user_id );
|
||||
} elseif ( self::APPLICATION_USER_ID === $user_id ) {
|
||||
return Application_User::from_wp_user( $user_id );
|
||||
} elseif ( $user_id > 0 ) {
|
||||
return User::from_wp_user( $user_id );
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'activitypub_user_not_found',
|
||||
\__( 'User not found', 'activitypub' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User by username.
|
||||
*
|
||||
* @param string $username The User-Name.
|
||||
*
|
||||
* @return \Acitvitypub\Model\User The User.
|
||||
*/
|
||||
public static function get_by_username( $username ) {
|
||||
// check for blog user.
|
||||
if ( Blog_User::get_default_username() === $username ) {
|
||||
return self::get_by_id( self::BLOG_USER_ID );
|
||||
}
|
||||
|
||||
if ( get_option( 'activitypub_blog_user_identifier' ) === $username ) {
|
||||
return self::get_by_id( self::BLOG_USER_ID );
|
||||
}
|
||||
|
||||
// check for application user.
|
||||
if ( 'application' === $username ) {
|
||||
return self::get_by_id( self::APPLICATION_USER_ID );
|
||||
}
|
||||
|
||||
// check for 'activitypub_username' meta
|
||||
$user = new WP_User_Query(
|
||||
array(
|
||||
'number' => 1,
|
||||
'hide_empty' => true,
|
||||
'fields' => 'ID',
|
||||
'meta_query' => array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => 'activitypub_user_identifier',
|
||||
'value' => $username,
|
||||
'compare' => 'LIKE',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( $user->results ) {
|
||||
return self::get_by_id( $user->results[0] );
|
||||
}
|
||||
|
||||
// check for login or nicename.
|
||||
$user = new WP_User_Query(
|
||||
array(
|
||||
'search' => $username,
|
||||
'search_columns' => array( 'user_login', 'user_nicename' ),
|
||||
'number' => 1,
|
||||
'hide_empty' => true,
|
||||
'fields' => 'ID',
|
||||
)
|
||||
);
|
||||
|
||||
if ( $user->results ) {
|
||||
return self::get_by_id( $user->results[0] );
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'activitypub_user_not_found',
|
||||
\__( 'User not found', 'activitypub' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User by resource.
|
||||
*
|
||||
* @param string $resource The User-Resource.
|
||||
*
|
||||
* @return \Acitvitypub\Model\User The User.
|
||||
*/
|
||||
public static function get_by_resource( $resource ) {
|
||||
if ( \strpos( $resource, '@' ) === false ) {
|
||||
return new WP_Error(
|
||||
'activitypub_unsupported_resource',
|
||||
\__( 'Resource is invalid', 'activitypub' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$resource = \str_replace( 'acct:', '', $resource );
|
||||
|
||||
$resource_identifier = \substr( $resource, 0, \strrpos( $resource, '@' ) );
|
||||
$resource_host = self::normalize_host( \substr( \strrchr( $resource, '@' ), 1 ) );
|
||||
$blog_host = self::normalize_host( \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) );
|
||||
|
||||
if ( $blog_host !== $resource_host ) {
|
||||
return new WP_Error(
|
||||
'activitypub_wrong_host',
|
||||
\__( 'Resource host does not match blog host', 'activitypub' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
return self::get_by_username( $resource_identifier );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User by resource.
|
||||
*
|
||||
* @param string $resource The User-Resource.
|
||||
*
|
||||
* @return \Acitvitypub\Model\User The User.
|
||||
*/
|
||||
public static function get_by_various( $id ) {
|
||||
if ( is_numeric( $id ) ) {
|
||||
return self::get_by_id( $id );
|
||||
} elseif ( filter_var( $id, FILTER_VALIDATE_URL ) ) {
|
||||
return self::get_by_resource( $id );
|
||||
} else {
|
||||
return self::get_by_username( $id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the host.
|
||||
*
|
||||
* @param string $host The host.
|
||||
*
|
||||
* @return string The normalized host.
|
||||
*/
|
||||
public static function normalize_host( $host ) {
|
||||
return \str_replace( 'www.', '', $host );
|
||||
}
|
||||
}
|
|
@ -1,43 +1,27 @@
|
|||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Http;
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Collection\Followers;
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub default JSON-context
|
||||
*
|
||||
* @return array the activitypub context
|
||||
*/
|
||||
function get_context() {
|
||||
$context = array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
array(
|
||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||
'PropertyValue' => 'schema:PropertyValue',
|
||||
'schema' => 'http://schema.org#',
|
||||
'pt' => 'https://joinpeertube.org/ns#',
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'value' => 'schema:value',
|
||||
'Hashtag' => 'as:Hashtag',
|
||||
'featured' => array(
|
||||
'@id' => 'toot:featured',
|
||||
'@type' => '@id',
|
||||
),
|
||||
'featuredTags' => array(
|
||||
'@id' => 'toot:featuredTags',
|
||||
'@type' => '@id',
|
||||
),
|
||||
),
|
||||
);
|
||||
$context = Activity::CONTEXT;
|
||||
|
||||
return \apply_filters( 'activitypub_json_context', $context );
|
||||
}
|
||||
|
||||
function safe_remote_post( $url, $body, $user_id ) {
|
||||
return \Activitypub\Http::post( $url, $body, $user_id );
|
||||
return Http::post( $url, $body, $user_id );
|
||||
}
|
||||
|
||||
function safe_remote_get( $url ) {
|
||||
return \Activitypub\Http::get( $url );
|
||||
return Http::get( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,9 +60,10 @@ function get_remote_metadata_by_actor( $actor, $cached = true ) {
|
|||
return $actor;
|
||||
}
|
||||
|
||||
$transient_key = 'activitypub_' . $actor;
|
||||
|
||||
// only check the cache if needed.
|
||||
if ( $cached ) {
|
||||
$transient_key = 'activitypub_' . $actor;
|
||||
$metadata = \get_transient( $transient_key );
|
||||
|
||||
if ( $metadata ) {
|
||||
|
@ -125,7 +110,7 @@ function get_remote_metadata_by_actor( $actor, $cached = true ) {
|
|||
* @return array The followers.
|
||||
*/
|
||||
function get_followers( $user_id ) {
|
||||
return Collection\Followers::get_followers( $user_id );
|
||||
return Followers::get_followers( $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,7 +121,7 @@ function get_followers( $user_id ) {
|
|||
* @return int The number of followers.
|
||||
*/
|
||||
function count_followers( $user_id ) {
|
||||
return Collection\Followers::count_followers( $user_id );
|
||||
return Followers::count_followers( $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -187,21 +172,6 @@ function url_to_authorid( $url ) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the custom Activity Pub description, if set, or default author description.
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
*
|
||||
* @return string The author description.
|
||||
*/
|
||||
function get_author_description( $user_id ) {
|
||||
$description = get_user_meta( $user_id, 'activitypub_user_description', true );
|
||||
if ( empty( $description ) ) {
|
||||
$description = get_user_meta( $user_id, 'description', true );
|
||||
}
|
||||
return \wpautop( \wp_kses( $description, 'default' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for Tombstone Objects
|
||||
*
|
||||
|
@ -271,7 +241,7 @@ function is_activitypub_request() {
|
|||
* ActivityPub requests are currently only made for
|
||||
* author archives, singular posts, and the homepage.
|
||||
*/
|
||||
if ( ! \is_author() && ! \is_singular() && ! \is_home() ) {
|
||||
if ( ! \is_author() && ! \is_singular() && ! \is_home() && ! defined( '\REST_REQUEST' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -302,3 +272,83 @@ function is_activitypub_request() {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if a user is disabled for ActivityPub.
|
||||
*
|
||||
* @param int $user_id The User-ID.
|
||||
*
|
||||
* @return boolean True if the user is disabled, false otherwise.
|
||||
*/
|
||||
function is_user_disabled( $user_id ) {
|
||||
$return = false;
|
||||
|
||||
switch ( $user_id ) {
|
||||
// if the user is the application user, it's always enabled.
|
||||
case \Activitypub\Collection\Users::APPLICATION_USER_ID:
|
||||
$return = false;
|
||||
break;
|
||||
// if the user is the blog user, it's only enabled in single-user mode.
|
||||
case \Activitypub\Collection\Users::BLOG_USER_ID:
|
||||
if ( defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) ) {
|
||||
$return = ACTIVITYPUB_DISABLE_BLOG_USER;
|
||||
break;
|
||||
}
|
||||
|
||||
$return = false;
|
||||
break;
|
||||
// if the user is any other user, it's enabled if it can publish posts.
|
||||
default:
|
||||
if ( ! \get_user_by( 'id', $user_id ) ) {
|
||||
$return = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( defined( 'ACTIVITYPUB_DISABLE_USER' ) ) {
|
||||
$return = ACTIVITYPUB_DISABLE_USER;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( ! \user_can( $user_id, 'publish_posts' ) ) {
|
||||
$return = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$return = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return apply_filters( 'activitypub_is_user_disabled', $return, $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the blog is in single-user mode.
|
||||
*
|
||||
* @return boolean True if the blog is in single-user mode, false otherwise.
|
||||
*/
|
||||
function is_single_user() {
|
||||
$return = false;
|
||||
|
||||
if (
|
||||
false === ACTIVITYPUB_DISABLE_BLOG_USER &&
|
||||
true === ACTIVITYPUB_DISABLE_USER
|
||||
) {
|
||||
$return = true;
|
||||
}
|
||||
|
||||
return apply_filters( 'activitypub_is_single_user', $return );
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_self_link' ) ) {
|
||||
/**
|
||||
* Returns the link for the currently displayed feed.
|
||||
*
|
||||
* @return string Correct link for the atom:self element.
|
||||
*/
|
||||
function get_self_link() {
|
||||
$host = wp_parse_url( home_url() );
|
||||
|
||||
return esc_url( apply_filters( 'self_link', set_url_scheme( 'http://' . $host['host'] . wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,241 +0,0 @@
|
|||
<?php
|
||||
namespace Activitypub\Model;
|
||||
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
|
||||
/**
|
||||
* ActivityPub Post Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/
|
||||
*/
|
||||
class Activity {
|
||||
/**
|
||||
* The JSON-LD context.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $context = array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
array(
|
||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||
'PropertyValue' => 'schema:PropertyValue',
|
||||
'schema' => 'http://schema.org#',
|
||||
'pt' => 'https://joinpeertube.org/ns#',
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'value' => 'schema:value',
|
||||
'Hashtag' => 'as:Hashtag',
|
||||
'featured' => array(
|
||||
'@id' => 'toot:featured',
|
||||
'@type' => '@id',
|
||||
),
|
||||
'featuredTags' => array(
|
||||
'@id' => 'toot:featuredTags',
|
||||
'@type' => '@id',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* The published date.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $published = '';
|
||||
|
||||
/**
|
||||
* The Activity-ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id = '';
|
||||
|
||||
/**
|
||||
* The Activity-Type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $type = 'Create';
|
||||
|
||||
/**
|
||||
* The Activity-Actor.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $actor = '';
|
||||
|
||||
/**
|
||||
* The Audience.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $to = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
|
||||
/**
|
||||
* The CC.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cc = array();
|
||||
|
||||
/**
|
||||
* The Activity-Object.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $object = null;
|
||||
|
||||
/**
|
||||
* The Class-Constructor.
|
||||
*
|
||||
* @param string $type The Activity-Type.
|
||||
* @param boolean $context The JSON-LD context.
|
||||
*/
|
||||
public function __construct( $type = 'Create', $context = true ) {
|
||||
if ( true !== $context ) {
|
||||
$this->context = null;
|
||||
}
|
||||
|
||||
$this->type = \ucfirst( $type );
|
||||
$this->published = \gmdate( 'Y-m-d\TH:i:s\Z', \time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic Getter/Setter
|
||||
*
|
||||
* @param string $method The method name.
|
||||
* @param string $params The method params.
|
||||
*
|
||||
* @return mixed The value.
|
||||
*/
|
||||
public function __call( $method, $params ) {
|
||||
$var = \strtolower( \substr( $method, 4 ) );
|
||||
|
||||
if ( \strncasecmp( $method, 'get', 3 ) === 0 ) {
|
||||
return $this->$var;
|
||||
}
|
||||
|
||||
if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
|
||||
if ( \strncasecmp( $method, 'add', 3 ) === 0 ) {
|
||||
if ( ! is_array( $this->$var ) ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
|
||||
if ( is_array( $params[0] ) ) {
|
||||
$this->$var = array_merge( $this->$var, $params[0] );
|
||||
} else {
|
||||
array_push( $this->$var, $params[0] );
|
||||
}
|
||||
|
||||
$this->$var = array_unique( $this->$var );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a Post-Object.
|
||||
*
|
||||
* @param Post $post The Post-Object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function from_post( Post $post ) {
|
||||
$this->object = $post->to_array();
|
||||
|
||||
if ( isset( $object['published'] ) ) {
|
||||
$this->published = $object['published'];
|
||||
}
|
||||
|
||||
$path = sprintf( 'users/%d/followers', intval( $post->get_post_author() ) );
|
||||
$this->add_to( get_rest_url_by_path( $path ) );
|
||||
|
||||
if ( isset( $this->object['attributedTo'] ) ) {
|
||||
$this->actor = $this->object['attributedTo'];
|
||||
}
|
||||
|
||||
foreach ( $post->get_tags() as $tag ) {
|
||||
if ( 'Mention' === $tag['type'] ) {
|
||||
$this->add_cc( $tag['href'] );
|
||||
}
|
||||
}
|
||||
|
||||
$type = \strtolower( $this->type );
|
||||
|
||||
if ( isset( $this->object['id'] ) ) {
|
||||
$this->id = add_query_arg( 'activity', $type, $this->object['id'] );
|
||||
}
|
||||
}
|
||||
|
||||
public function from_comment( $object ) {
|
||||
|
||||
}
|
||||
|
||||
public function to_comment() {
|
||||
|
||||
}
|
||||
|
||||
public function from_remote_array( $array ) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to an Array.
|
||||
*
|
||||
* @return array The Array.
|
||||
*/
|
||||
public function to_array() {
|
||||
$array = array_filter( \get_object_vars( $this ) );
|
||||
|
||||
if ( $this->context ) {
|
||||
$array = array( '@context' => $this->context ) + $array;
|
||||
}
|
||||
|
||||
unset( $array['context'] );
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to JSON
|
||||
*
|
||||
* @return string The JSON.
|
||||
*/
|
||||
public function to_json() {
|
||||
return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a Simple Array.
|
||||
*
|
||||
* @return string The array.
|
||||
*/
|
||||
public function to_simple_array() {
|
||||
$activity = array(
|
||||
'@context' => $this->context,
|
||||
'type' => $this->type,
|
||||
'actor' => $this->actor,
|
||||
'object' => $this->object,
|
||||
'to' => $this->to,
|
||||
'cc' => $this->cc,
|
||||
);
|
||||
|
||||
if ( $this->id ) {
|
||||
$activity['id'] = $this->id;
|
||||
}
|
||||
|
||||
return $activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a Simple JSON.
|
||||
*
|
||||
* @return string The JSON.
|
||||
*/
|
||||
public function to_simple_json() {
|
||||
return \wp_json_encode( $this->to_simple_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
|
||||
}
|
||||
}
|
101
includes/model/class-application-user.php
Normal file
101
includes/model/class-application-user.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
namespace Activitypub\Model;
|
||||
|
||||
use WP_Query;
|
||||
use Activitypub\Signature;
|
||||
use Activitypub\Collection\Users;
|
||||
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
|
||||
class Application_User extends Blog_User {
|
||||
/**
|
||||
* The User-ID
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_id = Users::APPLICATION_USER_ID; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* The User-Type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Application';
|
||||
|
||||
/**
|
||||
* Get the User-Url.
|
||||
*
|
||||
* @return string The User-Url.
|
||||
*/
|
||||
public function get_url() {
|
||||
return get_rest_url_by_path( 'application' );
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return 'application';
|
||||
}
|
||||
|
||||
public function get_username() {
|
||||
return $this::get_name();
|
||||
}
|
||||
|
||||
public function get__public_key() {
|
||||
$key = \get_option( 'activitypub_application_user_public_key' );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
$key = \get_option( 'activitypub_application_user_public_key' );
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get__private_key() {
|
||||
$key = \get_option( 'activitypub_application_user_private_key' );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
return \get_option( 'activitypub_application_user_private_key' );
|
||||
}
|
||||
|
||||
private function generate_key_pair() {
|
||||
$key_pair = Signature::generate_key_pair();
|
||||
|
||||
if ( ! is_wp_error( $key_pair ) ) {
|
||||
\update_option( 'activitypub_application_user_public_key', $key_pair['public_key'] );
|
||||
\update_option( 'activitypub_application_user_private_key', $key_pair['private_key'] );
|
||||
}
|
||||
}
|
||||
|
||||
public function get_inbox() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_outbox() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_followers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_following() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_attachment() {
|
||||
return array();
|
||||
}
|
||||
}
|
222
includes/model/class-blog-user.php
Normal file
222
includes/model/class-blog-user.php
Normal file
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
namespace Activitypub\Model;
|
||||
|
||||
use WP_Query;
|
||||
use Activitypub\Signature;
|
||||
use Activitypub\Collection\Users;
|
||||
|
||||
use function Activitypub\is_single_user;
|
||||
use function Activitypub\is_user_disabled;
|
||||
|
||||
class Blog_User extends User {
|
||||
/**
|
||||
* The User-ID
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_id = Users::BLOG_USER_ID; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* The User-Type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Group';
|
||||
|
||||
public static function from_wp_user( $user_id ) {
|
||||
if ( is_user_disabled( $user_id ) ) {
|
||||
return new WP_Error(
|
||||
'activitypub_user_not_found',
|
||||
\__( 'User not found', 'activitypub' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$object = new static();
|
||||
$object->_id = $user_id;
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Name.
|
||||
*
|
||||
* @return string The User-Name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return \esc_html( \get_bloginfo( 'name' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Description.
|
||||
*
|
||||
* @return string The User-Description.
|
||||
*/
|
||||
public function get_summary() {
|
||||
return \wpautop(
|
||||
\wp_kses(
|
||||
\get_bloginfo( 'description' ),
|
||||
'default'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Url.
|
||||
*
|
||||
* @return string The User-Url.
|
||||
*/
|
||||
public function get_url() {
|
||||
return \esc_url( \trailingslashit( get_home_url() ) . '@' . $this->get_preferred_username() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a default Username.
|
||||
*
|
||||
* @return string The auto-generated Username.
|
||||
*/
|
||||
public static function get_default_username() {
|
||||
// check if domain host has a subdomain
|
||||
$host = \wp_parse_url( \get_home_url(), \PHP_URL_HOST );
|
||||
$host = \preg_replace( '/^www\./i', '', $host );
|
||||
|
||||
/**
|
||||
* Filter the default blog username.
|
||||
*
|
||||
* @param string $host The default username.
|
||||
*/
|
||||
return apply_filters( 'activitypub_default_blog_username', $host );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the preferred User-Name.
|
||||
*
|
||||
* @return string The User-Name.
|
||||
*/
|
||||
public function get_preferred_username() {
|
||||
$username = \get_option( 'activitypub_blog_user_identifier' );
|
||||
|
||||
if ( $username ) {
|
||||
return $username;
|
||||
}
|
||||
|
||||
return self::get_default_username();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Icon.
|
||||
*
|
||||
* @return array|null The User-Icon.
|
||||
*/
|
||||
public function get_icon() {
|
||||
$image = wp_get_attachment_image_src( get_theme_mod( 'custom_logo' ) );
|
||||
|
||||
if ( $image ) {
|
||||
return array(
|
||||
'type' => 'Image',
|
||||
'url' => esc_url( $image[0] ),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Header-Image.
|
||||
*
|
||||
* @return array|null The User-Header-Image.
|
||||
*/
|
||||
public function get_header_image() {
|
||||
if ( \has_header_image() ) {
|
||||
return array(
|
||||
'type' => 'Image',
|
||||
'url' => esc_url( \get_header_image() ),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_published() {
|
||||
$first_post = new WP_Query(
|
||||
array(
|
||||
'orderby' => 'date',
|
||||
'order' => 'ASC',
|
||||
'number' => 1,
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! empty( $first_post->posts[0] ) ) {
|
||||
$time = \strtotime( $first_post->posts[0]->post_date_gmt );
|
||||
} else {
|
||||
$time = \time();
|
||||
}
|
||||
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', $time );
|
||||
}
|
||||
|
||||
public function get__public_key() {
|
||||
$key = \get_option( 'activitypub_blog_user_public_key' );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
$key = \get_option( 'activitypub_blog_user_public_key' );
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Private-Key.
|
||||
*
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get__private_key() {
|
||||
$key = \get_option( 'activitypub_blog_user_private_key' );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
return \get_option( 'activitypub_blog_user_private_key' );
|
||||
}
|
||||
|
||||
private function generate_key_pair() {
|
||||
$key_pair = Signature::generate_key_pair();
|
||||
|
||||
if ( ! is_wp_error( $key_pair ) ) {
|
||||
\update_option( 'activitypub_blog_user_public_key', $key_pair['public_key'] );
|
||||
\update_option( 'activitypub_blog_user_private_key', $key_pair['private_key'] );
|
||||
}
|
||||
}
|
||||
|
||||
public function get_attachment() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function get_canonical_url() {
|
||||
return \home_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of the object.
|
||||
*
|
||||
* If the Blog is in "single user" mode, return "Person" insted of "Group".
|
||||
*
|
||||
* @return string The type of the object.
|
||||
*/
|
||||
public function get_type() {
|
||||
if ( is_single_user() ) {
|
||||
return 'Person';
|
||||
} else {
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,69 +24,29 @@ class Follower extends Actor {
|
|||
*/
|
||||
protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* The complete Remote-Profile of the Follower
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_shared_inbox; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* The complete Remote-Profile of the Follower
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_actor; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* The latest received error.
|
||||
*
|
||||
* This will only temporary and will saved to $this->errors
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_error; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* A list of errors
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_errors; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* Magic function to return the Actor-URL when the Object is used as a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->get_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new Error
|
||||
*
|
||||
* @param mixed $error The latest HTTP-Error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_error( $error ) {
|
||||
$this->_errors = array();
|
||||
$this->_error = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the errors.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_errors() {
|
||||
if ( $this->_errors ) {
|
||||
return $this->_errors;
|
||||
return get_post_meta( $this->_id, 'activitypub_errors' );
|
||||
}
|
||||
|
||||
$this->_errors = get_post_meta( $this->_id, 'errors' );
|
||||
return $this->_errors;
|
||||
/**
|
||||
* Getter for URL attribute.
|
||||
*
|
||||
* Falls back to ID, if no URL is set. This is relevant for
|
||||
* Plattforms like Lemmy, where the ID is the URL.
|
||||
*
|
||||
* @return string The URL.
|
||||
*/
|
||||
public function get_url() {
|
||||
if ( $this->url ) {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,7 +55,7 @@ class Follower extends Actor {
|
|||
* @return void
|
||||
*/
|
||||
public function reset_errors() {
|
||||
delete_post_meta( $this->_id, 'errors' );
|
||||
delete_post_meta( $this->_id, 'activitypub_errors' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,7 +64,7 @@ class Follower extends Actor {
|
|||
* @return int The number of errors.
|
||||
*/
|
||||
public function count_errors() {
|
||||
$errors = $this->get__errors();
|
||||
$errors = $this->get_errors();
|
||||
|
||||
if ( is_array( $errors ) && ! empty( $errors ) ) {
|
||||
return count( $errors );
|
||||
|
@ -113,21 +73,13 @@ class Follower extends Actor {
|
|||
return 0;
|
||||
}
|
||||
|
||||
public function get_url() {
|
||||
if ( ! $this->url ) {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the latest error message.
|
||||
*
|
||||
* @return string The error message.
|
||||
*/
|
||||
public function get_latest_error_message() {
|
||||
$errors = $this->get__errors();
|
||||
$errors = $this->get_errors();
|
||||
|
||||
if ( is_array( $errors ) && ! empty( $errors ) ) {
|
||||
return reset( $errors );
|
||||
|
@ -151,13 +103,30 @@ class Follower extends Actor {
|
|||
* @return void
|
||||
*/
|
||||
public function save() {
|
||||
if ( ! $this->get__id() ) {
|
||||
global $wpdb;
|
||||
|
||||
$post_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
|
||||
esc_sql( $this->get_id() )
|
||||
)
|
||||
);
|
||||
|
||||
if ( $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
$this->set__id( $post->ID );
|
||||
}
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'ID' => $this->get__id(),
|
||||
'guid' => $this->get_id(),
|
||||
'post_title' => $this->get_name(),
|
||||
'guid' => esc_url_raw( $this->get_id() ),
|
||||
'post_title' => esc_html( $this->get_name() ),
|
||||
'post_author' => 0,
|
||||
'post_type' => Followers::POST_TYPE,
|
||||
'post_content' => $this->get_summary(),
|
||||
'post_name' => esc_url_raw( $this->get_id() ),
|
||||
'post_excerpt' => esc_html( $this->get_summary() ) ? $this->get_summary() : '',
|
||||
'post_status' => 'publish',
|
||||
'meta_input' => $this->get_post_meta_input(),
|
||||
);
|
||||
|
@ -172,16 +141,17 @@ class Follower extends Actor {
|
|||
* @return void
|
||||
*/
|
||||
public function upsert() {
|
||||
if ( $this->_id ) {
|
||||
$this->update();
|
||||
} else {
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the current Follower-Object.
|
||||
*
|
||||
* Beware that this os deleting a Follower for ALL users!!!
|
||||
*
|
||||
* To delete only the User connection (unfollow)
|
||||
* @see \Activitypub\Rest\Followers::remove_follower()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete() {
|
||||
|
@ -194,27 +164,9 @@ class Follower extends Actor {
|
|||
* @return void
|
||||
*/
|
||||
protected function get_post_meta_input() {
|
||||
$attributes = array( 'inbox', '_shared_inbox', 'icon', 'preferred_username', '_actor', 'url' );
|
||||
|
||||
$meta_input = array();
|
||||
|
||||
foreach ( $attributes as $attribute ) {
|
||||
if ( $this->get( $attribute ) ) {
|
||||
$meta_input[ $attribute ] = $this->get( $attribute );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->_error ) {
|
||||
if ( is_string( $this->_error ) ) {
|
||||
$_error = $this->_error;
|
||||
} elseif ( is_wp_error( $this->_error ) ) {
|
||||
$_error = $this->_error->get_error_message();
|
||||
} else {
|
||||
$_error = __( 'Unknown Error or misconfigured Error-Message', 'activitypub' );
|
||||
}
|
||||
|
||||
$meta_input['_errors'] = $_error;
|
||||
}
|
||||
$meta_input['activitypub_inbox'] = $this->get_shared_inbox();
|
||||
$meta_input['activitypub_actor_json'] = $this->to_json();
|
||||
|
||||
return $meta_input;
|
||||
}
|
||||
|
@ -239,37 +191,18 @@ class Follower extends Actor {
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts an ActivityPub Array to an Follower Object.
|
||||
* Get the shared inbox, with a fallback to the inbox.
|
||||
*
|
||||
* @param array $array The ActivityPub Array.
|
||||
*
|
||||
* @return Activitypub\Model\Follower The Follower Object.
|
||||
* @return string|null The URL to the shared inbox, the inbox or null.
|
||||
*/
|
||||
public static function from_array( $array ) {
|
||||
$object = parent::from_array( $array );
|
||||
$object->set__actor( $array );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$post_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
|
||||
esc_sql( $object->get_id() )
|
||||
)
|
||||
);
|
||||
|
||||
if ( $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
$object->set__id( $post->ID );
|
||||
public function get_shared_inbox() {
|
||||
if ( ! empty( $this->get_endpoints()['sharedInbox'] ) ) {
|
||||
return $this->get_endpoints()['sharedInbox'];
|
||||
} elseif ( ! empty( $this->get_inbox() ) ) {
|
||||
return $this->get_inbox();
|
||||
}
|
||||
|
||||
if ( ! empty( $object->get_endpoints()['sharedInbox'] ) ) {
|
||||
$object->_shared_inbox = $object->get_endpoints()['sharedInbox'];
|
||||
} elseif ( ! empty( $object->get_inbox() ) ) {
|
||||
$object->_shared_inbox = $object->get_inbox();
|
||||
}
|
||||
|
||||
return $object;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -279,16 +212,11 @@ class Follower extends Actor {
|
|||
*
|
||||
* @return array Activitypub\Model\Follower
|
||||
*/
|
||||
public static function from_custom_post_type( $post ) {
|
||||
$object = new static();
|
||||
|
||||
public static function init_from_cpt( $post ) {
|
||||
$actor_json = get_post_meta( $post->ID, 'activitypub_actor_json', true );
|
||||
$object = self::init_from_json( $actor_json );
|
||||
$object->set__id( $post->ID );
|
||||
$object->set_id( $post->guid );
|
||||
$object->set_name( $post->post_title );
|
||||
$object->set_summary( $post->post_content );
|
||||
$object->set_url( get_post_meta( $post->ID, 'url', true ) );
|
||||
$object->set_icon( get_post_meta( $post->ID, 'icon', true ) );
|
||||
$object->set_preferred_username( get_post_meta( $post->ID, 'preferred_username', true ) );
|
||||
$object->set_published( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_published ) ) );
|
||||
$object->set_updated( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) ) );
|
||||
|
||||
|
|
|
@ -1,23 +1,258 @@
|
|||
<?php
|
||||
namespace Activitypub\Model;
|
||||
|
||||
/**
|
||||
* ActivityPub User Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class User {
|
||||
use WP_Query;
|
||||
use WP_Error;
|
||||
use Activitypub\Signature;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Activity\Actor;
|
||||
|
||||
use function Activitypub\is_user_disabled;
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
|
||||
class User extends Actor {
|
||||
/**
|
||||
* The ID of the Blog User
|
||||
* The local User-ID (WP_User).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const BLOG_USER_ID = 0;
|
||||
protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* The ID of the Application User
|
||||
* The User-Type
|
||||
*
|
||||
* @var int
|
||||
* @var string
|
||||
*/
|
||||
const APPLICATION_USER_ID = -1;
|
||||
protected $type = 'Person';
|
||||
|
||||
public static function from_wp_user( $user_id ) {
|
||||
if ( is_user_disabled( $user_id ) ) {
|
||||
return new WP_Error(
|
||||
'activitypub_user_not_found',
|
||||
\__( 'User not found', 'activitypub' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$object = new static();
|
||||
$object->_id = $user_id;
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-ID.
|
||||
*
|
||||
* @return string The User-ID.
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->get_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Name.
|
||||
*
|
||||
* @return string The User-Name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return \esc_attr( \get_the_author_meta( 'display_name', $this->_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Description.
|
||||
*
|
||||
* @return string The User-Description.
|
||||
*/
|
||||
public function get_summary() {
|
||||
$description = get_user_meta( $this->_id, 'activitypub_user_description', true );
|
||||
if ( empty( $description ) ) {
|
||||
$description = get_user_meta( $this->_id, 'description', true );
|
||||
}
|
||||
return \wpautop( \wp_kses( $description, 'default' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Url.
|
||||
*
|
||||
* @return string The User-Url.
|
||||
*/
|
||||
public function get_url() {
|
||||
return \esc_url( \get_author_posts_url( $this->_id ) );
|
||||
}
|
||||
|
||||
public function get_at_url() {
|
||||
return \esc_url( \trailingslashit( get_home_url() ) . '@' . $this->get_username() );
|
||||
}
|
||||
|
||||
public function get_preferred_username() {
|
||||
return \esc_attr( \get_the_author_meta( 'login', $this->_id ) );
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
$icon = \esc_url(
|
||||
\get_avatar_url(
|
||||
$this->_id,
|
||||
array( 'size' => 120 )
|
||||
)
|
||||
);
|
||||
|
||||
return array(
|
||||
'type' => 'Image',
|
||||
'url' => $icon,
|
||||
);
|
||||
}
|
||||
|
||||
public function get_image() {
|
||||
if ( \has_header_image() ) {
|
||||
$image = \esc_url( \get_header_image() );
|
||||
return array(
|
||||
'type' => 'Image',
|
||||
'url' => $image,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_published() {
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( \get_the_author_meta( 'registered', $this->_id ) ) );
|
||||
}
|
||||
|
||||
public function get_public_key() {
|
||||
return array(
|
||||
'id' => $this->get_id() . '#main-key',
|
||||
'owner' => $this->get_id(),
|
||||
'publicKeyPem' => $this->get__public_key(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $this->get__id()
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get__public_key() {
|
||||
$key = \get_user_meta( $this->get__id(), 'magic_sig_public_key', true );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
return \get_user_meta( $this->get__id(), 'magic_sig_public_key', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $this->get__id()
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get__private_key() {
|
||||
$key = \get_user_meta( $this->get__id(), 'magic_sig_private_key', true );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
return \get_user_meta( $this->get__id(), 'magic_sig_private_key', true );
|
||||
}
|
||||
|
||||
private function generate_key_pair() {
|
||||
$key_pair = Signature::generate_key_pair();
|
||||
|
||||
if ( ! is_wp_error( $key_pair ) ) {
|
||||
\update_user_meta( $this->get__id(), 'magic_sig_public_key', $key_pair['public_key'], true );
|
||||
\update_user_meta( $this->get__id(), 'magic_sig_private_key', $key_pair['private_key'], true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Inbox-API-Endpoint.
|
||||
*
|
||||
* @return string The Inbox-Endpoint.
|
||||
*/
|
||||
public function get_inbox() {
|
||||
return get_rest_url_by_path( sprintf( 'users/%d/inbox', $this->get__id() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Outbox-API-Endpoint.
|
||||
*
|
||||
* @return string The Outbox-Endpoint.
|
||||
*/
|
||||
public function get_outbox() {
|
||||
return get_rest_url_by_path( sprintf( 'users/%d/outbox', $this->get__id() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Followers-API-Endpoint.
|
||||
*
|
||||
* @return string The Followers-Endpoint.
|
||||
*/
|
||||
public function get_followers() {
|
||||
return get_rest_url_by_path( sprintf( 'users/%d/followers', $this->get__id() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Following-API-Endpoint.
|
||||
*
|
||||
* @return string The Following-Endpoint.
|
||||
*/
|
||||
public function get_following() {
|
||||
return get_rest_url_by_path( sprintf( 'users/%d/following', $this->get__id() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the User-Output with Attachments.
|
||||
*
|
||||
* @return array The extended User-Output.
|
||||
*/
|
||||
public function get_attachment() {
|
||||
$array = array();
|
||||
|
||||
$array[] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => \__( 'Blog', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \home_url( '/' ) ) . '" target="_blank" href="' . \home_url( '/' ) . '">' . \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) . '</a>',
|
||||
\ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
|
||||
$array[] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => \__( 'Profile', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \get_author_posts_url( $this->get__id() ) ) . '" target="_blank" href="' . \get_author_posts_url( $this->get__id() ) . '">' . \wp_parse_url( \get_author_posts_url( $this->get__id() ), \PHP_URL_HOST ) . '</a>',
|
||||
\ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
|
||||
if ( \get_the_author_meta( 'user_url', $this->get__id() ) ) {
|
||||
$array[] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => \__( 'Website', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \get_the_author_meta( 'user_url', $this->get__id() ) ) . '" target="_blank" href="' . \get_the_author_meta( 'user_url', $this->get__id() ) . '">' . \wp_parse_url( \get_the_author_meta( 'user_url', $this->get__id() ), \PHP_URL_HOST ) . '</a>',
|
||||
\ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function get_resource() {
|
||||
return $this->get_preferred_username() . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
|
||||
}
|
||||
|
||||
public function get_canonical_url() {
|
||||
return $this->get_url();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
<?php
|
||||
namespace Activitypub\Peer;
|
||||
|
||||
/**
|
||||
* ActivityPub Users DB-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Users {
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function get_user_by_various( $data ) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine a url and try to determine the author ID it represents.
|
||||
*
|
||||
* Checks are supposedly from the hosted site blog.
|
||||
*
|
||||
* @param string $url Permalink to check.
|
||||
*
|
||||
* @return int User ID, or 0 on failure.
|
||||
*/
|
||||
public static function url_to_authorid( $url ) {
|
||||
global $wp_rewrite;
|
||||
|
||||
// check if url hase the same host
|
||||
if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) !== \wp_parse_url( $url, \PHP_URL_HOST ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// first, check to see if there is a 'author=N' to match against
|
||||
if ( \preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) {
|
||||
$id = \absint( $values[1] );
|
||||
if ( $id ) {
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if we are using rewrite rules
|
||||
$rewrite = $wp_rewrite->wp_rewrite_rules();
|
||||
|
||||
// not using rewrite rules, and 'author=N' method failed, so we're out of options
|
||||
if ( empty( $rewrite ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// generate rewrite rule for the author url
|
||||
$author_rewrite = $wp_rewrite->get_author_permastruct();
|
||||
$author_regexp = \str_replace( '%author%', '', $author_rewrite );
|
||||
|
||||
// match the rewrite rule with the passed url
|
||||
if ( \preg_match( '/https?:\/\/(.+)' . \preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) {
|
||||
$user = \get_user_by( 'slug', $match[2] );
|
||||
if ( $user ) {
|
||||
return $user->ID;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@ use WP_Error;
|
|||
use stdClass;
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Response;
|
||||
use Activitypub\Collection\Followers as FollowerCollection;
|
||||
use Activitypub\Collection\Users as User_Collection;
|
||||
use Activitypub\Collection\Followers as Follower_Collection;
|
||||
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
|
||||
|
@ -30,7 +31,7 @@ class Followers {
|
|||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
ACTIVITYPUB_REST_NAMESPACE,
|
||||
'/users/(?P<user_id>\d+)/followers',
|
||||
'/users/(?P<user_id>[\w\-\.]+)/followers',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
|
@ -51,21 +52,14 @@ class Followers {
|
|||
*/
|
||||
public static function get( $request ) {
|
||||
$user_id = $request->get_param( 'user_id' );
|
||||
$user = \get_user_by( 'ID', $user_id );
|
||||
$user = User_Collection::get_by_various( $user_id );
|
||||
|
||||
if ( ! $user ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
\__( 'User not found', 'activitypub' ),
|
||||
array(
|
||||
'status' => 404,
|
||||
'params' => array(
|
||||
'user_id' => \__( 'User not found', 'activitypub' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$page = $request->get_param( 'page', 1 );
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
*/
|
||||
|
@ -77,18 +71,29 @@ class Followers {
|
|||
|
||||
$json->id = \home_url( \add_query_arg( null, null ) );
|
||||
$json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
|
||||
$json->actor = \get_author_posts_url( $user_id );
|
||||
$json->actor = $user->get_id();
|
||||
$json->type = 'OrderedCollectionPage';
|
||||
|
||||
$json->partOf = get_rest_url_by_path( sprintf( 'users/%d/followers', $user_id ) ); // phpcs:ignore
|
||||
$json->first = $json->partOf; // phpcs:ignore
|
||||
$json->totalItems = FollowerCollection::count_followers( $user_id ); // phpcs:ignore
|
||||
$json->totalItems = Follower_Collection::count_followers( $user->get__id() ); // phpcs:ignore
|
||||
$json->partOf = get_rest_url_by_path( sprintf( 'users/%d/followers', $user->get__id() ) ); // phpcs:ignore
|
||||
|
||||
$json->first = \add_query_arg( 'page', 1, $json->partOf ); // phpcs:ignore
|
||||
$json->last = \add_query_arg( 'page', \ceil ( $json->totalItems / 20 ), $json->partOf ); // phpcs:ignore
|
||||
|
||||
if ( $page && ( ( \ceil ( $json->totalItems / 20 ) ) > $page ) ) { // phpcs:ignore
|
||||
$json->next = \add_query_arg( 'page', $page + 1, $json->partOf ); // phpcs:ignore
|
||||
}
|
||||
|
||||
if ( $page && ( $page > 1 ) ) { // phpcs:ignore
|
||||
$json->prev = \add_query_arg( 'page', $page - 1, $json->partOf ); // phpcs:ignore
|
||||
}
|
||||
|
||||
// phpcs:ignore
|
||||
$json->orderedItems = array_map(
|
||||
function( $item ) {
|
||||
return $item->get_url();
|
||||
},
|
||||
FollowerCollection::get_followers( $user_id )
|
||||
Follower_Collection::get_followers( $user->get__id(), 20, $page )
|
||||
);
|
||||
|
||||
$response = new WP_REST_Response( $json, 200 );
|
||||
|
@ -107,14 +112,12 @@ class Followers {
|
|||
|
||||
$params['page'] = array(
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
);
|
||||
|
||||
$params['user_id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'validate_callback' => function( $param, $request, $key ) {
|
||||
return user_can( $param, 'publish_posts' );
|
||||
},
|
||||
'type' => 'string',
|
||||
);
|
||||
|
||||
return $params;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
use Activitypub\Collection\Users as User_Collection;
|
||||
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
|
||||
/**
|
||||
|
@ -24,7 +26,7 @@ class Following {
|
|||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
ACTIVITYPUB_REST_NAMESPACE,
|
||||
'/users/(?P<user_id>\d+)/following',
|
||||
'/users/(?P<user_id>[\w\-\.]+)/following',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
|
@ -45,19 +47,10 @@ class Following {
|
|||
*/
|
||||
public static function get( $request ) {
|
||||
$user_id = $request->get_param( 'user_id' );
|
||||
$user = \get_user_by( 'ID', $user_id );
|
||||
$user = User_Collection::get_by_various( $user_id );
|
||||
|
||||
if ( ! $user ) {
|
||||
return new \WP_Error(
|
||||
'rest_invalid_param',
|
||||
\__( 'User not found', 'activitypub' ),
|
||||
array(
|
||||
'status' => 404,
|
||||
'params' => array(
|
||||
'user_id' => \__( 'User not found', 'activitypub' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -71,10 +64,10 @@ class Following {
|
|||
|
||||
$json->id = \home_url( \add_query_arg( null, null ) );
|
||||
$json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
|
||||
$json->actor = \get_author_posts_url( $user_id );
|
||||
$json->actor = $user->get_id();
|
||||
$json->type = 'OrderedCollectionPage';
|
||||
|
||||
$json->partOf = get_rest_url_by_path( sprintf( 'users/%d/following', $user_id ) ); // phpcs:ignore
|
||||
$json->partOf = get_rest_url_by_path( sprintf( 'users/%d/following', $user->get__id() ) ); // phpcs:ignore
|
||||
$json->totalItems = 0; // phpcs:ignore
|
||||
$json->orderedItems = apply_filters( 'activitypub_following', array(), $user ); // phpcs:ignore
|
||||
|
||||
|
@ -100,10 +93,7 @@ class Following {
|
|||
|
||||
$params['user_id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'validate_callback' => function( $param, $request, $key ) {
|
||||
return user_can( $param, 'publish_posts' );
|
||||
},
|
||||
'type' => 'string',
|
||||
);
|
||||
|
||||
return $params;
|
||||
|
|
|
@ -4,8 +4,8 @@ namespace Activitypub\Rest;
|
|||
use WP_Error;
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Response;
|
||||
use Activitypub\Signature;
|
||||
use Activitypub\Model\Activity;
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Collection\Users as User_Collection;
|
||||
|
||||
use function Activitypub\get_context;
|
||||
use function Activitypub\url_to_authorid;
|
||||
|
@ -48,7 +48,7 @@ class Inbox {
|
|||
|
||||
\register_rest_route(
|
||||
ACTIVITYPUB_REST_NAMESPACE,
|
||||
'/users/(?P<user_id>\d+)/inbox',
|
||||
'/users/(?P<user_id>[\w\-\.]+)/inbox',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
|
@ -74,6 +74,12 @@ class Inbox {
|
|||
*/
|
||||
public static function user_inbox_get( $request ) {
|
||||
$user_id = $request->get_param( 'user_id' );
|
||||
$user = User_Collection::get_by_various( $user_id );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$page = $request->get_param( 'page', 0 );
|
||||
|
||||
/*
|
||||
|
@ -87,7 +93,7 @@ class Inbox {
|
|||
$json->id = \home_url( \add_query_arg( null, null ) );
|
||||
$json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
|
||||
$json->type = 'OrderedCollectionPage';
|
||||
$json->partOf = get_rest_url_by_path( sprintf( 'users/%d/inbox', $user_id ) ); // phpcs:ignore
|
||||
$json->partOf = get_rest_url_by_path( sprintf( 'users/%d/inbox', $user->get__id() ) ); // phpcs:ignore
|
||||
|
||||
$json->totalItems = 0; // phpcs:ignore
|
||||
|
||||
|
@ -118,15 +124,19 @@ class Inbox {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function user_inbox_post( $request ) {
|
||||
|
||||
$user_id = $request->get_param( 'user_id' );
|
||||
$user = User_Collection::get_by_various( $user_id );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$data = $request->get_params();
|
||||
$type = $request->get_param( 'type' );
|
||||
$type = \strtolower( $type );
|
||||
|
||||
\do_action( 'activitypub_inbox', $data, $user_id, $type );
|
||||
\do_action( "activitypub_inbox_{$type}", $data, $user_id );
|
||||
\do_action( 'activitypub_inbox', $data, $user->get__id(), $type );
|
||||
\do_action( "activitypub_inbox_{$type}", $data, $user->get__id() );
|
||||
|
||||
return new WP_REST_Response( array(), 202 );
|
||||
}
|
||||
|
@ -139,7 +149,6 @@ class Inbox {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function shared_inbox_post( $request ) {
|
||||
|
||||
$data = $request->get_params();
|
||||
$type = $request->get_param( 'type' );
|
||||
$users = self::extract_recipients( $data );
|
||||
|
@ -185,10 +194,7 @@ class Inbox {
|
|||
|
||||
$params['user_id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'validate_callback' => function( $param, $request, $key ) {
|
||||
return user_can( $param, 'publish_posts' );
|
||||
},
|
||||
'type' => 'string',
|
||||
);
|
||||
|
||||
return $params;
|
||||
|
@ -208,10 +214,7 @@ class Inbox {
|
|||
|
||||
$params['user_id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'validate_callback' => function( $param, $request, $key ) {
|
||||
return user_can( $param, 'publish_posts' );
|
||||
},
|
||||
'type' => 'string',
|
||||
);
|
||||
|
||||
$params['id'] = array(
|
||||
|
|
|
@ -5,8 +5,9 @@ use stdClass;
|
|||
use WP_Error;
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Response;
|
||||
use Activitypub\Model\Post;
|
||||
use Activitypub\Model\Activity;
|
||||
use Activitypub\Transformer\Post;
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Collection\Users as User_Collection;
|
||||
|
||||
use function Activitypub\get_context;
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
|
@ -32,7 +33,7 @@ class Outbox {
|
|||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
ACTIVITYPUB_REST_NAMESPACE,
|
||||
'/users/(?P<user_id>\d+)/outbox',
|
||||
'/users/(?P<user_id>[\w\-\.]+)/outbox',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
|
@ -52,23 +53,15 @@ class Outbox {
|
|||
*/
|
||||
public static function user_outbox_get( $request ) {
|
||||
$user_id = $request->get_param( 'user_id' );
|
||||
$author = \get_user_by( 'ID', $user_id );
|
||||
$post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) );
|
||||
$user = User_Collection::get_by_various( $user_id );
|
||||
|
||||
if ( ! $author ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
\__( 'User not found', 'activitypub' ),
|
||||
array(
|
||||
'status' => 404,
|
||||
'params' => array(
|
||||
'user_id' => \__( 'User not found', 'activitypub' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$page = $request->get_param( 'page', 0 );
|
||||
$post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) );
|
||||
|
||||
$page = $request->get_param( 'page', 1 );
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
|
@ -80,14 +73,11 @@ class Outbox {
|
|||
$json->{'@context'} = get_context();
|
||||
$json->id = \home_url( \add_query_arg( null, null ) );
|
||||
$json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
|
||||
$json->actor = \get_author_posts_url( $user_id );
|
||||
$json->actor = $user->get_id();
|
||||
$json->type = 'OrderedCollectionPage';
|
||||
$json->partOf = get_rest_url_by_path( sprintf( 'users/%d/outbox', $user_id ) ); // phpcs:ignore
|
||||
$json->totalItems = 0; // phpcs:ignore
|
||||
|
||||
// phpcs:ignore
|
||||
$json->totalItems = 0;
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
$count_posts = \wp_count_posts( $post_type );
|
||||
$json->totalItems += \intval( $count_posts->publish ); // phpcs:ignore
|
||||
|
@ -100,22 +90,28 @@ class Outbox {
|
|||
$json->next = \add_query_arg( 'page', $page + 1, $json->partOf ); // phpcs:ignore
|
||||
}
|
||||
|
||||
if ( $page && ( $page > 1 ) ) { // phpcs:ignore
|
||||
$json->prev = \add_query_arg( 'page', $page - 1, $json->partOf ); // phpcs:ignore
|
||||
}
|
||||
|
||||
if ( $page ) {
|
||||
$posts = \get_posts(
|
||||
array(
|
||||
'posts_per_page' => 10,
|
||||
'author' => $user_id,
|
||||
'offset' => ( $page - 1 ) * 10,
|
||||
'paged' => $page,
|
||||
'post_type' => $post_types,
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
$activitypub_post = new Post( $post );
|
||||
$activitypub_activity = new Activity( 'Create', false );
|
||||
$post = Post::transform( $post )->to_object();
|
||||
$activity = new Activity();
|
||||
$activity->set_type( 'Create' );
|
||||
$activity->set_context( null );
|
||||
$activity->set_object( $post );
|
||||
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
$json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore
|
||||
$json->orderedItems[] = $activity->to_array(); // phpcs:ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,14 +140,12 @@ class Outbox {
|
|||
|
||||
$params['page'] = array(
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
);
|
||||
|
||||
$params['user_id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'validate_callback' => function( $param, $request, $key ) {
|
||||
return user_can( $param, 'publish_posts' );
|
||||
},
|
||||
'type' => 'string',
|
||||
);
|
||||
|
||||
return $params;
|
||||
|
|
|
@ -4,11 +4,7 @@ namespace Activitypub\Rest;
|
|||
use stdClass;
|
||||
use WP_REST_Response;
|
||||
use Activitypub\Signature;
|
||||
use Activitypub\Model\User;
|
||||
|
||||
use function Activitypub\get_context;
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
|
||||
use Activitypub\Model\Application_User;
|
||||
|
||||
/**
|
||||
* ActivityPub Server REST-Class
|
||||
|
@ -18,7 +14,6 @@ use function Activitypub\get_rest_url_by_path;
|
|||
* @see https://www.w3.org/TR/activitypub/#security-verification
|
||||
*/
|
||||
class Server {
|
||||
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
|
@ -50,21 +45,8 @@ class Server {
|
|||
* @return WP_REST_Response The JSON profile of the Application Actor.
|
||||
*/
|
||||
public static function application_actor() {
|
||||
$json = new stdClass();
|
||||
|
||||
$json->{'@context'} = get_context();
|
||||
$json->id = get_rest_url_by_path( 'application' );
|
||||
$json->type = 'Application';
|
||||
$json->preferredUsername = str_replace( array( '.' ), '-', wp_parse_url( get_site_url(), PHP_URL_HOST ) ); // phpcs:ignore WordPress.NamingConventions
|
||||
$json->name = get_bloginfo( 'name' );
|
||||
$json->summary = __( 'WordPress-ActivityPub application actor', 'activitypub' );
|
||||
$json->manuallyApprovesFollowers = true; // phpcs:ignore WordPress.NamingConventions
|
||||
$json->icon = array( get_site_icon_url() ); // phpcs:ignore WordPress.NamingConventions short array syntax
|
||||
$json->publicKey = array( // phpcs:ignore WordPress.NamingConventions
|
||||
'id' => get_rest_url_by_path( 'application#main-key' ),
|
||||
'owner' => get_rest_url_by_path( 'application' ),
|
||||
'publicKeyPem' => Signature::get_public_key( User::APPLICATION_USER_ID ), // phpcs:ignore WordPress.NamingConventions
|
||||
);
|
||||
$user = new Application_User();
|
||||
$json = $user->to_array();
|
||||
|
||||
$response = new WP_REST_Response( $json, 200 );
|
||||
|
||||
|
|
102
includes/rest/class-users.php
Normal file
102
includes/rest/class-users.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
use WP_Error;
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Response;
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Collection\Users as User_Collection;
|
||||
|
||||
use function Activitypub\is_activitypub_request;
|
||||
|
||||
/**
|
||||
* ActivityPub Followers REST-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/#followers
|
||||
*/
|
||||
class Users {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
ACTIVITYPUB_REST_NAMESPACE,
|
||||
'/users/(?P<user_id>[\w\-\.]+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( self::class, 'get' ),
|
||||
'args' => self::request_parameters(),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle GET request
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function get( $request ) {
|
||||
$user_id = $request->get_param( 'user_id' );
|
||||
$user = User_Collection::get_by_various( $user_id );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
// redirect to canonical URL if it is not an ActivityPub request
|
||||
if ( ! is_activitypub_request() ) {
|
||||
header( 'Location: ' . $user->get_canonical_url(), true, 301 );
|
||||
exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_outbox_pre' );
|
||||
|
||||
$user->set_context(
|
||||
Activity::CONTEXT
|
||||
);
|
||||
|
||||
$json = $user->to_array();
|
||||
|
||||
$response = new WP_REST_Response( $json, 200 );
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* The supported parameters
|
||||
*
|
||||
* @return array list of parameters
|
||||
*/
|
||||
public static function request_parameters() {
|
||||
$params = array();
|
||||
|
||||
$params['page'] = array(
|
||||
'type' => 'string',
|
||||
);
|
||||
|
||||
$params['user_id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ namespace Activitypub\Rest;
|
|||
|
||||
use WP_Error;
|
||||
use WP_REST_Response;
|
||||
use Activitypub\Collection\Users as User_Collection;
|
||||
|
||||
/**
|
||||
* ActivityPub WebFinger REST-Class
|
||||
|
@ -13,15 +14,20 @@ use WP_REST_Response;
|
|||
*/
|
||||
class Webfinger {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
* Initialize the class, registering WordPress hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
|
||||
\add_action( 'webfinger_user_data', array( self::class, 'add_webfinger_discovery' ), 10, 3 );
|
||||
\add_filter( 'webfinger_user_data', array( self::class, 'add_user_discovery' ), 10, 3 );
|
||||
\add_filter( 'webfinger_data', array( self::class, 'add_pseudo_user_discovery' ), 99, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
* Register routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
|
@ -39,54 +45,17 @@ class Webfinger {
|
|||
}
|
||||
|
||||
/**
|
||||
* Render JRD file
|
||||
* WebFinger endpoint.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
* @return WP_REST_Response
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response object.
|
||||
*/
|
||||
public static function webfinger( $request ) {
|
||||
$resource = $request->get_param( 'resource' );
|
||||
$response = self::get_profile( $resource );
|
||||
|
||||
if ( \strpos( $resource, '@' ) === false ) {
|
||||
return new WP_Error( 'activitypub_unsupported_resource', \__( 'Resource is invalid', 'activitypub' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$resource = \str_replace( 'acct:', '', $resource );
|
||||
|
||||
$resource_identifier = \substr( $resource, 0, \strrpos( $resource, '@' ) );
|
||||
$resource_host = \str_replace( 'www.', '', \substr( \strrchr( $resource, '@' ), 1 ) );
|
||||
$blog_host = \str_replace( 'www.', '', \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) );
|
||||
|
||||
if ( $blog_host !== $resource_host ) {
|
||||
return new WP_Error( 'activitypub_wrong_host', \__( 'Resource host does not match blog host', 'activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$user = \get_user_by( 'login', \esc_sql( $resource_identifier ) );
|
||||
|
||||
if ( ! $user || ! \user_can( $user, 'publish_posts' ) ) {
|
||||
return new WP_Error( 'activitypub_user_not_found', \__( 'User not found', 'activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$json = array(
|
||||
'subject' => $resource,
|
||||
'aliases' => array(
|
||||
\get_author_posts_url( $user->ID ),
|
||||
),
|
||||
'links' => array(
|
||||
array(
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => \get_author_posts_url( $user->ID ),
|
||||
),
|
||||
array(
|
||||
'rel' => 'http://webfinger.net/rel/profile-page',
|
||||
'type' => 'text/html',
|
||||
'href' => \get_author_posts_url( $user->ID ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return new WP_REST_Response( $json, 200 );
|
||||
return new WP_REST_Response( $response, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,14 +81,73 @@ class Webfinger {
|
|||
* @param array $array the jrd array
|
||||
* @param string $resource the WebFinger resource
|
||||
* @param WP_User $user the WordPress user
|
||||
*
|
||||
* @return array the jrd array
|
||||
*/
|
||||
public static function add_webfinger_discovery( $array, $resource, $user ) {
|
||||
public static function add_user_discovery( $array, $resource, $user ) {
|
||||
$user = User_Collection::get_by_id( $user->ID );
|
||||
|
||||
$array['links'][] = array(
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => \get_author_posts_url( $user->ID ),
|
||||
'href' => $user->get_url(),
|
||||
);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WebFinger discovery links
|
||||
*
|
||||
* @param array $array the jrd array
|
||||
* @param string $resource the WebFinger resource
|
||||
* @param WP_User $user the WordPress user
|
||||
*
|
||||
* @return array the jrd array
|
||||
*/
|
||||
public static function add_pseudo_user_discovery( $array, $resource ) {
|
||||
if ( $array ) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
return self::get_profile( $resource );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WebFinger profile.
|
||||
*
|
||||
* @param string $resource the WebFinger resource.
|
||||
*
|
||||
* @return array the WebFinger profile.
|
||||
*/
|
||||
public static function get_profile( $resource ) {
|
||||
$user = User_Collection::get_by_resource( $resource );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$aliases = array(
|
||||
$user->get_url(),
|
||||
);
|
||||
|
||||
$profile = array(
|
||||
'subject' => $resource,
|
||||
'aliases' => array_values( array_unique( $aliases ) ),
|
||||
'links' => array(
|
||||
array(
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => $user->get_url(),
|
||||
),
|
||||
array(
|
||||
'rel' => 'http://webfinger.net/rel/profile-page',
|
||||
'type' => 'text/html',
|
||||
'href' => $user->get_url(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $profile;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Activitypub\Table;
|
||||
|
||||
use WP_List_Table;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Collection\Followers as FollowerCollection;
|
||||
|
||||
if ( ! \class_exists( '\WP_List_Table' ) ) {
|
||||
|
@ -9,13 +10,31 @@ if ( ! \class_exists( '\WP_List_Table' ) ) {
|
|||
}
|
||||
|
||||
class Followers extends WP_List_Table {
|
||||
private $user_id;
|
||||
|
||||
public function __construct() {
|
||||
if ( get_current_screen()->id === 'settings_page_activitypub' ) {
|
||||
$this->user_id = Users::BLOG_USER_ID;
|
||||
} else {
|
||||
$this->user_id = \get_current_user_id();
|
||||
}
|
||||
|
||||
parent::__construct(
|
||||
array(
|
||||
'singular' => \__( 'Follower', 'activitypub' ),
|
||||
'plural' => \__( 'Followers', 'activitypub' ),
|
||||
'ajax' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function get_columns() {
|
||||
return array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'avatar' => \__( 'Avatar', 'activitypub' ),
|
||||
'name' => \__( 'Name', 'activitypub' ),
|
||||
'username' => \__( 'Username', 'activitypub' ),
|
||||
'identifier' => \__( 'Identifier', 'activitypub' ),
|
||||
'url' => \__( 'URL', 'activitypub' ),
|
||||
'updated' => \__( 'Last updated', 'activitypub' ),
|
||||
//'errors' => \__( 'Errors', 'activitypub' ),
|
||||
//'latest-error' => \__( 'Latest Error Message', 'activitypub' ),
|
||||
|
@ -36,14 +55,14 @@ class Followers extends WP_List_Table {
|
|||
$page_num = $this->get_pagenum();
|
||||
$per_page = 20;
|
||||
|
||||
$followers = FollowerCollection::get_followers( \get_current_user_id(), $per_page, ( $page_num - 1 ) * $per_page );
|
||||
$counter = FollowerCollection::count_followers( \get_current_user_id() );
|
||||
$followers = FollowerCollection::get_followers( $this->user_id, $per_page, $page_num );
|
||||
$counter = FollowerCollection::count_followers( $this->user_id );
|
||||
|
||||
$this->items = array();
|
||||
$this->set_pagination_args(
|
||||
array(
|
||||
'total_items' => $counter,
|
||||
'total_pages' => round( $counter / $per_page ),
|
||||
'total_pages' => ceil( $counter / $per_page ),
|
||||
'per_page' => $per_page,
|
||||
)
|
||||
);
|
||||
|
@ -53,7 +72,8 @@ class Followers extends WP_List_Table {
|
|||
'icon' => esc_attr( $follower->get_icon_url() ),
|
||||
'name' => esc_attr( $follower->get_name() ),
|
||||
'username' => esc_attr( $follower->get_preferred_username() ),
|
||||
'identifier' => esc_attr( $follower->get_url() ),
|
||||
'url' => esc_attr( $follower->get_url() ),
|
||||
'identifier' => esc_attr( $follower->get_id() ),
|
||||
'updated' => esc_attr( $follower->get_updated() ),
|
||||
'errors' => $follower->count_errors(),
|
||||
'latest-error' => $follower->get_latest_error_message(),
|
||||
|
@ -83,11 +103,11 @@ class Followers extends WP_List_Table {
|
|||
);
|
||||
}
|
||||
|
||||
public function column_identifier( $item ) {
|
||||
public function column_url( $item ) {
|
||||
return sprintf(
|
||||
'<a href="%s" target="_blank">%s</a>',
|
||||
$item['identifier'],
|
||||
$item['identifier']
|
||||
$item['url'],
|
||||
$item['url']
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -104,7 +124,7 @@ class Followers extends WP_List_Table {
|
|||
return false;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'edit_user', \get_current_user_id() ) ) {
|
||||
if ( ! current_user_can( 'edit_user', $this->user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -116,9 +136,13 @@ class Followers extends WP_List_Table {
|
|||
$followers = array( $followers );
|
||||
}
|
||||
foreach ( $followers as $follower ) {
|
||||
FollowerCollection::remove_follower( \get_current_user_id(), $follower );
|
||||
FollowerCollection::remove_follower( $this->user_id, $follower );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_user_count() {
|
||||
return FollowerCollection::count_followers( $this->user_id );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,83 +1,39 @@
|
|||
<?php
|
||||
namespace Activitypub\Model;
|
||||
namespace Activitypub\Transformer;
|
||||
|
||||
use WP_Post;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Model\Blog_User;
|
||||
use Activitypub\Activity\Base_Object;
|
||||
|
||||
use function Activitypub\is_single_user;
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
|
||||
/**
|
||||
* ActivityPub Post Class
|
||||
* WordPress Post Transformer
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
* The Post Transformer is responsible for transforming a WP_Post object into different othe
|
||||
* Object-Types.
|
||||
*
|
||||
* Currently supported are:
|
||||
*
|
||||
* - Activitypub\Activity\Base_Object
|
||||
*/
|
||||
class Post {
|
||||
|
||||
/**
|
||||
* The WordPress Post Object.
|
||||
* The WP_Post object.
|
||||
*
|
||||
* @var WP_Post
|
||||
*/
|
||||
private $post;
|
||||
|
||||
/**
|
||||
* The Post Author.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $post_author;
|
||||
|
||||
/**
|
||||
* The Object ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* The Object URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $url;
|
||||
|
||||
/**
|
||||
* The Object Summary.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $summary;
|
||||
|
||||
/**
|
||||
* The Object Summary
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* The Object Attachments. This is usually a list of Images.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $attachments;
|
||||
|
||||
/**
|
||||
* The Object Tags. This is usually the list of used Hashtags.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $tags;
|
||||
|
||||
/**
|
||||
* The Onject Type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $object_type;
|
||||
protected $wp_post;
|
||||
|
||||
/**
|
||||
* The Allowed Tags, used in the content.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $allowed_tags = array(
|
||||
protected $allowed_tags = array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'title' => array(),
|
||||
|
@ -125,157 +81,96 @@ class Post {
|
|||
);
|
||||
|
||||
/**
|
||||
* List of audience
|
||||
* Static function to Transform a WP_Post Object.
|
||||
*
|
||||
* Also used for visibility
|
||||
* This helps to chain the output of the Transformer.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $to = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
|
||||
/**
|
||||
* List of audience
|
||||
*
|
||||
* Also used for visibility
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cc = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public function __construct( $post ) {
|
||||
$this->post = \get_post( $post );
|
||||
$path = sprintf( 'users/%d/followers', intval( $this->get_post_author() ) );
|
||||
$this->add_to( get_rest_url_by_path( $path ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic function to implement getter and setter
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $params
|
||||
* @param WP_Post $wp_post The WP_Post object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __call( $method, $params ) {
|
||||
$var = \strtolower( \substr( $method, 4 ) );
|
||||
|
||||
if ( \strncasecmp( $method, 'get', 3 ) === 0 ) {
|
||||
if ( empty( $this->$var ) && ! empty( $this->post->$var ) ) {
|
||||
return $this->post->$var;
|
||||
}
|
||||
return $this->$var;
|
||||
}
|
||||
|
||||
if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
|
||||
if ( \strncasecmp( $method, 'add', 3 ) === 0 ) {
|
||||
if ( ! is_array( $this->$var ) ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
|
||||
if ( is_array( $params[0] ) ) {
|
||||
$this->$var = array_merge( $this->$var, $params[0] );
|
||||
} else {
|
||||
array_push( $this->$var, $params[0] );
|
||||
}
|
||||
|
||||
$this->$var = array_unique( $this->$var );
|
||||
}
|
||||
public static function transform( WP_Post $wp_post ) {
|
||||
return new static( $wp_post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this Object into an Array.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @param WP_Post $wp_post
|
||||
*/
|
||||
public function to_array() {
|
||||
$post = $this->post;
|
||||
public function __construct( WP_Post $wp_post ) {
|
||||
$this->wp_post = $wp_post;
|
||||
}
|
||||
|
||||
$array = array(
|
||||
'id' => $this->get_id(),
|
||||
'url' => $this->get_url(),
|
||||
'type' => $this->get_object_type(),
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_date_gmt ) ),
|
||||
'attributedTo' => \get_author_posts_url( $post->post_author ),
|
||||
'summary' => $this->get_summary(),
|
||||
'inReplyTo' => null,
|
||||
'content' => $this->get_content(),
|
||||
'contentMap' => array(
|
||||
/**
|
||||
* Transforms the WP_Post object to an ActivityPub Object
|
||||
*
|
||||
* @see \Activitypub\Activity\Base_Object
|
||||
*
|
||||
* @return \Activitypub\Activity\Base_Object The ActivityPub Object
|
||||
*/
|
||||
public function to_object() {
|
||||
$wp_post = $this->wp_post;
|
||||
$object = new Base_Object();
|
||||
|
||||
$object->set_id( \esc_url( \get_permalink( $wp_post->ID ) ) );
|
||||
$object->set_url( \esc_url( \get_permalink( $wp_post->ID ) ) );
|
||||
$object->set_type( $this->get_object_type() );
|
||||
|
||||
$published = \strtotime( $wp_post->post_date_gmt );
|
||||
|
||||
$object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
|
||||
|
||||
$updated = \strtotime( $wp_post->post_modified_gmt );
|
||||
|
||||
if ( $updated > $published ) {
|
||||
$object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
|
||||
}
|
||||
|
||||
$object->set_attributed_to( $this->get_attributed_to() );
|
||||
$object->set_content( $this->get_content() );
|
||||
$object->set_content_map(
|
||||
array(
|
||||
\strstr( \get_locale(), '_', true ) => $this->get_content(),
|
||||
),
|
||||
'to' => $this->get_to(),
|
||||
'cc' => $this->get_cc(),
|
||||
'attachment' => $this->get_attachments(),
|
||||
'tag' => $this->get_tags(),
|
||||
)
|
||||
);
|
||||
$path = sprintf( 'users/%d/followers', intval( $wp_post->post_author ) );
|
||||
|
||||
return \apply_filters( 'activitypub_post', $array, $this->post );
|
||||
$object->set_to(
|
||||
array(
|
||||
'https://www.w3.org/ns/activitystreams#Public',
|
||||
get_rest_url_by_path( $path ),
|
||||
)
|
||||
);
|
||||
$object->set_cc( $this->get_cc() );
|
||||
$object->set_attachment( $this->get_attachments() );
|
||||
$object->set_tag( $this->get_tags() );
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this Object into a JSON String
|
||||
* Returns the User-URL of the Author of the Post.
|
||||
*
|
||||
* @return string
|
||||
* If `single_user` mode is enabled, the URL of the Blog-User is returned.
|
||||
*
|
||||
* @return string The User-URL.
|
||||
*/
|
||||
public function to_json() {
|
||||
return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
|
||||
protected function get_attributed_to() {
|
||||
if ( is_single_user() ) {
|
||||
$user = new Blog_User();
|
||||
return $user->get_url();
|
||||
}
|
||||
|
||||
return Users::get_by_id( $this->wp_post->post_author )->get_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of an Activity Object
|
||||
* Generates all Image Attachments for a Post.
|
||||
*
|
||||
* @return string
|
||||
* @return array The Image Attachments.
|
||||
*/
|
||||
public function get_url() {
|
||||
if ( $this->url ) {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
$post = $this->post;
|
||||
|
||||
if ( 'trash' === get_post_status( $post ) ) {
|
||||
$permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
|
||||
} else {
|
||||
$permalink = \get_permalink( $post );
|
||||
}
|
||||
|
||||
$this->url = $permalink;
|
||||
|
||||
return $permalink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of an Activity Object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
if ( $this->id ) {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
$this->id = $this->get_url();
|
||||
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Image Attachments
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_attachments() {
|
||||
if ( $this->attachments ) {
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
protected function get_attachments() {
|
||||
$max_images = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) );
|
||||
|
||||
$images = array();
|
||||
|
@ -285,7 +180,7 @@ class Post {
|
|||
return $images;
|
||||
}
|
||||
|
||||
$id = $this->post->ID;
|
||||
$id = $this->wp_post->ID;
|
||||
|
||||
$image_ids = array();
|
||||
|
||||
|
@ -364,7 +259,7 @@ class Post {
|
|||
*
|
||||
* @return array|false Array of image data, or boolean false if no image is available.
|
||||
*/
|
||||
public function get_image( $id, $image_size = 'full' ) {
|
||||
protected function get_image( $id, $image_size = 'full' ) {
|
||||
/**
|
||||
* Hook into the image retrieval process. Before image retrieval.
|
||||
*
|
||||
|
@ -387,64 +282,22 @@ class Post {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Tags, used in the Post
|
||||
* Returns the ActivityStreams 2.0 Object-Type for a Post based on the
|
||||
* settings and the Post-Type.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_tags() {
|
||||
if ( $this->tags ) {
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
$tags = array();
|
||||
|
||||
$post_tags = \get_the_tags( $this->post->ID );
|
||||
if ( $post_tags ) {
|
||||
foreach ( $post_tags as $post_tag ) {
|
||||
$tag = array(
|
||||
'type' => 'Hashtag',
|
||||
'href' => \get_tag_link( $post_tag->term_id ),
|
||||
'name' => '#' . $post_tag->slug,
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$mentions = apply_filters( 'activitypub_extract_mentions', array(), $this->post->post_content, $this );
|
||||
if ( $mentions ) {
|
||||
foreach ( $mentions as $mention => $url ) {
|
||||
$tag = array(
|
||||
'type' => 'Mention',
|
||||
'href' => $url,
|
||||
'name' => $mention,
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$this->tags = $tags;
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the as2 object-type for a given post
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
|
||||
*
|
||||
* @return string the object-type
|
||||
* @return string The Object-Type.
|
||||
*/
|
||||
public function get_object_type() {
|
||||
if ( $this->object_type ) {
|
||||
return $this->object_type;
|
||||
}
|
||||
|
||||
protected function get_object_type() {
|
||||
if ( 'wordpress-post-format' !== \get_option( 'activitypub_object_type', 'note' ) ) {
|
||||
return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) );
|
||||
}
|
||||
|
||||
$post_type = \get_post_type( $this->post );
|
||||
$post_type = \get_post_type( $this->wp_post );
|
||||
switch ( $post_type ) {
|
||||
case 'post':
|
||||
$post_format = \get_post_format( $this->post );
|
||||
$post_format = \get_post_format( $this->wp_post );
|
||||
switch ( $post_format ) {
|
||||
case 'aside':
|
||||
case 'status':
|
||||
|
@ -490,25 +343,78 @@ class Post {
|
|||
break;
|
||||
}
|
||||
|
||||
$this->object_type = $object_type;
|
||||
|
||||
return $object_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Mentions, used in the Post.
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/spec/activitypub/#Mention
|
||||
*
|
||||
* @return array The list of Mentions.
|
||||
*/
|
||||
protected function get_cc() {
|
||||
$cc = array();
|
||||
|
||||
$mentions = $this->get_mentions();
|
||||
if ( $mentions ) {
|
||||
foreach ( $mentions as $mention => $url ) {
|
||||
$cc[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return $cc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Tags, used in the Post.
|
||||
*
|
||||
* This includes Hash-Tags and Mentions.
|
||||
*
|
||||
* @return array The list of Tags.
|
||||
*/
|
||||
protected function get_tags() {
|
||||
$tags = array();
|
||||
|
||||
$post_tags = \get_the_tags( $this->wp_post->ID );
|
||||
if ( $post_tags ) {
|
||||
foreach ( $post_tags as $post_tag ) {
|
||||
$tag = array(
|
||||
'type' => 'Hashtag',
|
||||
'href' => esc_url( \get_tag_link( $post_tag->term_id ) ),
|
||||
'name' => '#' . \esc_attr( $post_tag->slug ),
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$mentions = $this->get_mentions();
|
||||
if ( $mentions ) {
|
||||
foreach ( $mentions as $mention => $url ) {
|
||||
$tag = array(
|
||||
'type' => 'Mention',
|
||||
'href' => \esc_url( $url ),
|
||||
'name' => \esc_html( $mention ),
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content for the ActivityPub Item.
|
||||
*
|
||||
* @return string the content
|
||||
* The content will be generated based on the user settings.
|
||||
*
|
||||
* @return string The content.
|
||||
*/
|
||||
public function get_content() {
|
||||
protected function get_content() {
|
||||
global $post;
|
||||
|
||||
if ( $this->content ) {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$post = $this->post;
|
||||
$post = $this->wp_post;
|
||||
$content = $this->get_post_content_template();
|
||||
|
||||
// Fill in the shortcodes.
|
||||
|
@ -524,17 +430,15 @@ class Post {
|
|||
$content = \apply_filters( 'activitypub_the_content', $content, $post );
|
||||
$content = \html_entity_decode( $content, \ENT_QUOTES, 'UTF-8' );
|
||||
|
||||
$this->content = $content;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the template to use to generate the content of the activitypub item.
|
||||
*
|
||||
* @return string the template
|
||||
* @return string The Template.
|
||||
*/
|
||||
public function get_post_content_template() {
|
||||
protected function get_post_content_template() {
|
||||
if ( 'excerpt' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
|
||||
return "[ap_excerpt]\n\n[ap_permalink type=\"html\"]";
|
||||
}
|
||||
|
@ -549,4 +453,13 @@ class Post {
|
|||
|
||||
return \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the @-Mentions from the post content.
|
||||
*
|
||||
* @return array The list of @-Mentions.
|
||||
*/
|
||||
protected function get_mentions() {
|
||||
return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_post->post_content, $this->wp_post );
|
||||
}
|
||||
}
|
|
@ -11,6 +11,14 @@
|
|||
<a href="<?php echo \esc_url_raw( admin_url( 'options-general.php?page=activitypub&tab=settings' ) ); ?>" class="activitypub-settings-tab <?php echo \esc_attr( $args['settings'] ); ?>">
|
||||
<?php \esc_html_e( 'Settings', 'activitypub' ); ?>
|
||||
</a>
|
||||
|
||||
<?php if ( ! \Activitypub\is_user_disabled( \Activitypub\Collection\Users::BLOG_USER_ID ) ) : ?>
|
||||
|
||||
<a href="<?php echo \esc_url_raw( admin_url( 'options-general.php?page=activitypub&tab=followers' ) ); ?>" class="activitypub-settings-tab <?php echo \esc_attr( $args['followers'] ); ?>">
|
||||
<?php \esc_html_e( 'Followers', 'activitypub' ); ?>
|
||||
</a>
|
||||
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
</div>
|
||||
<hr class="wp-header-end">
|
||||
|
|
|
@ -1,92 +1,14 @@
|
|||
<?php
|
||||
$author_id = \get_the_author_meta( 'ID' );
|
||||
$user = \Activitypub\Collection\Users::get_by_id( \get_the_author_meta( 'ID' ) );
|
||||
|
||||
$json = new \stdClass();
|
||||
|
||||
$json->{'@context'} = \Activitypub\get_context();
|
||||
$json->id = \get_author_posts_url( $author_id );
|
||||
$json->type = 'Person';
|
||||
$json->name = \get_the_author_meta( 'display_name', $author_id );
|
||||
$json->summary = \html_entity_decode(
|
||||
\Activitypub\get_author_description( $author_id ),
|
||||
\ENT_QUOTES,
|
||||
'UTF-8'
|
||||
$user->set_context(
|
||||
\Activitypub\Activity\Activity::CONTEXT
|
||||
);
|
||||
$json->preferredUsername = \get_the_author_meta( 'login', $author_id ); // phpcs:ignore
|
||||
$json->url = \get_author_posts_url( $author_id );
|
||||
$json->icon = array(
|
||||
'type' => 'Image',
|
||||
'url' => \get_avatar_url( $author_id, array( 'size' => 120 ) ),
|
||||
);
|
||||
|
||||
$json->published = \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( \get_the_author_meta( 'registered', $author_id ) ) );
|
||||
|
||||
if ( \has_header_image() ) {
|
||||
$json->image = array(
|
||||
'type' => 'Image',
|
||||
'url' => \get_header_image(),
|
||||
);
|
||||
}
|
||||
|
||||
$json->inbox = \Activitypub\get_rest_url_by_path( sprintf( 'users/%d/inbox', $author_id ) );
|
||||
$json->outbox = \Activitypub\get_rest_url_by_path( sprintf( 'users/%d/outbox', $author_id ) );
|
||||
$json->followers = \Activitypub\get_rest_url_by_path( sprintf( 'users/%d/followers', $author_id ) );
|
||||
$json->following = \Activitypub\get_rest_url_by_path( sprintf( 'users/%d/following', $author_id ) );
|
||||
|
||||
$json->manuallyApprovesFollowers = \apply_filters( 'activitypub_json_manually_approves_followers', \__return_false() ); // phpcs:ignore
|
||||
|
||||
// phpcs:ignore
|
||||
$json->publicKey = array(
|
||||
'id' => \get_author_posts_url( $author_id ) . '#main-key',
|
||||
'owner' => \get_author_posts_url( $author_id ),
|
||||
'publicKeyPem' => \trim( \Activitypub\Signature::get_public_key( $author_id ) ),
|
||||
);
|
||||
|
||||
$json->tag = array();
|
||||
$json->attachment = array();
|
||||
|
||||
$json->attachment['blog_url'] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => \__( 'Blog', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \home_url( '/' ) ) . '" target="_blank" href="' . \home_url( '/' ) . '">' . \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) . '</a>',
|
||||
\ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
|
||||
$json->attachment['profile_url'] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => \__( 'Profile', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \get_author_posts_url( $author_id ) ) . '" target="_blank" href="' . \get_author_posts_url( $author_id ) . '">' . \wp_parse_url( \get_author_posts_url( $author_id ), \PHP_URL_HOST ) . '</a>',
|
||||
\ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
|
||||
if ( \get_the_author_meta( 'user_url', $author_id ) ) {
|
||||
$json->attachment['user_url'] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => \__( 'Website', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \get_the_author_meta( 'user_url', $author_id ) ) . '" target="_blank" href="' . \get_the_author_meta( 'user_url', $author_id ) . '">' . \wp_parse_url( \get_the_author_meta( 'user_url', $author_id ), \PHP_URL_HOST ) . '</a>',
|
||||
\ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// filter output
|
||||
$json = \apply_filters( 'activitypub_json_author_array', $json, $author_id );
|
||||
|
||||
// migrate to ActivityPub standard
|
||||
$json->attachment = array_values( $json->attachment );
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_json_author_pre', $author_id );
|
||||
\do_action( 'activitypub_json_author_pre', $user->get__id() );
|
||||
|
||||
$options = 0;
|
||||
// JSON_PRETTY_PRINT added in PHP 5.4
|
||||
|
@ -101,12 +23,12 @@ $options |= \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT;
|
|||
*
|
||||
* @param int $options The current options flags
|
||||
*/
|
||||
$options = \apply_filters( 'activitypub_json_author_options', $options, $author_id );
|
||||
$options = \apply_filters( 'activitypub_json_author_options', $options, $user->get__id() );
|
||||
|
||||
\header( 'Content-Type: application/activity+json' );
|
||||
echo \wp_json_encode( $json, $options );
|
||||
echo \wp_json_encode( $user->to_array(), $options );
|
||||
|
||||
/*
|
||||
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_json_author_post', $author_id );
|
||||
\do_action( 'activitypub_json_author_post', $user->get__id() );
|
||||
|
|
|
@ -1,66 +1,14 @@
|
|||
<?php
|
||||
$json = new \stdClass();
|
||||
$user = new \Activitypub\Model\Blog_User();
|
||||
|
||||
$json->{'@context'} = \Activitypub\get_context();
|
||||
$json->id = \get_home_url( '/' );
|
||||
$json->type = 'Organization';
|
||||
$json->name = \get_bloginfo( 'name' );
|
||||
$json->summary = \html_entity_decode(
|
||||
\get_bloginfo( 'description' ),
|
||||
\ENT_QUOTES,
|
||||
'UTF-8'
|
||||
$user->set_context(
|
||||
\Activitypub\Activity\Activity::CONTEXT
|
||||
);
|
||||
$json->preferredUsername = \get_bloginfo( 'name' ); // phpcs:ignore
|
||||
$json->url = \get_home_url( '/' );
|
||||
|
||||
if ( \has_site_icon() ) {
|
||||
$json->icon = array(
|
||||
'type' => 'Image',
|
||||
'url' => \get_site_icon_url( 120 ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( \has_header_image() ) {
|
||||
$json->image = array(
|
||||
'type' => 'Image',
|
||||
'url' => \get_header_image(),
|
||||
);
|
||||
}
|
||||
|
||||
$json->inbox = \Activitypub\get_rest_url_by_path( 'blog/inbox' );
|
||||
$json->outbox = \Activitypub\get_rest_url_by_path( 'blog/outbox' );
|
||||
$json->followers = \Activitypub\get_rest_url_by_path( 'blog/followers' );
|
||||
$json->following = \Activitypub\get_rest_url_by_path( 'blog/following' );
|
||||
|
||||
$json->manuallyApprovesFollowers = \apply_filters( 'activitypub_json_manually_approves_followers', \__return_false() ); // phpcs:ignore
|
||||
|
||||
// phpcs:ignore
|
||||
$json->publicKey = array(
|
||||
'id' => \get_home_url( '/' ) . '#main-key',
|
||||
'owner' => \get_home_url( '/' ),
|
||||
'publicKeyPem' => '',
|
||||
);
|
||||
|
||||
$json->tag = array();
|
||||
$json->attachment = array();
|
||||
|
||||
$json->attachment[] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => \__( 'Blog', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \home_url( '/' ) ) . '" target="_blank" href="' . \home_url( '/' ) . '">' . \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) . '</a>',
|
||||
\ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
|
||||
// filter output
|
||||
$json = \apply_filters( 'activitypub_json_blog_array', $json );
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_json_blog_pre' );
|
||||
\do_action( 'activitypub_json_author_pre', $user->get__id() );
|
||||
|
||||
$options = 0;
|
||||
// JSON_PRETTY_PRINT added in PHP 5.4
|
||||
|
@ -75,12 +23,12 @@ $options |= \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT;
|
|||
*
|
||||
* @param int $options The current options flags
|
||||
*/
|
||||
$options = \apply_filters( 'activitypub_json_blog_options', $options );
|
||||
$options = \apply_filters( 'activitypub_json_author_options', $options, $user->get__id() );
|
||||
|
||||
\header( 'Content-Type: application/activity+json' );
|
||||
echo \wp_json_encode( $json, $options );
|
||||
echo \wp_json_encode( $user->to_array(), $options );
|
||||
|
||||
/*
|
||||
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_json_blog_post' );
|
||||
\do_action( 'activitypub_json_author_post', $user->get__id() );
|
||||
|
|
30
templates/blog-user-followers-list.php
Normal file
30
templates/blog-user-followers-list.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
\load_template(
|
||||
\dirname( __FILE__ ) . '/admin-header.php',
|
||||
true,
|
||||
array(
|
||||
'settings' => '',
|
||||
'welcome' => '',
|
||||
'followers' => 'active',
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<div class="wrap">
|
||||
<h1><?php \esc_html_e( 'Followers', 'activitypub' ); ?></h1>
|
||||
|
||||
<?php $table = new \Activitypub\Table\Followers(); ?>
|
||||
|
||||
<?php // translators: The follower count. ?>
|
||||
<p><?php \printf( \esc_html__( 'You currently have %s followers.', 'activitypub' ), \esc_attr( $table->get_user_count() ) ); ?></p>
|
||||
|
||||
<form method="get">
|
||||
<input type="hidden" name="page" value="activitypub" />
|
||||
<input type="hidden" name="tab" value="followers" />
|
||||
<?php
|
||||
$table->prepare_items();
|
||||
$table->display();
|
||||
?>
|
||||
<?php wp_nonce_field( 'activitypub-followers-list', '_apnonce' ); ?>
|
||||
</form>
|
||||
</div>
|
|
@ -2,8 +2,8 @@
|
|||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$post = \get_post();
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$json = \array_merge( array( '@context' => \Activitypub\get_context() ), $activitypub_post->to_array() );
|
||||
$object = new \Activitypub\Transformer\Post( $post );
|
||||
$json = \array_merge( array( '@context' => \Activitypub\get_context() ), $object->to_object()->to_array() );
|
||||
|
||||
// filter output
|
||||
$json = \apply_filters( 'activitypub_json_post_array', $json );
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
array(
|
||||
'settings' => 'active',
|
||||
'welcome' => '',
|
||||
'followers' => '',
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<div class="privacy-settings-body hide-if-no-js">
|
||||
<div class="activitypub-settings-body hide-if-no-js">
|
||||
<div class="notice notice-info">
|
||||
<p>
|
||||
<?php
|
||||
|
@ -30,6 +31,35 @@
|
|||
<form method="post" action="options.php">
|
||||
<?php \settings_fields( 'activitypub' ); ?>
|
||||
|
||||
<?php if ( ! \Activitypub\is_user_disabled( \Activitypub\Collection\Users::BLOG_USER_ID ) ) : ?>
|
||||
|
||||
<h3><?php \esc_html_e( 'Blog-User', 'activitypub' ); ?></h3>
|
||||
|
||||
<p><?php \esc_html_e( 'All settings for the Blog-User (Catch-All Account)', 'activitypub' ); ?></p>
|
||||
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php \esc_html_e( 'User-Identifier', 'activitypub' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<label for="activitypub_blog_user_identifier">
|
||||
<input class="blog-user-identifier" name="activitypub_blog_user_identifier" id="activitypub_blog_user_identifier" type="text" value="<?php echo esc_attr( \get_option( 'activitypub_blog_user_identifier', \Activitypub\Model\Blog_User::get_default_username() ) ); ?>" />
|
||||
@<?php echo esc_html( \wp_parse_url( \home_url(), PHP_URL_HOST ) ); ?>
|
||||
</label>
|
||||
<p class="description">
|
||||
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php \do_settings_fields( 'activitypub', 'blog-user' ); ?>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<h3><?php \esc_html_e( 'Activities', 'activitypub' ); ?></h3>
|
||||
|
||||
<p><?php \esc_html_e( 'All activity related settings.', 'activitypub' ); ?></p>
|
||||
|
@ -42,16 +72,44 @@
|
|||
</th>
|
||||
<td>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_title_link" value="title" <?php echo \checked( 'title', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Title and link', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'Only the title and a link.', 'activitypub' ); ?></span>
|
||||
<label for="activitypub_post_content_type_title_link">
|
||||
<input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_title_link" value="title" <?php echo \checked( 'title', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> />
|
||||
<?php \esc_html_e( 'Title and link', 'activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'Only the title and a link.', 'activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_excerpt" value="excerpt" <?php echo \checked( 'excerpt', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Excerpt', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'A content summary, shortened to 400 characters and without markup.', 'activitypub' ); ?></span>
|
||||
<label for="activitypub_post_content_type_excerpt">
|
||||
<input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_excerpt" value="excerpt" <?php echo \checked( 'excerpt', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> />
|
||||
<?php \esc_html_e( 'Excerpt', 'activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'A content summary, shortened to 400 characters and without markup.', 'activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_content" value="content" <?php echo \checked( 'content', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Content (default)', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'The full content.', 'activitypub' ); ?></span>
|
||||
<label for="activitypub_post_content_type_content">
|
||||
<input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_content" value="content" <?php echo \checked( 'content', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> />
|
||||
<?php \esc_html_e( 'Content (default)', 'activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'The full content.', 'activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_custom" value="custom" <?php echo \checked( 'custom', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Custom', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'Use the text-area below, to customize your activities.', 'activitypub' ); ?></span>
|
||||
<label for="activitypub_post_content_type_custom">
|
||||
<input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_custom" value="custom" <?php echo \checked( 'custom', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> />
|
||||
<?php \esc_html_e( 'Custom', 'activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'Use the text-area below, to customize your activities.', 'activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</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>
|
||||
|
@ -98,13 +156,34 @@
|
|||
</th>
|
||||
<td>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_object_type" id="activitypub_object_type_note" value="note" <?php echo \checked( 'note', \get_option( 'activitypub_object_type', 'note' ) ); ?> /> <?php \esc_html_e( 'Note (default)', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'Should work with most platforms.', 'activitypub' ); ?></span>
|
||||
<label for="activitypub_object_type_note">
|
||||
<input type="radio" name="activitypub_object_type" id="activitypub_object_type_note" value="note" <?php echo \checked( 'note', \get_option( 'activitypub_object_type', 'note' ) ); ?> />
|
||||
<?php \esc_html_e( 'Note (default)', 'activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'Should work with most platforms.', 'activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_object_type" id="activitypub_object_type_article" value="article" <?php echo \checked( 'article', \get_option( 'activitypub_object_type', 'note' ) ); ?> /> <?php \esc_html_e( 'Article', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'The presentation of the "Article" might change on different platforms. Mastodon for example shows the "Article" type as a simple link.', 'activitypub' ); ?></span>
|
||||
<label for="activitypub_object_type_article">
|
||||
<input type="radio" name="activitypub_object_type" id="activitypub_object_type_article" value="article" <?php echo \checked( 'article', \get_option( 'activitypub_object_type', 'note' ) ); ?> />
|
||||
<?php \esc_html_e( 'Article', 'activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'The presentation of the "Article" might change on different platforms. Mastodon for example shows the "Article" type as a simple link.', 'activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_object_type" id="activitypub_object_type" value="wordpress-post-format" <?php echo \checked( 'wordpress-post-format', \get_option( 'activitypub_object_type', 'note' ) ); ?> /> <?php \esc_html_e( 'WordPress Post-Format', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'Maps the WordPress Post-Format to the ActivityPub Object Type.', 'activitypub' ); ?></span>
|
||||
<label>
|
||||
<input type="radio" name="activitypub_object_type" id="activitypub_object_type" value="wordpress-post-format" <?php echo \checked( 'wordpress-post-format', \get_option( 'activitypub_object_type', 'note' ) ); ?> />
|
||||
<?php \esc_html_e( 'WordPress Post-Format', 'activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'Maps the WordPress Post-Format to the ActivityPub Object Type.', 'activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<div class="wrap">
|
||||
<h1><?php \esc_html_e( 'Followers', 'activitypub' ); ?></h1>
|
||||
<?php Activitypub\Migration::maybe_migrate(); ?>
|
||||
<?php // translators: ?>
|
||||
<p><?php \printf( \esc_html__( 'You currently have %s followers.', 'activitypub' ), \esc_attr( \Activitypub\Collection\Followers::count_followers( \get_current_user_id() ) ) ); ?></p>
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
<?php $user = \Activitypub\Collection\Users::get_by_id( \get_current_user_id() ); ?>
|
||||
<h2 id="activitypub"><?php \esc_html_e( 'ActivityPub', 'activitypub' ); ?></h2>
|
||||
|
||||
<table class="form-table">
|
||||
|
@ -8,11 +9,11 @@
|
|||
</th>
|
||||
<td>
|
||||
<p>
|
||||
<code><?php echo \esc_html( \Activitypub\get_webfinger_resource( \get_current_user_id() ) ); ?></code> or
|
||||
<code><?php echo \esc_url( \get_author_posts_url( \get_current_user_id() ) ); ?></code>
|
||||
<code><?php echo \esc_html( $user->get_resource() ); ?></code> or
|
||||
<code><?php echo \esc_url( $user->get_url() ); ?></code>
|
||||
</p>
|
||||
<?php // translators: the webfinger resource ?>
|
||||
<p class="description"><?php \printf( \esc_html__( 'Try to follow "@%s" by searching for it on Mastodon,Friendica & Co.', 'activitypub' ), \esc_html( \Activitypub\get_webfinger_resource( \get_current_user_id() ) ) ); ?></p>
|
||||
<p class="description"><?php \printf( \esc_html__( 'Try to follow "@%s" by searching for it on Mastodon,Friendica & Co.', 'activitypub' ), \esc_html( $user->get_resource() ) ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="activitypub-user-description-wrap">
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
array(
|
||||
'settings' => '',
|
||||
'welcome' => 'active',
|
||||
'followers' => '',
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
@ -13,29 +14,65 @@
|
|||
<h2><?php \esc_html_e( 'Welcome', 'activitypub' ); ?></h2>
|
||||
|
||||
<p><?php \esc_html_e( 'With ActivityPub your blog becomes part of a federated social network. This means you can share and talk to everyone using the ActivityPub protocol, including users of Friendica, Pleroma and Mastodon.', 'activitypub' ); ?></p>
|
||||
|
||||
<?php if ( ! \Activitypub\is_user_disabled( \Activitypub\Collection\Users::BLOG_USER_ID ) ) : ?>
|
||||
|
||||
<h3><?php \esc_html_e( 'Blog Account', 'activitypub' ); ?></h3>
|
||||
<p>
|
||||
<?php
|
||||
$blog_user = new \Activitypub\Model\Blog_User();
|
||||
echo wp_kses(
|
||||
\sprintf(
|
||||
// translators:
|
||||
\__(
|
||||
'People can follow you by using the username <code>%1$s</code> or the URL <code>%2$s</code>. Users who can not access this settings page will find their username on the <a href="%3$s">Edit Profile</a> page.',
|
||||
'People can follow your Blog by using the username <code>%1$s</code> or the URL <code>%2$s</code>. This Blog-User will federate all posts written on your Blog, regardless of the User who posted it. You can customize the Blog-User on the <a href="%3$s">Settings</a> page.',
|
||||
'activitypub'
|
||||
),
|
||||
\esc_attr( \Activitypub\get_webfinger_resource( wp_get_current_user()->ID ) ),
|
||||
\esc_url_raw( \get_author_posts_url( wp_get_current_user()->ID ) ),
|
||||
\esc_attr( $blog_user->get_resource() ),
|
||||
\esc_url_raw( $blog_user->get_url() ),
|
||||
\esc_url_raw( \admin_url( '/options-general.php?page=activitypub&tab=settings' ) )
|
||||
),
|
||||
'default'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! \Activitypub\is_user_disabled( get_current_user_id() ) ) : ?>
|
||||
|
||||
<h3><?php \esc_html_e( 'Personal Account', 'activitypub' ); ?></h3>
|
||||
<p>
|
||||
<?php
|
||||
$user = \Activitypub\Collection\Users::get_by_id( wp_get_current_user()->ID );
|
||||
echo wp_kses(
|
||||
\sprintf(
|
||||
// translators:
|
||||
\__(
|
||||
'People can also follow you by using your Username <code>%1$s</code> or your Author-URL <code>%2$s</code>. Users who can not access this settings page will find their username on the <a href="%3$s">Edit Profile</a> page.',
|
||||
'activitypub'
|
||||
),
|
||||
\esc_attr( $user->get_resource() ),
|
||||
\esc_url_raw( $user->get_url() ),
|
||||
\esc_url_raw( \admin_url( 'profile.php#activitypub' ) )
|
||||
),
|
||||
'default'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<h3><?php \esc_html_e( 'Troubleshooting', 'activitypub' ); ?></h3>
|
||||
<p>
|
||||
<?php
|
||||
echo wp_kses(
|
||||
\sprintf(
|
||||
// translators:
|
||||
\__( 'If you have problems using this plugin, please check the <a href="%s">Site Health</a> to ensure that your site is compatible and/or use the "Help" tab (in the top right of the settings pages).', 'activitypub' ),
|
||||
\__(
|
||||
'If you have problems using this plugin, please check the <a href="%s">Site Health</a> to ensure that your site is compatible and/or use the "Help" tab (in the top right of the settings pages).',
|
||||
'activitypub'
|
||||
),
|
||||
\esc_url_raw( admin_url( 'site-health.php' ) )
|
||||
),
|
||||
'default'
|
||||
|
|
|
@ -34,21 +34,25 @@ class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HT
|
|||
$pre_http_request = new MockAction();
|
||||
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
\Activitypub\Activity_Dispatcher::send_create_activity( $activitypub_post );
|
||||
|
||||
$this->assertNotEmpty( $activitypub_post->get_content() );
|
||||
\Activitypub\Activity_Dispatcher::send_activity( get_post( $post ), 'Create' );
|
||||
|
||||
$this->assertSame( 2, $pre_http_request->get_call_count() );
|
||||
$all_args = $pre_http_request->get_args();
|
||||
$first_call_args = array_shift( $all_args );
|
||||
|
||||
$this->assertEquals( 'https://example.com/author/jon/inbox', $first_call_args[2] );
|
||||
|
||||
$second_call_args = array_shift( $all_args );
|
||||
$this->assertEquals( 'https://example.org/users/username/inbox', $second_call_args[2] );
|
||||
|
||||
$json = json_decode( $second_call_args[1]['body'] );
|
||||
$this->assertEquals( 'Create', $json->type );
|
||||
$this->assertEquals( 'http://example.org/?author=1', $json->actor );
|
||||
$this->assertEquals( 'http://example.org/?author=1', $json->object->attributedTo );
|
||||
|
||||
remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 );
|
||||
}
|
||||
|
||||
public function test_dispatch_mentions() {
|
||||
$post = \wp_insert_post(
|
||||
array(
|
||||
|
@ -76,10 +80,7 @@ class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HT
|
|||
$pre_http_request = new MockAction();
|
||||
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
\Activitypub\Activity_Dispatcher::send_create_activity( $activitypub_post );
|
||||
|
||||
$this->assertNotEmpty( $activitypub_post->get_content() );
|
||||
\Activitypub\Activity_Dispatcher::send_activity( get_post( $post ), 'Create' );
|
||||
|
||||
$this->assertSame( 1, $pre_http_request->get_call_count() );
|
||||
$all_args = $pre_http_request->get_args();
|
||||
|
@ -93,6 +94,80 @@ class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HT
|
|||
remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 );
|
||||
}
|
||||
|
||||
public function test_dispatch_announce() {
|
||||
$followers = array( 'https://example.com/author/jon' );
|
||||
|
||||
foreach ( $followers as $follower ) {
|
||||
\Activitypub\Collection\Followers::add_follower( \Activitypub\Collection\Users::BLOG_USER_ID, $follower );
|
||||
}
|
||||
|
||||
$post = \wp_insert_post(
|
||||
array(
|
||||
'post_author' => 1,
|
||||
'post_content' => 'hello',
|
||||
)
|
||||
);
|
||||
|
||||
$pre_http_request = new MockAction();
|
||||
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
|
||||
|
||||
\Activitypub\Activity_Dispatcher::send_activity_or_announce( get_post( $post ), 'Create' );
|
||||
|
||||
$all_args = $pre_http_request->get_args();
|
||||
$first_call_args = $all_args[0];
|
||||
|
||||
$this->assertSame( 1, $pre_http_request->get_call_count() );
|
||||
|
||||
$user = new \Activitypub\Model\Blog_User();
|
||||
|
||||
$json = json_decode( $first_call_args[1]['body'] );
|
||||
$this->assertEquals( 'Announce', $json->type );
|
||||
$this->assertEquals( $user->get_url(), $json->actor );
|
||||
|
||||
remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 );
|
||||
}
|
||||
|
||||
public function test_dispatch_blog_activity() {
|
||||
$followers = array( 'https://example.com/author/jon' );
|
||||
|
||||
add_filter(
|
||||
'activitypub_is_single_user',
|
||||
function( $return ) {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
foreach ( $followers as $follower ) {
|
||||
\Activitypub\Collection\Followers::add_follower( \Activitypub\Collection\Users::BLOG_USER_ID, $follower );
|
||||
}
|
||||
|
||||
$post = \wp_insert_post(
|
||||
array(
|
||||
'post_author' => 1,
|
||||
'post_content' => 'hello',
|
||||
)
|
||||
);
|
||||
|
||||
$pre_http_request = new MockAction();
|
||||
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
|
||||
|
||||
\Activitypub\Activity_Dispatcher::send_activity_or_announce( get_post( $post ), 'Create' );
|
||||
|
||||
$all_args = $pre_http_request->get_args();
|
||||
$first_call_args = $all_args[0];
|
||||
|
||||
$this->assertSame( 1, $pre_http_request->get_call_count() );
|
||||
|
||||
$user = new \Activitypub\Model\Blog_User();
|
||||
|
||||
$json = json_decode( $first_call_args[1]['body'] );
|
||||
$this->assertEquals( 'Create', $json->type );
|
||||
$this->assertEquals( $user->get_url(), $json->actor );
|
||||
$this->assertEquals( $user->get_url(), $json->object->attributedTo );
|
||||
|
||||
remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 );
|
||||
}
|
||||
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
|
||||
|
|
|
@ -17,10 +17,11 @@ class Test_Activitypub_Activity extends WP_UnitTestCase {
|
|||
10
|
||||
);
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_post = \Activitypub\Transformer\Post::transform( get_post( $post ) )->to_object();
|
||||
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create' );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
$activitypub_activity = new \Activitypub\Activity\Activity();
|
||||
$activitypub_activity->set_type( 'Create' );
|
||||
$activitypub_activity->set_object( $activitypub_post );
|
||||
|
||||
$this->assertContains( \Activitypub\get_rest_url_by_path( 'users/1/followers' ), $activitypub_activity->get_to() );
|
||||
$this->assertContains( 'https://example.com/alex', $activitypub_activity->get_cc() );
|
||||
|
@ -36,7 +37,7 @@ class Test_Activitypub_Activity extends WP_UnitTestCase {
|
|||
'content' => 'Hello world!',
|
||||
);
|
||||
|
||||
$object = \Activitypub\Activity\Base_Object::from_array( $test_array );
|
||||
$object = \Activitypub\Activity\Base_Object::init_from_array( $test_array );
|
||||
|
||||
$this->assertEquals( 'Hello world!', $object->get_content() );
|
||||
$this->assertEquals( $test_array, $object->to_array() );
|
||||
|
|
|
@ -10,13 +10,13 @@ class Test_Activitypub_Post extends WP_UnitTestCase {
|
|||
|
||||
$permalink = \get_permalink( $post );
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_post = \Activitypub\Transformer\Post::transform( get_post( $post ) )->to_object();
|
||||
|
||||
$this->assertEquals( $permalink, $activitypub_post->get_id() );
|
||||
|
||||
\wp_trash_post( $post );
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_post = \Activitypub\Transformer\Post::transform( get_post( $post ) )->to_object();
|
||||
|
||||
$this->assertEquals( $permalink, $activitypub_post->get_id() );
|
||||
}
|
||||
|
|
|
@ -10,9 +10,10 @@ class Test_Activitypub_Signature_Verification extends WP_UnitTestCase {
|
|||
)
|
||||
);
|
||||
$remote_actor = \get_author_posts_url( 2 );
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_activity = new Activitypub\Model\Activity( 'Create' );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
$activitypub_post = \Activitypub\Transformer\Post::transform( get_post( $post ) )->to_object();
|
||||
$activitypub_activity = new Activitypub\Activity\Activity( 'Create' );
|
||||
$activitypub_activity->set_type( 'Create' );
|
||||
$activitypub_activity->set_object( $activitypub_post );
|
||||
$activitypub_activity->add_cc( $remote_actor );
|
||||
$activity = $activitypub_activity->to_json();
|
||||
|
||||
|
@ -42,7 +43,9 @@ class Test_Activitypub_Signature_Verification extends WP_UnitTestCase {
|
|||
$signed_headers = $signature_block['headers'];
|
||||
$signed_data = Activitypub\Signature::get_signed_data( $signed_headers, $signature_block, $headers );
|
||||
|
||||
$public_key = Activitypub\Signature::get_public_key( 1 );
|
||||
$user = Activitypub\Collection\Users::get_by_id( 1 );
|
||||
|
||||
$public_key = $user->get__public_key();
|
||||
|
||||
// signature_verification
|
||||
$verified = \openssl_verify( $signed_data, $signature_block['signature'], $public_key, 'rsa-sha256' ) > 0;
|
||||
|
@ -53,6 +56,8 @@ class Test_Activitypub_Signature_Verification extends WP_UnitTestCase {
|
|||
add_filter(
|
||||
'pre_get_remote_metadata_by_actor',
|
||||
function( $json, $actor ) {
|
||||
$user = Activitypub\Collection\Users::get_by_id( 1 );
|
||||
$public_key = $user->get__public_key();
|
||||
// return ActivityPub Profile with signature
|
||||
return array(
|
||||
'id' => $actor,
|
||||
|
@ -60,7 +65,7 @@ class Test_Activitypub_Signature_Verification extends WP_UnitTestCase {
|
|||
'publicKey' => array(
|
||||
'id' => $actor . '#main-key',
|
||||
'owner' => $actor,
|
||||
'publicKeyPem' => \Activitypub\Signature::get_public_key( 1 ),
|
||||
'publicKeyPem' => $public_key,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -77,9 +82,10 @@ class Test_Activitypub_Signature_Verification extends WP_UnitTestCase {
|
|||
);
|
||||
$remote_actor = \get_author_posts_url( 2 );
|
||||
$remote_actor_inbox = Activitypub\get_rest_url_by_path( '/inbox' );
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_activity = new Activitypub\Model\Activity( 'Create' );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
$activitypub_post = \Activitypub\Transformer\Post::transform( \get_post( $post ) )->to_object();
|
||||
$activitypub_activity = new Activitypub\Activity\Activity();
|
||||
$activitypub_activity->set_type( 'Create' );
|
||||
$activitypub_activity->set_object( $activitypub_post );
|
||||
$activitypub_activity->add_cc( $remote_actor_inbox );
|
||||
$activity = $activitypub_activity->to_json();
|
||||
|
||||
|
|
|
@ -6,42 +6,42 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
|
|||
'url' => 'https://example.org/users/username',
|
||||
'inbox' => 'https://example.org/users/username/inbox',
|
||||
'name' => 'username',
|
||||
'prefferedUsername' => 'username',
|
||||
'preferredUsername' => 'username',
|
||||
),
|
||||
'jon@example.com' => array(
|
||||
'id' => 'https://example.com/author/jon',
|
||||
'url' => 'https://example.com/author/jon',
|
||||
'inbox' => 'https://example.com/author/jon/inbox',
|
||||
'name' => 'jon',
|
||||
'prefferedUsername' => 'jon',
|
||||
'preferredUsername' => 'jon',
|
||||
),
|
||||
'doe@example.org' => array(
|
||||
'id' => 'https://example.org/author/doe',
|
||||
'url' => 'https://example.org/author/doe',
|
||||
'inbox' => 'https://example.org/author/doe/inbox',
|
||||
'name' => 'doe',
|
||||
'prefferedUsername' => 'doe',
|
||||
'preferredUsername' => 'doe',
|
||||
),
|
||||
'sally@example.org' => array(
|
||||
'id' => 'http://sally.example.org',
|
||||
'url' => 'http://sally.example.org',
|
||||
'inbox' => 'http://sally.example.org/inbox',
|
||||
'name' => 'jon',
|
||||
'prefferedUsername' => 'jon',
|
||||
'preferredUsername' => 'jon',
|
||||
),
|
||||
'12345@example.com' => array(
|
||||
'id' => 'https://12345.example.com',
|
||||
'url' => 'https://12345.example.com',
|
||||
'inbox' => 'https://12345.example.com/inbox',
|
||||
'name' => '12345',
|
||||
'prefferedUsername' => '12345',
|
||||
'preferredUsername' => '12345',
|
||||
),
|
||||
'user2@example.com' => array(
|
||||
'id' => 'https://user2.example.com',
|
||||
'url' => 'https://user2.example.com',
|
||||
'inbox' => 'https://user2.example.com/inbox',
|
||||
'name' => 'user2',
|
||||
'prefferedUsername' => 'user2',
|
||||
'preferredUsername' => 'user2',
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -223,7 +223,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
|
|||
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' );
|
||||
|
||||
for ( $i = 1; $i <= 15; $i++ ) {
|
||||
add_post_meta( $follower->get__id(), 'errors', 'error ' . $i );
|
||||
add_post_meta( $follower->get__id(), 'activitypub_errors', 'error ' . $i );
|
||||
}
|
||||
|
||||
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' );
|
||||
|
@ -244,6 +244,29 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
|
|||
$this->assertEquals( 0, count( $followers ) );
|
||||
}
|
||||
|
||||
public function test_add_duplicate_follower() {
|
||||
$pre_http_request = new MockAction();
|
||||
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
|
||||
|
||||
$follower = 'https://12345.example.com';
|
||||
|
||||
\Activitypub\Collection\Followers::add_follower( 1, $follower );
|
||||
\Activitypub\Collection\Followers::add_follower( 1, $follower );
|
||||
\Activitypub\Collection\Followers::add_follower( 1, $follower );
|
||||
\Activitypub\Collection\Followers::add_follower( 1, $follower );
|
||||
\Activitypub\Collection\Followers::add_follower( 1, $follower );
|
||||
\Activitypub\Collection\Followers::add_follower( 1, $follower );
|
||||
|
||||
$db_followers = \Activitypub\Collection\Followers::get_followers( 1 );
|
||||
|
||||
$this->assertContains( $follower, $db_followers );
|
||||
|
||||
$follower = current( $db_followers );
|
||||
$meta = get_post_meta( $follower->get__id(), 'activitypub_user_id' );
|
||||
|
||||
$this->assertCount( 1, $meta );
|
||||
}
|
||||
|
||||
|
||||
public static function http_request_host_is_external( $in, $host ) {
|
||||
if ( in_array( $host, array( 'example.com', 'example.org' ), true ) ) {
|
||||
|
|
Loading…
Reference in a new issue