Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add advanced follow management #622

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
de3335b
application-user: set manuallyApprovesFollowers to true
Menrath Dec 23, 2023
54c18b2
Temporary commit to bypass ssl cert check
Menrath Dec 24, 2023
a3a918c
Announce all posts through the application actor
Menrath Dec 23, 2023
33b28b1
todo: show application actor table in admin menu for development
Menrath Dec 24, 2023
616667b
Add some comments and make variable more readable
Menrath Dec 25, 2023
03b6a8e
draft for managing follow requests
Menrath Dec 25, 2023
4494136
fix accept/approve typo
Menrath Dec 26, 2023
fefa29a
fix phpcs
Menrath Dec 26, 2023
16a1745
small fixes of bugs introduced by fixing codesniffer errors
Menrath Dec 26, 2023
7d853df
fix phpcs and sanitizing bugs
Menrath Dec 26, 2023
99d66fa
fix wront phpdoc
Menrath Dec 27, 2023
0fad06f
use the singe attribute on the get_post_meta function
Menrath Dec 27, 2023
32e08d7
fix: the application announces all posts no matter whether the blog u…
Menrath Dec 27, 2023
32acc51
decouple the adding/removing of follow relationships from adding/remo…
Menrath Dec 27, 2023
9475148
make the save of a follower return the post id instead of the object …
Menrath Dec 27, 2023
16141b8
fix phpcs
Menrath Dec 27, 2023
97823c0
fix call and wrong return of add_follower function
Menrath Dec 27, 2023
b1d65a6
move static function that retrieves all follows and pending follow re…
Menrath Dec 27, 2023
f8ad036
fix/adopt some unit tests
Menrath Dec 27, 2023
bf16ba5
fix/adpot unit tests
Menrath Dec 29, 2023
35fa38b
fix phpcs
Menrath Dec 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions activitypub.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ function () {
0
);

add_action(
'wp_ajax_activitypub_handle_follow_request',
function () {
$wp_list_table = new \Activitypub\Table\Follow_Requests();
$wp_list_table->ajax_response();
}
);

