Merge branch 'master' into signature_verification
This commit is contained in:
commit
4b294bb8a6
18 changed files with 735 additions and 182 deletions
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<!--- Provide a general summary of your changes in the Title above -->
|
||||||
|
|
||||||
|
Fixes #
|
||||||
|
|
||||||
|
## Proposed changes:
|
||||||
|
<!--- Explain what functional changes your PR includes -->
|
||||||
|
*
|
||||||
|
|
||||||
|
### Other information:
|
||||||
|
|
||||||
|
- [ ] Have you written new tests for your changes, if applicable?
|
||||||
|
|
||||||
|
## Testing instructions:
|
||||||
|
<!-- If you were reviewing this PR, how would you like the instructions to be presented? -->
|
||||||
|
<!-- Please include detailed testing steps, explaining how to test your change. -->
|
||||||
|
<!-- Bear in mind that context you working on is not obvious for everyone. -->
|
||||||
|
<!-- Adding "simple" configuration steps will help reviewers to get to your PR as quickly as possible. -->
|
||||||
|
<!-- "Before / After" screenshots can also be very helpful when the change is visual. -->
|
||||||
|
|
||||||
|
* Go to '..'
|
||||||
|
*
|
||||||
|
|
152
activitypub.php
152
activitypub.php
|
@ -24,87 +24,79 @@ function init() {
|
||||||
\defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 );
|
\defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 );
|
||||||
\defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=<p>)|(?<=<br>)|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' );
|
\defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=<p>)|(?<=<br>)|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' );
|
||||||
\defined( 'ACTIVITYPUB_USERNAME_REGEXP' ) || \define( 'ACTIVITYPUB_USERNAME_REGEXP', '(?:([A-Za-z0-9_-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))' );
|
\defined( 'ACTIVITYPUB_USERNAME_REGEXP' ) || \define( 'ACTIVITYPUB_USERNAME_REGEXP', '(?:([A-Za-z0-9_-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))' );
|
||||||
\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "<p><strong>[ap_title]</strong></p>\n\n[ap_content]\n\n<p>[ap_hashtags]</p>\n\n<p>[ap_shortlink]</p>" );
|
\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "<strong>[ap_title]</strong>\n\n[ap_content]\n\n[ap_hashtags]\n\n[ap_shortlink]" );
|
||||||
\defined( 'ACTIVITYPUB_SECURE_MODE' ) || \define( 'ACTIVITYPUB_SECURE_MODE', apply_filters( 'activitypub_secure_mode', $value = false ) );
|
\defined( 'ACTIVITYPUB_SECURE_MODE' ) || \define( 'ACTIVITYPUB_SECURE_MODE', apply_filters( 'activitypub_secure_mode', $value = false ) );
|
||||||
|
|
||||||
\define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
\define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
\define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
|
\define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
|
||||||
\define( 'ACTIVITYPUB_PLUGIN_FILE', plugin_dir_path( __FILE__ ) . '/' . basename( __FILE__ ) );
|
\define( 'ACTIVITYPUB_PLUGIN_FILE', plugin_dir_path( __FILE__ ) . '/' . basename( __FILE__ ) );
|
||||||
|
|
||||||
\define( 'ACTIVITYPUB_OBJECT', 'ACTIVITYPUB_OBJECT' );
|
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/table/class-followers.php';
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-http.php';
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-signature.php';
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-webfinger.php';
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/peer/class-followers.php';
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/functions.php';
|
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/model/class-activity.php';
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/model/class-post.php';
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/model/class-user.php';
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/model/class-follower.php';
|
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-migration.php';
|
|
||||||
Migration::init();
|
Migration::init();
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-activity-dispatcher.php';
|
|
||||||
Activity_Dispatcher::init();
|
Activity_Dispatcher::init();
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-activitypub.php';
|
|
||||||
Activitypub::init();
|
Activitypub::init();
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/collection/class-followers.php';
|
|
||||||
Collection\Followers::init();
|
Collection\Followers::init();
|
||||||
|
|
||||||
// Configure the REST API route
|
// Configure the REST API route
|
||||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-outbox.php';
|
|
||||||
Rest\Outbox::init();
|
Rest\Outbox::init();
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-inbox.php';
|
|
||||||
Rest\Inbox::init();
|
Rest\Inbox::init();
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-followers.php';
|
|
||||||
Rest\Followers::init();
|
Rest\Followers::init();
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-following.php';
|
|
||||||
Rest\Following::init();
|
Rest\Following::init();
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-server.php';
|
|
||||||
\Activitypub\Rest\Server::init();
|
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-webfinger.php';
|
|
||||||
Rest\Webfinger::init();
|
Rest\Webfinger::init();
|
||||||
|
|
||||||
|
Admin::init();
|
||||||
|
Hashtag::init();
|
||||||
|
Shortcodes::init();
|
||||||
|
Mention::init();
|
||||||
|
Debug::init();
|
||||||
|
Health_Check::init();
|
||||||
|
Scheduler::init();
|
||||||
|
}
|
||||||
|
\add_action( 'plugins_loaded', '\Activitypub\init' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Autoloader
|
||||||
|
*/
|
||||||
|
spl_autoload_register(
|
||||||
|
function ( $full_class ) {
|
||||||
|
$base_dir = \dirname( __FILE__ ) . '/includes/';
|
||||||
|
$base = 'activitypub';
|
||||||
|
|
||||||
|
$class = strtolower( $full_class );
|
||||||
|
|
||||||
|
if ( strncmp( $class, $base, strlen( $base ) ) === 0 ) {
|
||||||
|
$class = str_replace( 'activitypub\\', '', $class );
|
||||||
|
|
||||||
|
if ( false !== strpos( $class, '\\' ) ) {
|
||||||
|
$parts = explode( '\\', $class );
|
||||||
|
$class = array_pop( $parts );
|
||||||
|
$sub_dir = implode( '/', $parts );
|
||||||
|
$base_dir = $base_dir . $sub_dir . '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = 'class-' . strtr( $class, '_', '-' );
|
||||||
|
$file = $base_dir . $filename . '.php';
|
||||||
|
|
||||||
|
if ( file_exists( $file ) && is_readable( $file ) ) {
|
||||||
|
require_once $file;
|
||||||
|
} else {
|
||||||
|
// translators: %s is the class name
|
||||||
|
\wp_die( sprintf( esc_html__( 'Required class not found or not readable: %s', 'activitypub' ), esc_html( $full_class ) ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
require_once \dirname( __FILE__ ) . '/includes/functions.php';
|
||||||
|
|
||||||
// load NodeInfo endpoints only if blog is public
|
// load NodeInfo endpoints only if blog is public
|
||||||
if ( \get_option( 'blog_public', 1 ) ) {
|
if ( \get_option( 'blog_public', 1 ) ) {
|
||||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-nodeinfo.php';
|
|
||||||
Rest\NodeInfo::init();
|
Rest\NodeInfo::init();
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-admin.php';
|
$debug_file = \dirname( __FILE__ ) . '/includes/debug.php';
|
||||||
Admin::init();
|
if ( \WP_DEBUG && file_exists( $debug_file ) && is_readable( $debug_file ) ) {
|
||||||
|
require_once $debug_file;
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-hashtag.php';
|
|
||||||
Hashtag::init();
|
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-shortcodes.php';
|
|
||||||
Shortcodes::init();
|
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-mention.php';
|
|
||||||
Mention::init();
|
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-debug.php';
|
|
||||||
Debug::init();
|
|
||||||
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/class-health-check.php';
|
|
||||||
Health_Check::init();
|
|
||||||
|
|
||||||
if ( \WP_DEBUG ) {
|
|
||||||
require_once \dirname( __FILE__ ) . '/includes/debug.php';
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
\add_action( 'plugins_loaded', '\Activitypub\init' );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add plugin settings link
|
* Add plugin settings link
|
||||||
|
@ -118,34 +110,32 @@ function plugin_settings_link( $actions ) {
|
||||||
|
|
||||||
return \array_merge( $settings_link, $actions );
|
return \array_merge( $settings_link, $actions );
|
||||||
}
|
}
|
||||||
\add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), '\Activitypub\plugin_settings_link' );
|
\add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), __NAMESPACE__ . '\plugin_settings_link' );
|
||||||
|
|
||||||
/**
|
\register_activation_hook(
|
||||||
* Add rewrite rules
|
__FILE__,
|
||||||
*/
|
array(
|
||||||
function add_rewrite_rules() {
|
__NAMESPACE__ . '\Activitypub',
|
||||||
if ( ! \class_exists( 'Webfinger' ) ) {
|
'activate',
|
||||||
\add_rewrite_rule( '^.well-known/webfinger', 'index.php?rest_route=/activitypub/1.0/webfinger', 'top' );
|
)
|
||||||
}
|
);
|
||||||
|
|
||||||
if ( ! \class_exists( 'Nodeinfo' ) || ! (bool) \get_option( 'blog_public', 1 ) ) {
|
\register_deactivation_hook(
|
||||||
\add_rewrite_rule( '^.well-known/nodeinfo', 'index.php?rest_route=/activitypub/1.0/nodeinfo/discovery', 'top' );
|
__FILE__,
|
||||||
\add_rewrite_rule( '^.well-known/x-nodeinfo2', 'index.php?rest_route=/activitypub/1.0/nodeinfo2', 'top' );
|
array(
|
||||||
}
|
__NAMESPACE__ . '\Activitypub',
|
||||||
|
'deactivate',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
\add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
|
register_uninstall_hook(
|
||||||
}
|
__FILE__,
|
||||||
\add_action( 'init', '\Activitypub\add_rewrite_rules', 1 );
|
array(
|
||||||
|
__NAMESPACE__ . '\Activitypub',
|
||||||
|
'uninstall',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush rewrite rules;
|
|
||||||
*/
|
|
||||||
function flush_rewrite_rules() {
|
|
||||||
\Activitypub\add_rewrite_rules();
|
|
||||||
\flush_rewrite_rules();
|
|
||||||
}
|
|
||||||
\register_activation_hook( __FILE__, '\Activitypub\flush_rewrite_rules' );
|
|
||||||
\register_deactivation_hook( __FILE__, '\flush_rewrite_rules' );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only load code that needs BuddyPress to run once BP is loaded and initialized.
|
* Only load code that needs BuddyPress to run once BP is loaded and initialized.
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"squizlabs/php_codesniffer": "3.*",
|
"squizlabs/php_codesniffer": "3.*",
|
||||||
"wp-coding-standards/wpcs": "*",
|
"wp-coding-standards/wpcs": "*",
|
||||||
"yoast/phpunit-polyfills": "^1.0",
|
"yoast/phpunit-polyfills": "^1.0",
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.1"
|
"dealerdirect/phpcodesniffer-composer-installer": "^1.0.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": true
|
"allow-plugins": true
|
||||||
|
|
|
@ -5,6 +5,8 @@ use Activitypub\Model\Post;
|
||||||
use Activitypub\Model\Activity;
|
use Activitypub\Model\Activity;
|
||||||
use Activitypub\Collection\Followers;
|
use Activitypub\Collection\Followers;
|
||||||
|
|
||||||
|
use function Activitypub\safe_remote_post;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ActivityPub Activity_Dispatcher Class
|
* ActivityPub Activity_Dispatcher Class
|
||||||
*
|
*
|
||||||
|
@ -37,7 +39,7 @@ class Activity_Dispatcher {
|
||||||
/**
|
/**
|
||||||
* Send "update" activities.
|
* Send "update" activities.
|
||||||
*
|
*
|
||||||
* @param Activitypub\Model\Post $activitypub_post
|
* @param Activitypub\Model\Post $activitypub_post The ActivityPub Post.
|
||||||
*/
|
*/
|
||||||
public static function send_update_activity( Post $activitypub_post ) {
|
public static function send_update_activity( Post $activitypub_post ) {
|
||||||
self::send_activity( $activitypub_post, 'Update' );
|
self::send_activity( $activitypub_post, 'Update' );
|
||||||
|
@ -46,23 +48,23 @@ class Activity_Dispatcher {
|
||||||
/**
|
/**
|
||||||
* Send "delete" activities.
|
* Send "delete" activities.
|
||||||
*
|
*
|
||||||
* @param Activitypub\Model\Post $activitypub_post
|
* @param Activitypub\Model\Post $activitypub_post The ActivityPub Post.
|
||||||
*/
|
*/
|
||||||
public static function send_delete_activity( Post $activitypub_post ) {
|
public static function send_delete_activity( Post $activitypub_post ) {
|
||||||
self::send_activity( $activitypub_post, 'Delete' );
|
self::send_activity( $activitypub_post, 'Delete' );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Undocumented function
|
* Send Activities to followers and mentioned users.
|
||||||
*
|
*
|
||||||
* @param Activitypub\Model\Post $activitypub_post
|
* @param Activitypub\Model\Post $activitypub_post The ActivityPub Post.
|
||||||
* @param [type] $activity_type
|
* @param string $activity_type The Activity-Type.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function send_activity( Post $activitypub_post, $activity_type ) {
|
public static function send_activity( Post $activitypub_post, $activity_type ) {
|
||||||
// check if a migration is needed before sending new posts
|
// check if a migration is needed before sending new posts
|
||||||
\Activitypub\Migration::maybe_migrate();
|
Migration::maybe_migrate();
|
||||||
|
|
||||||
// get latest version of post
|
// get latest version of post
|
||||||
$user_id = $activitypub_post->get_post_author();
|
$user_id = $activitypub_post->get_post_author();
|
||||||
|
@ -79,7 +81,7 @@ class Activity_Dispatcher {
|
||||||
foreach ( $inboxes as $inbox ) {
|
foreach ( $inboxes as $inbox ) {
|
||||||
$activity = $activitypub_activity->to_json();
|
$activity = $activitypub_activity->to_json();
|
||||||
|
|
||||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
safe_remote_post( $inbox, $activity, $user_id );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,40 @@ class Activitypub {
|
||||||
\add_post_type_support( $post_type, 'activitypub' );
|
\add_post_type_support( $post_type, 'activitypub' );
|
||||||
}
|
}
|
||||||
|
|
||||||
\add_action( 'transition_post_status', array( self::class, 'schedule_post_activity' ), 33, 3 );
|
|
||||||
\add_action( 'wp_trash_post', array( self::class, 'trash_post' ), 1 );
|
\add_action( 'wp_trash_post', array( self::class, 'trash_post' ), 1 );
|
||||||
\add_action( 'untrash_post', array( self::class, 'untrash_post' ), 1 );
|
\add_action( 'untrash_post', array( self::class, 'untrash_post' ), 1 );
|
||||||
|
|
||||||
|
\add_action( 'init', array( self::class, 'add_rewrite_rules' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activation Hook
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function activate() {
|
||||||
|
self::flush_rewrite_rules();
|
||||||
|
|
||||||
|
Scheduler::register_schedules();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivation Hook
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function deactivate() {
|
||||||
|
self::flush_rewrite_rules();
|
||||||
|
|
||||||
|
Scheduler::deregister_schedules();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall Hook
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function uninstall() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,36 +131,6 @@ class Activitypub {
|
||||||
return $vars;
|
return $vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 ) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
|
||||||
|
|
||||||
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
|
|
||||||
\wp_schedule_single_event( \time(), 'activitypub_send_create_activity', array( $activitypub_post ) );
|
|
||||||
} elseif ( 'publish' === $new_status ) {
|
|
||||||
\wp_schedule_single_event( \time(), 'activitypub_send_update_activity', array( $activitypub_post ) );
|
|
||||||
} elseif ( 'trash' === $new_status ) {
|
|
||||||
\wp_schedule_single_event( \time(), 'activitypub_send_delete_activity', array( $activitypub_post ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the default avatar.
|
* Replaces the default avatar.
|
||||||
*
|
*
|
||||||
|
@ -148,7 +149,14 @@ class Activitypub {
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowed_comment_types = \apply_filters( 'get_avatar_comment_types', array( 'comment' ) );
|
$allowed_comment_types = \apply_filters( 'get_avatar_comment_types', array( 'comment' ) );
|
||||||
if ( ! empty( $id_or_email->comment_type ) && ! \in_array( $id_or_email->comment_type, (array) $allowed_comment_types, true ) ) {
|
if (
|
||||||
|
! empty( $id_or_email->comment_type ) &&
|
||||||
|
! \in_array(
|
||||||
|
$id_or_email->comment_type,
|
||||||
|
(array) $allowed_comment_types,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
) {
|
||||||
$args['url'] = false;
|
$args['url'] = false;
|
||||||
/** This filter is documented in wp-includes/link-template.php */
|
/** This filter is documented in wp-includes/link-template.php */
|
||||||
return \apply_filters( 'get_avatar_data', $args, $id_or_email );
|
return \apply_filters( 'get_avatar_data', $args, $id_or_email );
|
||||||
|
@ -193,7 +201,12 @@ class Activitypub {
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function trash_post( $post_id ) {
|
public static function trash_post( $post_id ) {
|
||||||
\add_post_meta( $post_id, 'activitypub_canonical_url', \get_permalink( $post_id ), true );
|
\add_post_meta(
|
||||||
|
$post_id,
|
||||||
|
'activitypub_canonical_url',
|
||||||
|
\get_permalink( $post_id ),
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -206,4 +219,40 @@ class Activitypub {
|
||||||
public static function untrash_post( $post_id ) {
|
public static function untrash_post( $post_id ) {
|
||||||
\delete_post_meta( $post_id, 'activitypub_canonical_url' );
|
\delete_post_meta( $post_id, 'activitypub_canonical_url' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add rewrite rules
|
||||||
|
*/
|
||||||
|
public static function add_rewrite_rules() {
|
||||||
|
if ( ! \class_exists( 'Webfinger' ) ) {
|
||||||
|
\add_rewrite_rule(
|
||||||
|
'^.well-known/webfinger',
|
||||||
|
'index.php?rest_route=/activitypub/1.0/webfinger',
|
||||||
|
'top'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! \class_exists( 'Nodeinfo' ) && true === (bool) \get_option( 'blog_public', 1 ) ) {
|
||||||
|
\add_rewrite_rule(
|
||||||
|
'^.well-known/nodeinfo',
|
||||||
|
'index.php?rest_route=/activitypub/1.0/nodeinfo/discovery',
|
||||||
|
'top'
|
||||||
|
);
|
||||||
|
\add_rewrite_rule(
|
||||||
|
'^.well-known/x-nodeinfo2',
|
||||||
|
'index.php?rest_route=/activitypub/1.0/nodeinfo2',
|
||||||
|
'top'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
\add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush rewrite rules;
|
||||||
|
*/
|
||||||
|
public static function flush_rewrite_rules() {
|
||||||
|
self::add_rewrite_rules();
|
||||||
|
\flush_rewrite_rules();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Activitypub;
|
namespace Activitypub;
|
||||||
|
|
||||||
use Acctivitypub\Model\Follower;
|
use Activitypub\Model\Follower;
|
||||||
|
use Activitypub\Collection\Followers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ActivityPub Migration Class
|
||||||
|
*
|
||||||
|
* @author Matthias Pfefferle
|
||||||
|
*/
|
||||||
class Migration {
|
class Migration {
|
||||||
|
/**
|
||||||
|
* Initialize the class, registering WordPress hooks
|
||||||
|
*/
|
||||||
public static function init() {
|
public static function init() {
|
||||||
\add_action( 'activitypub_schedule_migration', array( self::class, 'maybe_migrate' ) );
|
\add_action( 'activitypub_schedule_migration', array( self::class, 'maybe_migrate' ) );
|
||||||
}
|
}
|
||||||
|
@ -59,7 +68,7 @@ class Migration {
|
||||||
$followers = get_user_meta( $user_id, 'activitypub_followers', true );
|
$followers = get_user_meta( $user_id, 'activitypub_followers', true );
|
||||||
|
|
||||||
if ( $followers ) {
|
if ( $followers ) {
|
||||||
foreach ( $followers as $follower ) {
|
foreach ( $followers as $actor ) {
|
||||||
$meta = get_remote_metadata_by_actor( $actor );
|
$meta = get_remote_metadata_by_actor( $actor );
|
||||||
|
|
||||||
$follower = new Follower( $actor );
|
$follower = new Follower( $actor );
|
||||||
|
@ -74,7 +83,7 @@ class Migration {
|
||||||
|
|
||||||
$follower->upsert();
|
$follower->upsert();
|
||||||
|
|
||||||
$result = wp_set_object_terms( $user_id, $follower->get_actor(), self::TAXONOMY, true );
|
$result = wp_set_object_terms( $user_id, $follower->get_actor(), Followers::TAXONOMY, true );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
138
includes/class-scheduler.php
Normal file
138
includes/class-scheduler.php
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Activitypub;
|
||||||
|
|
||||||
|
use Activitypub\Model\Post;
|
||||||
|
use Activitypub\Collection\Followers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ActivityPub Scheduler Class
|
||||||
|
*
|
||||||
|
* @author Matthias Pfefferle
|
||||||
|
*/
|
||||||
|
class Scheduler {
|
||||||
|
/**
|
||||||
|
* Initialize the class, registering WordPress hooks
|
||||||
|
*/
|
||||||
|
public static function init() {
|
||||||
|
\add_action( 'transition_post_status', array( self::class, 'schedule_post_activity' ), 33, 3 );
|
||||||
|
|
||||||
|
\add_action( 'activitypub_update_followers', array( self::class, 'update_followers' ) );
|
||||||
|
\add_action( 'activitypub_cleanup_followers', array( self::class, 'cleanup_followers' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule all ActivityPub schedules.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function register_schedules() {
|
||||||
|
if ( ! \wp_next_scheduled( 'activitypub_update_followers' ) ) {
|
||||||
|
\wp_schedule_event( time(), 'hourly', 'activitypub_update_followers' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! \wp_next_scheduled( 'activitypub_cleanup_followers' ) ) {
|
||||||
|
\wp_schedule_event( time(), 'daily', 'activitypub_cleanup_followers' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unscedule all ActivityPub schedules.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function deregister_schedules() {
|
||||||
|
wp_unschedule_hook( 'activitypub_update_followers' );
|
||||||
|
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 ) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$activitypub_post = new Post( $post );
|
||||||
|
|
||||||
|
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
|
||||||
|
\wp_schedule_single_event(
|
||||||
|
\time(),
|
||||||
|
'activitypub_send_create_activity',
|
||||||
|
array( $activitypub_post )
|
||||||
|
);
|
||||||
|
} elseif ( 'publish' === $new_status ) {
|
||||||
|
\wp_schedule_single_event(
|
||||||
|
\time(),
|
||||||
|
'activitypub_send_update_activity',
|
||||||
|
array( $activitypub_post )
|
||||||
|
);
|
||||||
|
} elseif ( 'trash' === $new_status ) {
|
||||||
|
\wp_schedule_single_event(
|
||||||
|
\time(),
|
||||||
|
'activitypub_send_delete_activity',
|
||||||
|
array( $activitypub_post )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update followers
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function update_followers() {
|
||||||
|
$followers = Followers::get_outdated_followers();
|
||||||
|
|
||||||
|
foreach ( $followers as $follower ) {
|
||||||
|
$meta = get_remote_metadata_by_actor( $follower->get_actor() );
|
||||||
|
|
||||||
|
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
|
||||||
|
$follower->set_error( $meta );
|
||||||
|
} else {
|
||||||
|
$follower->from_meta( $meta );
|
||||||
|
}
|
||||||
|
|
||||||
|
$follower->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup followers
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function cleanup_followers() {
|
||||||
|
$followers = Followers::get_faulty_followers();
|
||||||
|
|
||||||
|
foreach ( $followers as $follower ) {
|
||||||
|
$meta = get_remote_metadata_by_actor( $follower->get_actor() );
|
||||||
|
|
||||||
|
if ( is_tombstone( $meta ) ) {
|
||||||
|
$follower->delete();
|
||||||
|
} elseif ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
|
||||||
|
if ( 5 <= $follower->count_errors() ) {
|
||||||
|
$follower->delete();
|
||||||
|
} else {
|
||||||
|
$follower->set_error( $meta );
|
||||||
|
$follower->update();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$follower->reset_errors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,14 +3,12 @@ namespace Activitypub;
|
||||||
|
|
||||||
class Shortcodes {
|
class Shortcodes {
|
||||||
/**
|
/**
|
||||||
* Class constructor, registering WordPress then shortcodes
|
* Initialize the class, registering WordPress hooks
|
||||||
*
|
|
||||||
* @param WP_Post $post A WordPress Post Object
|
|
||||||
*/
|
*/
|
||||||
public static function init() {
|
public static function init() {
|
||||||
foreach ( get_class_methods( 'Activitypub\Shortcodes' ) as $shortcode ) {
|
foreach ( get_class_methods( self::class ) as $shortcode ) {
|
||||||
if ( 'init' !== $shortcode ) {
|
if ( 'init' !== $shortcode ) {
|
||||||
add_shortcode( 'ap_' . $shortcode, array( 'Activitypub\Shortcodes', $shortcode ) );
|
add_shortcode( 'ap_' . $shortcode, array( self::class, $shortcode ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ class Followers {
|
||||||
'single' => true,
|
'single' => true,
|
||||||
'sanitize_callback' => function( $value ) {
|
'sanitize_callback' => function( $value ) {
|
||||||
if ( ! is_numeric( $value ) && (int) $value !== $value ) {
|
if ( ! is_numeric( $value ) && (int) $value !== $value ) {
|
||||||
$value = strtotime( 'now' );
|
$value = \time();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
|
@ -186,7 +186,7 @@ class Followers {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles "Unfollow" requests
|
* Handle "Unfollow" requests
|
||||||
*
|
*
|
||||||
* @param array $object The JSON "Undo" Activity
|
* @param array $object The JSON "Undo" Activity
|
||||||
* @param int $user_id The ID of the ID of the WordPress User
|
* @param int $user_id The ID of the ID of the WordPress User
|
||||||
|
@ -202,7 +202,7 @@ class Followers {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new Follower
|
* Add new 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
|
||||||
|
@ -316,7 +316,7 @@ class Followers {
|
||||||
*
|
*
|
||||||
* @return array The Term list of Followers, the format depends on $output
|
* @return array The Term list of Followers, the format depends on $output
|
||||||
*/
|
*/
|
||||||
public static function get_followers( $user_id, $output = ARRAY_N, $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,
|
'taxonomy' => self::TAXONOMY,
|
||||||
'hide_empty' => false,
|
'hide_empty' => false,
|
||||||
|
@ -329,25 +329,13 @@ class Followers {
|
||||||
|
|
||||||
$args = wp_parse_args( $args, $defaults );
|
$args = wp_parse_args( $args, $defaults );
|
||||||
$terms = new WP_Term_Query( $args );
|
$terms = new WP_Term_Query( $args );
|
||||||
|
|
||||||
$items = array();
|
$items = array();
|
||||||
|
|
||||||
// change output format
|
|
||||||
switch ( $output ) {
|
|
||||||
case ACTIVITYPUB_OBJECT:
|
|
||||||
foreach ( $terms->get_terms() as $follower ) {
|
foreach ( $terms->get_terms() as $follower ) {
|
||||||
$items[] = new Follower( $follower->name ); // phpcs:ignore
|
$items[] = new Follower( $follower->name ); // phpcs:ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
return $items;
|
return $items;
|
||||||
case OBJECT:
|
|
||||||
return $terms->get_terms();
|
|
||||||
case ARRAY_N:
|
|
||||||
default:
|
|
||||||
foreach ( $terms->get_terms() as $follower ) {
|
|
||||||
$items[] = $follower->name; // phpcs:ignore
|
|
||||||
}
|
|
||||||
return $items;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -404,4 +392,70 @@ class Followers {
|
||||||
|
|
||||||
return array_filter( $results );
|
return array_filter( $results );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all Followers that have not been updated for a given time
|
||||||
|
*
|
||||||
|
* @param enum $output The output format, supported ARRAY_N, OBJECT and ACTIVITYPUB_OBJECT.
|
||||||
|
* @param int $number Limits the result.
|
||||||
|
* @param int $older_than The time in seconds.
|
||||||
|
*
|
||||||
|
* @return mixed The Term list of Followers, the format depends on $output.
|
||||||
|
*/
|
||||||
|
public static function get_outdated_followers( $number = 50, $older_than = 604800 ) {
|
||||||
|
$args = array(
|
||||||
|
'taxonomy' => self::TAXONOMY,
|
||||||
|
'number' => $number,
|
||||||
|
'meta_key' => 'updated_at',
|
||||||
|
'orderby' => 'meta_value_num',
|
||||||
|
'order' => 'DESC',
|
||||||
|
'meta_query' => array(
|
||||||
|
array(
|
||||||
|
'key' => 'updated_at',
|
||||||
|
'value' => time() - $older_than,
|
||||||
|
'type' => 'numeric',
|
||||||
|
'compare' => '<=',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$terms = new WP_Term_Query( $args );
|
||||||
|
$items = array();
|
||||||
|
|
||||||
|
foreach ( $terms->get_terms() as $follower ) {
|
||||||
|
$items[] = new Follower( $follower->name ); // phpcs:ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all Followers that had errors
|
||||||
|
*
|
||||||
|
* @param enum $output The output format, supported ARRAY_N, OBJECT and ACTIVITYPUB_OBJECT
|
||||||
|
* @param integer $number The number of Followers to return.
|
||||||
|
*
|
||||||
|
* @return mixed The Term list of Followers, the format depends on $output.
|
||||||
|
*/
|
||||||
|
public static function get_faulty_followers( $number = 10 ) {
|
||||||
|
$args = array(
|
||||||
|
'taxonomy' => self::TAXONOMY,
|
||||||
|
'number' => $number,
|
||||||
|
'meta_query' => array(
|
||||||
|
array(
|
||||||
|
'key' => 'errors',
|
||||||
|
'compare' => 'EXISTS',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$terms = new WP_Term_Query( $args );
|
||||||
|
$items = array();
|
||||||
|
|
||||||
|
foreach ( $terms->get_terms() as $follower ) {
|
||||||
|
$items[] = new Follower( $follower->name ); // phpcs:ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@ namespace Activitypub;
|
||||||
*
|
*
|
||||||
* @param array $r Array of HTTP request args.
|
* @param array $r Array of HTTP request args.
|
||||||
* @param string $url The request URL.
|
* @param string $url The request URL.
|
||||||
* @return array $args Array or string of HTTP request arguments.
|
*
|
||||||
|
* @return array Array or string of HTTP request arguments.
|
||||||
*/
|
*/
|
||||||
function allow_localhost( $r, $url ) {
|
function allow_localhost( $r, $url ) {
|
||||||
$r['reject_unsafe_urls'] = false;
|
$r['reject_unsafe_urls'] = false;
|
||||||
|
|
|
@ -43,9 +43,9 @@ function safe_remote_get( $url ) {
|
||||||
/**
|
/**
|
||||||
* Returns a users WebFinger "resource"
|
* Returns a users WebFinger "resource"
|
||||||
*
|
*
|
||||||
* @param int $user_id
|
* @param int $user_id The User-ID.
|
||||||
*
|
*
|
||||||
* @return string The user-resource
|
* @return string The User-Resource.
|
||||||
*/
|
*/
|
||||||
function get_webfinger_resource( $user_id ) {
|
function get_webfinger_resource( $user_id ) {
|
||||||
return Webfinger::get_user_resource( $user_id );
|
return Webfinger::get_user_resource( $user_id );
|
||||||
|
@ -113,29 +113,24 @@ function get_remote_metadata_by_actor( $actor ) {
|
||||||
return $metadata;
|
return $metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_identifier_settings( $user_id ) {
|
/**
|
||||||
?>
|
* Returns the followers of a given user.
|
||||||
<table class="form-table">
|
*
|
||||||
<tbody>
|
* @param int $user_id The User-ID.
|
||||||
<tr>
|
*
|
||||||
<th scope="row">
|
* @return array The followers.
|
||||||
<label><?php \esc_html_e( 'Profile identifier', 'activitypub' ); ?></label>
|
*/
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<p><code><?php echo \esc_html( \Activitypub\get_webfinger_resource( $user_id ) ); ?></code> or <code><?php echo \esc_url( \get_author_posts_url( $user_id ) ); ?></code></p>
|
|
||||||
<?php // translators: the webfinger resource ?>
|
|
||||||
<p class="description"><?php \printf( \esc_html__( 'Try to follow "@%s" by searching for it on Mastodon,Friendica & Co.', 'activitypub' ), \esc_html( \Activitypub\get_webfinger_resource( $user_id ) ) ); ?></p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_followers( $user_id ) {
|
function get_followers( $user_id ) {
|
||||||
return Collection\Followers::get_followers( $user_id );
|
return Collection\Followers::get_followers( $user_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of followers for a given user.
|
||||||
|
*
|
||||||
|
* @param int $user_id The User-ID.
|
||||||
|
*
|
||||||
|
* @return int The number of followers.
|
||||||
|
*/
|
||||||
function count_followers( $user_id ) {
|
function count_followers( $user_id ) {
|
||||||
return Collection\Followers::count_followers( $user_id );
|
return Collection\Followers::count_followers( $user_id );
|
||||||
}
|
}
|
||||||
|
@ -192,7 +187,8 @@ function url_to_authorid( $url ) {
|
||||||
* Return the custom Activity Pub description, if set, or default author description.
|
* Return the custom Activity Pub description, if set, or default author description.
|
||||||
*
|
*
|
||||||
* @param int $user_id The user ID.
|
* @param int $user_id The user ID.
|
||||||
* @return string
|
*
|
||||||
|
* @return string The author description.
|
||||||
*/
|
*/
|
||||||
function get_author_description( $user_id ) {
|
function get_author_description( $user_id ) {
|
||||||
$description = get_user_meta( $user_id, 'activitypub_user_description', true );
|
$description = get_user_meta( $user_id, 'activitypub_user_description', true );
|
||||||
|
@ -222,3 +218,47 @@ function is_tombstone( $wp_error ) {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a request is for an ActivityPub request.
|
||||||
|
*
|
||||||
|
* @return bool False by default.
|
||||||
|
*/
|
||||||
|
function is_activitypub_request() {
|
||||||
|
global $wp_query;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ActivityPub requests are currently only made for
|
||||||
|
* author archives, singular posts, and the homepage.
|
||||||
|
*/
|
||||||
|
if ( ! \is_author() && ! \is_singular() && ! \is_home() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One can trigger an ActivityPub request by adding ?activitypub to the URL.
|
||||||
|
global $wp_query;
|
||||||
|
if ( isset( $wp_query->query_vars['activitypub'] ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The other (more common) option to make an ActivityPub request
|
||||||
|
* is to send an Accept header.
|
||||||
|
*/
|
||||||
|
if ( isset( $_SERVER['HTTP_ACCEPT'] ) ) {
|
||||||
|
$accept = $_SERVER['HTTP_ACCEPT'];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* $accept can be a single value, or a comma separated list of values.
|
||||||
|
* We want to support both scenarios,
|
||||||
|
* and return true when the header includes at least one of the following:
|
||||||
|
* - application/activity+json
|
||||||
|
* - application/ld+json
|
||||||
|
*/
|
||||||
|
if ( preg_match( '/(application\/(ld\+json|activity\+json))/', $accept ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,11 @@ namespace Activitypub\Model;
|
||||||
* @see https://www.w3.org/TR/activitypub/
|
* @see https://www.w3.org/TR/activitypub/
|
||||||
*/
|
*/
|
||||||
class Activity {
|
class Activity {
|
||||||
|
/**
|
||||||
|
* The JSON-LD context.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $context = array(
|
private $context = array(
|
||||||
'https://www.w3.org/ns/activitystreams',
|
'https://www.w3.org/ns/activitystreams',
|
||||||
'https://w3id.org/security/v1',
|
'https://w3id.org/security/v1',
|
||||||
|
@ -30,23 +35,79 @@ class Activity {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The published date.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $published = '';
|
private $published = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Activity-ID.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $id = '';
|
private $id = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Activity-Type.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $type = 'Create';
|
private $type = 'Create';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Activity-Actor.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $actor = '';
|
private $actor = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Audience.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $to = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
private $to = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CC.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $cc = array();
|
private $cc = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Activity-Object.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $object = null;
|
private $object = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class-Constructor.
|
||||||
|
*
|
||||||
|
* @param string $type The Activity-Type.
|
||||||
|
* @param boolean $context The JSON-LD context.
|
||||||
|
*/
|
||||||
public function __construct( $type = 'Create', $context = true ) {
|
public function __construct( $type = 'Create', $context = true ) {
|
||||||
if ( true !== $context ) {
|
if ( true !== $context ) {
|
||||||
$this->context = null;
|
$this->context = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->type = \ucfirst( $type );
|
$this->type = \ucfirst( $type );
|
||||||
$this->published = \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( 'now' ) );
|
$this->published = \gmdate( 'Y-m-d\TH:i:s\Z', \time() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic Getter/Setter
|
||||||
|
*
|
||||||
|
* @param string $method The method name.
|
||||||
|
* @param string $params The method params.
|
||||||
|
*
|
||||||
|
* @return mixed The value.
|
||||||
|
*/
|
||||||
public function __call( $method, $params ) {
|
public function __call( $method, $params ) {
|
||||||
$var = \strtolower( \substr( $method, 4 ) );
|
$var = \strtolower( \substr( $method, 4 ) );
|
||||||
|
|
||||||
|
@ -73,6 +134,13 @@ class Activity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert from a Post-Object.
|
||||||
|
*
|
||||||
|
* @param Post $post The Post-Object.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function from_post( Post $post ) {
|
public function from_post( Post $post ) {
|
||||||
$this->object = $post->to_array();
|
$this->object = $post->to_array();
|
||||||
|
|
||||||
|
@ -111,6 +179,11 @@ class Activity {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to an Array.
|
||||||
|
*
|
||||||
|
* @return array The Array.
|
||||||
|
*/
|
||||||
public function to_array() {
|
public function to_array() {
|
||||||
$array = array_filter( \get_object_vars( $this ) );
|
$array = array_filter( \get_object_vars( $this ) );
|
||||||
|
|
||||||
|
@ -126,12 +199,17 @@ class Activity {
|
||||||
/**
|
/**
|
||||||
* Convert to JSON
|
* Convert to JSON
|
||||||
*
|
*
|
||||||
* @return void
|
* @return string The JSON.
|
||||||
*/
|
*/
|
||||||
public function to_json() {
|
public function to_json() {
|
||||||
return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
|
return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to a Simple Array.
|
||||||
|
*
|
||||||
|
* @return string The array.
|
||||||
|
*/
|
||||||
public function to_simple_array() {
|
public function to_simple_array() {
|
||||||
$activity = array(
|
$activity = array(
|
||||||
'@context' => $this->context,
|
'@context' => $this->context,
|
||||||
|
@ -149,6 +227,11 @@ class Activity {
|
||||||
return $activity;
|
return $activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to a Simple JSON.
|
||||||
|
*
|
||||||
|
* @return string The JSON.
|
||||||
|
*/
|
||||||
public function to_simple_json() {
|
public function to_simple_json() {
|
||||||
return \wp_json_encode( $this->to_simple_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
|
return \wp_json_encode( $this->to_simple_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,8 +139,8 @@ class Follower {
|
||||||
/**
|
/**
|
||||||
* Magic function to implement getter and setter
|
* Magic function to implement getter and setter
|
||||||
*
|
*
|
||||||
* @param string $method
|
* @param string $method The method name.
|
||||||
* @param string $params
|
* @param string $params The method params.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
@ -159,6 +159,22 @@ class Follower {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic function to return the Actor-URL when the Object is used as a string
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
return $this->get_actor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefill the Object with the meta data.
|
||||||
|
*
|
||||||
|
* @param array $meta The meta data.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function from_meta( $meta ) {
|
public function from_meta( $meta ) {
|
||||||
$this->meta = $meta;
|
$this->meta = $meta;
|
||||||
|
|
||||||
|
@ -178,9 +194,16 @@ class Follower {
|
||||||
$this->shared_inbox = $meta['inbox'];
|
$this->shared_inbox = $meta['inbox'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->updated_at = \strtotime( 'now' );
|
$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 ) {
|
public function get( $attribute ) {
|
||||||
if ( $this->$attribute ) {
|
if ( $this->$attribute ) {
|
||||||
return $this->$attribute;
|
return $this->$attribute;
|
||||||
|
@ -201,6 +224,23 @@ class Follower {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set new Error
|
||||||
|
*
|
||||||
|
* @param mixed $error The latest HTTP-Error.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set_error( $error ) {
|
||||||
|
$this->errors = array();
|
||||||
|
$this->error = $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the errors.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
public function get_errors() {
|
public function get_errors() {
|
||||||
if ( $this->errors ) {
|
if ( $this->errors ) {
|
||||||
return $this->errors;
|
return $this->errors;
|
||||||
|
@ -210,6 +250,20 @@ class Follower {
|
||||||
return $this->errors;
|
return $this->errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset (delete) all errors.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function reset_errors() {
|
||||||
|
delete_term_meta( $this->id, 'errors' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the errors.
|
||||||
|
*
|
||||||
|
* @return int The number of errors.
|
||||||
|
*/
|
||||||
public function count_errors() {
|
public function count_errors() {
|
||||||
$errors = $this->get_errors();
|
$errors = $this->get_errors();
|
||||||
|
|
||||||
|
@ -220,6 +274,11 @@ class Follower {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the latest 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();
|
||||||
|
|
||||||
|
@ -230,6 +289,13 @@ 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 ) {
|
public function get_meta_by( $attribute ) {
|
||||||
$meta = $this->get_meta();
|
$meta = $this->get_meta();
|
||||||
|
|
||||||
|
@ -248,6 +314,11 @@ class Follower {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the meta data.
|
||||||
|
*
|
||||||
|
* @return array $meta The meta data.
|
||||||
|
*/
|
||||||
public function get_meta() {
|
public function get_meta() {
|
||||||
if ( $this->meta ) {
|
if ( $this->meta ) {
|
||||||
return $this->meta;
|
return $this->meta;
|
||||||
|
@ -256,6 +327,11 @@ class Follower {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current Follower-Object.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function update() {
|
public function update() {
|
||||||
$term = wp_update_term(
|
$term = wp_update_term(
|
||||||
$this->id,
|
$this->id,
|
||||||
|
@ -265,10 +341,15 @@ class Follower {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->updated_at = \strtotime( 'now' );
|
$this->updated_at = \time();
|
||||||
$this->update_term_meta();
|
$this->update_term_meta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the current Follower-Object.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function save() {
|
public function save() {
|
||||||
$term = wp_insert_term(
|
$term = wp_insert_term(
|
||||||
$this->actor,
|
$this->actor,
|
||||||
|
@ -284,6 +365,11 @@ class Follower {
|
||||||
$this->update_term_meta();
|
$this->update_term_meta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upsert the current Follower-Object.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function upsert() {
|
public function upsert() {
|
||||||
if ( $this->id ) {
|
if ( $this->id ) {
|
||||||
$this->update();
|
$this->update();
|
||||||
|
@ -292,6 +378,20 @@ class Follower {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the current Follower-Object.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function delete() {
|
||||||
|
wp_delete_term( $this->id, Followers::TAXONOMY );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the term meta.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
protected function update_term_meta() {
|
protected function update_term_meta() {
|
||||||
$attributes = array( 'inbox', 'shared_inbox', 'avatar', 'updated_at', 'name', 'username' );
|
$attributes = array( 'inbox', 'shared_inbox', 'avatar', 'updated_at', 'name', 'username' );
|
||||||
|
|
||||||
|
@ -312,6 +412,5 @@ class Follower {
|
||||||
|
|
||||||
add_term_meta( $this->id, 'errors', $error );
|
add_term_meta( $this->id, 'errors', $error );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -537,6 +537,6 @@ class Post {
|
||||||
return "[ap_content]\n\n[ap_hashtags]\n\n[ap_permalink type=\"html\"]";
|
return "[ap_content]\n\n[ap_hashtags]\n\n[ap_permalink type=\"html\"]";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $content;
|
return \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,13 @@ class Followers {
|
||||||
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/followers" ); // phpcs:ignore
|
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/followers" ); // phpcs:ignore
|
||||||
$json->first = $json->partOf; // phpcs:ignore
|
$json->first = $json->partOf; // phpcs:ignore
|
||||||
$json->totalItems = FollowerCollection::count_followers( $user_id ); // phpcs:ignore
|
$json->totalItems = FollowerCollection::count_followers( $user_id ); // phpcs:ignore
|
||||||
$json->orderedItems = FollowerCollection::get_followers( $user_id, ARRAY_N ); // phpcs:ignore
|
// phpcs:ignore
|
||||||
|
$json->orderedItems = array_map(
|
||||||
|
function( $item ) {
|
||||||
|
return $item->get_actor();
|
||||||
|
},
|
||||||
|
FollowerCollection::get_followers( $user_id )
|
||||||
|
);
|
||||||
|
|
||||||
$response = new WP_REST_Response( $json, 200 );
|
$response = new WP_REST_Response( $json, 200 );
|
||||||
$response->header( 'Content-Type', 'application/activity+json' );
|
$response->header( 'Content-Type', 'application/activity+json' );
|
||||||
|
|
|
@ -35,7 +35,7 @@ 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(), ACTIVITYPUB_OBJECT, $per_page, ( $page_num - 1 ) * $per_page );
|
$follower = 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();
|
||||||
|
|
|
@ -115,6 +115,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu
|
||||||
|
|
||||||
= Next =
|
= Next =
|
||||||
|
|
||||||
|
* Compatibility: add a new conditional, `\Activitypub\is_activitypub_request()`, to allow third-party plugins to detect ActivityPub requests.
|
||||||
* Compatibility: add hooks to allow modifying images returned in ActivityPub requests.
|
* Compatibility: add hooks to allow modifying images returned in ActivityPub requests.
|
||||||
* Compatibility: indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.2.
|
* Compatibility: indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.2.
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,13 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
|
||||||
|
|
||||||
$this->assertEquals( 3, \count( $db_followers ) );
|
$this->assertEquals( 3, \count( $db_followers ) );
|
||||||
|
|
||||||
|
$db_followers = array_map(
|
||||||
|
function( $item ) {
|
||||||
|
return $item->get_actor();
|
||||||
|
},
|
||||||
|
$db_followers
|
||||||
|
);
|
||||||
|
|
||||||
$this->assertSame( array( 'https://example.com/author/jon', 'https://example.org/author/doe', 'http://sally.example.org' ), $db_followers );
|
$this->assertSame( array( 'https://example.com/author/jon', 'https://example.org/author/doe', 'http://sally.example.org' ), $db_followers );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +97,60 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
|
||||||
$this->assertNull( $follower );
|
$this->assertNull( $follower );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_get_outdated_followers() {
|
||||||
|
$followers = array( 'https://example.com/author/jon', 'https://example.org/author/doe', 'http://sally.example.org' );
|
||||||
|
|
||||||
|
$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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$follower = new \Activitypub\Model\Follower( 'https://example.com/author/jon' );
|
||||||
|
|
||||||
|
update_term_meta( $follower->get_id(), 'updated_at', \time() - 804800 );
|
||||||
|
|
||||||
|
$followers = \Activitypub\Collection\Followers::get_outdated_followers();
|
||||||
|
$this->assertEquals( 1, count( $followers ) );
|
||||||
|
$this->assertEquals( 'https://example.com/author/jon', $followers[0] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_get_faulty_followers() {
|
||||||
|
$followers = array( 'https://example.com/author/jon', 'https://example.org/author/doe', 'http://sally.example.org' );
|
||||||
|
|
||||||
|
$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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$follower = new \Activitypub\Model\Follower( 'http://sally.example.org' );
|
||||||
|
|
||||||
|
for ( $i = 1; $i <= 15; $i++ ) {
|
||||||
|
add_term_meta( $follower->get_id(), 'errors', 'error ' . $i );
|
||||||
|
}
|
||||||
|
|
||||||
|
$follower = new \Activitypub\Model\Follower( 'http://sally.example.org' );
|
||||||
|
$count = $follower->count_errors();
|
||||||
|
|
||||||
|
$followers = \Activitypub\Collection\Followers::get_faulty_followers();
|
||||||
|
|
||||||
|
$this->assertEquals( 1, count( $followers ) );
|
||||||
|
$this->assertEquals( 'http://sally.example.org', $followers[0] );
|
||||||
|
|
||||||
|
$follower->reset_errors();
|
||||||
|
|
||||||
|
$follower = new \Activitypub\Model\Follower( 'http://sally.example.org' );
|
||||||
|
$count = $follower->count_errors();
|
||||||
|
|
||||||
|
$followers = \Activitypub\Collection\Followers::get_faulty_followers();
|
||||||
|
|
||||||
|
$this->assertEquals( 0, count( $followers ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function http_request_host_is_external( $in, $host ) {
|
public static function http_request_host_is_external( $in, $host ) {
|
||||||
if ( in_array( $host, array( 'example.com', 'example.org' ), true ) ) {
|
if ( in_array( $host, array( 'example.com', 'example.org' ), true ) ) {
|
||||||
return true;
|
return true;
|
||||||
|
|
Loading…
Reference in a new issue