Skip to content

Instantly share code, notes, and snippets.

@obenland
Created July 24, 2025 21:25
Show Gist options
  • Select an option

  • Save obenland/58f04709da1b60dfe56bfa192b929f50 to your computer and use it in GitHub Desktop.

Select an option

Save obenland/58f04709da1b60dfe56bfa192b929f50 to your computer and use it in GitHub Desktop.
<?php
/**
* Plugin Name: ActivityPub Comment Debug Logger
* Description: Debug logger for ActivityPub comment creation flow from Mastodon to WordPress
* Version: 1.0.0
*
* @package activitypub
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class ActivityPub_Comment_Debug_Logger
*/
class ActivityPub_Comment_Debug_Logger {
private static $instance = null;
private $log_prefix = '[AP-DEBUG]';
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_hooks();
}
/**
* Initialize all debug hooks
*/
private function init_hooks() {
// Only hook into Create activities and comment creation
add_action( 'activitypub_inbox_create', array( $this, 'log_create_activity' ), 1, 3 );
add_action( 'wp_insert_comment', array( $this, 'log_comment_insertion' ), 1, 2 );
add_action( 'comment_post', array( $this, 'log_comment_created' ), 1, 3 );
// Hook into ActivityPub comment meta to confirm it's an AP comment
add_action( 'add_comment_meta', array( $this, 'log_comment_meta' ), 1, 3 );
}
/**
* Log Create activity and check for potential failures
*/
public function log_create_activity( $user, $activity, $request ) {
// Only log if this is a reply/comment
if ( ! isset( $activity['object']['inReplyTo'] ) || empty( $activity['object']['inReplyTo'] ) ) {
return; // Not a comment, skip logging
}
$this->log( '=== COMMENT CREATE ATTEMPT ===' );
// Check for duplicate first
if ( function_exists( 'Activitypub\object_id_to_comment' ) ) {
$existing_comment = \Activitypub\object_id_to_comment( $activity['object']['id'] );
if ( $existing_comment ) {
$this->log( 'STOPPED: Duplicate comment exists (ID: ' . $existing_comment->comment_ID . ')' );
return;
}
}
// Check if parent post exists
$post_id = url_to_postid( $activity['object']['inReplyTo'] );
if ( ! $post_id ) {
$this->log( 'STOPPED: Parent post not found for: ' . $activity['object']['inReplyTo'] );
return;
}
// Check if user is valid
if ( ! is_object( $user ) || ! isset( $user->ID ) ) {
$this->log( 'STOPPED: Invalid user object' );
return;
}
// Check for spam/disallowed content
if ( function_exists( 'wp_check_comment_disallowed_list' ) ) {
$author = $activity['actor'] ?? '';
$content = isset( $activity['object']['content'] ) ? strip_tags( $activity['object']['content'] ) : '';
if ( wp_check_comment_disallowed_list( $author, '', $content, '', '', '' ) ) {
$this->log( 'STOPPED: Content flagged as spam/disallowed' );
return;
}
}
$this->log( 'PROCEEDING: All checks passed, attempting comment creation' );
}
/**
* Log comment insertion result
*/
public function log_comment_insertion( $comment_id, $comment ) {
if ( $comment_id && ! is_wp_error( $comment_id ) ) {
$this->log( 'SUCCESS: Comment inserted with ID ' . $comment_id );
} else {
$this->log( 'FAILED: Comment insertion failed - ' . ( is_wp_error( $comment_id ) ? $comment_id->get_error_message() : 'unknown error' ) );
}
}
/**
* Log comment creation completion
*/
public function log_comment_created( $comment_id, $approved, $commentdata ) {
$status = ( $approved === 1 ) ? 'approved' : ( $approved === 0 ? 'pending' : 'other' );
$this->log( 'COMPLETED: Comment ' . $comment_id . ' created (' . $status . ')' );
}
/**
* Log ActivityPub metadata - confirms it's an AP comment
*/
public function log_comment_meta( $comment_id, $meta_key, $meta_value ) {
if ( $meta_key === 'protocol' && $meta_value === 'activitypub' ) {
$this->log( 'CONFIRMED: Comment ' . $comment_id . ' is ActivityPub comment' );
}
}
/**
* Main logging function
*/
private function log( $message ) {
$timestamp = current_time( 'Y-m-d H:i:s' );
$formatted_message = sprintf(
'%s %s [%s] %s',
$timestamp,
$this->log_prefix,
$this->get_caller_info(),
$message
);
error_log( $formatted_message );
// Also log to a specific file if WP_DEBUG_LOG is enabled.
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
$log_file = WP_CONTENT_DIR . '/activitypub-debug.log';
error_log( $formatted_message . PHP_EOL, 3, $log_file );
}
}
/**
* Get caller information for better debugging
*/
private function get_caller_info() {
$trace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 4 );
// Find the first caller that's not this class.
for ( $i = 1; $i < count( $trace ); $i++ ) {
if ( isset( $trace[ $i ]['class'] ) && __CLASS__ !== $trace[ $i ]['class'] ) {
$class = $trace[ $i ]['class'];
$function = $trace[ $i ]['function'] ?? '';
return $class . '::' . $function;
} elseif ( ! isset( $trace[ $i ]['class'] ) ) {
$function = $trace[ $i ]['function'] ?? '';
$file = isset( $trace[ $i ]['file'] ) ? basename( $trace[ $i ]['file'] ) : '';
return $file . '::' . $function;
}
}
return 'unknown';
}
}
// Initialize the debug logger only for ActivityPub inbox POST requests.
add_action(
'rest_api_init',
function () {
// Only run on POST requests to ActivityPub inbox endpoints.
if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
$request_uri = $_SERVER['REQUEST_URI'] ?? '';
if ( strpos( $request_uri, '/inbox' ) !== false &&
( strpos( $request_uri, '/wp-json/activitypub/' ) !== false || strpos( $request_uri, '/activitypub/' ) !== false ) ) {
ActivityPub_Comment_Debug_Logger::get_instance();
}
}
},
5
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment