a follower is now a valid ActivityPub Actor

this helps with API handling
This commit is contained in:
Matthias Pfefferle 2023-06-23 14:54:29 +02:00
parent d47a048329
commit 5478be1355
14 changed files with 914 additions and 318 deletions

View file

@ -0,0 +1,562 @@
<?php
/**
* Inspired by the PHP ActivityPub Library by @Landrok
*
* @link https://github.com/landrok/activitypub
*/
namespace Activitypub\Activity;
use WP_Error;
use function Activitypub\camel_to_snake_case;
/**
* ObjectType 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.
*
* @see https://www.w3.org/TR/activitystreams-core/#object
*/
class Activity_Object {
/**
* The object's unique global identifier
*
* @see https://www.w3.org/TR/activitypub/#obj-id
*
* @var string
*/
protected $id;
/**
* @var string
*/
protected $type = 'Object';
/**
* A resource attached or related to an object that potentially
* requires special handling.
* The intent is to provide a model that is at least semantically
* similar to attachments in email.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $attachment;
/**
* One or more entities to which this object is attributed.
* The attributed entities might not be Actors. For instance, an
* object might be attributed to the completion of another activity.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attributedto
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $attributed_to;
/**
* One or more entities that represent the total population of
* entities for which the object can considered to be relevant.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-audience
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $audience;
/**
* The content or textual representation of the Object encoded as a
* JSON string. By default, the value of content is HTML.
* The mediaType property can be used in the object to indicate a
* different content type.
*
* The content MAY be expressed using multiple language-tagged
* values.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-content
*
* @var string|null
*/
protected $content;
/**
* 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;
/**
* The content MAY be expressed using multiple language-tagged
* values.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-content
*
* @var array|null
*/
protected $content_map;
/**
* A simple, human-readable, plain-text name for the object.
* HTML markup MUST NOT be included.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-name
*
* @var string|null xsd:string
*/
protected $name;
/**
* The name MAY be expressed using multiple language-tagged values.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-name
*
* @var array|null rdf:langString
*/
protected $name_map;
/**
* The date and time describing the actual or expected ending time
* of the object.
* When used with an Activity object, for instance, the endTime
* property specifies the moment the activity concluded or
* is expected to conclude.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-endtime
*
* @var string|null
*/
protected $end_time;
/**
* The entity (e.g. an application) that generated the object.
*
* @var string|null
*/
protected $generator;
/**
* An entity that describes an icon for this object.
* The image should have an aspect ratio of one (horizontal)
* to one (vertical) and should be suitable for presentation
* at a small size.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-icon
*
* @var string
* | Image
* | Link
* | array<Image>
* | array<Link>
* | null
*/
protected $icon;
/**
* An entity that describes an image for this object.
* Unlike the icon property, there are no aspect ratio
* or display size limitations assumed.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image-term
*
* @var string
* | Image
* | Link
* | array<Image>
* | array<Link>
* | null
*/
protected $image;
/**
* One or more entities for which this object is considered a
* response.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-inreplyto
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $in_reply_to;
/**
* One or more physical or logical locations associated with the
* object.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-location
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $location;
/**
* An entity that provides a preview of this object.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-preview
*
* @var string
* | ObjectType
* | Link
* | null
*/
protected $preview;
/**
* The date and time at which the object was published
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published
*
* @var string|null xsd:dateTime
*/
protected $published;
/**
* A Collection containing objects considered to be responses to
* this object.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-replies
*
* @var string
* | Collection
* | Link
* | null
*/
protected $replies;
/**
* The date and time describing the actual or expected starting time
* of the object.
* When used with an Activity object, for instance, the startTime
* property specifies the moment the activity began
* or is scheduled to begin.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-starttime
*
* @var string|null xsd:dateTime
*/
protected $start_time;
/**
* A natural language summarization of the object encoded as HTML.
* Multiple language tagged summaries MAY be provided.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-summary
*
* @var string
* | ObjectType
* | Link
* | null
*/
protected $summary;
/**
* The content MAY be expressed using multiple language-tagged
* values.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-summary
*
* @var array<string>|null
*/
protected $summary_map;
/**
* One or more "tags" that have been associated with an objects.
* A tag can be any kind of Object.
* The key difference between attachment and tag is that the former
* implies association by inclusion, while the latter implies
* associated by reference.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tag
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $tag;
/**
* The date and time at which the object was updated
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-updated
*
* @var string|null xsd:dateTime
*/
protected $updated;
/**
* One or more links to representations of the object.
*
* @var string
* | array<string>
* | Link
* | array<Link>
* | null
*/
protected $url;
/**
* An entity considered to be part of the public primary audience
* of an Object
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-to
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $to;
/**
* An Object that is part of the private primary audience of this
* Object.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-bto
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $bto;
/**
* An Object that is part of the public secondary audience of this
* Object.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-cc
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $cc;
/**
* One or more Objects that are part of the private secondary
* audience of this Object.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-bcc
*
* @var string
* | ObjectType
* | Link
* | array<ObjectType>
* | array<Link>
* | null
*/
protected $bcc;
/**
* The MIME media type of the value of the content property.
* If not specified, the content property is assumed to contain
* text/html content.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-mediatype
*
* @var string|null
*/
protected $media_type;
/**
* When the object describes a time-bound resource, such as an audio
* or video, a meeting, etc, the duration property indicates the
* object's approximate duration.
* The value MUST be expressed as an xsd:duration as defined by
* xmlschema11-2, section 3.3.6 (e.g. a period of 5 seconds is
* represented as "PT5S").
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
*
* @var string|null
*/
protected $duration;
/**
* Intended to convey some sort of source from which the content
* markup was derived, as a form of provenance, or to support
* future editing by clients.
*
* @see https://www.w3.org/TR/activitypub/#source-property
*
* @var ObjectType
*/
protected $source;
/**
* Magic function to implement getter and setter
*
* @param string $method The method name.
* @param string $params The method params.
*
* @return void
*/
public function __call( $method, $params ) {
$var = \strtolower( \substr( $method, 4 ) );
if ( \strncasecmp( $method, 'get', 3 ) === 0 ) {
if ( $this->has( $var ) ) {
return $this->get( $var );
}
return $this->$var;
}
if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
$this->set( $var, $params[0] );
}
if ( \strncasecmp( $method, 'add', 3 ) === 0 ) {
$this->add( $var, $params[0] );
}
}
/**
* Generic getter.
*
* @param string $key The key to get.
*
* @return mixed The value.
*/
public function get( $key ) {
if ( ! $this->has( $key ) ) {
return new WP_Error( 'invalid_key', 'Invalid key' );
}
return $this->$key;
}
/**
* Check if the object has a key
*
* @param string $key The key to check.
*
* @return boolean True if the object has the key.
*/
public function has( $key ) {
return property_exists( $this, $key );
}
/**
* Generic setter.
*
* @param string $key The key to set.
* @param string $value The value to set.
*
* @return mixed The value.
*/
public function set( $key, $value ) {
if ( ! $this->has( $key ) ) {
return new WP_Error( 'invalid_key', 'Invalid key' );
}
$this->$key = $value;
return $this->$key;
}
/**
* Generic adder.
*
* @param string $key The key to set.
* @param mixed $value The value to add.
*
* @return mixed The value.
*/
public function add( $key, $value ) {
if ( ! $this->has( $key ) ) {
return new WP_Error( 'invalid_key', 'Invalid key' );
}
if ( ! isset( $this->$key ) ) {
$this->$key = array();
}
$this->$key[] = $value;
return $this->$key;
}
/**
* Convert JSON input to an array.
*
* @return string The JSON string.
*
* @return array An Object built from the JSON string.
*/
public static function from_json( $json ) {
$array = wp_json_decode( $json, true );
return self::from_array( $array );
}
/**
* Convert JSON input to an array.
*
* @return string The object array.
*
* @return array An Object built from the JSON string.
*/
public static function from_array( $array ) {
$object = new static();
foreach ( $array as $key => $value ) {
$key = camel_to_snake_case( $key );
$object->set( $key, $value );
}
return $object;
}
}

View file

@ -0,0 +1,117 @@
<?php
/**
* Inspired by the PHP ActivityPub Library by @Landrok
*
* @link https://github.com/landrok/activitypub
*/
namespace Activitypub\Activity;
class Actor extends Activity_Object {
/**
* @var string
*/
protected $type = 'Object';
/**
* A reference to an ActivityStreams OrderedCollection comprised of
* all the messages received by the actor.
*
* @see https://www.w3.org/TR/activitypub/#inbox
*
* @var string
* | null
*/
protected $inbox;
/**
* A reference to an ActivityStreams OrderedCollection comprised of
* all the messages produced by the actor.
*
* @see https://www.w3.org/TR/activitypub/#outbox
*
* @var string
* | null
*/
protected $outbox;
/**
* A link to an ActivityStreams collection of the actors that this
* actor is following.
*
* @see https://www.w3.org/TR/activitypub/#following
*
* @var string
*/
protected $following;
/**
* A link to an ActivityStreams collection of the actors that
* follow this actor.
*
* @see https://www.w3.org/TR/activitypub/#followers
*
* @var string
*/
protected $followers;
/**
* A link to an ActivityStreams collection of objects this actor has
* liked.
*
* @see https://www.w3.org/TR/activitypub/#liked
*
* @var string
*/
protected $liked;
/**
* A list of supplementary Collections which may be of interest.
*
* @see https://www.w3.org/TR/activitypub/#streams-property
*
* @var array
*/
protected $streams = array();
/**
* A short username which may be used to refer to the actor, with no
* uniqueness guarantees.
*
* @see https://www.w3.org/TR/activitypub/#preferredUsername
*
* @var string|null
*/
protected $preferred_username;
/**
* A JSON object which maps additional typically server/domain-wide
* endpoints which may be useful either for this actor or someone
* referencing this actor. This mapping may be nested inside the
* actor document as the value or may be a link to a JSON-LD
* document with these properties.
*
* @see https://www.w3.org/TR/activitypub/#endpoints
*
* @var string|array|null
*/
protected $endpoints;
/**
* It's not part of the ActivityPub protocol but it's a quite common
* practice to handle an actor public key with a publicKey array:
* [
* 'id' => 'https://my-example.com/actor#main-key'
* 'owner' => 'https://my-example.com/actor',
* 'publicKeyPem' => '-----BEGIN PUBLIC KEY-----
* MIIBI [...]
* DQIDAQAB
* -----END PUBLIC KEY-----'
* ]
*
* @see https://www.w3.org/wiki/SocialCG/ActivityPub/Authentication_Authorization#Signing_requests_using_HTTP_Signatures
*
* @var string|array|null
*/
protected $public_key;
}

View file

@ -0,0 +1,20 @@
<?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 Activity_Object {
/**
* @var string
*/
protected $type = 'Person';
}

View file

