Merge pull request #347 from Automattic/try/posts-for-followers

Followers: use custom post types and postmeta to store
This commit is contained in:
Matthias Pfefferle 2023-06-21 17:49:24 +02:00 committed by GitHub
commit a69afb5f89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 331 additions and 262 deletions

View file

@ -151,17 +151,6 @@ add_action(
0 0
); );
add_action(
'plugins_loaded',
function() {
if ( defined( 'WP_SWEEP_VERSION' ) ) {
require_once \dirname( __FILE__ ) . '/integration/class-wp-sweep.php';
Integration\Wp_Sweep::init();
}
},
0
);
/** /**
* `get_plugin_data` wrapper * `get_plugin_data` wrapper
* *

View file

@ -6,11 +6,19 @@ services:
environment: environment:
MYSQL_DATABASE: activitypub-test MYSQL_DATABASE: activitypub-test
MYSQL_ROOT_PASSWORD: activitypub-test MYSQL_ROOT_PASSWORD: activitypub-test
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3306"]
interval: 5s
timeout: 2s
retries: 5
test-php: test-php:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on:
test-db:
condition: service_healthy
links: links:
- test-db - test-db
volumes: volumes:

View file

@ -83,7 +83,7 @@ class Migration {
$follower->upsert(); $follower->upsert();
$result = wp_set_object_terms( $user_id, $follower->get_actor(), Followers::TAXONOMY, true ); add_post_meta( $follower->get_id(), 'user_id', $user_id );
} }
} }
} }

View file

@ -3,7 +3,7 @@ namespace Activitypub\Collection;
use WP_Error; use WP_Error;
use Exception; use Exception;
use WP_Term_Query; use WP_Query;
use Activitypub\Http; use Activitypub\Http;
use Activitypub\Webfinger; use Activitypub\Webfinger;
use Activitypub\Model\Activity; use Activitypub\Model\Activity;
@ -15,10 +15,11 @@ use function Activitypub\get_remote_metadata_by_actor;
/** /**
* ActivityPub Followers Collection * ActivityPub Followers Collection
* *
* @author Matt Wiebe
* @author Matthias Pfefferle * @author Matthias Pfefferle
*/ */
class Followers { class Followers {
const TAXONOMY = 'activitypub-followers'; const POST_TYPE = 'ap_follower';
const CACHE_KEY_INBOXES = 'follower_inboxes_%s'; const CACHE_KEY_INBOXES = 'follower_inboxes_%s';
/** /**
@ -27,8 +28,8 @@ class Followers {
* @return void * @return void
*/ */
public static function init() { public static function init() {
// register "followers" taxonomy // register "followers" post_type
self::register_taxonomy(); self::register_post_type();
\add_action( 'activitypub_inbox_follow', array( self::class, 'handle_follow_request' ), 10, 2 ); \add_action( 'activitypub_inbox_follow', array( self::class, 'handle_follow_request' ), 10, 2 );
\add_action( 'activitypub_inbox_undo', array( self::class, 'handle_undo_request' ), 10, 2 ); \add_action( 'activitypub_inbox_undo', array( self::class, 'handle_undo_request' ), 10, 2 );
@ -41,31 +42,26 @@ class Followers {
* *
* @return void * @return void
*/ */
public static function register_taxonomy() { private static function register_post_type() {
$args = array( register_post_type(
'labels' => array( self::POST_TYPE,
'name' => _x( 'Followers', 'taxonomy general name', 'activitypub' ), array(
'singular_name' => _x( 'Followers', 'taxonomy singular name', 'activitypub' ), 'labels' => array(
'menu_name' => __( 'Followers', 'activitypub' ), 'name' => _x( 'Followers', 'post_type plural name', 'activitypub' ),
), 'singular_name' => _x( 'Follower', 'post_type single name', 'activitypub' ),
'hierarchical' => false, ),
'show_ui' => false, 'public' => false,
'show_in_menu' => false, 'hierarchical' => false,
'show_in_nav_menus' => false, 'rewrite' => false,
'show_admin_column' => false, 'query_var' => false,
'query_var' => false, 'delete_with_user' => false,
'rewrite' => false, 'can_export' => true,
'public' => false, 'supports' => array(),
'capabilities' => array( )
'edit_terms' => null,
),
); );
register_taxonomy( self::TAXONOMY, 'user', $args ); register_post_meta(
register_taxonomy_for_object_type( self::TAXONOMY, 'user' ); self::POST_TYPE,
register_term_meta(
self::TAXONOMY,
'name', 'name',
array( array(
'type' => 'string', 'type' => 'string',
@ -76,8 +72,8 @@ class Followers {
) )
); );
register_term_meta( register_post_meta(
self::TAXONOMY, self::POST_TYPE,
'username', 'username',
array( array(
'type' => 'string', 'type' => 'string',
@ -88,56 +84,48 @@ class Followers {
) )
); );
register_term_meta( register_post_meta(
self::TAXONOMY, self::POST_TYPE,
'avatar', 'avatar',
array( array(
'type' => 'string', 'type' => 'string',
'single' => true, 'single' => true,
'sanitize_callback' => function( $value ) { 'sanitize_callback' => array( self::class, 'sanitize_url' ),
if ( filter_var( $value, FILTER_VALIDATE_URL ) === false ) {
return '';
}
return esc_url_raw( $value );
},
) )
); );
register_term_meta( register_post_meta(
self::TAXONOMY, self::POST_TYPE,
'url',
array(
'type' => 'string',
'single' => false,
'sanitize_callback' => array( self::class, 'sanitize_url' ),
)
);
register_post_meta(
self::POST_TYPE,
'inbox', 'inbox',
array( array(
'type' => 'string', 'type' => 'string',
'single' => true, 'single' => true,
'sanitize_callback' => function( $value ) { 'sanitize_callback' => array( self::class, 'sanitize_url' ),
if ( filter_var( $value, FILTER_VALIDATE_URL ) === false ) {
throw new Exception( '"inbox" has to be a valid URL' );
}
return esc_url_raw( $value );
},
) )
); );
register_term_meta( register_post_meta(
self::TAXONOMY, self::POST_TYPE,
'shared_inbox', 'shared_inbox',
array( array(
'type' => 'string', 'type' => 'string',
'single' => true, 'single' => true,
'sanitize_callback' => function( $value ) { 'sanitize_callback' => array( self::class, 'sanitize_url' ),
if ( filter_var( $value, FILTER_VALIDATE_URL ) === false ) {
return null;
}
return esc_url_raw( $value );
},
) )
); );
register_term_meta( register_post_meta(
self::TAXONOMY, self::POST_TYPE,
'updated_at', 'updated_at',
array( array(
'type' => 'string', 'type' => 'string',
@ -152,8 +140,8 @@ class Followers {
) )
); );
register_term_meta( register_post_meta(
self::TAXONOMY, self::POST_TYPE,
'errors', 'errors',
array( array(
'type' => 'string', 'type' => 'string',
@ -168,7 +156,15 @@ class Followers {
) )
); );
do_action( 'activitypub_after_register_taxonomy' ); do_action( 'activitypub_after_register_post_type' );
}
public static function sanitize_url( $value ) {
if ( filter_var( $value, FILTER_VALIDATE_URL ) === false ) {
return null;
}
return esc_url_raw( $value );
} }
/** /**
@ -221,14 +217,14 @@ class Followers {
$follower->from_meta( $meta ); $follower->from_meta( $meta );
$follower->upsert(); $follower->upsert();
$result = wp_set_object_terms( $user_id, $follower->get_actor(), self::TAXONOMY, true ); $meta = get_post_meta( $follower->get_id(), 'user_id' );
if ( is_wp_error( $result ) ) { if ( is_array( $meta ) && ! in_array( $user_id, $meta, true ) ) {
return $result; add_post_meta( $follower->get_id(), 'user_id', $user_id );
} else {
wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
return $follower;
} }
return $follower;
} }
/** /**
@ -241,33 +237,41 @@ class Followers {
*/ */
public static function remove_follower( $user_id, $actor ) { public static function remove_follower( $user_id, $actor ) {
wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
return wp_remove_object_terms( $user_id, $actor, self::TAXONOMY );
$follower = self::get_follower( $user_id, $actor );
if ( ! $follower ) {
return false;
}
return delete_post_meta( $follower->get_id(), 'user_id', $user_id );
} }
/** /**
* Remove a Follower * Get a Follower
* *
* @param int $user_id The ID of the WordPress User * @param int $user_id The ID of the WordPress User
* @param string $actor The Actor URL * @param string $actor The Actor URL
* *
* @return \Activitypub\Model\Follower The Follower object * @return \Activitypub\Model\Follower The Follower object
*/ */
public static function get_follower( $user_id, $actor ) { public static function get_follower( $user_id, $actor ) {
$terms = new WP_Term_Query( global $wpdb;
array(
'name' => $actor, $post_id = $wpdb->get_var(
'taxonomy' => self::TAXONOMY, $wpdb->prepare(
'hide_empty' => false, "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",
'object_ids' => $user_id, array(
'number' => 1, esc_sql( self::POST_TYPE ),
esc_sql( $user_id ),
esc_sql( $actor ),
)
) )
); );
$term = $terms->get_terms(); if ( $post_id ) {
$post = get_post( $post_id );
if ( is_array( $term ) && ! empty( $term ) ) { return new Follower( $post );
$term = reset( $term );
return new Follower( $term->name );
} }
return null; return null;
@ -321,21 +325,25 @@ class Followers {
*/ */
public static function get_followers( $user_id, $number = null, $offset = null, $args = array() ) { public static function get_followers( $user_id, $number = null, $offset = null, $args = array() ) {
$defaults = array( $defaults = array(
'taxonomy' => self::TAXONOMY, 'post_type' => self::POST_TYPE,
'hide_empty' => false, 'posts_per_page' => $number,
'object_ids' => $user_id, 'offset' => $offset,
'number' => $number, 'orderby' => 'ID',
'offset' => $offset, 'order' => 'DESC',
'orderby' => 'id', 'meta_query' => array(
'order' => 'ASC', array(
'key' => 'user_id',
'value' => $user_id,
),
),
); );
$args = wp_parse_args( $args, $defaults ); $args = wp_parse_args( $args, $defaults );
$terms = new WP_Term_Query( $args ); $query = new WP_Query( $args );
$items = array(); $items = array();
foreach ( $terms->get_terms() as $follower ) { foreach ( $query->get_posts() as $post ) {
$items[] = new Follower( $follower->name ); // phpcs:ignore $items[] = new Follower( $post ); // phpcs:ignore
} }
return $items; return $items;
@ -344,20 +352,15 @@ class Followers {
/** /**
* Get all Followers * Get all Followers
* *
* @param array $args The WP_Term_Query arguments. * @param array $args The WP_Query arguments.
* *
* @return array The Term list of Followers. * @return array The Term list of Followers.
*/ */
public static function get_all_followers( $args = array() ) { public static function get_all_followers( $user_id = null ) {
$defaults = array( $args = array(
'taxonomy' => self::TAXONOMY, 'meta_query' => array(),
'hide_empty' => false,
); );
return self::get_followers( $user_id, null, null, $args );
$args = wp_parse_args( $args, $defaults );
$terms = new WP_Term_Query( $args );
return $terms->get_terms();
} }
/** /**
@ -368,7 +371,20 @@ class Followers {
* @return int The number of Followers * @return int The number of Followers
*/ */
public static function count_followers( $user_id ) { public static function count_followers( $user_id ) {
return count( self::get_followers( $user_id ) ); $query = new WP_Query(
array(
'post_type' => self::POST_TYPE,
'fields' => 'ids',
'meta_query' => array(
array(
'key' => 'user_id',
'value' => $user_id,
),
),
)
);
return $query->found_posts;
} }
/** /**
@ -387,35 +403,37 @@ class Followers {
} }
// get all Followers of a ID of the WordPress User // get all Followers of a ID of the WordPress User
$terms = new WP_Term_Query( $posts = new WP_Query(
array( array(
'taxonomy' => self::TAXONOMY, 'post_type' => self::POST_TYPE,
'hide_empty' => false,
'object_ids' => $user_id,
'fields' => 'ids', 'fields' => 'ids',
'meta_query' => array( 'meta_query' => array(
array( array(
'key' => 'inbox', 'key' => 'inbox',
'compare' => 'EXISTS', 'compare' => 'EXISTS',
), ),
array(
'key' => 'user_id',
'value' => $user_id,
),
), ),
) )
); );
$terms = $terms->get_terms(); $posts = $posts->get_posts();
if ( ! $terms ) { if ( ! $posts ) {
return array(); return array();
} }
global $wpdb; global $wpdb;
$results = $wpdb->get_col( $results = $wpdb->get_col(
$wpdb->prepare( $wpdb->prepare(
"SELECT DISTINCT meta_value FROM {$wpdb->termmeta} "SELECT DISTINCT meta_value FROM {$wpdb->postmeta}
WHERE term_id IN (" . implode( ', ', array_fill( 0, count( $terms ), '%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",
$terms $posts
) )
); );
@ -436,26 +454,24 @@ class Followers {
*/ */
public static function get_outdated_followers( $number = 50, $older_than = 604800 ) { public static function get_outdated_followers( $number = 50, $older_than = 604800 ) {
$args = array( $args = array(
'taxonomy' => self::TAXONOMY, 'post_type' => self::POST_TYPE,
'number' => $number, 'posts_per_page' => $number,
'meta_key' => 'updated_at', 'orderby' => 'modified',
'orderby' => 'meta_value_num', 'order' => 'DESC',
'order' => 'DESC', 'post_status' => 'any', // 'any' includes 'trash
'meta_query' => array( 'date_query' => array(
array( array(
'key' => 'updated_at', 'column' => 'post_modified_gmt',
'value' => time() - $older_than, 'before' => gmdate( 'Y-m-d', \time() - $older_than ),
'type' => 'numeric',
'compare' => '<=',
), ),
), ),
); );
$terms = new WP_Term_Query( $args ); $posts = new WP_Query( $args );
$items = array(); $items = array();
foreach ( $terms->get_terms() as $follower ) { foreach ( $posts->get_posts() as $follower ) {
$items[] = new Follower( $follower->name ); // phpcs:ignore $items[] = new Follower( $follower ); // phpcs:ignore
} }
return $items; return $items;
@ -471,21 +487,21 @@ class Followers {
*/ */
public static function get_faulty_followers( $number = 10 ) { public static function get_faulty_followers( $number = 10 ) {
$args = array( $args = array(
'taxonomy' => self::TAXONOMY, 'post_type' => self::POST_TYPE,
'number' => $number, 'posts_per_page' => $number,
'meta_query' => array( 'meta_query' => array(
array( array(
'key' => 'errors', 'key' => 'errors',
'compare' => 'EXISTS', 'compare' => 'EXISTS',
), ),
), ),
); );
$terms = new WP_Term_Query( $args ); $posts = new WP_Query( $args );
$items = array(); $items = array();
foreach ( $terms->get_terms() as $follower ) { foreach ( $posts->get_posts() as $follower ) {
$items[] = new Follower( $follower->name ); // phpcs:ignore $items[] = new Follower( $follower ); // phpcs:ignore
} }
return $items; return $items;

View file

@ -1,6 +1,7 @@
<?php <?php
namespace Activitypub\Model; namespace Activitypub\Model;
use WP_Query;
use Activitypub\Collection\Followers; use Activitypub\Collection\Followers;
/** /**
@ -9,6 +10,7 @@ use Activitypub\Collection\Followers;
* This Object represents a single Follower. * This Object represents a single Follower.
* There is no direct reference to a WordPress User here. * There is no direct reference to a WordPress User here.
* *
* @author Matt Wiebe
* @author Matthias Pfefferle * @author Matthias Pfefferle
* *
* @see https://www.w3.org/TR/activitypub/#follow-activity-inbox * @see https://www.w3.org/TR/activitypub/#follow-activity-inbox
@ -28,16 +30,6 @@ class Follower {
*/ */
private $actor; private $actor;
/**
* The Object slug
*
* This is a requirement of the Term-Meta but will not
* be actively used in the ActivityPub context.
*
* @var string
*/
private $slug;
/** /**
* The Object Name * The Object Name
* *
@ -108,6 +100,12 @@ class Follower {
*/ */
private $errors; private $errors;
/**
* The WordPress User ID, or 0 for whole site.
* @var int
*/
private $user_id;
/** /**
* Maps the meta fields to the local db fields * Maps the meta fields to the local db fields
* *
@ -122,17 +120,35 @@ class Follower {
/** /**
* Constructor * Constructor
* *
* @param WP_Post $post * @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 ) { public function __construct( $actor ) {
$this->actor = $actor; $post = null;
$term = get_term_by( 'name', $actor, Followers::TAXONOMY ); if ( \is_a( $actor, 'WP_Post' ) ) {
$post = $actor;
} else {
global $wpdb;
if ( $term ) { $post_id = $wpdb->get_var(
$this->id = $term->term_id; $wpdb->prepare(
$this->slug = $term->slug; "SELECT ID FROM $wpdb->posts WHERE guid=%s",
$this->meta = json_decode( $term->meta ); 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;
} }
} }
@ -205,20 +221,21 @@ class Follower {
* @return mixed The attribute value. * @return mixed The attribute value.
*/ */
public function get( $attribute ) { public function get( $attribute ) {
if ( $this->$attribute ) { if ( ! is_null( $this->$attribute ) ) {
return $this->$attribute; return $this->$attribute;
} }
$attribute = get_term_meta( $this->id, $attribute, true ); $attribute_value = get_post_meta( $this->id, $attribute, true );
if ( $attribute ) {
$this->$attribute = $attribute; if ( $attribute_value ) {
return $attribute; $this->$attribute = $attribute_value;
return $attribute_value;
} }
$attribute = $this->get_meta_by( $attribute ); $attribute_value = $this->get_meta_by( $attribute );
if ( $attribute ) { if ( $attribute_value ) {
$this->$attribute = $attribute; $this->$attribute = $attribute_value;
return $attribute; return $attribute_value;
} }
return null; return null;
@ -246,7 +263,7 @@ class Follower {
return $this->errors; return $this->errors;
} }
$this->errors = get_term_meta( $this->id, 'errors' ); $this->errors = get_post_meta( $this->id, 'errors' );
return $this->errors; return $this->errors;
} }
@ -256,7 +273,7 @@ class Follower {
* @return void * @return void
*/ */
public function reset_errors() { public function reset_errors() {
delete_term_meta( $this->id, 'errors' ); delete_post_meta( $this->id, 'errors' );
} }
/** /**
@ -298,7 +315,9 @@ class Follower {
*/ */
public function get_meta_by( $attribute ) { public function get_meta_by( $attribute ) {
$meta = $this->get_meta(); $meta = $this->get_meta();
if ( ! is_array( $meta ) ) {
return null;
}
// try mapped data (see $this->map_meta) // try mapped data (see $this->map_meta)
foreach ( $this->map_meta as $remote => $local ) { foreach ( $this->map_meta as $remote => $local ) {
if ( $attribute === $local && isset( $meta[ $remote ] ) ) { if ( $attribute === $local && isset( $meta[ $remote ] ) ) {
@ -306,11 +325,6 @@ class Follower {
} }
} }
// try ActivityPub attribtes
if ( ! empty( $this->map_meta[ $attribute ] ) ) {
return $this->map_meta[ $attribute ];
}
return null; return null;
} }
@ -333,16 +347,8 @@ class Follower {
* @return void * @return void
*/ */
public function update() { public function update() {
$term = wp_update_term(
$this->id,
Followers::TAXONOMY,
array(
'description' => wp_json_encode( $this->get_meta( true ) ),
)
);
$this->updated_at = \time(); $this->updated_at = \time();
$this->update_term_meta(); $this->save();
} }
/** /**
@ -351,18 +357,19 @@ class Follower {
* @return void * @return void
*/ */
public function save() { public function save() {
$term = wp_insert_term( $args = array(
$this->actor, 'ID' => $this->id,
Followers::TAXONOMY, 'guid' => $this->actor,
array( 'post_title' => $this->get_name(),
'slug' => sanitize_title( $this->get_actor() ), 'post_author' => 0,
'description' => wp_json_encode( $this->get_meta() ), 'post_type' => Followers::POST_TYPE,
) 'post_content' => wp_json_encode( $this->meta ),
'post_status' => 'publish',
'post_modified' => gmdate( 'Y-m-d H:i:s', $this->updated_at ),
'meta_input' => $this->get_post_meta_input(),
); );
$post_id = wp_insert_post( $args );
$this->id = $term['term_id']; $this->id = $post_id;
$this->update_term_meta();
} }
/** /**
@ -384,20 +391,22 @@ class Follower {
* @return void * @return void
*/ */
public function delete() { public function delete() {
wp_delete_term( $this->id, Followers::TAXONOMY ); wp_delete_post( $this->id );
} }
/** /**
* Update the term meta. * Update the post meta.
* *
* @return void * @return void
*/ */
protected function update_term_meta() { protected function get_post_meta_input() {
$attributes = array( 'inbox', 'shared_inbox', 'avatar', 'updated_at', 'name', 'username' ); $attributes = array( 'inbox', 'shared_inbox', 'avatar', 'name', 'username' );
$meta_input = array();
foreach ( $attributes as $attribute ) { foreach ( $attributes as $attribute ) {
if ( $this->get( $attribute ) ) { if ( $this->get( $attribute ) ) {
update_term_meta( $this->id, $attribute, $this->get( $attribute ) ); $meta_input[ $attribute ] = $this->get( $attribute );
} }
} }
@ -410,7 +419,9 @@ class Follower {
$error = __( 'Unknown Error or misconfigured Error-Message', 'activitypub' ); $error = __( 'Unknown Error or misconfigured Error-Message', 'activitypub' );
} }
add_term_meta( $this->id, 'errors', $error ); $meta_input['errors'] = array( $error );
} }
return $meta_input;
} }
} }

View file

@ -16,6 +16,7 @@ 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' ),
'errors' => \__( 'Errors', 'activitypub' ), 'errors' => \__( 'Errors', 'activitypub' ),
'latest-error' => \__( 'Latest Error Message', 'activitypub' ), 'latest-error' => \__( 'Latest Error Message', 'activitypub' ),
); );
@ -35,8 +36,8 @@ class Followers extends WP_List_Table {
$page_num = $this->get_pagenum(); $page_num = $this->get_pagenum();
$per_page = 20; $per_page = 20;
$follower = FollowerCollection::get_followers( \get_current_user_id(), $per_page, ( $page_num - 1 ) * $per_page ); $followers = FollowerCollection::get_followers( \get_current_user_id(), $per_page, ( $page_num - 1 ) * $per_page );
$counter = FollowerCollection::count_followers( \get_current_user_id() ); $counter = FollowerCollection::count_followers( \get_current_user_id() );
$this->items = array(); $this->items = array();
$this->set_pagination_args( $this->set_pagination_args(
@ -47,12 +48,13 @@ class Followers extends WP_List_Table {
) )
); );
foreach ( $follower as $follower ) { foreach ( $followers as $follower ) {
$item = array( $item = array(
'avatar' => esc_attr( $follower->get_avatar() ), 'avatar' => esc_attr( $follower->get_avatar() ),
'name' => esc_attr( $follower->get_name() ), 'name' => esc_attr( $follower->get_name() ),
'username' => esc_attr( $follower->get_username() ), 'username' => esc_attr( $follower->get_username() ),
'identifier' => esc_attr( $follower->get_actor() ), 'identifier' => esc_attr( $follower->get_actor() ),
'updated_at' => esc_attr( $follower->get_updated_at() ),
'errors' => $follower->count_errors(), 'errors' => $follower->count_errors(),
'latest-error' => $follower->get_latest_error_message(), 'latest-error' => $follower->get_latest_error_message(),
); );
@ -110,7 +112,12 @@ class Followers extends WP_List_Table {
switch ( $this->current_action() ) { switch ( $this->current_action() ) {
case 'delete': case 'delete':
FollowerCollection::remove_follower( \get_current_user_id(), $followers ); if ( ! is_array( $followers ) ) {
$followers = array( $followers );
}
foreach ( $followers as $follower ) {
FollowerCollection::remove_follower( \get_current_user_id(), $follower );
}
break; break;
} }
} }

View file

@ -1,50 +0,0 @@
<?php
namespace Activitypub\Integration;
use Activitypub\Collection\Followers;
/**
* Manages the compatibility with WP Sweep.
*
* @link https://wordpress.org/plugins/wp-sweep/
* @link https://github.com/polylang/polylang/tree/master/integrations/wp-sweep
*/
class Wp_Sweep {
/**
* Setups actions.
*
* @return void
*/
public static function init() {
add_filter( 'wp_sweep_excluded_taxonomies', array( self::class, 'excluded_taxonomies' ) );
add_filter( 'wp_sweep_excluded_termids', array( self::class, 'excluded_termids' ), 0 );
}
/**
* Add 'activitypub-followers' to excluded taxonomies otherwise terms loose their language
* and translation group.
*
* @param array $excluded_taxonomies List of taxonomies excluded from sweeping.
*
* @return array The list of taxonomies excluded from sweeping.
*/
public static function excluded_taxonomies( $excluded_taxonomies ) {
return array_merge( $excluded_taxonomies, array( Followers::TAXONOMY ) );
}
/**
* Add the translation of the default taxonomy terms and our language terms to the excluded terms.
*
* @param array $excluded_term_ids List of term ids excluded from sweeping.
*
* @return array The list of term ids excluded from sweeping.
*/
public static function excluded_termids( $excluded_term_ids ) {
// We got a list of excluded terms (defaults and parents). Let exclude their translations too.
$followers = Followers::get_all_followers( array( 'fields' => 'ids' ) );
$excluded_term_ids = array_merge( $excluded_term_ids, $followers );
return array_unique( $excluded_term_ids );
}
}

View file

@ -31,6 +31,12 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
'name' => '12345', 'name' => '12345',
'prefferedUsername' => '12345', 'prefferedUsername' => '12345',
), ),
'user2@example.com' => array(
'url' => 'https://user2.example.com',
'inbox' => 'https://user2.example.com/inbox',
'name' => 'user2',
'prefferedUsername' => 'user2',
),
); );
public function set_up() { public function set_up() {
@ -65,7 +71,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$db_followers $db_followers
); );
$this->assertSame( array( 'https://example.com/author/jon', 'https://example.org/author/doe', 'http://sally.example.org' ), $db_followers ); $this->assertEquals( array( 'http://sally.example.org', 'https://example.org/author/doe', 'https://example.com/author/jon' ), $db_followers );
} }
public function test_add_follower() { public function test_add_follower() {
@ -73,15 +79,21 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 ); add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
$follower = 'https://12345.example.com'; $follower = 'https://12345.example.com';
$follower2 = 'https://user2.example.com';
\Activitypub\Collection\Followers::add_follower( 1, $follower ); \Activitypub\Collection\Followers::add_follower( 1, $follower );
\Activitypub\Collection\Followers::add_follower( 2, $follower );
\Activitypub\Collection\Followers::add_follower( 2, $follower2 );
$db_followers = \Activitypub\Collection\Followers::get_followers( 1 ); $db_followers = \Activitypub\Collection\Followers::get_followers( 1 );
$db_followers2 = \Activitypub\Collection\Followers::get_followers( 2 );
$this->assertContains( $follower, $db_followers ); $this->assertContains( $follower, $db_followers );
$this->assertContains( $follower2, $db_followers2 );
} }
public function test_get_follower() { public function test_get_follower() {
$followers = array( 'https://example.com/author/jon' ); $followers = array( 'https://example.com/author/jon' );
$followers2 = array( 'https://user2.example.com' );
$pre_http_request = new MockAction(); $pre_http_request = new MockAction();
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 ); add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
@ -90,11 +102,66 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
\Activitypub\Collection\Followers::add_follower( 1, $follower ); \Activitypub\Collection\Followers::add_follower( 1, $follower );
} }
foreach ( $followers2 as $follower ) {
\Activitypub\Collection\Followers::add_follower( 2, $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_actor() );
$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 );
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://user2.example.com' );
$this->assertNull( $follower );
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower->get_actor() );
$follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://user2.example.com' );
$this->assertEquals( 'https://user2.example.com', $follower2->get_actor() );
}
public function test_delete_follower() {
$followers = array(
'https://example.com/author/jon',
'https://example.org/author/doe',
);
$followers2 = array( 'https://user2.example.com' );
$pre_http_request = new MockAction();
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
foreach ( $followers as $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( 2, $follower );
}
foreach ( $followers2 as $follower2 ) {
\Activitypub\Collection\Followers::add_follower( 2, $follower2 );
}
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower->get_actor() );
$followers = \Activitypub\Collection\Followers::get_followers( 1 );
$this->assertEquals( 2, count( $followers ) );
$follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower2->get_actor() );
\Activitypub\Collection\Followers::remove_follower( 1, 'https://example.com/author/jon' );
$follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' );
$this->assertNull( $follower );
$follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower2->get_actor() );
$followers = \Activitypub\Collection\Followers::get_followers( 1 );
$this->assertEquals( 1, count( $followers ) );
} }
public function test_get_outdated_followers() { public function test_get_outdated_followers() {
@ -109,7 +176,28 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$follower = new \Activitypub\Model\Follower( 'https://example.com/author/jon' ); $follower = new \Activitypub\Model\Follower( 'https://example.com/author/jon' );
update_term_meta( $follower->get_id(), 'updated_at', \time() - 804800 ); global $wpdb;
//eg. time one year ago..
$time = time() - 804800;
$mysql_time_format = 'Y-m-d H:i:s';
$post_modified = gmdate( $mysql_time_format, $time );
$post_modified_gmt = gmdate( $mysql_time_format, ( $time + get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) );
$post_id = $follower->get_id();
$wpdb->query(
$wpdb->prepare(
"UPDATE $wpdb->posts SET post_modified = %s, post_modified_gmt = %s WHERE ID = %s",
array(
$post_modified,
$post_modified_gmt,
$post_id,
)
)
);
clean_post_cache( $post_id );
$followers = \Activitypub\Collection\Followers::get_outdated_followers(); $followers = \Activitypub\Collection\Followers::get_outdated_followers();
$this->assertEquals( 1, count( $followers ) ); $this->assertEquals( 1, count( $followers ) );
@ -129,7 +217,7 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$follower = new \Activitypub\Model\Follower( 'http://sally.example.org' ); $follower = new \Activitypub\Model\Follower( 'http://sally.example.org' );
for ( $i = 1; $i <= 15; $i++ ) { for ( $i = 1; $i <= 15; $i++ ) {
add_term_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 = new \Activitypub\Model\Follower( 'http://sally.example.org' );