/**
* `get_plugin_data` wrapper
*
Expand Down
33 changes: 33 additions & 0 deletions assets/css/activitypub-admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,36 @@ input.blog-user-identifier {
border-bottom: none;
margin-bottom: 0;
}

.activitypub-settings-label {
display: inline-block;
padding: .25em .4em;
font-size: 85%;
font-weight: 700;
line-height: 1.25;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25rem;
}

.activitypub-settings-label-success {
color: #fff;
background-color: #28a745;
}

.activitypub-settings-label-warning {
color: #212529;
background-color: #ffc107;
}

.activitypub-settings-label-danger {
color: #fff;
background-color: #dc3545;
}

.activitypub-settings-action-buttons {
display: flex;
gap: 5px;
flex-wrap: nowrap;
}
47 changes: 47 additions & 0 deletions assets/js/activitypub-admin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
jQuery( function( $ ) {
const { __ } = wp.i18n;
// Accordion handling in various areas.
$( '.activitypub-settings-accordion' ).on( 'click', '.activitypub-settings-accordion-trigger', function() {
var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) );
Expand All @@ -17,5 +18,51 @@ jQuery( function( $ ) {
$( '.activate-now' ).removeClass( 'thickbox open-plugin-details-modal' );
}, 1200 );
} );

$( '.activitypub-settings-action-buttons' ).on( 'click', '.button', function() {
var button = $ (this );
var actionValue = button.data('action');
window.console.log( actionValue );
$.ajax({
type: 'POST',
url: actionValue,
success: function ( response ) {
var statusText = button.closest( 'td' ).siblings( '.column-status' ).children( 'span' ).first();
if ( 'deleted' === response ) {
button.closest( 'tr' ).remove();
}
if ( 'approved' === response ) {
button.parent().find( '[data-action*="follow_action=reject"] ').attr( 'type', 'button' );
button.parent().find( '[data-action*="follow_action=delete"]' ).attr( 'type', 'hidden' );
statusText.text( __( 'Approved', 'activitypub' ) );
statusText.removeClass( 'activitypub-settings-label-danger' );
statusText.removeClass( 'activitypub-settings-label-warning' );
statusText.addClass( 'activitypub-settings-label-success' );
}
if ( 'rejected' === response ) {
// TODO: clarify this behavior together with Mobilizon and others.
button.closest( 'tr' ).remove();
// statusText.text( __( 'Rejected', 'activitypub' ) );
// statusText.removeClass( 'activitypub-settings-label-success' );
// statusText.removeClass( 'activitypub-settings-label-warning' );
// statusText.addClass( 'activitypub-settings-label-danger' );
// button.parent().find( '[data-action*="follow_action=approve"]' ).attr( 'type', 'button' );
// button.parent().find( '[data-action*="follow_action=delete"]' ).attr( 'type', 'button' );
}
button.attr( 'type', 'hidden' );
// Check if table is completely empty.
var tbody = button.closest( 'tbody' );
if ( 0 == tbody.find( 'tr' ).length ) {
var text = __( 'No items found.', 'core' );
var newRow = $('<tr>').append($('<td>', { class: 'colspanchange', colspan: 7, text: text }));
tbody.append(newRow);
tbody.append("Some appended text.");
}
},
error: function ( error ) {
// TODO: Handle the error
}
});
} );

} );
34 changes: 26 additions & 8 deletions includes/class-activity-dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Activitypub\Transformer\Post;
use Activitypub\Transformer\Comment;

use function Activitypub\get_rest_url_by_path;
use function Activitypub\is_single_user;
use function Activitypub\is_user_disabled;
use function Activitypub\safe_remote_post;
Expand Down Expand Up @@ -40,7 +41,7 @@ public static function init() {
* @return void
*/
public static function send_activity_or_announce( $wp_object, $type ) {
// check if a migration is needed before sending new posts
// check if a migration is needed before sending new post
Migration::maybe_migrate();

if ( is_user_type_disabled( 'blog' ) ) {
Expand All @@ -49,8 +50,10 @@ public static function send_activity_or_announce( $wp_object, $type ) {

if ( is_single_user() ) {
self::send_activity( $wp_object, $type, Users::BLOG_USER_ID );
self::send_announce( $wp_object, $type, Users::APPLICATION_USER_ID );
} else {
self::send_announce( $wp_object, $type );
self::send_announce( $wp_object, $type, Users::BLOG_USER_ID );
self::send_announce( $wp_object, $type, Users::APPLICATION_USER_ID );
}
}

Expand Down Expand Up @@ -88,21 +91,36 @@ public static function send_activity( $wp_object, $type, $user_id = null ) {
*
* @return void
*/
public static function send_announce( $wp_object, $type ) {
public static function send_announce( $wp_object, $type, $user_id = null ) {
if ( ! in_array( $type, array( 'Create', 'Update' ), true ) ) {
return;
}

if ( is_user_disabled( Users::BLOG_USER_ID ) ) {
return;
$transformer = Factory::get_transformer( $wp_object );

if ( null !== $user_id && Users::APPLICATION_USER_ID !== $user_id ) {
$transformer->change_wp_user_id( $user_id );
}

$transformer = Factory::get_transformer( $wp_object );
$transformer->change_wp_user_id( Users::BLOG_USER_ID );
if ( ! $user_id ) {
$user_id = $transformer->get_wp_user_id();
}

if ( is_user_disabled( $user_id ) ) {
return;
}

$user_id = $transformer->get_wp_user_id();
$activity = $transformer->to_activity( 'Announce' );

// TODO: properly fix this for the instance-to-instance federation with Mobilizon.
// Error:
// Failed to map identity from signature (payload actor mismatch)
// key_id=http://wp.lan/wp-json/activitypub/1.0/application, actor=http://wp.lan/@blog
// Of course, the announce must be sent as the Application actor because he also signed it!
if ( Users::APPLICATION_USER_ID === $user_id ) {
$activity->set_actor( get_rest_url_by_path( 'application' ) );
}

self::send_activity_to_inboxes( $activity, $user_id );
}

Expand Down
12 changes: 6 additions & 6 deletions includes/class-activitypub.php
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ public static function plugin_update_message( $data ) {
* @return void
*/
private static function register_post_types() {
\register_post_type(
register_post_type(
Followers::POST_TYPE,
array(
'labels' => array(
Expand All @@ -406,7 +406,7 @@ private static function register_post_types() {
)
);

\register_post_meta(
register_post_meta(
Followers::POST_TYPE,
'activitypub_inbox',
array(
Expand All @@ -416,7 +416,7 @@ private static function register_post_types() {
)
);

\register_post_meta(
register_post_meta(
Followers::POST_TYPE,
'activitypub_errors',
array(
Expand All @@ -432,7 +432,7 @@ private static function register_post_types() {
)
);

\register_post_meta(
register_post_meta(
Followers::POST_TYPE,
'activitypub_user_id',
array(
Expand All @@ -444,7 +444,7 @@ private static function register_post_types() {
)
);

\register_post_meta(
register_post_meta(
Followers::POST_TYPE,
'activitypub_actor_json',
array(
Expand All @@ -456,7 +456,7 @@ private static function register_post_types() {
)
);

\do_action( 'activitypub_after_register_post_type' );
do_action( 'activitypub_after_register_post_type' );
}

/**
Expand Down
6 changes: 3 additions & 3 deletions includes/class-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public static function settings_page() {
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/settings.php' );
break;
case 'followers':
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/blog-user-followers-list.php' );
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/admin-followers-list.php' );
break;
case 'welcome':
default:
Expand Down Expand Up @@ -300,8 +300,8 @@ public static function save_user_description( $user_id ) {

public static function enqueue_scripts( $hook_suffix ) {
if ( false !== strpos( $hook_suffix, 'activitypub' ) ) {
wp_enqueue_style( 'activitypub-admin-styles', plugins_url( 'assets/css/activitypub-admin.css', ACTIVITYPUB_PLUGIN_FILE ), array(), '1.0.0' );
wp_enqueue_script( 'activitypub-admin-styles', plugins_url( 'assets/js/activitypub-admin.js', ACTIVITYPUB_PLUGIN_FILE ), array( 'jquery' ), '1.0.0', false );
wp_enqueue_style( 'activitypub-admin-styles', plugins_url( 'assets/css/activitypub-admin.css', ACTIVITYPUB_PLUGIN_FILE ), array(), '1.0.1' );
wp_enqueue_script( 'activitypub-admin-styles', plugins_url( 'assets/js/activitypub-admin.js', ACTIVITYPUB_PLUGIN_FILE ), array( 'jquery' ), '1.0.1', false );
}
}
}
4 changes: 4 additions & 0 deletions includes/class-signature.php
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ public static function verify_http_signature( $request ) {
$headers['(request-target)'][0] = strtolower( $headers['request_method'][0] ) . ' ' . $headers['request_uri'][0];
}

// TODO: Fix the signtature verification. Posts from mobilizon fail at the moment.
// The next line is temporaty. It must be removed in production and releases.
return true;

if ( ! isset( $headers['signature'] ) ) {
return new WP_Error( 'activitypub_signature', __( 'Request not signed', 'activitypub' ), array( 'status' => 401 ) );
}
Expand Down
77 changes: 77 additions & 0 deletions includes/collection/class-follow-requests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php
namespace Activitypub\Collection;

use WP_Error;
use WP_Query;
use Activitypub\Http;
use Activitypub\Webfinger;
use Activitypub\Activity\Base_Object;
use Activitypub\Model\Follower;

use function Activitypub\is_tombstone;
use function Activitypub\get_remote_metadata_by_actor;

/**
* ActivityPub Follow Requests Collection
*
* @author André Menrath
*/
class Follow_Requests {
/**
* Get a follow request together with information from the follower.
*
* @param int $user_id The ID of the WordPress User, which may be 0 for the blog and -1 for the application user
* @param int $per_page Number of items per page
* @param int $page_num The current page
* @param int $args May contain custom ordering or search terms.
*
* @return array Containing an array of all follow requests and the total numbers.
*/
public static function get_follow_requests_for_user( $user_id, $per_page, $page_num, $args ) {
$order = isset( $args['order'] ) && strtolower( $args['order'] ) === 'asc' ? 'ASC' : 'DESC';
$orderby = isset( $args['orderby'] ) ? sanitize_text_field( $args['orderby'] ) : 'published';
$search = isset( $args['s'] ) ? sanitize_text_field( $args['s'] ) : '';

$offset = (int) $per_page * ( (int) $page_num - 1 );

global $wpdb;
$follow_requests = $wpdb->get_results(
$wpdb->prepare(
"SELECT SQL_CALC_FOUND_ROWS follow_request.ID AS id, follow_request.post_date AS published, follow_request.guid, follow_request.post_status AS 'status', follower.post_title AS 'post_title', follower.guid AS follower_guid, follower.id AS follower_id, follower.post_modified AS follower_modified
FROM {$wpdb->posts} AS follow_request
LEFT JOIN {$wpdb->posts} AS follower ON follow_request.post_parent = follower.ID
LEFT JOIN {$wpdb->postmeta} AS meta ON follow_request.ID = meta.post_id
WHERE follow_request.post_type = 'ap_follow_request'
AND (follower.post_title LIKE %s OR follower.guid LIKE %s)
AND meta.meta_key = 'activitypub_user_id'
AND meta.meta_value = %s
ORDER BY %s %s
LIMIT %d OFFSET %d",
'%' . $wpdb->esc_like( $search ) . '%',
'%' . $wpdb->esc_like( $search ) . '%',
$user_id,
$orderby,
$order,
$per_page,
$offset
)
);
$current_total_items = $wpdb->get_var( 'SELECT FOUND_ROWS()' );

// Second step: Get the total rows without the LIMIT
$total_items = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(follow_request.ID)
FROM {$wpdb->posts} AS follow_request
LEFT JOIN {$wpdb->posts} AS follower ON follow_request.post_parent = follower.ID
LEFT JOIN {$wpdb->postmeta} AS meta ON follow_request.ID = meta.post_id
WHERE follow_request.post_type = 'ap_follow_request'
AND meta.meta_key = 'activitypub_user_id'
AND meta.meta_value = %s",
$user_id
)
);

return compact( 'follow_requests', 'current_total_items', 'total_items' );
}
}
Loading
Loading