@ -22,7 +22,8 @@ class Migration {
} }
public static function get_version() { public static function get_version() {
return get_option( 'activitypub_db_version', 0 ); return 0;
//return get_option( 'activitypub_db_version', 0 );
} }
/** /**
@ -71,19 +72,19 @@ class Migration {
foreach ( $followers as $actor ) { foreach ( $followers as $actor ) {
$meta = get_remote_metadata_by_actor( $actor ); $meta = get_remote_metadata_by_actor( $actor );
$follower = new Follower( $actor );
if ( is_tombstone( $meta ) ) { if ( is_tombstone( $meta ) ) {
continue; continue;
} if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) { }
$follower = Follower::from_array( $meta );
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
$follower->set_error( $meta ); $follower->set_error( $meta );
} else {
$follower->from_meta( $meta );
} }
$follower->upsert(); $follower->upsert();
add_post_meta( $follower->get_id(), 'user_id', $user_id ); add_post_meta( $follower->get__id(), '_user_id', $user_id );
} }
} }
} }

View file

@ -100,7 +100,7 @@ class Scheduler {
$followers = Followers::get_outdated_followers(); $followers = Followers::get_outdated_followers();
foreach ( $followers as $follower ) { foreach ( $followers as $follower ) {
$meta = get_remote_metadata_by_actor( $follower->get_actor(), true ); $meta = get_remote_metadata_by_actor( $follower->get_url(), true );
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) { if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
$follower->set_error( $meta ); $follower->set_error( $meta );
@ -121,7 +121,7 @@ class Scheduler {
$followers = Followers::get_faulty_followers(); $followers = Followers::get_faulty_followers();
foreach ( $followers as $follower ) { foreach ( $followers as $follower ) {
$meta = get_remote_metadata_by_actor( $follower->get_actor(), true ); $meta = get_remote_metadata_by_actor( $follower->get_url(), true );
if ( is_tombstone( $meta ) ) { if ( is_tombstone( $meta ) ) {
$follower->delete(); $follower->delete();

View file

@ -62,19 +62,7 @@ class Followers {
register_post_meta( register_post_meta(
self::POST_TYPE, self::POST_TYPE,
'name', 'preferred_username',
array(
'type' => 'string',
'single' => true,
'sanitize_callback' => function( $value ) {
return sanitize_user( $value );
},
)
);
register_post_meta(
self::POST_TYPE,
'username',
array( array(
'type' => 'string', 'type' => 'string',
'single' => true, 'single' => true,
@ -86,11 +74,11 @@ class Followers {
register_post_meta( register_post_meta(
self::POST_TYPE, self::POST_TYPE,
'avatar', 'icon',
array( array(
'type' => 'string', //'type' => 'string',
'single' => true, 'single' => true,
'sanitize_callback' => array( self::class, 'sanitize_url' ), //'sanitize_callback' => array( self::class, 'sanitize_url' ),
) )
); );
@ -116,7 +104,7 @@ class Followers {
register_post_meta( register_post_meta(
self::POST_TYPE, self::POST_TYPE,
'shared_inbox', '_shared_inbox',
array( array(
'type' => 'string', 'type' => 'string',
'single' => true, 'single' => true,
@ -126,7 +114,7 @@ class Followers {
register_post_meta( register_post_meta(
self::POST_TYPE, self::POST_TYPE,
'updated_at', 'updated',
array( array(
'type' => 'string', 'type' => 'string',
'single' => true, 'single' => true,
@ -142,7 +130,7 @@ class Followers {
register_post_meta( register_post_meta(
self::POST_TYPE, self::POST_TYPE,
'errors', '_errors',
array( array(
'type' => 'string', 'type' => 'string',
'single' => false, 'single' => false,
@ -156,6 +144,18 @@ class Followers {
) )
); );
register_post_meta(
self::POST_TYPE,
'_actor',
array(
'type' => 'string',
'single' => false,
'sanitize_callback' => function( $value ) {
return esc_sql( $value );
},
)
);
do_action( 'activitypub_after_register_post_type' ); do_action( 'activitypub_after_register_post_type' );
} }
@ -213,14 +213,13 @@ class Followers {
return $meta; return $meta;
} }
$follower = new Follower( $actor ); $follower = Follower::from_array( $meta );
$follower->from_meta( $meta );
$follower->upsert(); $follower->upsert();
$meta = get_post_meta( $follower->get_id(), 'user_id' ); $meta = get_post_meta( $follower->get__id(), '_user_id' );
if ( is_array( $meta ) && ! in_array( $user_id, $meta, true ) ) { if ( is_array( $meta ) && ! in_array( $user_id, $meta, true ) ) {
add_post_meta( $follower->get_id(), 'user_id', $user_id ); add_post_meta( $follower->get__id(), '_user_id', $user_id );
wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
} }
@ -244,7 +243,7 @@ class Followers {
return false; return false;
} }
return delete_post_meta( $follower->get_id(), 'user_id', $user_id ); return delete_post_meta( $follower->get__id(), '_user_id', $user_id );
} }
/** /**
@ -260,7 +259,7 @@ class Followers {
$post_id = $wpdb->get_var( $post_id = $wpdb->get_var(
$wpdb->prepare( $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 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",
array( array(
esc_sql( self::POST_TYPE ), esc_sql( self::POST_TYPE ),
esc_sql( $user_id ), esc_sql( $user_id ),
@ -271,7 +270,7 @@ class Followers {
if ( $post_id ) { if ( $post_id ) {
$post = get_post( $post_id ); $post = get_post( $post_id );
return new Follower( $post ); return Follower::from_custom_post_type( $post );
} }
return null; return null;
@ -295,7 +294,7 @@ class Followers {
} }
if ( isset( $object['user_id'] ) ) { if ( isset( $object['user_id'] ) ) {
unset( $object['user_id'] ); unset( $object['_user_id'] );
unset( $object['@context'] ); unset( $object['@context'] );
} }
@ -304,7 +303,7 @@ class Followers {
// send "Accept" activity // send "Accept" activity
$activity = new Activity( 'Accept' ); $activity = new Activity( 'Accept' );
$activity->set_object( $object ); $activity->set_activity_object( $object );
$activity->set_actor( \get_author_posts_url( $user_id ) ); $activity->set_actor( \get_author_posts_url( $user_id ) );
$activity->set_to( $actor ); $activity->set_to( $actor );
$activity->set_id( \get_author_posts_url( $user_id ) . '#follow-' . \preg_replace( '~^https?://~', '', $actor ) ); $activity->set_id( \get_author_posts_url( $user_id ) . '#follow-' . \preg_replace( '~^https?://~', '', $actor ) );
@ -332,7 +331,7 @@ class Followers {
'order' => 'DESC', 'order' => 'DESC',
'meta_query' => array( 'meta_query' => array(
array( array(
'key' => 'user_id', 'key' => '_user_id',
'value' => $user_id, 'value' => $user_id,
), ),
), ),
@ -343,7 +342,7 @@ class Followers {
$items = array(); $items = array();
foreach ( $query->get_posts() as $post ) { foreach ( $query->get_posts() as $post ) {
$items[] = new Follower( $post ); // phpcs:ignore $items[] = Follower::from_custom_post_type( $post ); // phpcs:ignore
} }
return $items; return $items;
@ -377,7 +376,7 @@ class Followers {
'fields' => 'ids', 'fields' => 'ids',
'meta_query' => array( 'meta_query' => array(
array( array(
'key' => 'user_id', 'key' => '_user_id',
'value' => $user_id, 'value' => $user_id,
), ),
), ),
@ -409,11 +408,11 @@ class Followers {
'fields' => 'ids', 'fields' => 'ids',
'meta_query' => array( 'meta_query' => array(
array( array(
'key' => 'inbox', 'key' => '_shared_inbox',
'compare' => 'EXISTS', 'compare' => 'EXISTS',
), ),
array( array(
'key' => 'user_id', 'key' => '_user_id',
'value' => $user_id, 'value' => $user_id,
), ),
), ),
@ -431,7 +430,7 @@ class Followers {
$wpdb->prepare( $wpdb->prepare(
"SELECT DISTINCT meta_value FROM {$wpdb->postmeta} "SELECT DISTINCT meta_value FROM {$wpdb->postmeta}
WHERE post_id IN (" . implode( ', ', array_fill( 0, count( $posts ), '%d' ) ) . ") WHERE post_id IN (" . implode( ', ', array_fill( 0, count( $posts ), '%d' ) ) . ")
AND meta_key = 'shared_inbox' AND meta_key = '_shared_inbox'
AND meta_value IS NOT NULL", AND meta_value IS NOT NULL",
$posts $posts
) )
@ -471,7 +470,7 @@ class Followers {
$items = array(); $items = array();
foreach ( $posts->get_posts() as $follower ) { foreach ( $posts->get_posts() as $follower ) {
$items[] = new Follower( $follower ); // phpcs:ignore $items[] = Follower::from_custom_post_type( $follower ); // phpcs:ignore
} }
return $items; return $items;
@ -501,7 +500,7 @@ class Followers {
$items = array(); $items = array();
foreach ( $posts->get_posts() as $follower ) { foreach ( $posts->get_posts() as $follower ) {
$items[] = new Follower( $follower ); // phpcs:ignore $items[] = Follower::from_custom_post_type( $follower ); // phpcs:ignore
} }
return $items; return $items;

View file

@ -237,6 +237,17 @@ function get_rest_url_by_path( $path = '' ) {
return \get_rest_url( null, $namespaced_path ); return \get_rest_url( null, $namespaced_path );
} }
/**
* Convert a string from camelCase to snake_case.
*
* @param string $string The string to convert.
*
* @return string The converted string.
*/
function camel_to_snake_case( $string ) {
return strtolower( preg_replace( '/(?<!^)[A-Z]/', '_$0', $string ) );
}
/** /**
* Check if a request is for an ActivityPub request. * Check if a request is for an ActivityPub request.
* *

View file

@ -2,6 +2,7 @@
namespace Activitypub\Model; namespace Activitypub\Model;
use WP_Query; use WP_Query;
use Activitypub\Activity\Actor;
use Activitypub\Collection\Followers; use Activitypub\Collection\Followers;
/** /**
@ -15,81 +16,27 @@ use Activitypub\Collection\Followers;
* *
* @see https://www.w3.org/TR/activitypub/#follow-activity-inbox * @see https://www.w3.org/TR/activitypub/#follow-activity-inbox
*/ */
class Follower { class Follower extends Actor {
/** /**
* The Object ID * The complete Remote-Profile of the Follower
* *
* @var int * @var array
*/ */
private $id; protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/**
* The Actor-URL of the Follower
*
* @var string
*/
private $actor;
/**
* The Object Name
*
* This is the same as the Actor-URL
*
* @var string
*/
private $name;
/**
* The Username
*
* @var string
*/
private $username;
/**
* The User-Summary
*
* @var string
*/
private $summary;
/**
* The Avatar URL
*
* @var string
*/
private $avatar;
/**
* The URL to the Followers Inbox
*
* @var string
*/
private $inbox;
/**
* The URL to the Servers Shared-Inbox
*
* If the Server does not support Shared-Inboxes,
* the Inbox will be stored.
*
* @var string
*/
private $shared_inbox;
/**
* The date, the Follower was updated
*
* @var string untixtimestamp
*/
private $updated_at;
/** /**
* The complete Remote-Profile of the Follower * The complete Remote-Profile of the Follower
* *
* @var array * @var array
*/ */
private $meta; 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. * The latest received error.
@ -98,89 +45,14 @@ class Follower {
* *
* @var string * @var string
*/ */
private $error; protected $_error; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/** /**
* A list of errors * A list of errors
* *
* @var array * @var array
*/ */
private $errors; protected $_errors; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/**
* The WordPress User ID, or 0 for whole site.
* @var int
*/
private $user_id;
/**
* Maps the meta fields to the local db fields
*
* @var array
*/
private $map_meta = array(
'name' => 'name',
'preferredUsername' => 'username',
'inbox' => 'inbox',
);
/**
* Constructor
*
* @param string|WP_Post $actor The Actor-URL or WP_Post Object.
* @param int $user_id The WordPress User ID. 0 Represents the whole site.
*/
public function __construct( $actor ) {
$post = null;
if ( \is_a( $actor, 'WP_Post' ) ) {
$post = $actor;
} else {
global $wpdb;
$post_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
esc_sql( $actor )
)
);
if ( $post_id ) {
$post = get_post( $post_id );
} else {
$this->actor = $actor;
}
}
if ( $post ) {
$this->id = $post->ID;
$this->actor = $post->guid;
$this->updated_at = $post->post_modified;
}
}
/**
* Magic function to implement getter and setter
*
* @param string $method The method name.
* @param string $params The method params.
*
* @return void
*/
public function __call( $method, $params ) {
$var = \strtolower( \substr( $method, 4 ) );
if ( \strncasecmp( $method, 'get', 3 ) === 0 ) {
if ( empty( $this->$var ) ) {
return $this->get( $var );
}
return $this->$var;
}
if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
$this->$var = $params[0];
}
}
/** /**
* Magic function to return the Actor-URL when the Object is used as a string * Magic function to return the Actor-URL when the Object is used as a string
@ -188,64 +60,7 @@ class Follower {
* @return string * @return string
*/ */
public function __toString() { public function __toString() {
return $this->get_actor(); return $this->get_url();
}
/**
* Prefill the Object with the meta data.
*
* @param array $meta The meta data.
*
* @return void
*/
public function from_meta( $meta ) {
$this->meta = $meta;
foreach ( $this->map_meta as $remote => $internal ) {
if ( ! empty( $meta[ $remote ] ) ) {
$this->$internal = $meta[ $remote ];
}
}
if ( ! empty( $meta['icon']['url'] ) ) {
$this->avatar = $meta['icon']['url'];
}
if ( ! empty( $meta['endpoints']['sharedInbox'] ) ) {
$this->shared_inbox = $meta['endpoints']['sharedInbox'];
} elseif ( ! empty( $meta['inbox'] ) ) {
$this->shared_inbox = $meta['inbox'];
}
$this->updated_at = \time();
}
/**
* Get the data by the given attribute
*
* @param string $attribute The attribute name.
*
* @return mixed The attribute value.
*/
public function get( $attribute ) {
if ( ! is_null( $this->$attribute ) ) {
return $this->$attribute;
}
$attribute_value = get_post_meta( $this->id, $attribute, true );
if ( $attribute_value ) {
$this->$attribute = $attribute_value;
return $attribute_value;
}
$attribute_value = $this->get_meta_by( $attribute );
if ( $attribute_value ) {
$this->$attribute = $attribute_value;
return $attribute_value;
}
return null;
} }
/** /**
@ -256,8 +71,8 @@ class Follower {
* @return void * @return void
*/ */
public function set_error( $error ) { public function set_error( $error ) {
$this->errors = array(); $this->_errors = array();
$this->error = $error; $this->_error = $error;
} }
/** /**
@ -266,12 +81,12 @@ class Follower {
* @return mixed * @return mixed
*/ */
public function get_errors() { public function get_errors() {
if ( $this->errors ) { if ( $this->_errors ) {
return $this->errors; return $this->_errors;
} }
$this->errors = get_post_meta( $this->id, 'errors' ); $this->_errors = get_post_meta( $this->_id, 'errors' );
return $this->errors; return $this->_errors;
} }
/** /**
@ -280,7 +95,7 @@ class Follower {
* @return void * @return void
*/ */
public function reset_errors() { public function reset_errors() {
delete_post_meta( $this->id, 'errors' ); delete_post_meta( $this->_id, 'errors' );
} }
/** /**
@ -289,7 +104,7 @@ class Follower {
* @return int The number of errors. * @return int The number of errors.
*/ */
public function count_errors() { public function count_errors() {
$errors = $this->get_errors(); $errors = $this->get__errors();
if ( is_array( $errors ) && ! empty( $errors ) ) { if ( is_array( $errors ) && ! empty( $errors ) ) {
return count( $errors ); return count( $errors );
@ -298,13 +113,21 @@ class Follower {
return 0; return 0;
} }
public function get_url() {
if ( ! $this->url ) {
return $this->id;
}
return $this->url;
}
/** /**
* Return the latest error message. * Return the latest error message.
* *
* @return string The error message. * @return string The error message.
*/ */
public function get_latest_error_message() { public function get_latest_error_message() {
$errors = $this->get_errors(); $errors = $this->get__errors();
if ( is_array( $errors ) && ! empty( $errors ) ) { if ( is_array( $errors ) && ! empty( $errors ) ) {
return reset( $errors ); return reset( $errors );
@ -313,35 +136,12 @@ class Follower {
return ''; return '';
} }
/**
* Get the meta data by the given attribute.
*
* @param string $attribute The attribute name.
*
* @return mixed $attribute The attribute value.
*/
public function get_meta_by( $attribute ) {
$meta = $this->get_meta();
if ( ! is_array( $meta ) ) {
return null;
}
// try mapped data (see $this->map_meta)
foreach ( $this->map_meta as $remote => $local ) {
if ( $attribute === $local && isset( $meta[ $remote ] ) ) {
return $meta[ $remote ];
}
}
return null;
}
/** /**
* Update the current Follower-Object. * Update the current Follower-Object.
* *
* @return void * @return void
*/ */
public function update() { public function update() {
$this->updated_at = \time();
$this->save(); $this->save();
} }
@ -352,18 +152,18 @@ class Follower {
*/ */
public function save() { public function save() {
$args = array( $args = array(
'ID' => $this->id, 'ID' => $this->get__id(),
'guid' => $this->actor, 'guid' => $this->get_id(),
'post_title' => $this->get_name(), 'post_title' => $this->get_name(),
'post_author' => 0, 'post_author' => 0,
'post_type' => Followers::POST_TYPE, 'post_type' => Followers::POST_TYPE,
'post_content' => $this->get_summary(), 'post_content' => $this->get_summary(),
'post_status' => 'publish', 'post_status' => 'publish',
'post_modified' => gmdate( 'Y-m-d H:i:s', $this->updated_at ),
'meta_input' => $this->get_post_meta_input(), 'meta_input' => $this->get_post_meta_input(),
); );
$post_id = wp_insert_post( $args ); $post_id = wp_insert_post( $args );
$this->id = $post_id; $this->_id = $post_id;
} }
/** /**
@ -372,7 +172,7 @@ class Follower {
* @return void * @return void
*/ */
public function upsert() { public function upsert() {
if ( $this->id ) { if ( $this->_id ) {
$this->update(); $this->update();
} else { } else {
$this->save(); $this->save();
@ -385,7 +185,7 @@ class Follower {
* @return void * @return void
*/ */
public function delete() { public function delete() {
wp_delete_post( $this->id ); wp_delete_post( $this->_id );
} }
/** /**
@ -394,7 +194,7 @@ class Follower {
* @return void * @return void
*/ */
protected function get_post_meta_input() { protected function get_post_meta_input() {
$attributes = array( 'inbox', 'shared_inbox', 'avatar', 'name', 'username', 'meta' ); $attributes = array( 'inbox', '_shared_inbox', 'icon', 'preferred_username', '_actor', 'url' );
$meta_input = array(); $meta_input = array();
@ -404,18 +204,94 @@ class Follower {
} }
} }
if ( $this->error ) { if ( $this->_error ) {
if ( is_string( $this->error ) ) { if ( is_string( $this->_error ) ) {
$error = $this->error; $_error = $this->_error;
} elseif ( is_wp_error( $this->error ) ) { } elseif ( is_wp_error( $this->_error ) ) {
$error = $this->error->get_error_message(); $_error = $this->_error->get_error_message();
} else { } else {
$error = __( 'Unknown Error or misconfigured Error-Message', 'activitypub' ); $_error = __( 'Unknown Error or misconfigured Error-Message', 'activitypub' );
} }
$meta_input['errors'] = array( $error ); $meta_input['_errors'] = $_error;
} }
return $meta_input; return $meta_input;
} }
/**
* Get the Icon URL (Avatar)
*
* @return string The URL to the Avatar.
*/
public function get_icon_url() {
$icon = $this->get_icon();
if ( ! $icon ) {
return '';
}
if ( is_array( $icon ) ) {
return $icon['url'];
}
return $icon;
}
/**
* Converts an ActivityPub Array to an Follower Object.
*
* @param array $array The ActivityPub Array.
*
* @return Activitypub\Model\Follower The Follower Object.
*/
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 );
}
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;
}
/**
* Convert a Custom-Post-Type input to an Activitypub\Model\Follower.
*
* @return string The JSON string.
*
* @return array Activitypub\Model\Follower
*/
public static function from_custom_post_type( $post ) {
$object = new static();
$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 ) ) );
return $object;
}
} }

View file

@ -86,7 +86,7 @@ class Followers {
// phpcs:ignore // phpcs:ignore
$json->orderedItems = array_map( $json->orderedItems = array_map(
function( $item ) { function( $item ) {
return $item->get_actor(); return $item->get_url();
}, },
FollowerCollection::get_followers( $user_id ) FollowerCollection::get_followers( $user_id )
); );

View file

@ -16,9 +16,9 @@ class Followers extends WP_List_Table {
'name' => \__( 'Name', 'activitypub' ), 'name' => \__( 'Name', 'activitypub' ),
'username' => \__( 'Username', 'activitypub' ), 'username' => \__( 'Username', 'activitypub' ),
'identifier' => \__( 'Identifier', 'activitypub' ), 'identifier' => \__( 'Identifier', 'activitypub' ),
'updated_at' => \__( 'Last updated', 'activitypub' ), 'updated' => \__( 'Last updated', 'activitypub' ),
'errors' => \__( 'Errors', 'activitypub' ), //'errors' => \__( 'Errors', 'activitypub' ),
'latest-error' => \__( 'Latest Error Message', 'activitypub' ), //'latest-error' => \__( 'Latest Error Message', 'activitypub' ),
); );
} }
@ -50,11 +50,11 @@ class Followers extends WP_List_Table {
foreach ( $followers as $follower ) { foreach ( $followers as $follower ) {
$item = array( $item = array(
'avatar' => esc_attr( $follower->get_avatar() ), 'icon' => esc_attr( $follower->get_icon_url() ),
'name' => esc_attr( $follower->get_name() ), 'name' => esc_attr( $follower->get_name() ),
'username' => esc_attr( $follower->get_username() ), 'username' => esc_attr( $follower->get_preferred_username() ),
'identifier' => esc_attr( $follower->get_actor() ), 'identifier' => esc_attr( $follower->get_url() ),
'updated_at' => esc_attr( $follower->get_updated_at() ), 'updated' => esc_attr( $follower->get_updated() ),
'errors' => $follower->count_errors(), 'errors' => $follower->count_errors(),
'latest-error' => $follower->get_latest_error_message(), 'latest-error' => $follower->get_latest_error_message(),
); );
@ -79,7 +79,7 @@ class Followers extends WP_List_Table {
public function column_avatar( $item ) { public function column_avatar( $item ) {
return sprintf( return sprintf(
'<img src="%s" width="25px;" />', '<img src="%s" width="25px;" />',
$item['avatar'] $item['icon']
); );
} }

View file

@ -1,16 +1,16 @@
<div class="wrap"> <div class="wrap">
<h1><?php \esc_html_e( 'Followers', 'activitypub' ); ?></h1> <h1><?php \esc_html_e( 'Followers', 'activitypub' ); ?></h1>
<?php Activitypub\Migration::maybe_migrate(); ?>
<?php // translators: ?> <?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> <p><?php \printf( \esc_html__( 'You currently have %s followers.', 'activitypub' ), \esc_attr( \Activitypub\Collection\Followers::count_followers( \get_current_user_id() ) ) ); ?></p>
<?php $token_table = new \Activitypub\Table\Followers(); ?> <?php $table = new \Activitypub\Table\Followers(); ?>
<form method="get"> <form method="get">
<input type="hidden" name="page" value="activitypub-followers-list" /> <input type="hidden" name="page" value="activitypub-followers-list" />
<?php <?php
$token_table->prepare_items(); $table->prepare_items();
$token_table->display(); $table->display();
?> ?>
<?php wp_nonce_field( 'activitypub-followers-list', '_apnonce' ); ?> <?php wp_nonce_field( 'activitypub-followers-list', '_apnonce' ); ?>
</form> </form>

View file

@ -2,12 +2,14 @@
class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HTTP { class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HTTP {
public static $users = array( public static $users = array(
'username@example.org' => array( 'username@example.org' => array(
'id' => 'https://example.org/users/username',
'url' => 'https://example.org/users/username', 'url' => 'https://example.org/users/username',
'inbox' => 'https://example.org/users/username/inbox', 'inbox' => 'https://example.org/users/username/inbox',
'name' => 'username', 'name' => 'username',
'prefferedUsername' => 'username', 'prefferedUsername' => 'username',
), ),
'jon@example.com' => array( 'jon@example.com' => array(
'id' => 'https://example.com/author/jon',
'url' => 'https://example.com/author/jon', 'url' => 'https://example.com/author/jon',
'inbox' => 'https://example.com/author/jon/inbox', 'inbox' => 'https://example.com/author/jon/inbox',
'name' => 'jon', 'name' => 'jon',
@ -56,6 +58,7 @@ class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HT
); );
self::$users['https://example.com/alex'] = array( self::$users['https://example.com/alex'] = array(
'id' => 'https://example.com/alex',
'url' => 'https://example.com/alex', 'url' => 'https://example.com/alex',
'inbox' => 'https://example.com/alex/inbox', 'inbox' => 'https://example.com/alex/inbox',
'name' => 'alex', 'name' => 'alex',

View file

@ -2,6 +2,7 @@
class Test_Activitypub_Mention extends ActivityPub_TestCase_Cache_HTTP { class Test_Activitypub_Mention extends ActivityPub_TestCase_Cache_HTTP {
public static $users = array( public static $users = array(
'username@example.org' => array( 'username@example.org' => array(
'id' => 'https://example.org/users/username',
'url' => 'https://example.org/users/username', 'url' => 'https://example.org/users/username',
'name' => 'username', 'name' => 'username',
), ),

View file

@ -2,36 +2,42 @@
class Test_Db_Activitypub_Followers extends WP_UnitTestCase { class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
public static $users = array( public static $users = array(
'username@example.org' => array( 'username@example.org' => array(
'id' => 'https://example.org/users/username',
'url' => 'https://example.org/users/username', 'url' => 'https://example.org/users/username',
'inbox' => 'https://example.org/users/username/inbox', 'inbox' => 'https://example.org/users/username/inbox',
'name' => 'username', 'name' => 'username',
'prefferedUsername' => 'username', 'prefferedUsername' => 'username',
), ),
'jon@example.com' => array( 'jon@example.com' => array(
'id' => 'https://example.com/author/jon',
'url' => 'https://example.com/author/jon', 'url' => 'https://example.com/author/jon',
'inbox' => 'https://example.com/author/jon/inbox', 'inbox' => 'https://example.com/author/jon/inbox',
'name' => 'jon', 'name' => 'jon',
'prefferedUsername' => 'jon', 'prefferedUsername' => 'jon',
), ),
'doe@example.org' => array( 'doe@example.org' => array(
'id' => 'https://example.org/author/doe',
'url' => 'https://example.org/author/doe', 'url' => 'https://example.org/author/doe',
'inbox' => 'https://example.org/author/doe/inbox', 'inbox' => 'https://example.org/author/doe/inbox',
'name' => 'doe', 'name' => 'doe',
'prefferedUsername' => 'doe', 'prefferedUsername' => 'doe',
), ),
'sally@example.org' => array( 'sally@example.org' => array(
'id' => 'http://sally.example.org',
'url' => 'http://sally.example.org', 'url' => 'http://sally.example.org',
'inbox' => 'http://sally.example.org/inbox', 'inbox' => 'http://sally.example.org/inbox',
'name' => 'jon', 'name' => 'jon',
'prefferedUsername' => 'jon', 'prefferedUsername' => 'jon',
), ),
'12345@example.com' => array( '12345@example.com' => array(
'id' => 'https://12345.example.com',
'url' => 'https://12345.example.com', 'url' => 'https://12345.example.com',
'inbox' => 'https://12345.example.com/inbox', 'inbox' => 'https://12345.example.com/inbox',
'name' => '12345', 'name' => '12345',
'prefferedUsername' => '12345', 'prefferedUsername' => '12345',
), ),
'user2@example.com' => array( 'user2@example.com' => array(
'id' => 'https://user2.example.com',
'url' => 'https://user2.example.com', 'url' => 'https://user2.example.com',
'inbox' => 'https://user2.example.com/inbox', 'inbox' => 'https://user2.example.com/inbox',
'name' => 'user2', 'name' => 'user2',
@ -66,7 +72,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$db_followers = array_map( $db_followers = array_map(
function( $item ) { function( $item ) {
return $item->get_actor(); return $item->get_url();
}, },
$db_followers $db_followers
); );
@ -107,7 +113,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
} }
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower->get_actor() ); $this->assertEquals( 'https://example.com/author/jon', $follower->get_url() );
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' );
$this->assertNull( $follower ); $this->assertNull( $follower );
@ -116,10 +122,10 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$this->assertNull( $follower ); $this->assertNull( $follower );
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower->get_actor() ); $this->assertEquals( 'https://example.com/author/jon', $follower->get_url() );
$follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://user2.example.com' ); $follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://user2.example.com' );
$this->assertEquals( 'https://user2.example.com', $follower2->get_actor() ); $this->assertEquals( 'https://user2.example.com', $follower2->get_url() );
} }
public function test_delete_follower() { public function test_delete_follower() {
@ -144,13 +150,13 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
} }
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower->get_actor() ); $this->assertEquals( 'https://example.com/author/jon', $follower->get_url() );
$followers = \Activitypub\Collection\Followers::get_followers( 1 ); $followers = \Activitypub\Collection\Followers::get_followers( 1 );
$this->assertEquals( 2, count( $followers ) ); $this->assertEquals( 2, count( $followers ) );
$follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://example.com/author/jon' ); $follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower2->get_actor() ); $this->assertEquals( 'https://example.com/author/jon', $follower2->get_url() );
\Activitypub\Collection\Followers::remove_follower( 1, 'https://example.com/author/jon' ); \Activitypub\Collection\Followers::remove_follower( 1, 'https://example.com/author/jon' );
@ -158,7 +164,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$this->assertNull( $follower ); $this->assertNull( $follower );
$follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://example.com/author/jon' ); $follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower2->get_actor() ); $this->assertEquals( 'https://example.com/author/jon', $follower2->get_url() );
$followers = \Activitypub\Collection\Followers::get_followers( 1 ); $followers = \Activitypub\Collection\Followers::get_followers( 1 );
$this->assertEquals( 1, count( $followers ) ); $this->assertEquals( 1, count( $followers ) );
@ -174,7 +180,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
\Activitypub\Collection\Followers::add_follower( 1, $follower ); \Activitypub\Collection\Followers::add_follower( 1, $follower );
} }
$follower = new \Activitypub\Model\Follower( 'https://example.com/author/jon' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' );
global $wpdb; global $wpdb;
@ -184,7 +190,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$post_modified = gmdate( $mysql_time_format, $time ); $post_modified = gmdate( $mysql_time_format, $time );
$post_modified_gmt = gmdate( $mysql_time_format, ( $time + get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ); $post_modified_gmt = gmdate( $mysql_time_format, ( $time + get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) );
$post_id = $follower->get_id(); $post_id = $follower->get__id();
$wpdb->query( $wpdb->query(
$wpdb->prepare( $wpdb->prepare(
@ -214,13 +220,13 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
\Activitypub\Collection\Followers::add_follower( 1, $follower ); \Activitypub\Collection\Followers::add_follower( 1, $follower );
} }
$follower = new \Activitypub\Model\Follower( 'http://sally.example.org' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' );
for ( $i = 1; $i <= 15; $i++ ) { for ( $i = 1; $i <= 15; $i++ ) {
add_post_meta( $follower->get_id(), 'errors', 'error ' . $i ); add_post_meta( $follower->get__id(), 'errors', 'error ' . $i );
} }
$follower = new \Activitypub\Model\Follower( 'http://sally.example.org' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' );
$count = $follower->count_errors(); $count = $follower->count_errors();
$followers = \Activitypub\Collection\Followers::get_faulty_followers(); $followers = \Activitypub\Collection\Followers::get_faulty_followers();
@ -230,7 +236,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$follower->reset_errors(); $follower->reset_errors();
$follower = new \Activitypub\Model\Follower( 'http://sally.example.org' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' );
$count = $follower->count_errors(); $count = $follower->count_errors();
$followers = \Activitypub\Collection\Followers::get_faulty_followers(); $followers = \Activitypub\Collection\Followers::get_faulty_followers();