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

Outbox Collection #593

Draft
wants to merge 64 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5565c26
init
pfefferle Dec 8, 2023
7448cd6
outbox should not be public!
pfefferle Dec 8, 2023
6ac167f
add basic `to_activity` function
pfefferle Dec 11, 2023
704030c
Merge branch 'master' into add/outbox-collection
pfefferle Dec 11, 2023
89ece40
do not allow to instance post transformer
pfefferle Dec 11, 2023
0ba6d7b
Merge branch 'master' into add/outbox-collection
pfefferle Dec 12, 2023
efda4a5
fix tests
pfefferle Dec 12, 2023
1558551
Merge branch 'master' into add/outbox-collection
pfefferle Dec 13, 2023
6edc4b5
Merge branch 'master' into add/outbox-collection
pfefferle Dec 21, 2023
a38c565
Merge branch 'master' into add/outbox-collection
pfefferle Dec 22, 2023
2a3a6c3
Merge branch 'trunk' into add/outbox-collection
pfefferle Sep 27, 2024
5d66494
fix phpcs
pfefferle Sep 27, 2024
b1d260d
revert change
pfefferle Sep 27, 2024
81b0b88
remove unused `use` declarations
pfefferle Sep 27, 2024
8c0c3fa
the handler should not handle outgoing stuff
pfefferle Sep 27, 2024
3eb8ca6
Merge branch 'trunk' into add/outbox-collection
pfefferle Oct 22, 2024
43a3343
Update class-activitypub.php
pfefferle Oct 22, 2024
8746447
fix PHPCS
pfefferle Oct 22, 2024
242dea4
more PHPCS fixes
pfefferle Oct 22, 2024
f64aade
fix namespace issue
pfefferle Oct 22, 2024
5ca19a6
remove unneeded function
pfefferle Oct 22, 2024
9f525b0
fix sticky post endpoint
pfefferle Oct 22, 2024
e0aeefd
no need to check for User-ID
pfefferle Oct 22, 2024
2d3ba4b
Merge branch 'trunk' into add/outbox-collection
pfefferle Oct 23, 2024
1433e4f
Merge branch 'trunk' into add/outbox-collection
pfefferle Oct 23, 2024
943fc02
Merge branch 'trunk' into add/outbox-collection
pfefferle Oct 24, 2024
7f0a227
Merge branch 'trunk' into add/outbox-collection
pfefferle Oct 25, 2024
fb98bd2
Merge branch 'trunk' into add/outbox-collection
pfefferle Oct 26, 2024
945b335
Merge branch 'trunk' into add/outbox-collection
pfefferle Nov 5, 2024
9345991
support JSON and Arrays beside WP_Comments and WP_Posts
pfefferle Nov 5, 2024
d1e9839
fix auto-complete issue
pfefferle Nov 5, 2024
59ab8b9
simplify code.
pfefferle Nov 5, 2024
8133a1c
convert JSON to Activity_Object to not have everything duplicated
pfefferle Nov 5, 2024
0bdb30d
add support for maps and mentions
pfefferle Nov 5, 2024
593740f
Merge branch 'trunk' into add/outbox-collection
pfefferle Nov 7, 2024
748a266
it takes a string (JSON) or an array
pfefferle Nov 12, 2024
db789ff
Merge branch 'trunk' into add/outbox-collection
pfefferle Nov 13, 2024
22aea42
simplified based on the feedback of @obenland
pfefferle Nov 13, 2024
3648062
Merge branch 'add/outbox-collection' of https://github.com/Automattic…
pfefferle Nov 13, 2024
9b20505
rearrange code a bit
pfefferle Nov 14, 2024
617eff7
add missing uses
pfefferle Nov 14, 2024
ad5f35a
fix typo
pfefferle Nov 14, 2024
c1f63b5
add missing uses
pfefferle Nov 14, 2024
193ec0f
Merge branch 'trunk' into add/outbox-collection
pfefferle Nov 14, 2024
b00e64d
Merge branch 'trunk' into add/outbox-collection
pfefferle Nov 20, 2024
b005be7
Merge branch 'trunk' into add/outbox-collection
pfefferle Nov 20, 2024
726729f
Merge branch 'trunk' into add/outbox-collection
pfefferle Nov 20, 2024
ec9d389
Merge branch 'trunk' into add/outbox-collection
pfefferle Nov 21, 2024
37144b8
Merge branch 'trunk' into add/outbox-collection
pfefferle Nov 22, 2024
fe29ea4
Added Changelog-Entry
pfefferle Nov 22, 2024
5b31a6a
Merge branch 'trunk' into add/outbox-collection
pfefferle Dec 5, 2024
8f4f8b1
apply changes from @obenland
pfefferle Dec 6, 2024
f26eb3d
delay scheduling a bit
pfefferle Dec 6, 2024
77f8f06
change scheduling and add possibility to add items to outbox
pfefferle Dec 6, 2024
abef3dc
remove deprecated functions
pfefferle Dec 6, 2024
b93568d
add filter
pfefferle Dec 6, 2024
8d48752
Merge branch 'trunk' into add/outbox-collection
pfefferle Dec 6, 2024
17cc89b
fix indents
pfefferle Dec 6, 2024
535899c
removed deprecated functions
pfefferle Dec 6, 2024
9d8c270
add visibility and normalize autor id
pfefferle Dec 9, 2024
757c8c9
add taxonomies to store actor and actitiy-type informations
pfefferle Dec 11, 2024
400a70a
Merge branch 'trunk' into add/outbox-collection
pfefferle Dec 11, 2024
dd44ee2
Merge branch 'trunk' into add/outbox-collection
pfefferle Dec 11, 2024
451e339
Merge branch 'trunk' into add/outbox-collection
pfefferle Dec 17, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Outbox queue
* `icon` support for `Audio` and `Video` attachments
* Send "new follower" emails
* Send "direct message" emails
Expand Down
10 changes: 3 additions & 7 deletions includes/activity/class-base-object.php
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ public static function init_from_json( $json ) {
$array = \json_decode( $json, true );

if ( ! is_array( $array ) ) {
$array = array();
return new WP_Error( 'invalid_json', __( 'Invalid JSON', 'activitypub' ), array( 'status' => 400 ) );
}

return self::init_from_array( $array );
Expand All @@ -600,15 +600,11 @@ public static function init_from_json( $json ) {
*/
public static function init_from_array( $data ) {
if ( ! is_array( $data ) ) {
return new WP_Error( 'invalid_array', __( 'Invalid array', 'activitypub' ), array( 'status' => 404 ) );
return new WP_Error( 'invalid_array', __( 'Invalid array', 'activitypub' ), array( 'status' => 400 ) );
}

$object = new static();

foreach ( $data as $key => $value ) {
$key = camel_to_snake_case( $key );
call_user_func( array( $object, 'set_' . $key ), $value );
}
$object->from_array( $data );

return $object;
}
Expand Down
56 changes: 55 additions & 1 deletion includes/class-activitypub.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace Activitypub;

use Exception;
use Activitypub\Collection\Outbox;
use Activitypub\Collection\Followers;
use Activitypub\Collection\Extra_Fields;

Expand Down Expand Up @@ -455,7 +456,7 @@ public static function plugin_update_message( $data ) {
}

/**
* Register the "Followers" Taxonomy.
* Register Custom Post Types.
*/
private static function register_post_types() {
\register_post_type(
Expand Down Expand Up @@ -525,6 +526,59 @@ private static function register_post_types() {
)
);

// Register Outbox Post-Type.
register_post_type(
Outbox::POST_TYPE,
array(
'labels' => array(
'name' => _x( 'Outbox', 'post_type plural name', 'activitypub' ),
'singular_name' => _x( 'Outbox Item', 'post_type single name', 'activitypub' ),
),
'capabilities' => array(
'create_posts' => false,
//'edit_posts' => false,
//'delete_posts' => false,
),
'map_meta_cap' => true,
'public' => true,
'hierarchical' => true,
'rewrite' => false,
'query_var' => false,
'delete_with_user' => true,
'can_export' => true,
'supports' => array(),
'taxonomies' => array( 'ap_actor', 'ap_activity_type' ),
)
);

\register_taxonomy(
'ap_actor',
Outbox::POST_TYPE,
array(
'labels' => array(
'name' => _x( 'Actor', 'post_type plural name', 'activitypub' ),
'singular_name' => _x( '', 'post_type single name', 'activitypub' ),
),
'hierarchical' => true,
'public' => false,
)
);
\register_taxonomy_for_object_type( 'ap_actor', Outbox::POST_TYPE );

\register_taxonomy(
'ap_activity_type',
Outbox::POST_TYPE,
array(
'labels' => array(
'name' => _x( 'Activity Type', 'post_type plural name', 'activitypub' ),
'singular_name' => _x( '', 'post_type single name', 'activitypub' ),
),
'hierarchical' => true,
'public' => false,
)
);
\register_taxonomy_for_object_type( 'ap_activity_type', Outbox::POST_TYPE );

// Both User and Blog Extra Fields types have the same args.
$args = array(
'labels' => array(
Expand Down
253 changes: 18 additions & 235 deletions includes/class-scheduler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

namespace Activitypub;

use Activitypub\Scheduler\Post;
use Activitypub\Scheduler\Actor;
use Activitypub\Scheduler\Comment;
use Activitypub\Collection\Followers;

/**
Expand All @@ -20,63 +23,27 @@ class Scheduler {
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
// Post transitions.
\add_action( 'transition_post_status', array( self::class, 'schedule_post_activity' ), 33, 3 );
\add_action(
'edit_attachment',
function ( $post_id ) {
self::schedule_post_activity( 'publish', 'publish', $post_id );
}
);
\add_action(
'add_attachment',
function ( $post_id ) {
self::schedule_post_activity( 'publish', '', $post_id );
}
);
\add_action(
'delete_attachment',
function ( $post_id ) {
self::schedule_post_activity( 'trash', '', $post_id );
}
);

if ( ! ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS ) {
// Comment transitions.
\add_action( 'transition_comment_status', array( self::class, 'schedule_comment_activity' ), 20, 3 );
\add_action(
'edit_comment',
function ( $comment_id ) {
self::schedule_comment_activity( 'approved', 'approved', $comment_id );
}
);
\add_action(
'wp_insert_comment',
function ( $comment_id ) {
self::schedule_comment_activity( 'approved', '', $comment_id );
}
);
}
self::register_schedulers();

// Follower Cleanups.
\add_action( 'activitypub_update_followers', array( self::class, 'update_followers' ) );
\add_action( 'activitypub_cleanup_followers', array( self::class, 'cleanup_followers' ) );
}

// Profile updates for blog options.
if ( ! is_user_type_disabled( 'blog' ) ) {
\add_action( 'update_option_site_icon', array( self::class, 'blog_user_update' ) );
\add_action( 'update_option_blogdescription', array( self::class, 'blog_user_update' ) );
\add_action( 'update_option_blogname', array( self::class, 'blog_user_update' ) );
\add_filter( 'pre_set_theme_mod_custom_logo', array( self::class, 'blog_user_update' ) );
\add_filter( 'pre_set_theme_mod_header_image', array( self::class, 'blog_user_update' ) );
}
/**
* Register handlers.
*/
public static function register_schedulers() {
Post::init();
Actor::init();
Comment::init();

// Profile updates for user options.
if ( ! is_user_type_disabled( 'user' ) ) {
\add_action( 'wp_update_user', array( self::class, 'user_update' ) );
\add_action( 'updated_user_meta', array( self::class, 'user_meta_update' ), 10, 3 );
// @todo figure out a feasible way of updating the header image since it's not unique to any user.
}
/**
* Register additional schedulers.
*
* @since 5.0.0
*/
do_action( 'activitypub_register_schedulers' );
}

/**
Expand All @@ -102,125 +69,6 @@ public static function deregister_schedules() {
wp_unschedule_hook( 'activitypub_cleanup_followers' );
}


/**
* Schedule Activities.
*
* @param string $new_status New post status.
* @param string $old_status Old post status.
* @param \WP_Post $post Post object.
*/
public static function schedule_post_activity( $new_status, $old_status, $post ) {
$post = get_post( $post );

if ( ! $post || is_post_disabled( $post ) ) {
return;
}

if ( 'ap_extrafield' === $post->post_type ) {
self::schedule_profile_update( $post->post_author );
return;
}

if ( 'ap_extrafield_blog' === $post->post_type ) {
self::schedule_profile_update( 0 );
return;
}

// Do not send activities if post is password protected.
if ( \post_password_required( $post ) ) {
return;
}

// Check if post-type supports ActivityPub.
$post_types = \get_post_types_by_support( 'activitypub' );
if ( ! \in_array( $post->post_type, $post_types, true ) ) {
return;
}

switch ( $new_status ) {
case 'publish':
$type = ( 'publish' === $old_status ) ? 'Update' : 'Create';
break;

case 'draft':
$type = ( 'publish' === $old_status ) ? 'Update' : false;
break;

case 'trash':
$type = 'federated' === get_wp_object_state( $post ) ? 'Delete' : false;
break;

default:
$type = false;
}

// No activity to schedule.
if ( empty( $type ) ) {
return;
}

$hook = 'activitypub_send_post';
$args = array( $post->ID, $type );

if ( false === wp_next_scheduled( $hook, $args ) ) {
set_wp_object_state( $post, 'federate' );
\wp_schedule_single_event( \time() + 10, $hook, $args );
}
}

/**
* Schedule Comment Activities.
*
* @see transition_comment_status()
*
* @param string $new_status New comment status.
* @param string $old_status Old comment status.
* @param \WP_Comment $comment Comment object.
*/
public static function schedule_comment_activity( $new_status, $old_status, $comment ) {
$comment = get_comment( $comment );

// Federate only comments that are written by a registered user.
if ( ! $comment || ! $comment->user_id ) {
return;
}

$type = false;

if (
'approved' === $new_status &&
'approved' !== $old_status
) {
$type = 'Create';
} elseif ( 'approved' === $new_status ) {
$type = 'Update';
\update_comment_meta( $comment->comment_ID, 'activitypub_comment_modified', time(), true );
} elseif (
'trash' === $new_status ||
'spam' === $new_status
) {
$type = 'Delete';
}

if ( empty( $type ) ) {
return;
}

// Check if comment should be federated or not.
if ( ! should_comment_be_federated( $comment ) ) {
return;
}

$hook = 'activitypub_send_comment';
$args = array( $comment->comment_ID, $type );

if ( false === wp_next_scheduled( $hook, $args ) ) {
set_wp_object_state( $comment, 'federate' );
\wp_schedule_single_event( \time(), $hook, $args );
}
}

/**
* Update followers.
*/
Expand Down Expand Up @@ -290,69 +138,4 @@ public static function cleanup_followers() {
}
}
}

/**
* Send a profile update when relevant user meta is updated.
*
* @param int $meta_id Meta ID being updated.
* @param int $user_id User ID being updated.
* @param string $meta_key Meta key being updated.
*/
public static function user_meta_update( $meta_id, $user_id, $meta_key ) {
// Don't bother if the user can't publish.
if ( ! \user_can( $user_id, 'activitypub' ) ) {
return;
}

// The user meta fields that affect a profile.
$fields = array(
'activitypub_description',
'activitypub_header_image',
'description',
'user_url',
'display_name',
);
if ( in_array( $meta_key, $fields, true ) ) {
self::schedule_profile_update( $user_id );
}
}

/**
* Send a profile update when a user is updated.
*
* @param int $user_id User ID being updated.
*/
public static function user_update( $user_id ) {
// Don't bother if the user can't publish.
if ( ! \user_can( $user_id, 'activitypub' ) ) {
return;
}

self::schedule_profile_update( $user_id );
}

/**
* Theme mods only have a dynamic filter so we fudge it like this.
*
* @param mixed $value Optional. The value to be updated. Default null.
*
* @return mixed
*/
public static function blog_user_update( $value = null ) {
self::schedule_profile_update( 0 );
return $value;
}

/**
* Send a profile update to all followers. Gets hooked into all relevant options/meta etc.
*
* @param int $user_id The user ID to update (Could be 0 for Blog-User).
*/
public static function schedule_profile_update( $user_id ) {
\wp_schedule_single_event(
\time() + 10,
'activitypub_send_update_profile_activity',
array( $user_id )
);
}
}
Loading
Loading