Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix outbox not using object types other than Base_Object #1268

Open
wants to merge 25 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bc5f587
Fix outbox not using object types other than Base_Object
Menrath Feb 4, 2025
41e9481
Merge branch 'trunk' into trunk
pfefferle Feb 5, 2025
7a1fa87
Merge branch 'trunk' into trunk
Menrath Feb 5, 2025
700272a
Update Following endpoint to extend WP_REST_Controller (#1267)
obenland Feb 5, 2025
6155f4b
Change usage of fediverse symbol (#1239)
pfefferle Feb 5, 2025
1e35c38
Update Followers endpoint to extend WP_REST_Controller (#1266)
obenland Feb 5, 2025
d0ea2ef
Add: Show ActivityPub preview in row actions, when block editor enabl…
Menrath Feb 5, 2025
7ee2180
ActivityPub Query: Improve third party plugin support (#1272)
pfefferle Feb 5, 2025
cda0a7f
Add assertions for php unit test of Oubox:add
Menrath Feb 6, 2025
db491a2
Merge branch 'trunk' into trunk
Menrath Feb 6, 2025
eecf908
Add context to test object
Menrath Feb 6, 2025
aac315c
Fix test data
Menrath Feb 6, 2025
43a1d24
Use assertJsonStringEqualsJsonString for comparing two JSON strings.
Menrath Feb 6, 2025
9bb9ce1
Merge branch 'main' into trunk
Menrath Feb 6, 2025
b27d8c5
Fix NodeInfo rel (#1275)
pfefferle Feb 6, 2025
9667147
Release 5.1.0 (#1276)
pfefferle Feb 6, 2025
57f3d30
No need to set default object class in post meta.
Menrath Feb 6, 2025
4ae2456
Fix Changelog.
Menrath Feb 6, 2025
6184421
Merge branch 'trunk' into trunk
pfefferle Feb 6, 2025
2d16eb5
Merge branch 'trunk' into trunk
pfefferle Feb 13, 2025
279f556
Use an Arbitrary_Object as default
pfefferle Feb 13, 2025
efaaac7
remove post-meta
pfefferle Feb 13, 2025
ef2e515
remove whitespace
pfefferle Feb 13, 2025
e4a0a9f
Fix PHPCS
pfefferle Feb 13, 2025
325f958
Merge branch 'trunk' into trunk
pfefferle Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Outbox items older than 6 months will be purged to avoid performance issues.
* REST API endpoints for likes and shares.

### Fixed

* Fixed an issue where the outbox could not send object types other than `Base_Object` (introduced in 5.0.0).

### Changed

* Increased probability of Outbox items being processed with the correct author.
Expand Down
9 changes: 1 addition & 8 deletions includes/activity/class-activity.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,7 @@ class Activity extends Base_Object {
public function set_object( $data ) {
// Convert array to object.
if ( is_array( $data ) ) {
// Check if the item is an Activity or an Object.
if ( is_activity( $data ) ) {
$data = self::init_from_array( $data );
} elseif ( is_actor( $data ) ) {
$data = Actor::init_from_array( $data );
} else {
$data = Base_Object::init_from_array( $data );
}
$data = Arbitrary_Object::init_from_array( $data );
}

// Set object.
Expand Down
321 changes: 321 additions & 0 deletions includes/activity/class-arbitrary-object.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
<?php
/**
* Arbitrary Object.
*
* @package Activitypub
*/

namespace Activitypub\Activity;

use function Activitypub\camel_to_snake_case;
use function Activitypub\snake_to_camel_case;

/**
* Arbitrary Object.
*
* This class is used to create arbitrary objects.
* It is used to create objects that might be unknown by the plugin but
* conform to the ActivityStreams vocabulary.
*
* @since Unreleased
*/
#[AllowDynamicProperties]
class Arbitrary_Object {
/**
* The JSON-LD context for the object.
*
* @var array
*/
const JSON_LD_CONTEXT = array(
'https://www.w3.org/ns/activitystreams',
);

/**
* The object's unique global identifier
*
* @see https://www.w3.org/TR/activitypub/#obj-id
*
* @var string
*/
protected $id;

/**
* Magic function, to transform the object to string.
*
* @return string The object id.
*/
public function __toString() {
return $this->to_string();
}

/**
* Function to transform the object to string.
*
* @return string The object id.
*/
public function to_string() {
return $this->get_id();
}

/**
* Magic function to implement getter and setter.
*
* @param string $method The method name.
* @param string $params The method params.
*/
public function __call( $method, $params ) {
$var = \strtolower( \substr( $method, 4 ) );

if ( \strncasecmp( $method, 'get', 3 ) === 0 ) {
if ( ! $this->has( $var ) ) {
return null;
}

return $this->$var;
}

if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
return $this->set( $var, $params[0] );
}

if ( \strncasecmp( $method, 'add', 3 ) === 0 ) {
return $this->add( $var, $params[0] );
}
}

/**
* Generic getter.
*
* @param string $key The key to get.
*
* @return mixed The value.
*/
public function get( $key ) {
return call_user_func( array( $this, 'get_' . $key ) );
}

/**
* Generic setter.
*
* @param string $key The key to set.
* @param string $value The value to set.
*
* @return mixed The value.
*/
public function set( $key, $value ) {
$this->$key = $value;

return $this;
}

/**
* Generic adder.
*
* @param string $key The key to set.
* @param mixed $value The value to add.
*
* @return mixed The value.
*/
public function add( $key, $value ) {
if ( ! isset( $this->$key ) ) {
$this->$key = array();
}

if ( is_string( $this->$key ) ) {
$this->$key = array( $this->$key );
}

$attributes = $this->$key;

if ( is_array( $value ) ) {
$attributes = array_merge( $attributes, $value );
} else {
$attributes[] = $value;
}

$this->$key = array_unique( $attributes );

return $this->$key;
}

/**
* Check if the object has a key
*
* @param string $key The key to check.
*
* @return boolean True if the object has the key.
*/
public function has( $key ) {
return property_exists( $this, $key );
}

/**
* Convert JSON input to an array.
*
* @param string $json The JSON string.
*
* @return Base_Object|\WP_Error An Object built from the JSON string or WP_Error when it's not a JSON string.
*/
public static function init_from_json( $json ) {
$array = \json_decode( $json, true );

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

return self::init_from_array( $array );
}

/**
* Convert input array to a Base_Object.
*
* @param array $data The object array.
*
* @return Base_Object|\WP_Error An Object built from the input array or WP_Error when it's not an array.
*/
public static function init_from_array( $data ) {
if ( ! is_array( $data ) ) {
return new \WP_Error( 'invalid_array', __( 'Invalid array', 'activitypub' ), array( 'status' => 400 ) );
}

$object = new static();
$object->from_array( $data );

return $object;
}

/**
* Convert JSON input to an array and pre-fill the object.
*
* @param array $data The array.
*/
public function from_array( $data ) {
foreach ( $data as $key => $value ) {
if ( $value ) {
$key = camel_to_snake_case( $key );
call_user_func( array( $this, 'set_' . $key ), $value );
}
}
}

/**
* Convert JSON input to an array and pre-fill the object.
*
* @param string $json The JSON string.
*/
public function from_json( $json ) {
$array = \json_decode( $json, true );

$this->from_array( $array );
}

/**
* Convert Object to an array.
*
* It tries to get the object attributes if they exist
* and falls back to the getters. Empty values are ignored.
*
* @param bool $include_json_ld_context Whether to include the JSON-LD context. Default true.
*
* @return array An array built from the Object.
*/
public function to_array( $include_json_ld_context = true ) {
$array = array();
$vars = get_object_vars( $this );

foreach ( $vars as $key => $value ) {
if ( \is_wp_error( $value ) ) {
continue;
}

// Ignore all _prefixed keys.
if ( '_' === substr( $key, 0, 1 ) ) {
continue;
}

// If value is empty, try to get it from a getter.
if ( ! $value ) {
$value = call_user_func( array( $this, 'get_' . $key ) );
}

if ( is_object( $value ) ) {
$value = $value->to_array( false );
}

// If value is still empty, ignore it for the array and continue.
if ( isset( $value ) ) {
$array[ snake_to_camel_case( $key ) ] = $value;
}
}

if ( $include_json_ld_context ) {
// Get JsonLD context and move it to '@context' at the top.
$array = array_merge( array( '@context' => $this->get_json_ld_context() ), $array );
}

$class = new \ReflectionClass( $this );
$class = strtolower( $class->getShortName() );

/**
* Filter the array of the ActivityPub object.
*
* @param array $array The array of the ActivityPub object.
* @param string $class The class of the ActivityPub object.
* @param int $id The ID of the ActivityPub object.
* @param Base_Object $object The ActivityPub object.
*
* @return array The filtered array of the ActivityPub object.
*/
$array = \apply_filters( 'activitypub_activity_object_array', $array, $class, $this->id, $this );

/**
* Filter the array of the ActivityPub object by class.
*
* @param array $array The array of the ActivityPub object.
* @param int $id The ID of the ActivityPub object.
* @param Base_Object $object The ActivityPub object.
*
* @return array The filtered array of the ActivityPub object.
*/
return \apply_filters( "activitypub_activity_{$class}_object_array", $array, $this->id, $this );
}

/**
* Convert Object to JSON.
*
* @param bool $include_json_ld_context Whether to include the JSON-LD context. Default true.
*
* @return string The JSON string.
*/
public function to_json( $include_json_ld_context = true ) {
$array = $this->to_array( $include_json_ld_context );
$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_encode_options', $options );

return \wp_json_encode( $array, $options );
}

/**
* Returns the keys of the object vars.
*
* @return array The keys of the object vars.
*/
public function get_object_var_keys() {
return \array_keys( \get_object_vars( $this ) );
}

/**
* Returns the JSON-LD context of this object.
*
* @return array $context A compacted JSON-LD context for the ActivityPub object.
*/
public function get_json_ld_context() {
return static::JSON_LD_CONTEXT;
}
}
Loading
Loading