Initial commit
This commit is contained in:
21
wp-content/plugins/activitypub/LICENSE
Normal file
21
wp-content/plugins/activitypub/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Matthias Pfefferle
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
100
wp-content/plugins/activitypub/activitypub.php
Normal file
100
wp-content/plugins/activitypub/activitypub.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: ActivityPub
|
||||
* Plugin URI: https://github.com/pfefferle/wordpress-activitypub/
|
||||
* Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.
|
||||
* Version: 0.10.0
|
||||
* Author: Matthias Pfefferle
|
||||
* Author URI: https://notiz.blog/
|
||||
* License: MIT
|
||||
* License URI: http://opensource.org/licenses/MIT
|
||||
* Requires PHP: 5.6
|
||||
* Text Domain: activitypub
|
||||
* Domain Path: /languages
|
||||
*/
|
||||
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* Initialize plugin
|
||||
*/
|
||||
function init() {
|
||||
\defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|^)#(\w*[A-Za-z_]+\w*)' );
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/table/followers-list.php';
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-signature.php';
|
||||
require_once \dirname( __FILE__ ) . '/includes/peer/class-followers.php';
|
||||
require_once \dirname( __FILE__ ) . '/includes/functions.php';
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-activity-dispatcher.php';
|
||||
\Activitypub\Activity_Dispatcher::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/model/class-activity.php';
|
||||
require_once \dirname( __FILE__ ) . '/includes/model/class-post.php';
|
||||
\Activitypub\Model\Post::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-activitypub.php';
|
||||
\Activitypub\Activitypub::init();
|
||||
|
||||
// Configure the REST API route
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-outbox.php';
|
||||
\Activitypub\Rest\Outbox::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-inbox.php';
|
||||
\Activitypub\Rest\Inbox::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-followers.php';
|
||||
\Activitypub\Rest\Followers::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-following.php';
|
||||
\Activitypub\Rest\Following::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-webfinger.php';
|
||||
\Activitypub\Rest\Webfinger::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-nodeinfo.php';
|
||||
\Activitypub\Rest\NodeInfo::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-admin.php';
|
||||
\Activitypub\Admin::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-hashtag.php';
|
||||
\Activitypub\Hashtag::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-debug.php';
|
||||
\Activitypub\Debug::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-health-check.php';
|
||||
\Activitypub\Health_Check::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-server.php';
|
||||
\add_filter( 'wp_rest_server_class', function() {
|
||||
return '\Activitypub\Rest\Server';
|
||||
} );
|
||||
}
|
||||
add_action( 'plugins_loaded', '\Activitypub\init' );
|
||||
|
||||
/**
|
||||
* Add rewrite rules
|
||||
*/
|
||||
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' ) ) {
|
||||
\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_action( 'init', '\Activitypub\add_rewrite_rules', 1 );
|
||||
|
||||
/**
|
||||
* 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' );
|
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* ActivityPub Activity_Dispatcher Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/
|
||||
*/
|
||||
class Activity_Dispatcher {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'activitypub_send_post_activity', array( '\Activitypub\Activity_Dispatcher', 'send_post_activity' ) );
|
||||
\add_action( 'activitypub_send_update_activity', array( '\Activitypub\Activity_Dispatcher', 'send_update_activity' ) );
|
||||
// \add_action( 'activitypub_send_delete_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_activity' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "create" activities
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public static function send_post_activity( $post_id ) {
|
||||
$post = \get_post( $post_id );
|
||||
$user_id = $post->post_author;
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL );
|
||||
$activitypub_activity->from_post( $activitypub_post->to_array() );
|
||||
|
||||
foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) {
|
||||
$activitypub_activity->set_to( $to );
|
||||
$activity = $activitypub_activity->to_json(); // phpcs:ignore
|
||||
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "update" activities
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public static function send_update_activity( $post_id ) {
|
||||
$post = \get_post( $post_id );
|
||||
$user_id = $post->post_author;
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Update', \Activitypub\Model\Activity::TYPE_FULL );
|
||||
$activitypub_activity->from_post( $activitypub_post->to_array() );
|
||||
|
||||
foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) {
|
||||
$activitypub_activity->set_to( $to );
|
||||
$activity = $activitypub_activity->to_json(); // phpcs:ignore
|
||||
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "delete" activities
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public static function send_delete_activity( $post_id ) {
|
||||
$post = \get_post( $post_id );
|
||||
$user_id = $post->post_author;
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Delete', \Activitypub\Model\Activity::TYPE_FULL );
|
||||
$activitypub_activity->from_post( $activitypub_post->to_array() );
|
||||
|
||||
foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) {
|
||||
$activitypub_activity->set_to( $to );
|
||||
$activity = $activitypub_activity->to_json(); // phpcs:ignore
|
||||
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
}
|
||||
}
|
179
wp-content/plugins/activitypub/includes/class-activitypub.php
Normal file
179
wp-content/plugins/activitypub/includes/class-activitypub.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* ActivityPub Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Activitypub {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'template_include', array( '\Activitypub\Activitypub', 'render_json_template' ), 99 );
|
||||
\add_filter( 'query_vars', array( '\Activitypub\Activitypub', 'add_query_vars' ) );
|
||||
\add_action( 'init', array( '\Activitypub\Activitypub', 'add_rewrite_endpoint' ) );
|
||||
\add_filter( 'pre_get_avatar_data', array( '\Activitypub\Activitypub', 'pre_get_avatar_data' ), 11, 2 );
|
||||
|
||||
// Add support for ActivityPub to custom post types
|
||||
$post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) : array();
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
\add_post_type_support( $post_type, 'activitypub' );
|
||||
}
|
||||
|
||||
\add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a AS2 JSON version of an author, post or page
|
||||
*
|
||||
* @param string $template the path to the template object
|
||||
*
|
||||
* @return string the new path to the JSON template
|
||||
*/
|
||||
public static function render_json_template( $template ) {
|
||||
if ( ! \is_author() && ! \is_singular() ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
if ( \is_author() ) {
|
||||
$json_template = \dirname( __FILE__ ) . '/../templates/json-author.php';
|
||||
} elseif ( \is_singular() ) {
|
||||
$json_template = \dirname( __FILE__ ) . '/../templates/json-post.php';
|
||||
}
|
||||
|
||||
global $wp_query;
|
||||
|
||||
if ( isset( $wp_query->query_vars['activitypub'] ) ) {
|
||||
return $json_template;
|
||||
}
|
||||
|
||||
if ( ! isset( $_SERVER['HTTP_ACCEPT'] ) ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$accept_header = $_SERVER['HTTP_ACCEPT'];
|
||||
|
||||
if (
|
||||
\stristr( $accept_header, 'application/activity+json' ) ||
|
||||
\stristr( $accept_header, 'application/ld+json' )
|
||||
) {
|
||||
return $json_template;
|
||||
}
|
||||
|
||||
// accept header as an array
|
||||
$accept = \explode( ',', \trim( $accept_header ) );
|
||||
|
||||
if (
|
||||
\in_array( 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', $accept, true ) ||
|
||||
\in_array( 'application/activity+json', $accept, true ) ||
|
||||
\in_array( 'application/ld+json', $accept, true ) ||
|
||||
\in_array( 'application/json', $accept, true )
|
||||
) {
|
||||
return $json_template;
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the 'photos' query variable so WordPress
|
||||
* won't mangle it.
|
||||
*/
|
||||
public static function add_query_vars( $vars ) {
|
||||
$vars[] = 'activitypub';
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add our rewrite endpoint to permalinks and pages.
|
||||
*/
|
||||
public static function add_rewrite_endpoint() {
|
||||
\add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule Activities
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
|
||||
\wp_schedule_single_event( \time(), 'activitypub_send_post_activity', array( $post->ID ) );
|
||||
} elseif ( 'publish' === $new_status ) {
|
||||
\wp_schedule_single_event( \time(), 'activitypub_send_update_activity', array( $post->ID ) );
|
||||
} elseif ( 'trash' === $new_status ) {
|
||||
\wp_schedule_single_event( \time(), 'activitypub_send_delete_activity', array( get_permalink( $post ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the default avatar
|
||||
*
|
||||
* @param array $args Arguments passed to get_avatar_data(), after processing.
|
||||
* @param int|string|object $id_or_email A user ID, email address, or comment object
|
||||
*
|
||||
* @return array $args
|
||||
*/
|
||||
public static function pre_get_avatar_data( $args, $id_or_email ) {
|
||||
if (
|
||||
! $id_or_email instanceof \WP_Comment ||
|
||||
! isset( $id_or_email->comment_type ) ||
|
||||
$id_or_email->user_id
|
||||
) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
$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 ) ) {
|
||||
$args['url'] = false;
|
||||
/** This filter is documented in wp-includes/link-template.php */
|
||||
return \apply_filters( 'get_avatar_data', $args, $id_or_email );
|
||||
}
|
||||
|
||||
// check if comment has an avatar
|
||||
$avatar = self::get_avatar_url( $id_or_email->comment_ID );
|
||||
|
||||
if ( $avatar ) {
|
||||
if ( ! isset( $args['class'] ) || ! \is_array( $args['class'] ) ) {
|
||||
$args['class'] = array( 'u-photo' );
|
||||
} else {
|
||||
$args['class'][] = 'u-photo';
|
||||
$args['class'] = \array_unique( $args['class'] );
|
||||
}
|
||||
$args['url'] = $avatar;
|
||||
$args['class'][] = 'avatar-activitypub';
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to retrieve Avatar URL if stored in meta
|
||||
*
|
||||
*
|
||||
* @param int|WP_Comment $comment
|
||||
*
|
||||
* @return string $url
|
||||
*/
|
||||
public static function get_avatar_url( $comment ) {
|
||||
if ( \is_numeric( $comment ) ) {
|
||||
$comment = \get_comment( $comment );
|
||||
}
|
||||
return \get_comment_meta( $comment->comment_ID, 'avatar_url', true );
|
||||
}
|
||||
}
|
149
wp-content/plugins/activitypub/includes/class-admin.php
Normal file
149
wp-content/plugins/activitypub/includes/class-admin.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* ActivityPub Admin Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Admin {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'admin_menu', array( '\Activitypub\Admin', 'admin_menu' ) );
|
||||
\add_action( 'admin_init', array( '\Activitypub\Admin', 'register_settings' ) );
|
||||
\add_action( 'show_user_profile', array( '\Activitypub\Admin', 'add_fediverse_profile' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin menu entry
|
||||
*/
|
||||
public static function admin_menu() {
|
||||
$settings_page = \add_options_page(
|
||||
'ActivityPub',
|
||||
'ActivityPub',
|
||||
'manage_options',
|
||||
'activitypub',
|
||||
array( '\Activitypub\Admin', 'settings_page' )
|
||||
);
|
||||
|
||||
\add_action( 'load-' . $settings_page, array( '\Activitypub\Admin', 'add_settings_help_tab' ) );
|
||||
|
||||
$followers_list_page = \add_users_page( \__( 'Followers', 'activitypub' ), __( 'Followers (Fediverse)', 'activitypub' ), 'read', 'activitypub-followers-list', array( '\Activitypub\Admin', 'followers_list_page' ) );
|
||||
|
||||
\add_action( 'load-' . $followers_list_page, array( '\Activitypub\Admin', 'add_followers_list_help_tab' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings page
|
||||
*/
|
||||
public static function settings_page() {
|
||||
\load_template( \dirname( __FILE__ ) . '/../templates/settings.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load user settings page
|
||||
*/
|
||||
public static function followers_list_page() {
|
||||
\load_template( \dirname( __FILE__ ) . '/../templates/followers-list.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register PubSubHubbub settings
|
||||
*/
|
||||
public static function register_settings() {
|
||||
\register_setting(
|
||||
'activitypub', 'activitypub_post_content_type', array(
|
||||
'type' => 'string',
|
||||
'description' => \__( 'Use title and link, summary or full content', 'activitypub' ),
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'enum' => array( 'title', 'excerpt', 'content' ),
|
||||
),
|
||||
),
|
||||
'default' => 'content',
|
||||
)
|
||||
);
|
||||
\register_setting(
|
||||
'activitypub', 'activitypub_object_type', array(
|
||||
'type' => 'string',
|
||||
'description' => \__( 'The Activity-Object-Type', 'activitypub' ),
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'enum' => array( 'note', 'article', 'wordpress-post-format' ),
|
||||
),
|
||||
),
|
||||
'default' => 'note',
|
||||
)
|
||||
);
|
||||
\register_setting(
|
||||
'activitypub', 'activitypub_use_shortlink', array(
|
||||
'type' => 'boolean',
|
||||
'description' => \__( 'Use the Shortlink instead of the permalink', 'activitypub' ),
|
||||
'default' => 0,
|
||||
)
|
||||
);
|
||||
\register_setting(
|
||||
'activitypub', 'activitypub_use_hashtags', array(
|
||||
'type' => 'boolean',
|
||||
'description' => \__( 'Add hashtags in the content as native tags and replace the #tag with the tag-link', 'activitypub' ),
|
||||
'default' => 0,
|
||||
)
|
||||
);
|
||||
\register_setting(
|
||||
'activitypub', 'activitypub_add_tags_as_hashtags', array(
|
||||
'type' => 'boolean',
|
||||
'description' => \__( 'Add all tags as hashtags at the end of each activity', 'activitypub' ),
|
||||
'default' => 0,
|
||||
)
|
||||
);
|
||||
\register_setting(
|
||||
'activitypub', 'activitypub_support_post_types', array(
|
||||
'type' => 'string',
|
||||
'description' => \esc_html__( 'Enable ActivityPub support for post types', 'activitypub' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => array( 'post', 'pages' ),
|
||||
)
|
||||
);
|
||||
\register_setting(
|
||||
'activitypub', 'activitypub_blacklist', array(
|
||||
'type' => 'string',
|
||||
'description' => \esc_html__( 'Block fediverse instances', 'activitypub' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => 'gab.com',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function add_settings_help_tab() {
|
||||
\get_current_screen()->add_help_tab(
|
||||
array(
|
||||
'id' => 'overview',
|
||||
'title' => \__( 'Overview', 'activitypub' ),
|
||||
'content' =>
|
||||
'<p>' . \__( 'ActivityPub is a decentralized social networking protocol based on the ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended standard published by the W3C Social Web Working Group. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and subscribing to content.', 'activitypub' ) . '</p>',
|
||||
)
|
||||
);
|
||||
|
||||
\get_current_screen()->set_help_sidebar(
|
||||
'<p><strong>' . \__( 'For more information:', 'activitypub' ) . '</strong></p>' .
|
||||
'<p>' . \__( '<a href="https://activitypub.rocks/">Test Suite</a>', 'activitypub' ) . '</p>' .
|
||||
'<p>' . \__( '<a href="https://www.w3.org/TR/activitypub/">W3C Spec</a>', 'activitypub' ) . '</p>' .
|
||||
'<p>' . \__( '<a href="https://github.com/pfefferle/wordpress-activitypub/issues">Give us feedback</a>', 'activitypub' ) . '</p>' .
|
||||
'<hr />' .
|
||||
'<p>' . \__( '<a href="https://notiz.blog/donate">Donate</a>', 'activitypub' ) . '</p>'
|
||||
);
|
||||
}
|
||||
|
||||
public static function add_followers_list_help_tab() {
|
||||
// todo
|
||||
}
|
||||
|
||||
public static function add_fediverse_profile( $user ) {
|
||||
?>
|
||||
<h2><?php \esc_html_e( 'Fediverse', 'activitypub' ); ?></h2>
|
||||
<?php
|
||||
\Activitypub\get_identifier_settings( $user->ID );
|
||||
}
|
||||
}
|
30
wp-content/plugins/activitypub/includes/class-debug.php
Normal file
30
wp-content/plugins/activitypub/includes/class-debug.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* ActivityPub Debug Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Debug {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
if ( WP_DEBUG_LOG ) {
|
||||
\add_action( 'activitypub_safe_remote_post_response', array( '\Activitypub\Debug', 'log_remote_post_responses' ), 10, 4 );
|
||||
}
|
||||
}
|
||||
|
||||
public static function log_remote_post_responses( $response, $url, $body, $user_id ) {
|
||||
\error_log( "Request to: {$url} with response: " . \print_r( $response, true ) );
|
||||
}
|
||||
|
||||
public static function write_log( $log ) {
|
||||
if ( \is_array( $log ) || \is_object( $log ) ) {
|
||||
\error_log( \print_r( $log, true ) );
|
||||
} else {
|
||||
\error_log( $log );
|
||||
}
|
||||
}
|
||||
}
|
96
wp-content/plugins/activitypub/includes/class-hashtag.php
Normal file
96
wp-content/plugins/activitypub/includes/class-hashtag.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* ActivityPub Hashtag Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Hashtag {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
if ( '1' === \get_option( 'activitypub_use_hashtags', '1' ) ) {
|
||||
\add_filter( 'wp_insert_post', array( '\Activitypub\Hashtag', 'insert_post' ), 99, 2 );
|
||||
\add_filter( 'the_content', array( '\Activitypub\Hashtag', 'the_content' ), 99, 2 );
|
||||
}
|
||||
if ( '1' === \get_option( 'activitypub_add_tags_as_hashtags', '0' ) ) {
|
||||
\add_filter( 'activitypub_the_summary', array( '\Activitypub\Hashtag', 'add_hashtags_to_content' ), 10, 2 );
|
||||
\add_filter( 'activitypub_the_content', array( '\Activitypub\Hashtag', 'add_hashtags_to_content' ), 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to save #tags as real WordPress tags
|
||||
*
|
||||
* @param int $id the rev-id
|
||||
* @param array $data the post-data as array
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static function insert_post( $id, $data ) {
|
||||
if ( \preg_match_all( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', $data->post_content, $match ) ) {
|
||||
$tags = \implode( ', ', $match[1] );
|
||||
|
||||
\wp_add_post_tags( $data->post_parent, $tags );
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to replace the #tags in the content with links
|
||||
*
|
||||
* @param string $the_content the post-content
|
||||
*
|
||||
* @return string the filtered post-content
|
||||
*/
|
||||
public static function the_content( $the_content ) {
|
||||
$the_content = \preg_replace_callback( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', array( '\Activitypub\Hashtag', 'replace_with_links' ), $the_content );
|
||||
|
||||
return $the_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback for preg_replace to build the term links
|
||||
*
|
||||
* @param array $result the preg_match results
|
||||
* @return string the final string
|
||||
*/
|
||||
public static function replace_with_links( $result ) {
|
||||
$tag = $result[1];
|
||||
$tag_object = \get_term_by( 'name', $tag, 'post_tag' );
|
||||
|
||||
if ( $tag_object ) {
|
||||
$link = \get_term_link( $tag_object, 'post_tag' );
|
||||
return \sprintf( '<a rel="tag" class="u-tag u-category" href="%s">#%s</a>', $link, $tag );
|
||||
}
|
||||
|
||||
return '#' . $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all tags as hashtags to the post/summary content
|
||||
*
|
||||
* @param string $content
|
||||
* @param WP_Post $post
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function add_hashtags_to_content( $content, $post ) {
|
||||
$tags = \get_the_tags( $post->ID );
|
||||
|
||||
if ( ! $tags ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$hash_tags = array();
|
||||
|
||||
foreach ( $tags as $tag ) {
|
||||
$hash_tags[] = \sprintf( '<a rel="tag" class="u-tag u-category" href="%s">#%s</a>', \get_tag_link( $tag ), $tag->slug );
|
||||
}
|
||||
|
||||
return $content . '<p>' . \implode( ' ', $hash_tags ) . '</p>';
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* ActivityPub Health_Check Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Health_Check {
|
||||
public static function init() {
|
||||
}
|
||||
}
|
105
wp-content/plugins/activitypub/includes/class-signature.php
Normal file
105
wp-content/plugins/activitypub/includes/class-signature.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* ActivityPub Signature Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Signature {
|
||||
|
||||
/**
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_public_key( $user_id, $force = false ) {
|
||||
$key = \get_user_meta( $user_id, 'magic_sig_public_key' );
|
||||
|
||||
if ( $key && ! $force ) {
|
||||
return $key[0];
|
||||
}
|
||||
|
||||
self::generate_key_pair( $user_id );
|
||||
$key = \get_user_meta( $user_id, 'magic_sig_public_key' );
|
||||
|
||||
return $key[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_private_key( $user_id, $force = false ) {
|
||||
$key = \get_user_meta( $user_id, 'magic_sig_private_key' );
|
||||
|
||||
if ( $key && ! $force ) {
|
||||
return $key[0];
|
||||
}
|
||||
|
||||
self::generate_key_pair( $user_id );
|
||||
$key = \get_user_meta( $user_id, 'magic_sig_private_key' );
|
||||
|
||||
return $key[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the pair keys
|
||||
*
|
||||
* @param int $user_id
|
||||
*/
|
||||
public static function generate_key_pair( $user_id ) {
|
||||
$config = array(
|
||||
'digest_alg' => 'sha512',
|
||||
'private_key_bits' => 2048,
|
||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||
);
|
||||
|
||||
$key = \openssl_pkey_new( $config );
|
||||
$priv_key = null;
|
||||
|
||||
\openssl_pkey_export( $key, $priv_key );
|
||||
|
||||
// private key
|
||||
\update_user_meta( $user_id, 'magic_sig_private_key', $priv_key );
|
||||
|
||||
$detail = \openssl_pkey_get_details( $key );
|
||||
|
||||
// public key
|
||||
\update_user_meta( $user_id, 'magic_sig_public_key', $detail['key'] );
|
||||
}
|
||||
|
||||
public static function generate_signature( $user_id, $url, $date ) {
|
||||
$key = self::get_private_key( $user_id );
|
||||
|
||||
$url_parts = \wp_parse_url( $url );
|
||||
|
||||
$host = $url_parts['host'];
|
||||
$path = '/';
|
||||
|
||||
// add path
|
||||
if ( ! empty( $url_parts['path'] ) ) {
|
||||
$path = $url_parts['path'];
|
||||
}
|
||||
|
||||
// add query
|
||||
if ( ! empty( $url_parts['query'] ) ) {
|
||||
$path .= '?' . $url_parts['query'];
|
||||
}
|
||||
|
||||
$signed_string = "(request-target): post $path\nhost: $host\ndate: $date";
|
||||
|
||||
$signature = null;
|
||||
\openssl_sign( $signed_string, $signature, $key, OPENSSL_ALGO_SHA256 );
|
||||
$signature = \base64_encode( $signature ); // phpcs:ignore
|
||||
|
||||
$key_id = \get_author_posts_url( $user_id ) . '#main-key';
|
||||
|
||||
return \sprintf( 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date",signature="%s"', $key_id, $signature );
|
||||
}
|
||||
|
||||
public static function verify_signature( $headers, $signature ) {
|
||||
|
||||
}
|
||||
}
|
336
wp-content/plugins/activitypub/includes/functions.php
Normal file
336
wp-content/plugins/activitypub/includes/functions.php
Normal file
@ -0,0 +1,336 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub default JSON-context
|
||||
*
|
||||
* @return array the activitypub context
|
||||
*/
|
||||
function get_context() {
|
||||
$context = array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
array(
|
||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||
'PropertyValue' => 'schema:PropertyValue',
|
||||
'schema' => 'http://schema.org#',
|
||||
'value' => 'schema:value',
|
||||
),
|
||||
);
|
||||
|
||||
return \apply_filters( 'activitypub_json_context', $context );
|
||||
}
|
||||
|
||||
function safe_remote_post( $url, $body, $user_id ) {
|
||||
$date = \gmdate( 'D, d M Y H:i:s T' );
|
||||
$signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date );
|
||||
|
||||
$wp_version = \get_bloginfo( 'version' );
|
||||
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
|
||||
$args = array(
|
||||
'timeout' => 100,
|
||||
'limit_response_size' => 1048576,
|
||||
'redirection' => 3,
|
||||
'user-agent' => "$user_agent; ActivityPub",
|
||||
'headers' => array(
|
||||
'Accept' => 'application/activity+json',
|
||||
'Content-Type' => 'application/activity+json',
|
||||
'Signature' => $signature,
|
||||
'Date' => $date,
|
||||
),
|
||||
'body' => $body,
|
||||
);
|
||||
|
||||
$response = \wp_safe_remote_post( $url, $args );
|
||||
|
||||
\do_action( 'activitypub_safe_remote_post_response', $response, $url, $body, $user_id );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
function safe_remote_get( $url, $user_id ) {
|
||||
$date = \gmdate( 'D, d M Y H:i:s T' );
|
||||
$signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date );
|
||||
|
||||
$wp_version = \get_bloginfo( 'version' );
|
||||
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
|
||||
$args = array(
|
||||
'timeout' => 100,
|
||||
'limit_response_size' => 1048576,
|
||||
'redirection' => 3,
|
||||
'user-agent' => "$user_agent; ActivityPub",
|
||||
'headers' => array(
|
||||
'Accept' => 'application/activity+json',
|
||||
'Content-Type' => 'application/activity+json',
|
||||
'Signature' => $signature,
|
||||
'Date' => $date,
|
||||
),
|
||||
);
|
||||
|
||||
$response = \wp_safe_remote_get( $url, $args );
|
||||
|
||||
\do_action( 'activitypub_safe_remote_get_response', $response, $url, $user_id );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a users WebFinger "resource"
|
||||
*
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return string The user-resource
|
||||
*/
|
||||
function get_webfinger_resource( $user_id ) {
|
||||
// use WebFinger plugin if installed
|
||||
if ( \function_exists( '\get_webfinger_resource' ) ) {
|
||||
return \get_webfinger_resource( $user_id, false );
|
||||
}
|
||||
|
||||
$user = \get_user_by( 'id', $user_id );
|
||||
|
||||
return $user->user_login . '@' . \wp_parse_url( \home_url(), PHP_URL_HOST );
|
||||
}
|
||||
|
||||
/**
|
||||
* [get_metadata_by_actor description]
|
||||
*
|
||||
* @param sting $actor
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function get_remote_metadata_by_actor( $actor ) {
|
||||
$metadata = \get_transient( 'activitypub_' . $actor );
|
||||
|
||||
if ( $metadata ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
if ( ! \wp_http_validate_url( $actor ) ) {
|
||||
return new \WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), $actor );
|
||||
}
|
||||
|
||||
$user = \get_users( array (
|
||||
'number' => 1,
|
||||
'who' => 'authors',
|
||||
'fields' => 'ID',
|
||||
) );
|
||||
|
||||
// we just need any user to generate a request signature
|
||||
$user_id = \reset( $user );
|
||||
|
||||
$response = \Activitypub\safe_remote_get( $actor, $user_id );
|
||||
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$metadata = \wp_remote_retrieve_body( $response );
|
||||
$metadata = \json_decode( $metadata, true );
|
||||
|
||||
if ( ! $metadata ) {
|
||||
return new \WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), $actor );
|
||||
}
|
||||
|
||||
\set_transient( 'activitypub_' . $actor, $metadata, WEEK_IN_SECONDS );
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* [get_inbox_by_actor description]
|
||||
* @param [type] $actor [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
function get_inbox_by_actor( $actor ) {
|
||||
$metadata = \Activitypub\get_remote_metadata_by_actor( $actor );
|
||||
|
||||
if ( \is_wp_error( $metadata ) ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
if ( isset( $metadata['endpoints'] ) && isset( $metadata['endpoints']['sharedInbox'] ) ) {
|
||||
return $metadata['endpoints']['sharedInbox'];
|
||||
}
|
||||
|
||||
if ( \array_key_exists( 'inbox', $metadata ) ) {
|
||||
return $metadata['inbox'];
|
||||
}
|
||||
|
||||
return new \WP_Error( 'activitypub_no_inbox', __( 'No "Inbox" found', 'activitypub' ), $metadata );
|
||||
}
|
||||
|
||||
/**
|
||||
* [get_inbox_by_actor description]
|
||||
* @param [type] $actor [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
function get_publickey_by_actor( $actor, $key_id ) {
|
||||
$metadata = \Activitypub\get_remote_metadata_by_actor( $actor );
|
||||
|
||||
if ( \is_wp_error( $metadata ) ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
if (
|
||||
isset( $metadata['publicKey'] ) &&
|
||||
isset( $metadata['publicKey']['id'] ) &&
|
||||
isset( $metadata['publicKey']['owner'] ) &&
|
||||
isset( $metadata['publicKey']['publicKeyPem'] ) &&
|
||||
$key_id === $metadata['publicKey']['id'] &&
|
||||
$actor === $metadata['publicKey']['owner']
|
||||
) {
|
||||
return $metadata['publicKey']['publicKeyPem'];
|
||||
}
|
||||
|
||||
return new \WP_Error( 'activitypub_no_public_key', \__( 'No "Public-Key" found', 'activitypub' ), $metadata );
|
||||
}
|
||||
|
||||
function get_follower_inboxes( $user_id ) {
|
||||
$followers = \Activitypub\Peer\Followers::get_followers( $user_id );
|
||||
$inboxes = array();
|
||||
|
||||
foreach ( $followers as $follower ) {
|
||||
$inbox = \Activitypub\get_inbox_by_actor( $follower );
|
||||
if ( ! $inbox || \is_wp_error( $inbox ) ) {
|
||||
continue;
|
||||
}
|
||||
// init array if empty
|
||||
if ( ! isset( $inboxes[ $inbox ] ) ) {
|
||||
$inboxes[ $inbox ] = array();
|
||||
}
|
||||
$inboxes[ $inbox ][] = $follower;
|
||||
}
|
||||
|
||||
return $inboxes;
|
||||
}
|
||||
|
||||
function get_identifier_settings( $user_id ) {
|
||||
?>
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<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" in the Mastodon/Friendica search field.', 'activitypub' ), \esc_html( \Activitypub\get_webfinger_resource( $user_id ) ) ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
|
||||
function get_followers( $user_id ) {
|
||||
$followers = \Activitypub\Peer\Followers::get_followers( $user_id );
|
||||
|
||||
if ( ! $followers ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $followers;
|
||||
}
|
||||
|
||||
function count_followers( $user_id ) {
|
||||
$followers = \Activitypub\get_followers( $user_id );
|
||||
|
||||
return \count( $followers );
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine a url and try to determine the author ID it represents.
|
||||
*
|
||||
* Checks are supposedly from the hosted site blog.
|
||||
*
|
||||
* @param string $url Permalink to check.
|
||||
*
|
||||
* @return int User ID, or 0 on failure.
|
||||
*/
|
||||
function url_to_authorid( $url ) {
|
||||
global $wp_rewrite;
|
||||
|
||||
// check if url hase the same host
|
||||
if ( wp_parse_url( site_url(), PHP_URL_HOST ) !== wp_parse_url( $url, PHP_URL_HOST ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// first, check to see if there is a 'author=N' to match against
|
||||
if ( \preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) {
|
||||
$id = absint( $values[1] );
|
||||
if ( $id ) {
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if we are using rewrite rules
|
||||
$rewrite = $wp_rewrite->wp_rewrite_rules();
|
||||
|
||||
// not using rewrite rules, and 'author=N' method failed, so we're out of options
|
||||
if ( empty( $rewrite ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// generate rewrite rule for the author url
|
||||
$author_rewrite = $wp_rewrite->get_author_permastruct();
|
||||
$author_regexp = \str_replace( '%author%', '', $author_rewrite );
|
||||
|
||||
// match the rewrite rule with the passed url
|
||||
if ( \preg_match( '/https?:\/\/(.+)' . \preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) {
|
||||
$user = get_user_by( 'slug', $match[2] );
|
||||
if ( $user ) {
|
||||
return $user->ID;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blacklist from the WordPress options table
|
||||
*
|
||||
* @return array the list of blacklisted hosts
|
||||
*
|
||||
* @uses apply_filters() Calls 'activitypub_blacklist' filter
|
||||
*/
|
||||
function get_blacklist() {
|
||||
$blacklist = \get_option( 'activitypub_blacklist' );
|
||||
$blacklist_hosts = \explode( PHP_EOL, $blacklist );
|
||||
|
||||
// if no values have been set, revert to the defaults
|
||||
if ( ! $blacklist || ! $blacklist_hosts || ! \is_array( $blacklist_hosts ) ) {
|
||||
$blacklist_hosts = array(
|
||||
'gab.com',
|
||||
);
|
||||
}
|
||||
|
||||
// clean out any blank values
|
||||
foreach ( $blacklist_hosts as $key => $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
unset( $blacklist_hosts[ $key ] );
|
||||
} else {
|
||||
$blacklist_hosts[ $key ] = \trim( $blacklist_hosts[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
return \apply_filters( 'activitypub_blacklist', $blacklist_hosts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an URL is blacklisted
|
||||
*
|
||||
* @param string $url an URL to check
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
function is_blacklisted( $url ) {
|
||||
foreach ( \ActivityPub\get_blacklist() as $blacklisted_host ) {
|
||||
if ( \strpos( $url, $blacklisted_host ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace Activitypub\Model;
|
||||
|
||||
/**
|
||||
* ActivityPub Post Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/
|
||||
*/
|
||||
class Activity {
|
||||
private $context = array( 'https://www.w3.org/ns/activitystreams' );
|
||||
private $published = '';
|
||||
private $id = '';
|
||||
private $type = 'Create';
|
||||
private $actor = '';
|
||||
private $to = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
private $cc = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
private $object = null;
|
||||
|
||||
const TYPE_SIMPLE = 'simple';
|
||||
const TYPE_FULL = 'full';
|
||||
const TYPE_NONE = 'none';
|
||||
|
||||
public function __construct( $type = 'Create', $context = self::TYPE_SIMPLE ) {
|
||||
if ( 'none' === $context ) {
|
||||
$this->context = null;
|
||||
} elseif ( 'full' === $context ) {
|
||||
$this->context = \Activitypub\get_context();
|
||||
}
|
||||
|
||||
$this->type = \ucfirst( $type );
|
||||
$this->published = \date( 'Y-m-d\TH:i:s\Z', \strtotime( 'now' ) );
|
||||
}
|
||||
|
||||
public function __call( $method, $params ) {
|
||||
$var = \strtolower( \substr( $method, 4 ) );
|
||||
|
||||
if ( \strncasecmp( $method, 'get', 3 ) === 0 ) {
|
||||
return $this->$var;
|
||||
}
|
||||
|
||||
if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
}
|
||||
|
||||
public function from_post( $object ) {
|
||||
$this->object = $object;
|
||||
$this->published = $object['published'];
|
||||
$this->actor = $object['attributedTo'];
|
||||
$this->id = $object['id'];
|
||||
}
|
||||
|
||||
public function from_comment( $object ) {
|
||||
|
||||
}
|
||||
|
||||
public function to_array() {
|
||||
$array = \get_object_vars( $this );
|
||||
|
||||
if ( $this->context ) {
|
||||
$array = array( '@context' => $this->context ) + $array;
|
||||
}
|
||||
|
||||
unset( $array['context'] );
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function to_json() {
|
||||
return \wp_json_encode( $this->to_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT );
|
||||
}
|
||||
|
||||
public function to_simple_array() {
|
||||
$activity = array(
|
||||
'@context' => $this->context,
|
||||
'type' => $this->type,
|
||||
'actor' => $this->actor,
|
||||
'object' => $this->object,
|
||||
'to' => $this->to,
|
||||
'cc' => $this->cc,
|
||||
);
|
||||
|
||||
if ( $this->id ) {
|
||||
$activity['id'] = $this->id;
|
||||
}
|
||||
|
||||
return $activity;
|
||||
}
|
||||
|
||||
public function to_simple_json() {
|
||||
return \wp_json_encode( $this->to_simple_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT );
|
||||
}
|
||||
}
|
320
wp-content/plugins/activitypub/includes/model/class-post.php
Normal file
320
wp-content/plugins/activitypub/includes/model/class-post.php
Normal file
@ -0,0 +1,320 @@
|
||||
<?php
|
||||
namespace Activitypub\Model;
|
||||
|
||||
/**
|
||||
* ActivityPub Post Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Post {
|
||||
private $post;
|
||||
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'activitypub_the_summary', array( '\Activitypub\Model\Post', 'add_backlink_to_content' ), 15, 2 );
|
||||
\add_filter( 'activitypub_the_content', array( '\Activitypub\Model\Post', 'add_backlink_to_content' ), 15, 2 );
|
||||
}
|
||||
|
||||
public function __construct( $post = null ) {
|
||||
$this->post = \get_post( $post );
|
||||
}
|
||||
|
||||
public function get_post() {
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
public function get_post_author() {
|
||||
return $this->post->post_author;
|
||||
}
|
||||
|
||||
public function to_array() {
|
||||
$post = $this->post;
|
||||
|
||||
$array = array(
|
||||
'id' => \get_permalink( $post ),
|
||||
'type' => $this->get_object_type(),
|
||||
'published' => \date( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_date ) ),
|
||||
'attributedTo' => \get_author_posts_url( $post->post_author ),
|
||||
'summary' => $this->get_the_title(),
|
||||
'inReplyTo' => null,
|
||||
'content' => $this->get_the_content(),
|
||||
'contentMap' => array(
|
||||
\strstr( \get_locale(), '_', true ) => $this->get_the_content(),
|
||||
),
|
||||
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
|
||||
'cc' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
|
||||
'attachment' => $this->get_attachments(),
|
||||
'tag' => $this->get_tags(),
|
||||
);
|
||||
|
||||
return \apply_filters( 'activitypub_post', $array );
|
||||
}
|
||||
|
||||
public function to_json() {
|
||||
return \wp_json_encode( $this->to_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT );
|
||||
}
|
||||
|
||||
public function get_attachments() {
|
||||
$max_images = \apply_filters( 'activitypub_max_images', 3 );
|
||||
|
||||
$images = array();
|
||||
|
||||
// max images can't be negative or zero
|
||||
if ( $max_images <= 0 ) {
|
||||
$max_images = 1;
|
||||
}
|
||||
|
||||
$id = $this->post->ID;
|
||||
|
||||
$image_ids = array();
|
||||
// list post thumbnail first if this post has one
|
||||
if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) {
|
||||
$image_ids[] = \get_post_thumbnail_id( $id );
|
||||
$max_images--;
|
||||
}
|
||||
// then list any image attachments
|
||||
$query = new \WP_Query(
|
||||
array(
|
||||
'post_parent' => $id,
|
||||
'post_status' => 'inherit',
|
||||
'post_type' => 'attachment',
|
||||
'post_mime_type' => 'image',
|
||||
'order' => 'ASC',
|
||||
'orderby' => 'menu_order ID',
|
||||
'posts_per_page' => $max_images,
|
||||
)
|
||||
);
|
||||
foreach ( $query->get_posts() as $attachment ) {
|
||||
if ( ! \in_array( $attachment->ID, $image_ids, true ) ) {
|
||||
$image_ids[] = $attachment->ID;
|
||||
}
|
||||
}
|
||||
|
||||
$image_ids = \array_unique( $image_ids );
|
||||
|
||||
// get URLs for each image
|
||||
foreach ( $image_ids as $id ) {
|
||||
$alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
|
||||
$thumbnail = \wp_get_attachment_image_src( $id, 'full' );
|
||||
$mimetype = \get_post_mime_type( $id );
|
||||
|
||||
if ( $thumbnail ) {
|
||||
$image = array(
|
||||
'type' => 'Image',
|
||||
'url' => $thumbnail[0],
|
||||
'mediaType' => $mimetype
|
||||
);
|
||||
if ( $alt ) {
|
||||
$image['name'] = $alt;
|
||||
}
|
||||
$images[] = $image;
|
||||
}
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
public function get_tags() {
|
||||
$tags = array();
|
||||
|
||||
$post_tags = \get_the_tags( $this->post->ID );
|
||||
if ( $post_tags ) {
|
||||
foreach ( $post_tags as $post_tag ) {
|
||||
$tag = array(
|
||||
'type' => 'Hashtag',
|
||||
'href' => \get_tag_link( $post_tag->term_id ),
|
||||
'name' => '#' . $post_tag->slug,
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the as2 object-type for a given post
|
||||
*
|
||||
* @param string $type the object-type
|
||||
* @param Object $post the post-object
|
||||
*
|
||||
* @return string the object-type
|
||||
*/
|
||||
public function get_object_type() {
|
||||
if ( 'wordpress-post-format' !== \get_option( 'activitypub_object_type', 'note' ) ) {
|
||||
return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) );
|
||||
}
|
||||
|
||||
$post_type = \get_post_type( $this->post );
|
||||
switch ( $post_type ) {
|
||||
case 'post':
|
||||
$post_format = \get_post_format( $this->post );
|
||||
switch ( $post_format ) {
|
||||
case 'aside':
|
||||
case 'status':
|
||||
case 'quote':
|
||||
case 'note':
|
||||
$object_type = 'Note';
|
||||
break;
|
||||
case 'gallery':
|
||||
case 'image':
|
||||
$object_type = 'Image';
|
||||
break;
|
||||
case 'video':
|
||||
$object_type = 'Video';
|
||||
break;
|
||||
case 'audio':
|
||||
$object_type = 'Audio';
|
||||
break;
|
||||
default:
|
||||
$object_type = 'Article';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'page':
|
||||
$object_type = 'Page';
|
||||
break;
|
||||
case 'attachment':
|
||||
$mime_type = \get_post_mime_type();
|
||||
$media_type = \preg_replace( '/(\/[a-zA-Z]+)/i', '', $mime_type );
|
||||
switch ( $media_type ) {
|
||||
case 'audio':
|
||||
$object_type = 'Audio';
|
||||
break;
|
||||
case 'video':
|
||||
$object_type = 'Video';
|
||||
break;
|
||||
case 'image':
|
||||
$object_type = 'Image';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$object_type = 'Article';
|
||||
break;
|
||||
}
|
||||
|
||||
return $object_type;
|
||||
}
|
||||
|
||||
public function get_the_content() {
|
||||
if ( 'excerpt' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
|
||||
return $this->get_the_post_summary();
|
||||
}
|
||||
|
||||
if ( 'title' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
|
||||
return $this->get_the_title();
|
||||
}
|
||||
|
||||
return $this->get_the_post_content();
|
||||
}
|
||||
|
||||
public function get_the_title() {
|
||||
if ( 'Article' === $this->get_object_type() ) {
|
||||
$title = \get_the_title( $this->post );
|
||||
|
||||
return \html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the excerpt for a post for use outside of the loop.
|
||||
*
|
||||
* @param int Optional excerpt length.
|
||||
*
|
||||
* @return string The excerpt.
|
||||
*/
|
||||
public function get_the_post_excerpt( $excerpt_length = 400 ) {
|
||||
$post = $this->post;
|
||||
|
||||
$excerpt = \get_post_field( 'post_excerpt', $post );
|
||||
|
||||
if ( '' === $excerpt ) {
|
||||
|
||||
$content = \get_post_field( 'post_content', $post );
|
||||
|
||||
// An empty string will make wp_trim_excerpt do stuff we do not want.
|
||||
if ( '' !== $content ) {
|
||||
|
||||
$excerpt = \strip_shortcodes( $content );
|
||||
|
||||
/** This filter is documented in wp-includes/post-template.php */
|
||||
$excerpt = \apply_filters( 'the_content', $excerpt );
|
||||
$excerpt = \str_replace( ']]>', ']]>', $excerpt );
|
||||
|
||||
$excerpt_length = \apply_filters( 'excerpt_length', $excerpt_length );
|
||||
|
||||
/** This filter is documented in wp-includes/formatting.php */
|
||||
$excerpt_more = \apply_filters( 'excerpt_more', ' [...]' );
|
||||
|
||||
$excerpt = \wp_trim_words( $excerpt, $excerpt_length, $excerpt_more );
|
||||
}
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content for a post for use outside of the loop.
|
||||
*
|
||||
* @return string The content.
|
||||
*/
|
||||
public function get_the_post_content() {
|
||||
$post = $this->post;
|
||||
|
||||
$content = \get_post_field( 'post_content', $post );
|
||||
|
||||
$filtered_content = \apply_filters( 'the_content', $content );
|
||||
$filtered_content = \apply_filters( 'activitypub_the_content', $filtered_content, $this->post );
|
||||
|
||||
$decoded_content = \html_entity_decode( $filtered_content, ENT_QUOTES, 'UTF-8' );
|
||||
|
||||
$allowed_html = \apply_filters( 'activitypub_allowed_html', '<a><p><ul><ol><li><code><blockquote><pre>' );
|
||||
|
||||
return \trim( \preg_replace( '/[\r\n]{2,}/', '', \strip_tags( $decoded_content, $allowed_html ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the excerpt for a post for use outside of the loop.
|
||||
*
|
||||
* @param int Optional excerpt length.
|
||||
*
|
||||
* @return string The excerpt.
|
||||
*/
|
||||
public function get_the_post_summary( $summary_length = 400 ) {
|
||||
$summary = $this->get_the_post_excerpt( $summary_length );
|
||||
|
||||
$filtered_summary = \apply_filters( 'the_excerpt', $summary );
|
||||
$filtered_summary = \apply_filters( 'activitypub_the_summary', $filtered_summary, $this->post );
|
||||
|
||||
$decoded_summary = \html_entity_decode( $filtered_summary, ENT_QUOTES, 'UTF-8' );
|
||||
|
||||
$allowed_html = \apply_filters( 'activitypub_allowed_html', '<a><p>' );
|
||||
|
||||
return \trim( \preg_replace( '/[\r\n]{2,}/', '', \strip_tags( $decoded_summary, $allowed_html ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a backlink to the post/summary content
|
||||
*
|
||||
* @param string $content
|
||||
* @param WP_Post $post
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function add_backlink_to_content( $content, $post ) {
|
||||
$link = '';
|
||||
|
||||
if ( \get_option( 'activitypub_use_shortlink', 0 ) ) {
|
||||
$link = \esc_url( \wp_get_shortlink( $post->ID ) );
|
||||
} else {
|
||||
$link = \esc_url( \get_permalink( $post->ID ) );
|
||||
}
|
||||
|
||||
return $content . '<p><a href="' . $link . '">' . $link . '</a></p>';
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace Activitypub\Peer;
|
||||
|
||||
/**
|
||||
* ActivityPub Followers DB-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Followers {
|
||||
|
||||
public static function get_followers( $author_id ) {
|
||||
$followers = \get_user_option( 'activitypub_followers', $author_id );
|
||||
|
||||
if ( ! $followers ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ( $followers as $key => $follower ) {
|
||||
if (
|
||||
\is_array( $follower ) &&
|
||||
isset( $follower['type'] ) &&
|
||||
'Person' === $follower['type'] &&
|
||||
isset( $follower['id'] ) &&
|
||||
false !== \filter_var( $follower['id'], FILTER_VALIDATE_URL )
|
||||
) {
|
||||
$followers[ $key ] = $follower['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return $followers;
|
||||
}
|
||||
|
||||
public static function count_followers( $author_id ) {
|
||||
$followers = self::get_followers( $author_id );
|
||||
|
||||
return \count( $followers );
|
||||
}
|
||||
|
||||
public static function add_follower( $actor, $author_id ) {
|
||||
$followers = \get_user_option( 'activitypub_followers', $author_id );
|
||||
|
||||
if ( ! \is_string( $actor ) ) {
|
||||
if (
|
||||
\is_array( $actor ) &&
|
||||
isset( $actor['type'] ) &&
|
||||
'Person' === $actor['type'] &&
|
||||
isset( $actor['id'] ) &&
|
||||
false !== \filter_var( $actor['id'], FILTER_VALIDATE_URL )
|
||||
) {
|
||||
$actor = $actor['id'];
|
||||
}
|
||||
|
||||
return new \WP_Error( 'invalid_actor_object', \__( 'Unknown Actor schema', 'activitypub' ), array(
|
||||
'status' => 404,
|
||||
) );
|
||||
}
|
||||
|
||||
if ( ! \is_array( $followers ) ) {
|
||||
$followers = array( $actor );
|
||||
} else {
|
||||
$followers[] = $actor;
|
||||
}
|
||||
|
||||
$followers = \array_unique( $followers );
|
||||
|
||||
\update_user_meta( $author_id, 'activitypub_followers', $followers );
|
||||
}
|
||||
|
||||
public static function remove_follower( $actor, $author_id ) {
|
||||
$followers = \get_user_option( 'activitypub_followers', $author_id );
|
||||
|
||||
foreach ( $followers as $key => $value ) {
|
||||
if ( $value === $actor ) {
|
||||
unset( $followers[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
\update_user_meta( $author_id, 'activitypub_followers', $followers );
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
/**
|
||||
* ActivityPub Followers REST-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/#followers
|
||||
*/
|
||||
class Followers {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Followers', 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/users/(?P<id>\d+)/followers', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Followers', 'get' ),
|
||||
'args' => self::request_parameters(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle GET request
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function get( $request ) {
|
||||
$user_id = $request->get_param( 'id' );
|
||||
$user = \get_user_by( 'ID', $user_id );
|
||||
|
||||
if ( ! $user ) {
|
||||
return new \WP_Error( 'rest_invalid_param', \__( 'User not found', 'activitypub' ), array(
|
||||
'status' => 404,
|
||||
'params' => array(
|
||||
'user_id' => \__( 'User not found', 'activitypub' ),
|
||||
),
|
||||
) );
|
||||
}
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_outbox_pre' );
|
||||
|
||||
$json = new \stdClass();
|
||||
|
||||
$json->{'@context'} = \Activitypub\get_context();
|
||||
|
||||
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/followers" ); // phpcs:ignore
|
||||
$json->totalItems = \Activitypub\count_followers( $user_id ); // phpcs:ignore
|
||||
$json->orderedItems = \Activitypub\Peer\Followers::get_followers( $user_id ); // phpcs:ignore
|
||||
|
||||
$json->first = $json->partOf; // phpcs:ignore
|
||||
|
||||
$json->first = \get_rest_url( null, "/activitypub/1.0/users/$user_id/followers" );
|
||||
|
||||
$response = new \WP_REST_Response( $json, 200 );
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* The supported parameters
|
||||
*
|
||||
* @return array list of parameters
|
||||
*/
|
||||
public static function request_parameters() {
|
||||
$params = array();
|
||||
|
||||
$params['page'] = array(
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
$params['id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
/**
|
||||
* ActivityPub Following REST-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/#following
|
||||
*/
|
||||
class Following {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Following', 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/users/(?P<id>\d+)/following', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Following', 'get' ),
|
||||
'args' => self::request_parameters(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle GET request
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function get( $request ) {
|
||||
$user_id = $request->get_param( 'id' );
|
||||
$user = \get_user_by( 'ID', $user_id );
|
||||
|
||||
if ( ! $user ) {
|
||||
return new \WP_Error( 'rest_invalid_param', \__( 'User not found', 'activitypub' ), array(
|
||||
'status' => 404,
|
||||
'params' => array(
|
||||
'user_id' => \__( 'User not found', 'activitypub' ),
|
||||
),
|
||||
) );
|
||||
}
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_outbox_pre' );
|
||||
|
||||
$json = new \stdClass();
|
||||
|
||||
$json->{'@context'} = \Activitypub\get_context();
|
||||
|
||||
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/following" ); // phpcs:ignore
|
||||
$json->totalItems = 0; // phpcs:ignore
|
||||
$json->orderedItems = array(); // phpcs:ignore
|
||||
|
||||
$json->first = $json->partOf; // phpcs:ignore
|
||||
|
||||
$json->first = \get_rest_url( null, "/activitypub/1.0/users/$user_id/following" );
|
||||
|
||||
$response = new \WP_REST_Response( $json, 200 );
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* The supported parameters
|
||||
*
|
||||
* @return array list of parameters
|
||||
*/
|
||||
public static function request_parameters() {
|
||||
$params = array();
|
||||
|
||||
$params['page'] = array(
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
$params['id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
271
wp-content/plugins/activitypub/includes/rest/class-inbox.php
Normal file
271
wp-content/plugins/activitypub/includes/rest/class-inbox.php
Normal file
@ -0,0 +1,271 @@
|
||||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
/**
|
||||
* ActivityPub Inbox REST-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/#inbox
|
||||
*/
|
||||
class Inbox {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Inbox', 'register_routes' ) );
|
||||
\add_filter( 'rest_pre_serve_request', array( '\Activitypub\Rest\Inbox', 'serve_request' ), 11, 4 );
|
||||
\add_action( 'activitypub_inbox_follow', array( '\Activitypub\Rest\Inbox', 'handle_follow' ), 10, 2 );
|
||||
\add_action( 'activitypub_inbox_unfollow', array( '\Activitypub\Rest\Inbox', 'handle_unfollow' ), 10, 2 );
|
||||
//\add_action( 'activitypub_inbox_like', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 );
|
||||
//\add_action( 'activitypub_inbox_announce', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 );
|
||||
\add_action( 'activitypub_inbox_create', array( '\Activitypub\Rest\Inbox', 'handle_create' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/inbox', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/users/(?P<user_id>\d+)/inbox', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox' ),
|
||||
'args' => self::request_parameters(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into the REST API request to verify the signature.
|
||||
*
|
||||
* @param bool $served Whether the request has already been served.
|
||||
* @param WP_HTTP_ResponseInterface $result Result to send to the client. Usually a WP_REST_Response.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
* @param WP_REST_Server $server Server instance.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public static function serve_request( $served, $result, $request, $server ) {
|
||||
if ( '/activitypub' !== \substr( $request->get_route(), 0, 12 ) ) {
|
||||
return $served;
|
||||
}
|
||||
|
||||
$signature = $request->get_header( 'signature' );
|
||||
|
||||
if ( ! $signature ) {
|
||||
return $served;
|
||||
}
|
||||
|
||||
$headers = $request->get_headers();
|
||||
|
||||
// verify signature
|
||||
//\Activitypub\Signature::verify_signature( $headers, $key );
|
||||
|
||||
return $served;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the user-inbox
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function user_inbox( $request ) {
|
||||
$user_id = $request->get_param( 'user_id' );
|
||||
|
||||
$data = $request->get_params();
|
||||
$type = $request->get_param( 'type' );
|
||||
|
||||
\do_action( 'activitypub_inbox', $data, $user_id, $type );
|
||||
\do_action( "activitypub_inbox_{$type}", $data, $user_id );
|
||||
|
||||
return new \WP_REST_Response( array(), 202 );
|
||||
}
|
||||
|
||||
/**
|
||||
* The shared inbox
|
||||
*
|
||||
* @param [type] $request [description]
|
||||
*
|
||||
* @return WP_Error not yet implemented
|
||||
*/
|
||||
public static function shared_inbox( $request ) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The supported parameters
|
||||
*
|
||||
* @return array list of parameters
|
||||
*/
|
||||
public static function request_parameters() {
|
||||
$params = array();
|
||||
|
||||
$params['page'] = array(
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
$params['user_id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
$params['id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'validate_callback' => function( $param, $request, $key ) {
|
||||
if ( ! \is_string( $param ) ) {
|
||||
$param = $param['id'];
|
||||
}
|
||||
return ! \Activitypub\is_blacklisted( $param );
|
||||
},
|
||||
'sanitize_callback' => 'esc_url_raw',
|
||||
);
|
||||
|
||||
$params['actor'] = array(
|
||||
'required' => true,
|
||||
'type' => array( 'object', 'string' ),
|
||||
'validate_callback' => function( $param, $request, $key ) {
|
||||
if ( ! \is_string( $param ) ) {
|
||||
$param = $param['id'];
|
||||
}
|
||||
return ! \Activitypub\is_blacklisted( $param );
|
||||
},
|
||||
'sanitize_callback' => function( $param, $request, $key ) {
|
||||
if ( ! \is_string( $param ) ) {
|
||||
$param = $param['id'];
|
||||
}
|
||||
return \esc_url_raw( $param );
|
||||
},
|
||||
);
|
||||
|
||||
$params['type'] = array(
|
||||
'required' => true,
|
||||
'type' => 'enum',
|
||||
'enum' => array( 'Create' ),
|
||||
'sanitize_callback' => function( $param, $request, $key ) {
|
||||
return \strtolower( $param );
|
||||
},
|
||||
);
|
||||
|
||||
$params['object'] = array(
|
||||
'required' => true,
|
||||
'type' => 'object',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Follow" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_follow( $object, $user_id ) {
|
||||
// save follower
|
||||
\Activitypub\Peer\Followers::add_follower( $object['actor'], $user_id );
|
||||
|
||||
// get inbox
|
||||
$inbox = \Activitypub\get_inbox_by_actor( $object['actor'] );
|
||||
|
||||
// send "Accept" activity
|
||||
$activity = new \Activitypub\Model\Activity( 'Accept', \Activitypub\Model\Activity::TYPE_SIMPLE );
|
||||
$activity->set_object( $object );
|
||||
$activity->set_actor( \get_author_posts_url( $user_id ) );
|
||||
$activity->set_to( $object['actor'] );
|
||||
$activity->set_id( \get_author_posts_url( $user_id ) . '#follow' . \preg_replace( '~^https?://~', '', $object['actor'] ) );
|
||||
|
||||
$activity = $activity->to_simple_json();
|
||||
|
||||
$response = \Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Unfollow" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_unfollow( $object, $user_id ) {
|
||||
\Activitypub\Peer\Followers::remove_follower( $object['actor'], $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Reaction" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_reaction( $object, $user_id ) {
|
||||
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
|
||||
|
||||
$commentdata = array(
|
||||
'comment_post_ID' => \url_to_postid( $object['object'] ),
|
||||
'comment_author' => \esc_attr( $meta['name'] ),
|
||||
'comment_author_email' => '',
|
||||
'comment_author_url' => \esc_url_raw( $object['actor'] ),
|
||||
'comment_content' => \esc_url_raw( $object['actor'] ),
|
||||
'comment_type' => \esc_attr( \strtolower( $object['type'] ) ),
|
||||
'comment_parent' => 0,
|
||||
'comment_meta' => array(
|
||||
'source_url' => \esc_url_raw( $object['id'] ),
|
||||
'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
|
||||
'protocol' => 'activitypub',
|
||||
),
|
||||
);
|
||||
|
||||
// disable flood control
|
||||
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
|
||||
|
||||
$state = \wp_new_comment( $commentdata, true );
|
||||
|
||||
// re-add flood control
|
||||
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Create" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_create( $object, $user_id ) {
|
||||
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
|
||||
|
||||
$commentdata = array(
|
||||
'comment_post_ID' => \url_to_postid( $object['object']['inReplyTo'] ),
|
||||
'comment_author' => \esc_attr( $meta['name'] ),
|
||||
'comment_author_url' => \esc_url_raw( $object['actor'] ),
|
||||
'comment_content' => \wp_filter_kses( $object['object']['content'] ),
|
||||
'comment_type' => '',
|
||||
'comment_author_email' => '',
|
||||
'comment_parent' => 0,
|
||||
'comment_meta' => array(
|
||||
'source_url' => \esc_url_raw( $object['object']['url'] ),
|
||||
'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
|
||||
'protocol' => 'activitypub',
|
||||
),
|
||||
);
|
||||
|
||||
// disable flood control
|
||||
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
|
||||
|
||||
$state = \wp_new_comment( $commentdata, true );
|
||||
|
||||
// re-add flood control
|
||||
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
||||
}
|
||||
}
|
191
wp-content/plugins/activitypub/includes/rest/class-nodeinfo.php
Normal file
191
wp-content/plugins/activitypub/includes/rest/class-nodeinfo.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
/**
|
||||
* ActivityPub NodeInfo REST-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see http://nodeinfo.diaspora.software/
|
||||
*/
|
||||
class Nodeinfo {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Nodeinfo', 'register_routes' ) );
|
||||
\add_filter( 'nodeinfo_data', array( '\Activitypub\Rest\Nodeinfo', 'add_nodeinfo_discovery' ), 10, 2 );
|
||||
\add_filter( 'nodeinfo2_data', array( '\Activitypub\Rest\Nodeinfo', 'add_nodeinfo2_discovery' ), 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/nodeinfo/discovery', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'discovery' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/nodeinfo', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/nodeinfo2', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo2' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render NodeInfo file
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function nodeinfo( $request ) {
|
||||
$nodeinfo = array();
|
||||
|
||||
$nodeinfo['version'] = '2.0';
|
||||
$nodeinfo['software'] = array(
|
||||
'name' => 'wordpress',
|
||||
'version' => \get_bloginfo( 'version' ),
|
||||
);
|
||||
|
||||
$users = \count_users();
|
||||
$posts = \wp_count_posts();
|
||||
$comments = \wp_count_comments();
|
||||
|
||||
$nodeinfo['usage'] = array(
|
||||
'users' => array(
|
||||
'total' => (int) $users['total_users'],
|
||||
),
|
||||
'localPosts' => (int) $posts->publish,
|
||||
'localComments' => (int) $comments->approved,
|
||||
);
|
||||
|
||||
$nodeinfo['openRegistrations'] = false;
|
||||
$nodeinfo['protocols'] = array( 'activitypub' );
|
||||
|
||||
$nodeinfo['services'] = array(
|
||||
'inbound' => array(),
|
||||
'outbound' => array(),
|
||||
);
|
||||
|
||||
$nodeinfo['metadata'] = array(
|
||||
'email' => \get_option( 'admin_email' ),
|
||||
);
|
||||
|
||||
return new \WP_REST_Response( $nodeinfo, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render NodeInfo file
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function nodeinfo2( $request ) {
|
||||
$nodeinfo = array();
|
||||
|
||||
$nodeinfo['version'] = '1.0';
|
||||
$nodeinfo['server'] = array(
|
||||
'baseUrl' => home_url( '/' ),
|
||||
'name' => \get_bloginfo( 'name' ),
|
||||
'software' => 'wordpress',
|
||||
'version' => \get_bloginfo( 'version' ),
|
||||
);
|
||||
|
||||
$users = \count_users();
|
||||
$posts = \wp_count_posts();
|
||||
$comments = \wp_count_comments();
|
||||
|
||||
$nodeinfo['usage'] = array(
|
||||
'users' => array(
|
||||
'total' => (int) $users['total_users'],
|
||||
),
|
||||
'localPosts' => (int) $posts->publish,
|
||||
'localComments' => (int) $comments->approved,
|
||||
);
|
||||
|
||||
$nodeinfo['openRegistrations'] = false;
|
||||
$nodeinfo['protocols'] = array( 'activitypub' );
|
||||
|
||||
$nodeinfo['services'] = array(
|
||||
'inbound' => array(),
|
||||
'outbound' => array(),
|
||||
);
|
||||
|
||||
$nodeinfo['metadata'] = array(
|
||||
'email' => \get_option( 'admin_email' ),
|
||||
);
|
||||
|
||||
return new \WP_REST_Response( $nodeinfo, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render NodeInfo discovery file
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function discovery( $request ) {
|
||||
$discovery = array();
|
||||
$discovery['links'] = array(
|
||||
array(
|
||||
'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||
'href' => \get_rest_url( null, 'activitypub/1.0/nodeinfo' ),
|
||||
),
|
||||
);
|
||||
|
||||
return new \WP_REST_Response( $discovery, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend NodeInfo data
|
||||
*
|
||||
* @param array $nodeinfo NodeInfo data
|
||||
* @param string The NodeInfo Version
|
||||
*
|
||||
* @return array The extended array
|
||||
*/
|
||||
public static function add_nodeinfo_discovery( $nodeinfo, $version ) {
|
||||
if ( '2.0' === $version ) {
|
||||
$nodeinfo['protocols'][] = 'activitypub';
|
||||
} else {
|
||||
$nodeinfo['protocols']['inbound'][] = 'activitypub';
|
||||
$nodeinfo['protocols']['outbound'][] = 'activitypub';
|
||||
}
|
||||
|
||||
return $nodeinfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend NodeInfo2 data
|
||||
*
|
||||
* @param array $nodeinfo NodeInfo2 data
|
||||
*
|
||||
* @return array The extended array
|
||||
*/
|
||||
public static function add_nodeinfo2_discovery( $nodeinfo ) {
|
||||
$nodeinfo['protocols'][] = 'activitypub';
|
||||
|
||||
return $nodeinfo;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
/**
|
||||
* ActivityPub OStatus REST-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://www.w3.org/community/ostatus/
|
||||
*/
|
||||
class Ostatus {
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/ostatus/remote-follow', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Ostatus', 'get' ),
|
||||
// 'args' => self::request_parameters(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function get() {
|
||||
// @todo implement
|
||||
}
|
||||
}
|
126
wp-content/plugins/activitypub/includes/rest/class-outbox.php
Normal file
126
wp-content/plugins/activitypub/includes/rest/class-outbox.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
/**
|
||||
* ActivityPub Outbox REST-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitypub/#outbox
|
||||
*/
|
||||
class Outbox {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Outbox', 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/users/(?P<id>\d+)/outbox', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Outbox', 'user_outbox' ),
|
||||
'args' => self::request_parameters(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the user-outbox
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function user_outbox( $request ) {
|
||||
$user_id = $request->get_param( 'id' );
|
||||
$author = \get_user_by( 'ID', $user_id );
|
||||
|
||||
if ( ! $author ) {
|
||||
return new \WP_Error( 'rest_invalid_param', __( 'User not found', 'activitypub' ), array(
|
||||
'status' => 404,
|
||||
'params' => array(
|
||||
'user_id' => \__( 'User not found', 'activitypub' ),
|
||||
),
|
||||
) );
|
||||
}
|
||||
|
||||
$page = $request->get_param( 'page', 0 );
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_outbox_pre' );
|
||||
|
||||
$json = new \stdClass();
|
||||
|
||||
$json->{'@context'} = \Activitypub\get_context();
|
||||
$json->id = \home_url( \add_query_arg( null, null ) );
|
||||
$json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
|
||||
$json->actor = \get_author_posts_url( $user_id );
|
||||
$json->type = 'OrderedCollectionPage';
|
||||
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/outbox" ); // phpcs:ignore
|
||||
|
||||
$count_posts = \wp_count_posts();
|
||||
$json->totalItems = \intval( $count_posts->publish ); // phpcs:ignore
|
||||
|
||||
$posts = \get_posts( array(
|
||||
'posts_per_page' => 10,
|
||||
'author' => $user_id,
|
||||
'offset' => $page * 10,
|
||||
) );
|
||||
|
||||
$json->first = \add_query_arg( 'page', 0, $json->partOf ); // phpcs:ignore
|
||||
$json->last = \add_query_arg( 'page', ( \ceil ( $json->totalItems / 10 ) ) - 1, $json->partOf ); // phpcs:ignore
|
||||
|
||||
if ( ( \ceil ( $json->totalItems / 10 ) ) - 1 > $page ) { // phpcs:ignore
|
||||
$json->next = \add_query_arg( 'page', ++$page, $json->partOf ); // phpcs:ignore
|
||||
}
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_NONE );
|
||||
$activitypub_activity->from_post( $activitypub_post->to_array() );
|
||||
$json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore
|
||||
}
|
||||
|
||||
// filter output
|
||||
$json = \apply_filters( 'activitypub_outbox_array', $json );
|
||||
|
||||
/*
|
||||
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_outbox_post' );
|
||||
|
||||
$response = new \WP_REST_Response( $json, 200 );
|
||||
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* The supported parameters
|
||||
*
|
||||
* @return array list of parameters
|
||||
*/
|
||||
public static function request_parameters() {
|
||||
$params = array();
|
||||
|
||||
$params['page'] = array(
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
$params['id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
/**
|
||||
* Custom (hopefully temporary) ActivityPub Rest Server
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Server extends \WP_REST_Server {
|
||||
/**
|
||||
* Overwrite dispatch function to quick fix missing subtype featur
|
||||
*
|
||||
* @see https://core.trac.wordpress.org/ticket/49404
|
||||
*
|
||||
* @param WP_REST_Request $request Request to attempt dispatching.
|
||||
* @return WP_REST_Response Response returned by the callback.
|
||||
*/
|
||||
public function dispatch( $request ) {
|
||||
$content_type = $request->get_content_type();
|
||||
|
||||
// check for content-sub-types like 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
if ( \preg_match( '/application\/([a-zA-Z+_-]+\+)json/', $content_type['value'] ) ) {
|
||||
$request->set_header( 'Content-Type', 'application/json' );
|
||||
}
|
||||
|
||||
// make request filterable
|
||||
$request = apply_filters( 'activitypub_pre_dispatch_request', $request );
|
||||
|
||||
return parent::dispatch( $request );
|
||||
}
|
||||
}
|
119
wp-content/plugins/activitypub/includes/rest/class-webfinger.php
Normal file
119
wp-content/plugins/activitypub/includes/rest/class-webfinger.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
/**
|
||||
* ActivityPub WebFinger REST-Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*
|
||||
* @see https://webfinger.net/
|
||||
*/
|
||||
class Webfinger {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Webfinger', 'register_routes' ) );
|
||||
\add_action( 'webfinger_user_data', array( '\Activitypub\Rest\Webfinger', 'add_webfinger_discovery' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
\register_rest_route(
|
||||
'activitypub/1.0', '/webfinger', array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Webfinger', 'webfinger' ),
|
||||
'args' => self::request_parameters(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render JRD file
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function webfinger( $request ) {
|
||||
$resource = $request->get_param( 'resource' );
|
||||
|
||||
$matches = array();
|
||||
$matched = \preg_match( '/^acct:([^@]+)@(.+)$/', $resource, $matches );
|
||||
|
||||
if ( ! $matched ) {
|
||||
return new \WP_Error( 'activitypub_unsupported_resource', \__( 'Resource is invalid', 'activitypub' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$resource_identifier = $matches[1];
|
||||
$resource_host = $matches[2];
|
||||
|
||||
if ( \wp_parse_url( \home_url( '/' ), PHP_URL_HOST ) !== $resource_host ) {
|
||||
return new \WP_Error( 'activitypub_wrong_host', \__( 'Resource host does not match blog host', 'activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$user = \get_user_by( 'login', \esc_sql( $resource_identifier ) );
|
||||
|
||||
if ( ! $user ) {
|
||||
return new \WP_Error( 'activitypub_user_not_found', \__( 'User not found', 'activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$json = array(
|
||||
'subject' => $resource,
|
||||
'aliases' => array(
|
||||
\get_author_posts_url( $user->ID ),
|
||||
),
|
||||
'links' => array(
|
||||
array(
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => \get_author_posts_url( $user->ID ),
|
||||
),
|
||||
array(
|
||||
'rel' => 'http://webfinger.net/rel/profile-page',
|
||||
'type' => 'text/html',
|
||||
'href' => \get_author_posts_url( $user->ID ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return new \WP_REST_Response( $json, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* The supported parameters
|
||||
*
|
||||
* @return array list of parameters
|
||||
*/
|
||||
public static function request_parameters() {
|
||||
$params = array();
|
||||
|
||||
$params['resource'] = array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'pattern' => '^acct:([^@]+)@(.+)$',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WebFinger discovery links
|
||||
*
|
||||
* @param array $array the jrd array
|
||||
* @param string $resource the WebFinger resource
|
||||
* @param WP_User $user the WordPress user
|
||||
*/
|
||||
public static function add_webfinger_discovery( $array, $resource, $user ) {
|
||||
$array['links'][] = array(
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => \get_author_posts_url( $user->ID ),
|
||||
);
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace Activitypub\Table;
|
||||
|
||||
if ( ! \class_exists( '\WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
|
||||
class Followers_List extends \WP_List_Table {
|
||||
public function get_columns() {
|
||||
return array(
|
||||
'identifier' => \__( 'Identifier', 'activitypub' ),
|
||||
);
|
||||
}
|
||||
|
||||
public function get_sortable_columns() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function prepare_items() {
|
||||
$columns = $this->get_columns();
|
||||
$hidden = array();
|
||||
|
||||
$this->process_action();
|
||||
$this->_column_headers = array( $columns, $hidden, $this->get_sortable_columns() );
|
||||
|
||||
$this->items = array();
|
||||
|
||||
foreach ( \Activitypub\Peer\Followers::get_followers( \get_current_user_id() ) as $follower ) {
|
||||
$this->items[]['identifier'] = \esc_attr( $follower );
|
||||
}
|
||||
}
|
||||
|
||||
public function column_default( $item, $column_name ) {
|
||||
return $item[ $column_name ];
|
||||
}
|
||||
}
|
307
wp-content/plugins/activitypub/languages/activitypub.pot
Normal file
307
wp-content/plugins/activitypub/languages/activitypub.pot
Normal file
@ -0,0 +1,307 @@
|
||||
# Copyright (C) 2020 Matthias Pfefferle
|
||||
# This file is distributed under the MIT.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ActivityPub 0.10.0\n"
|
||||
"Report-Msgid-Bugs-To: "
|
||||
"https://wordpress.org/support/plugin/wordpress-activitypub\n"
|
||||
"POT-Creation-Date: 2020-03-15 19:34:14+00:00\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2020-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"X-Generator: grunt-wp-i18n 1.0.3\n"
|
||||
|
||||
#: includes/class-admin.php:33
|
||||
msgid "Followers"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:33 templates/followers-list.php:2
|
||||
msgid "Followers (Fediverse)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:59
|
||||
msgid "Use title and link, summary or full content"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:71
|
||||
msgid "The Activity-Object-Type"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:83 templates/settings.php:36
|
||||
msgid "Use the Shortlink instead of the permalink"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:90
|
||||
msgid ""
|
||||
"Add hashtags in the content as native tags and replace the #tag with the "
|
||||
"tag-link"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:97
|
||||
msgid "Add all tags as hashtags at the end of each activity"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:104
|
||||
msgid "Enable ActivityPub support for post types"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:112
|
||||
msgid "Block fediverse instances"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:123
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:125
|
||||
msgid ""
|
||||
"ActivityPub is a decentralized social networking protocol based on the "
|
||||
"ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended "
|
||||
"standard published by the W3C Social Web Working Group. It provides a "
|
||||
"client to server API for creating, updating and deleting content, as well "
|
||||
"as a federated server to server API for delivering notifications and "
|
||||
"subscribing to content."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:130
|
||||
msgid "For more information:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:131
|
||||
msgid "<a href=\"https://activitypub.rocks/\">Test Suite</a>"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:132
|
||||
msgid "<a href=\"https://www.w3.org/TR/activitypub/\">W3C Spec</a>"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:133
|
||||
msgid ""
|
||||
"<a href=\"https://github.com/pfefferle/wordpress-activitypub/issues\">Give "
|
||||
"us feedback</a>"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:135
|
||||
msgid "<a href=\"https://notiz.blog/donate\">Donate</a>"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php:145
|
||||
msgid "Fediverse"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:110
|
||||
msgid "The \"actor\" is no valid URL"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:132
|
||||
msgid "No valid JSON data"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:160
|
||||
msgid "No \"Inbox\" found"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:186
|
||||
msgid "No \"Public-Key\" found"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:214
|
||||
msgid "Profile identifier"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:219
|
||||
#. translators: the webfinger resource
|
||||
msgid "Try to follow \"@%s\" in the Mastodon/Friendica search field."
|
||||
msgstr ""
|
||||
|
||||
#: includes/peer/class-followers.php:53
|
||||
msgid "Unknown Actor schema"
|
||||
msgstr ""
|
||||
|
||||
#: includes/rest/class-followers.php:46 includes/rest/class-followers.php:49
|
||||
#: includes/rest/class-following.php:46 includes/rest/class-following.php:49
|
||||
#: includes/rest/class-outbox.php:45 includes/rest/class-outbox.php:48
|
||||
#: includes/rest/class-webfinger.php:61
|
||||
msgid "User not found"
|
||||
msgstr ""
|
||||
|
||||
#: includes/rest/class-webfinger.php:48
|
||||
msgid "Resource is invalid"
|
||||
msgstr ""
|
||||
|
||||
#: includes/rest/class-webfinger.php:55
|
||||
msgid "Resource host does not match blog host"
|
||||
msgstr ""
|
||||
|
||||
#: includes/table/followers-list.php:11
|
||||
msgid "Identifier"
|
||||
msgstr ""
|
||||
|
||||
#: templates/followers-list.php:4
|
||||
msgid "You currently have %s followers."
|
||||
msgstr ""
|
||||
|
||||
#: templates/json-author.php:48
|
||||
msgid "Blog"
|
||||
msgstr ""
|
||||
|
||||
#: templates/json-author.php:58
|
||||
msgid "Profile"
|
||||
msgstr ""
|
||||
|
||||
#: templates/json-author.php:69
|
||||
msgid "Website"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:2
|
||||
msgid "ActivityPub Settings"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:4
|
||||
msgid ""
|
||||
"ActivityPub turns your blog into a federated social network. This means you "
|
||||
"can share and talk to everyone using the ActivityPub protocol, including "
|
||||
"users of Friendica, Pleroma and Mastodon."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:9
|
||||
msgid "Activities"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:11
|
||||
msgid "All activity related settings."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:17
|
||||
msgid "Post-Content"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:21
|
||||
msgid "Title and link"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:21
|
||||
msgid "Only the title and a link."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:24
|
||||
msgid "Excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:24
|
||||
msgid "A content summary, shortened to 400 characters and without markup."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:27
|
||||
msgid "Content (default)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:27
|
||||
msgid "The full content."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:33
|
||||
msgid "Backlink"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:42
|
||||
msgid "Activity-Object-Type"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:46
|
||||
msgid "Note (default)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:46
|
||||
msgid "Should work with most platforms."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:49
|
||||
msgid "Article"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:49
|
||||
msgid ""
|
||||
"The presentation of the \"Article\" might change on different platforms. "
|
||||
"Mastodon for example shows the \"Article\" type as a simple link."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:52
|
||||
msgid "WordPress Post-Format"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:52
|
||||
msgid "Maps the WordPress Post-Format to the ActivityPub Object Type."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:57
|
||||
msgid "Supported post types"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:60
|
||||
msgid "Enable ActivityPub support for the following post types:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:77
|
||||
msgid "Hashtags"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:81
|
||||
msgid ""
|
||||
"Add hashtags in the content as native tags and replace the "
|
||||
"<code>#tag</code> with the tag-link."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:84
|
||||
msgid "Add all tags as hashtags to the end of each activity."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:93
|
||||
msgid "Server"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:95
|
||||
msgid "Server related settings."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:106
|
||||
msgid "Blacklist"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:110
|
||||
msgid ""
|
||||
"A list of hosts, you want to block, one host per line. Please use only the "
|
||||
"host/domain of the server you want to block, without <code>http://</code> "
|
||||
"and without <code>www.</code>. For example <code>example.com</code>."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings.php:124
|
||||
msgid ""
|
||||
"If you like this plugin, what about a small <a "
|
||||
"href=\"https://notiz.blog/donate\">donation</a>?"
|
||||
msgstr ""
|
||||
|
||||
#. Plugin Name of the plugin/theme
|
||||
msgid "ActivityPub"
|
||||
msgstr ""
|
||||
|
||||
#. Plugin URI of the plugin/theme
|
||||
msgid "https://github.com/pfefferle/wordpress-activitypub/"
|
||||
msgstr ""
|
||||
|
||||
#. Description of the plugin/theme
|
||||
msgid ""
|
||||
"The ActivityPub protocol is a decentralized social networking protocol "
|
||||
"based upon the ActivityStreams 2.0 data format."
|
||||
msgstr ""
|
||||
|
||||
#. Author of the plugin/theme
|
||||
msgid "Matthias Pfefferle"
|
||||
msgstr ""
|
||||
|
||||
#. Author URI of the plugin/theme
|
||||
msgid "https://notiz.blog/"
|
||||
msgstr ""
|
276
wp-content/plugins/activitypub/readme.txt
Normal file
276
wp-content/plugins/activitypub/readme.txt
Normal file
@ -0,0 +1,276 @@
|
||||
=== ActivityPub ===
|
||||
Contributors: pfefferle
|
||||
Donate link: https://notiz.blog/donate/
|
||||
Tags: OStatus, fediverse, activitypub, activitystream
|
||||
Requires at least: 4.7
|
||||
Tested up to: 5.3
|
||||
Stable tag: 0.10.0
|
||||
Requires PHP: 5.6
|
||||
License: MIT
|
||||
License URI: http://opensource.org/licenses/MIT
|
||||
|
||||
The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.
|
||||
|
||||
== Description ==
|
||||
|
||||
This is **BETA** software, see the FAQ to see the current feature set or rather what is still planned.
|
||||
|
||||
The plugin implements the ActivityPub protocol for your blog. Your readers will be able to follow your blogposts on Mastodon and other federated platforms that support ActivityPub.
|
||||
|
||||
The plugin works with the following federated platforms:
|
||||
|
||||
* [Mastodon](https://joinmastodon.org/)
|
||||
* [Pleroma](https://pleroma.social/)
|
||||
* [Friendica](https://friendi.ca/)
|
||||
* [HubZilla](https://hubzilla.org/)
|
||||
* [Pixelfed](https://pixelfed.org/)
|
||||
* [SocialHome](https://socialhome.network/)
|
||||
* [Misskey](https://join.misskey.page/)
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= What is the status of this plugin? =
|
||||
|
||||
Implemented:
|
||||
|
||||
* profile pages (JSON representation)
|
||||
* custom links
|
||||
* functional inbox/outbox
|
||||
* follow (accept follows)
|
||||
* share posts
|
||||
* receive comments/reactions
|
||||
|
||||
To implement:
|
||||
|
||||
* signature verification
|
||||
* better WordPress integration
|
||||
* better configuration possibilities
|
||||
* threaded comments support
|
||||
|
||||
= What is "ActivityPub for WordPress" =
|
||||
|
||||
*ActivityPub for WordPress* extends WordPress with some Fediverse features, but it does not compete with platforms like Friendica or Mastodon. If you want to run a **decentralized social network**, please use [Mastodon](https://joinmastodon.org/) or [GNU social](https://gnu.io/social/).
|
||||
|
||||
= What are the differences between this plugin and Pterotype? =
|
||||
|
||||
**Compatibility**
|
||||
|
||||
*ActivityPub for WordPress* is compatible with OStatus and IndieWeb plugin suites. *Pterotype* is incompatible with the standalone [WebFinger plugin](https://wordpress.org/plugins/webfinger/), so it can't be run together with OStatus.
|
||||
|
||||
**Custom tables**
|
||||
|
||||
*Pterotype* creates/uses a bunch of custom tables, *ActivityPub for WordPress* only uses the native tables and adds as little meta data as possible.
|
||||
|
||||
= What if you are running your blog in a subdirectory? =
|
||||
|
||||
In order for webfinger to work, it must be mapped to the root directory of the URL on which your blog resides.
|
||||
|
||||
**Apache**
|
||||
|
||||
Add the following to the .htaccess file in the root directory:
|
||||
|
||||
RedirectMatch "^\/\.well-known(.*)$" "\/blog\/\.well-known$1"
|
||||
|
||||
Where 'blog' is the path to the subdirectory at which your blog resides.
|
||||
|
||||
**Nginx**
|
||||
|
||||
Add the following to the site.conf in sites-available:
|
||||
|
||||
location ~* /.well-known {
|
||||
allow all;
|
||||
try_files $uri $uri/ /blog/?$args;
|
||||
}
|
||||
|
||||
Where 'blog' is the path to the subdirectory at which your blog resides.
|
||||
|
||||
== Changelog ==
|
||||
|
||||
Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
|
||||
|
||||
= 0.10.0 =
|
||||
|
||||
* add image alt text to the ActivityStreams attachment property in a format that Mastodon can read. props [@BenLubar](https://github.com/BenLubar)
|
||||
* use the "summary" property for a title as Mastodon does. props [@BenLubar](https://github.com/BenLubar)
|
||||
* support authorized fetch to avoid having comments from "Anonymous". props [@BenLubar](https://github.com/BenLubar)
|
||||
* add new post type: "title and link only". props [@bgcarlisle](https://github.com/bgcarlisle)
|
||||
|
||||
= 0.9.1 =
|
||||
|
||||
* disable shared inbox
|
||||
* disable delete activity
|
||||
|
||||
= 0.9.0 =
|
||||
|
||||
* some code refactorings
|
||||
* fix #73
|
||||
|
||||
= 0.8.3 =
|
||||
|
||||
* fixed accept header bug
|
||||
|
||||
= 0.8.2 =
|
||||
|
||||
* add all required accept header
|
||||
* better/simpler accept-header handling
|
||||
* add debugging mechanism
|
||||
* Add setting to enable AP for different (public) Post-Types
|
||||
* explicit use of global functions
|
||||
|
||||
= 0.8.1 =
|
||||
|
||||
* fixed PHP warnings
|
||||
|
||||
= 0.8.0 =
|
||||
|
||||
* Moved followers list to user-menu
|
||||
|
||||
= 0.7.4 =
|
||||
|
||||
* added admin_email to metadata, to be able to "Manage your instance" on https://fediverse.network/manage/
|
||||
|
||||
= 0.7.3 =
|
||||
|
||||
* refactorings
|
||||
* fixed PHP warnings
|
||||
* better hashtag regex
|
||||
|
||||
= 0.7.2 =
|
||||
|
||||
* fixed JSON representation of posts https://merveilles.town/@xuv/101907542498716956
|
||||
|
||||
= 0.7.1 =
|
||||
|
||||
* fixed inbox problems with pleroma
|
||||
|
||||
= 0.7.0 =
|
||||
|
||||
* finally fixed pleroma compatibility
|
||||
* added "following" endpoint
|
||||
* simplified "followers" endpoint
|
||||
* fixed default value problem
|
||||
|
||||
= 0.6.0 =
|
||||
|
||||
* add tags as hashtags to the end of each activity
|
||||
* fixed pleroma following issue
|
||||
* followers-list improvements
|
||||
|
||||
= 0.5.1 =
|
||||
|
||||
* fixed name-collision that caused an infinite loop
|
||||
|
||||
= 0.5.0 =
|
||||
|
||||
* complete refactoring
|
||||
* fixed bug #30: Password-protected posts are federated
|
||||
* only send Activites when ActivityPub is enabled for this post-type
|
||||
|
||||
= 0.4.4 =
|
||||
|
||||
* show avatars
|
||||
|
||||
= 0.4.3 =
|
||||
|
||||
* finally fixed backlink in excerpt/summary posts
|
||||
|
||||
= 0.4.2 =
|
||||
|
||||
* fixed backlink in excerpt/summary posts (thanks @depone)
|
||||
|
||||
= 0.4.1 =
|
||||
|
||||
* finally fixed contact list
|
||||
|
||||
= 0.4.0 =
|
||||
|
||||
* added settings to enable/disable hashtag support
|
||||
* fixed follower list
|
||||
* send activities only for new posts, otherwise send updates
|
||||
|
||||
= 0.3.2 =
|
||||
|
||||
* added "followers" endpoint
|
||||
* change activity content from blog 'excerpt' to blog 'content'
|
||||
|
||||
= 0.3.1 =
|
||||
|
||||
* better json encoding
|
||||
|
||||
= 0.3.0 =
|
||||
|
||||
* basic hashtag support
|
||||
* temporarily deactivated likes and boosts
|
||||
* added support for actor objects
|
||||
* fixed encoding issue
|
||||
|
||||
= 0.2.1 =
|
||||
|
||||
* customizable backlink (permalink or shorturl)
|
||||
* show profile-identifiers also on profile settings
|
||||
|
||||
= 0.2.0 =
|
||||
|
||||
* added option to switch between content and excerpt
|
||||
* removed html and duplicate new-lines
|
||||
|
||||
= 0.1.1 =
|
||||
|
||||
* fixed "excerpt" in AS JSON
|
||||
* added settings for the activity-summary and for the activity-object-type
|
||||
|
||||
= 0.1.0 =
|
||||
|
||||
* added basic WebFinger support
|
||||
* added basic NodeInfo support
|
||||
* fully functional "follow" activity
|
||||
* send new posts to your followers
|
||||
* receive comments from your followers
|
||||
|
||||
= 0.0.2 =
|
||||
|
||||
* refactoring
|
||||
* functional inbox
|
||||
* nicer profile views
|
||||
|
||||
= 0.0.1 =
|
||||
|
||||
* initial
|
||||
|
||||
== Installation ==
|
||||
|
||||
Follow the normal instructions for [installing WordPress plugins](https://wordpress.org/support/article/managing-plugins/).
|
||||
|
||||
= Automatic Plugin Installation =
|
||||
|
||||
To add a WordPress Plugin using the [built-in plugin installer](https://codex.wordpress.org/Administration_Screens#Add_New_Plugins):
|
||||
|
||||
1. Go to [Plugins](https://codex.wordpress.org/Administration_Screens#Plugins) > [Add New](https://codex.wordpress.org/Plugins_Add_New_Screen).
|
||||
1. Type "`activitypub`" into the **Search Plugins** box.
|
||||
1. Find the WordPress Plugin you wish to install.
|
||||
1. Click **Details** for more information about the Plugin and instructions you may wish to print or save to help setup the Plugin.
|
||||
1. Click **Install Now** to install the WordPress Plugin.
|
||||
1. The resulting installation screen will list the installation as successful or note any problems during the install.
|
||||
1. If successful, click **Activate Plugin** to activate it, or **Return to Plugin Installer** for further actions.
|
||||
|
||||
= Manual Plugin Installation =
|
||||
|
||||
There are a few cases when manually installing a WordPress Plugin is appropriate.
|
||||
|
||||
* If you wish to control the placement and the process of installing a WordPress Plugin.
|
||||
* If your server does not permit automatic installation of a WordPress Plugin.
|
||||
* If you want to try the [latest development version](https://github.com/pfefferle/wordpress-activitypub).
|
||||
|
||||
Installation of a WordPress Plugin manually requires FTP familiarity and the awareness that you may put your site at risk if you install a WordPress Plugin incompatible with the current version or from an unreliable source.
|
||||
|
||||
Backup your site completely before proceeding.
|
||||
|
||||
To install a WordPress Plugin manually:
|
||||
|
||||
* Download your WordPress Plugin to your desktop.
|
||||
* Download from [the WordPress directory](https://wordpress.org/plugins/activitypub/)
|
||||
* Download from [GitHub](https://github.com/pfefferle/wordpress-activitypub/releases)
|
||||
* If downloaded as a zip archive, extract the Plugin folder to your desktop.
|
||||
* With your FTP program, upload the Plugin folder to the `wp-content/plugins` folder in your WordPress directory online.
|
||||
* Go to [Plugins screen](https://codex.wordpress.org/Administration_Screens#Plugins) and find the newly uploaded Plugin in the list.
|
||||
* Click **Activate** to activate it.
|
15
wp-content/plugins/activitypub/templates/followers-list.php
Normal file
15
wp-content/plugins/activitypub/templates/followers-list.php
Normal file
@ -0,0 +1,15 @@
|
||||
<div class="wrap">
|
||||
<h1><?php \esc_html_e( 'Followers (Fediverse)', 'activitypub' ); ?></h1>
|
||||
|
||||
<p><?php \printf( \__( 'You currently have %s followers.', 'activitypub' ), \esc_attr( \Activitypub\Peer\Followers::count_followers( \get_current_user_id() ) ) ); ?></p>
|
||||
|
||||
<?php $token_table = new \Activitypub\Table\Followers_List(); ?>
|
||||
|
||||
<form method="get">
|
||||
<input type="hidden" name="page" value="indieauth_user_token" />
|
||||
<?php
|
||||
$token_table->prepare_items();
|
||||
$token_table->display();
|
||||
?>
|
||||
</form>
|
||||
</div>
|
113
wp-content/plugins/activitypub/templates/json-author.php
Normal file
113
wp-content/plugins/activitypub/templates/json-author.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
$author_id = \get_the_author_meta( 'ID' );
|
||||
|
||||
$json = new \stdClass();
|
||||
|
||||
$json->{'@context'} = \Activitypub\get_context();
|
||||
$json->id = \get_author_posts_url( $author_id );
|
||||
$json->type = 'Person';
|
||||
$json->name = \get_the_author_meta( 'display_name', $author_id );
|
||||
$json->summary = \html_entity_decode(
|
||||
\get_the_author_meta( 'description', $author_id ),
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
);
|
||||
$json->preferredUsername = \get_the_author_meta( 'login', $author_id ); // phpcs:ignore
|
||||
$json->url = \get_author_posts_url( $author_id );
|
||||
$json->icon = array(
|
||||
'type' => 'Image',
|
||||
'url' => \get_avatar_url( $author_id, array( 'size' => 120 ) ),
|
||||
);
|
||||
|
||||
if ( \has_header_image() ) {
|
||||
$json->image = array(
|
||||
'type' => 'Image',
|
||||
'url' => \get_header_image(),
|
||||
);
|
||||
}
|
||||
|
||||
$json->inbox = \get_rest_url( null, "/activitypub/1.0/users/$author_id/inbox" );
|
||||
$json->outbox = \get_rest_url( null, "/activitypub/1.0/users/$author_id/outbox" );
|
||||
$json->followers = \get_rest_url( null, "/activitypub/1.0/users/$author_id/followers" );
|
||||
$json->following = \get_rest_url( null, "/activitypub/1.0/users/$author_id/following" );
|
||||
|
||||
$json->manuallyApprovesFollowers = \apply_filters( 'activitypub_json_manually_approves_followers', __return_false() ); // phpcs:ignore
|
||||
|
||||
// phpcs:ignore
|
||||
$json->publicKey = array(
|
||||
'id' => \get_author_posts_url( $author_id ) . '#main-key',
|
||||
'owner' => \get_author_posts_url( $author_id ),
|
||||
'publicKeyPem' => \trim( \Activitypub\Signature::get_public_key( $author_id ) ),
|
||||
);
|
||||
|
||||
$json->tag = array();
|
||||
$json->attachment = array();
|
||||
|
||||
$json->attachment[] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => __( 'Blog', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \home_url( '/' ) ) . '" target="_blank" href="' . \home_url( '/' ) . '">' . \wp_parse_url( \home_url( '/' ), PHP_URL_HOST ) . '</a>',
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
|
||||
$json->attachment[] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => __( 'Profile', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \get_author_posts_url( $author_id ) ) . '" target="_blank" href="' . \get_author_posts_url( $author_id ) . '">' . \wp_parse_url( \get_author_posts_url( $author_id ), PHP_URL_HOST ) . '</a>',
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
|
||||
if ( \get_the_author_meta( 'user_url', $author_id ) ) {
|
||||
$json->attachment[] = array(
|
||||
'type' => 'PropertyValue',
|
||||
'name' => __( 'Website', 'activitypub' ),
|
||||
'value' => \html_entity_decode(
|
||||
'<a rel="me" title="' . \esc_attr( \get_the_author_meta( 'user_url', $author_id ) ) . '" target="_blank" href="' . \get_the_author_meta( 'user_url', $author_id ) . '">' . \wp_parse_url( \get_the_author_meta( 'user_url', $author_id ), PHP_URL_HOST ) . '</a>',
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
$json->endpoints = array(
|
||||
'sharedInbox' => \get_rest_url( null, '/activitypub/1.0/inbox' ),
|
||||
);
|
||||
*/
|
||||
|
||||
// filter output
|
||||
$json = \apply_filters( 'activitypub_json_author_array', $json );
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_json_author_pre' );
|
||||
|
||||
$options = 0;
|
||||
// JSON_PRETTY_PRINT added in PHP 5.4
|
||||
if ( \get_query_var( 'pretty' ) ) {
|
||||
$options |= JSON_PRETTY_PRINT; // phpcs:ignore
|
||||
}
|
||||
|
||||
$options |= JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT;
|
||||
|
||||
/*
|
||||
* Options to be passed to json_encode()
|
||||
*
|
||||
* @param int $options The current options flags
|
||||
*/
|
||||
$options = \apply_filters( 'activitypub_json_author_options', $options );
|
||||
|
||||
\header( 'Content-Type: application/activity+json' );
|
||||
echo \wp_json_encode( $json, $options );
|
||||
|
||||
/*
|
||||
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_json_author_post' );
|
36
wp-content/plugins/activitypub/templates/json-post.php
Normal file
36
wp-content/plugins/activitypub/templates/json-post.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
$post = \get_post();
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$json = \array_merge( array( '@context' => \Activitypub\get_context() ), $activitypub_post->to_array() );
|
||||
|
||||
// filter output
|
||||
$json = \apply_filters( 'activitypub_json_post_array', $json );
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_json_post_pre' );
|
||||
|
||||
$options = 0;
|
||||
// JSON_PRETTY_PRINT added in PHP 5.4
|
||||
if ( \get_query_var( 'pretty' ) ) {
|
||||
$options |= JSON_PRETTY_PRINT; // phpcs:ignore
|
||||
}
|
||||
|
||||
$options |= JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT;
|
||||
|
||||
/*
|
||||
* Options to be passed to json_encode()
|
||||
*
|
||||
* @param int $options The current options flags
|
||||
*/
|
||||
$options = \apply_filters( 'activitypub_json_post_options', $options );
|
||||
|
||||
\header( 'Content-Type: application/activity+json' );
|
||||
echo \wp_json_encode( $json, $options );
|
||||
|
||||
/*
|
||||
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
||||
*/
|
||||
\do_action( 'activitypub_json_post_post' );
|
126
wp-content/plugins/activitypub/templates/settings.php
Normal file
126
wp-content/plugins/activitypub/templates/settings.php
Normal file
@ -0,0 +1,126 @@
|
||||
<div class="wrap">
|
||||
<h1><?php \esc_html_e( 'ActivityPub Settings', 'activitypub' ); ?></h1>
|
||||
|
||||
<p><?php \esc_html_e( 'ActivityPub turns your blog into a federated social network. This means you can share and talk to everyone using the ActivityPub protocol, including users of Friendica, Pleroma and Mastodon.', 'activitypub' ); ?></p>
|
||||
|
||||
<form method="post" action="options.php">
|
||||
<?php \settings_fields( 'activitypub' ); ?>
|
||||
|
||||
<h2><?php \esc_html_e( 'Activities', 'activitypub' ); ?></h2>
|
||||
|
||||
<p><?php \esc_html_e( 'All activity related settings.', 'activitypub' ); ?></p>
|
||||
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e( 'Post-Content', 'activitypub' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_title_link" value="title" <?php echo \checked( 'title', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Title and link', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'Only the title and a link.', 'activitypub' ); ?></span>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_excerpt" value="excerpt" <?php echo \checked( 'excerpt', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Excerpt', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'A content summary, shortened to 400 characters and without markup.', 'activitypub' ); ?></span>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_content" value="content" <?php echo \checked( 'content', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Content (default)', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'The full content.', 'activitypub' ); ?></span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php \esc_html_e( 'Backlink', 'activitypub' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<p><label><input type="checkbox" name="activitypub_use_shortlink" id="activitypub_use_shortlink" value="1" <?php echo \checked( '1', \get_option( 'activitypub_use_shortlink', '0' ) ); ?> /> <?php \esc_html_e( 'Use the Shortlink instead of the permalink', 'activitypub' ); ?></label></p>
|
||||
<p class="description"><?php \printf( esc_html( 'I can recommend %sHum%s, to prettify the Shortlinks', 'activitypub' ), '<a href="https://wordpress.org/plugins/hum/" target="_blank">', '</a>' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php \esc_html_e( 'Activity-Object-Type', 'activitypub' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_object_type" id="activitypub_object_type_note" value="note" <?php echo \checked( 'note', \get_option( 'activitypub_object_type', 'note' ) ); ?> /> <?php \esc_html_e( 'Note (default)', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'Should work with most platforms.', 'activitypub' ); ?></span>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_object_type" id="activitypub_object_type_article" value="article" <?php echo \checked( 'article', \get_option( 'activitypub_object_type', 'note' ) ); ?> /> <?php \esc_html_e( 'Article', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'The presentation of the "Article" might change on different platforms. Mastodon for example shows the "Article" type as a simple link.', 'activitypub' ); ?></span>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="radio" name="activitypub_object_type" id="activitypub_object_type" value="wordpress-post-format" <?php echo \checked( 'wordpress-post-format', \get_option( 'activitypub_object_type', 'note' ) ); ?> /> <?php \esc_html_e( 'WordPress Post-Format', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'Maps the WordPress Post-Format to the ActivityPub Object Type.', 'activitypub' ); ?></span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><?php \esc_html_e( 'Supported post types', 'activitypub' ); ?></th>
|
||||
<td>
|
||||
<fieldset>
|
||||
<?php \esc_html_e( 'Enable ActivityPub support for the following post types:', 'activitypub' ); ?>
|
||||
|
||||
<?php $post_types = \get_post_types( array( 'public' => true ), 'objects' ); ?>
|
||||
<?php $support_post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) : array(); ?>
|
||||
<ul>
|
||||
<?php foreach ( $post_types as $post_type ) { ?>
|
||||
<li>
|
||||
<input type="checkbox" id="activitypub_support_post_types" name="activitypub_support_post_types[]" value="<?php echo \esc_attr( $post_type->name ); ?>" <?php echo \checked( true, \in_array( $post_type->name, $support_post_types, true ) ); ?> />
|
||||
<label for="<?php echo \esc_attr( $post_type->name ); ?>"><?php echo \esc_html( $post_type->label ); ?></label>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php \esc_html_e( 'Hashtags', 'activitypub' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<p>
|
||||
<label><input type="checkbox" name="activitypub_use_hashtags" id="activitypub_use_hashtags" value="1" <?php echo \checked( '1', \get_option( 'activitypub_use_hashtags', '1' ) ); ?> /> <?php \_e( 'Add hashtags in the content as native tags and replace the <code>#tag</code> with the tag-link.', 'activitypub' ); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="activitypub_add_tags_as_hashtags" id="activitypub_add_tags_as_hashtags" value="1" <?php echo \checked( '1', \get_option( 'activitypub_add_tags_as_hashtags', '0' ) ); ?> /> <?php \_e( 'Add all tags as hashtags to the end of each activity.', 'activitypub' ); ?></label>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php \do_settings_fields( 'activitypub', 'activity' ); ?>
|
||||
|
||||
<h2><?php \esc_html_e( 'Server', 'activitypub' ); ?></h2>
|
||||
|
||||
<p><?php \esc_html_e( 'Server related settings.', 'activitypub' ); ?></p>
|
||||
|
||||
<?php
|
||||
// load the existing blacklist from the WordPress options table
|
||||
$activitypub_blacklist = \trim( \implode( PHP_EOL, \ActivityPub\get_blacklist() ), PHP_EOL );
|
||||
?>
|
||||
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php \esc_html_e( 'Blacklist', 'activitypub' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<textarea name="activitypub_blacklist" id="activitypub_blacklist" rows="10" cols="50" class="large-text"><?php echo $activitypub_blacklist; ?></textarea>
|
||||
<p class="description"><?php \_e( 'A list of hosts, you want to block, one host per line. Please use only the host/domain of the server you want to block, without <code>http://</code> and without <code>www.</code>. For example <code>example.com</code>.', 'activitypub' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php \do_settings_fields( 'activitypub', 'server' ); ?>
|
||||
|
||||
<?php \do_settings_sections( 'activitypub' ); ?>
|
||||
|
||||
<?php \submit_button(); ?>
|
||||
</form>
|
||||
|
||||
<p>
|
||||
<small><?php _e( 'If you like this plugin, what about a small <a href="https://notiz.blog/donate">donation</a>?', 'activitypub' ); ?></small>
|
||||
</p>
|
||||
</div>
|
Reference in New Issue
Block a user