PluginProbe
ActivityPub / 3.2.5
ActivityPub v3.2.5
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
← All changes | includes/transformer/class-base.php +37 -647 9.2.03.2.5 View file →
@@ -1,32 +1,19 @@
1 1 <?php
2 -/**
3 - * Base Transformer Class file.
4 - *
5 - * @package Activitypub
6 - */
2 +namespace Activitypub\Transformer;
7 3
8 -namespace Activitypub\Transformer;
4 +use WP_Error;
5 +use WP_Post;
6 +use WP_Comment;
9 7
10 8 use Activitypub\Activity\Activity;
11 9 use Activitypub\Activity\Base_Object;
12 -use Activitypub\Collection\Actors;
13 -use Activitypub\Http;
14 10
15 -use function Activitypub\get_upload_baseurl;
16 -use function Activitypub\object_to_uri;
17 -
18 11 /**
19 - * WordPress Base Transformer.
12 + * WordPress Base Transformer
20 13 *
21 - * Transformers are responsible for transforming WordPress objects into different ActivityPub
14 + * Transformers are responsible for transforming a WordPress objects into different ActivityPub
22 15 * Object-Types or Activities.
23 - *
24 - * @method string|null get_content() Returns the content for the transformed item.
25 - * @method string|array|null get_icon() Returns an icon for the transformed item.
26 - * @method string|null get_id() Returns the ID for the transformed item.
27 - * @method string|null get_name() Returns the name for the transformed item.
28 - * @method string|null get_summary() Returns the summary for the transformed item.
29 16 */
30 17 abstract class Base {
31 18 /**
32 19 * The WP_Post or WP_Comment object.
@@ -32,219 +19,78 @@
32 19 * The WP_Post or WP_Comment object.
33 20 *
34 21 * This is the source object of the transformer.
35 22 *
36 - * @var \WP_Post|\WP_Comment|Base_Object|string|array|\WP_Term
23 + * @var WP_Post|WP_Comment
37 24 */
38 - protected $item;
39 -
40 - /**
41 - * The WP_Post or WP_Comment object.
42 - *
43 - * @var \WP_Post|\WP_Comment
44 - */
45 25 protected $wp_object;
46 26
47 27 /**
48 - * The content visibility.
49 - *
50 - * @var string
51 - */
52 - protected $content_visibility;
53 -
54 - /**
55 28 * Static function to Transform a WordPress Object.
56 29 *
57 30 * This helps to chain the output of the Transformer.
58 31 *
59 - * @param \WP_Post|\WP_Comment|Base_Object|string|array|\WP_term $item The item that should be transformed.
32 + * @param WP_Post|WP_Comment $wp_object The WordPress object
60 33 *
61 34 * @return Base
62 35 */
63 - public static function transform( $item ) {
64 - return new static( $item );
36 + public static function transform( $object ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.objectFound
37 + return new static( $object );
65 38 }
66 39
67 40 /**
68 41 * Base constructor.
69 42 *
70 - * @param \WP_Post|\WP_Comment|Base_Object|string|array|\WP_Term $item The item that should be transformed.
43 + * @param WP_Post|WP_Comment $wp_object The WordPress object
71 44 */
72 - public function __construct( $item ) {
73 - $this->item = $item;
74 - $this->wp_object = $item;
45 + public function __construct( $wp_object ) {
46 + $this->wp_object = $wp_object;
75 47 }
76 48
77 49 /**
78 50 * Transform all properties with available get(ter) functions.
79 51 *
80 - * @param Base_Object $activity_object The ActivityPub Object.
52 + * @param Base_Object|object $object
81 53 *
82 - * @return Base_Object|\WP_Error The transformed ActivityPub Object or WP_Error on failure.
54 + * @return Base_Object|object $object
83 55 */
84 - protected function transform_object_properties( $activity_object ) {
85 - if ( ! $activity_object || \is_wp_error( $activity_object ) ) {
86 - return $activity_object;
87 - }
56 + protected function transform_object_properties( $activitypub_object ) {
57 + $vars = $activitypub_object->get_object_var_keys();
88 58
89 - // Save activity in the context of an activitypub request.
90 - \add_filter( 'activitypub_is_activitypub_request', '__return_true' );
91 -
92 - $vars = $activity_object->get_object_var_keys();
93 -
94 59 foreach ( $vars as $var ) {
95 60 $getter = 'get_' . $var;
96 61
97 - if ( \method_exists( $this, $getter ) ) {
98 - $value = \call_user_func( array( $this, $getter ) );
62 + if ( method_exists( $this, $getter ) ) {
63 + $value = call_user_func( array( $this, $getter ) );
99 64
100 - if ( null !== $value ) {
65 + if ( isset( $value ) ) {
101 66 $setter = 'set_' . $var;
102 67
103 - /**
104 - * Filter the value before it is set to the Activity-Object `$activity_object`.
105 - *
106 - * @param mixed $value The value that should be set.
107 - * @param mixed $item The Object.
108 - */
109 - $value = \apply_filters( "activitypub_transform_{$setter}", $value, $this->item );
110 -
111 - /**
112 - * Filter the value before it is set to the Activity-Object `$activity_object`.
113 - *
114 - * @param mixed $value The value that should be set.
115 - * @param string $var The variable name.
116 - * @param mixed $item The Object.
117 - */
118 - $value = \apply_filters( 'activitypub_transform_set', $value, $var, $this->item );
119 -
120 - \call_user_func( array( $activity_object, $setter ), $value );
68 + call_user_func( array( $activitypub_object, $setter ), $value );
121 69 }
122 70 }
123 71 }
124 -
125 - // Remove activity in the context of an activitypub request.
126 - \remove_filter( 'activitypub_is_activitypub_request', '__return_true' );
127 -
128 - return $activity_object;
72 + return $activitypub_object;
129 73 }
130 74
131 75 /**
132 - * Transform the item into an ActivityPub Object.
76 + * Transform the WordPress Object into an ActivityPub Object.
133 77 *
134 - * @return Base_Object The Activity-Object.
78 + * @return Activitypub\Activity\Base_Object
135 79 */
136 80 public function to_object() {
137 - $activity_object = new Base_Object();
138 - $activity_object = $this->transform_object_properties( $activity_object );
81 + $activitypub_object = new Base_Object();
82 + $activitypub_object = $this->transform_object_properties( $activitypub_object );
139 83
140 - if ( \is_wp_error( $activity_object ) ) {
141 - return $activity_object;
142 - }
143 -
144 - return $this->set_audience( $activity_object );
84 + return $activitypub_object;
145 85 }
146 86
147 87 /**
148 - * Get the content visibility.
149 - *
150 - * @return string The content visibility.
151 - */
152 - public function get_content_visibility() {
153 - if ( ! $this->content_visibility ) {
154 - return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC;
155 - }
156 -
157 - return $this->content_visibility;
158 - }
159 -
160 - /**
161 - * Set the content visibility.
162 - *
163 - * @param string $content_visibility The content visibility.
164 - */
165 - public function set_content_visibility( $content_visibility ) {
166 - $this->content_visibility = $content_visibility;
167 -
168 - return $this;
169 - }
170 -
171 - /**
172 - * Set the audience.
173 - *
174 - * @param Base_Object $activity_object The ActivityPub Object.
175 - *
176 - * @return Base_Object The ActivityPub Object.
177 - */
178 - protected function set_audience( $activity_object ) {
179 - $public = 'https://www.w3.org/ns/activitystreams#Public';
180 - $followers = null;
181 - $replied_to = null;
182 -
183 - $actor = Actors::get_by_resource( $this->get_attributed_to() );
184 - if ( ! \is_wp_error( $actor ) ) {
185 - $followers = $actor->get_followers();
186 - }
187 -
188 - $mentions = \array_values( $this->get_mentions() );
189 -
190 - if ( $this->get_in_reply_to() ) {
191 - $object = Http::get_remote_object( $this->get_in_reply_to() );
192 - if ( $object && ! \is_wp_error( $object ) && isset( $object['attributedTo'] ) ) {
193 - $replied_to = array( object_to_uri( $object['attributedTo'] ) );
194 - }
195 - }
196 -
197 - switch ( $this->get_content_visibility() ) {
198 - case ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC:
199 - $activity_object->add_to( $public );
200 - $activity_object->add_cc( $followers );
201 - $activity_object->add_cc( $mentions );
202 - $activity_object->add_cc( $replied_to );
203 - break;
204 - case ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC:
205 - $activity_object->add_to( $followers );
206 - $activity_object->add_to( $mentions );
207 - $activity_object->add_to( $replied_to );
208 - $activity_object->add_cc( $public );
209 - break;
210 - case ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE:
211 - $activity_object->add_to( $mentions );
212 - $activity_object->add_to( $replied_to );
213 - }
214 -
215 - return $activity_object;
216 - }
217 -
218 - /**
219 - * Transform the item to an ActivityPub ID.
220 - *
221 - * @return string The ID of the WordPress Object.
222 - */
223 - public function to_id() {
224 - /* @var Attachment|Comment|Json|Post|User $this Object transformer. */
225 - return $this->get_id();
226 - }
227 -
228 - /**
229 - * Returns a Tombstone object for the item.
230 - *
231 - * @return Base_Object The Tombstone object.
232 - */
233 - public function to_tombstone() {
234 - $object = new Base_Object();
235 - $object->set_type( 'Tombstone' );
236 - $object->set_id( $this->to_id() );
237 -
238 - return $object;
239 - }
240 -
241 - /**
242 88 * Transforms the ActivityPub Object to an Activity
243 89 *
244 90 * @param string $type The Activity-Type.
245 91 *
246 - * @return Activity The Activity.
92 + * @return \Activitypub\Activity\Activity The Activity.
247 93 */
248 94 public function to_activity( $type ) {
249 95 $object = $this->to_object();
250 96
@@ -250,13 +96,13 @@
250 96
251 97 $activity = new Activity();
252 98 $activity->set_type( $type );
253 99
254 - // Pre-fill the Activity with data (for example, cc and to).
100 + // Pre-fill the Activity with data (for example cc and to).
255 101 $activity->set_object( $object );
256 102
257 - // Use simple Object (only ID-URI) for Like and Announce.
258 - if ( 'Like' === $type ) {
103 + // Use simple Object (only ID-URI) for Like and Announce
104 + if ( in_array( $type, array( 'Like', 'Announce' ), true ) ) {
259 105 $activity->set_object( $object->get_id() );
260 106 }
261 107
262 108 return $activity;
@@ -262,473 +108,17 @@
262 108 return $activity;
263 109 }
264 110
265 111 /**
266 - * Returns a generic locale based on the Blog settings.
112 + * Returns the ID of the WordPress Object.
267 113 *
268 - * @return string The locale of the blog.
114 + * @return int The ID of the WordPress Object
269 115 */
270 - protected function get_locale() {
271 - $lang = \strtolower( \strtok( \get_locale(), '_-' ) );
116 + abstract public function get_wp_user_id();
272 117
273 - /**
274 - * Filter the locale of the post.
275 - *
276 - * @param string $lang The locale of the post.
277 - * @param mixed $item The post object.
278 - *
279 - * @return string The filtered locale of the post.
280 - */
281 - return \apply_filters( 'activitypub_locale', $lang, $this->item );
282 - }
283 -
284 118 /**
285 - * Returns the default media type for an Object.
119 + * Change the User-ID of the WordPress Post.
286 120 *
287 - * @return string The media type.
121 + * @return int The User-ID of the WordPress Post
288 122 */
289 - public function get_media_type() {
290 - return 'text/html';
291 - }
292 -
293 - /**
294 - * Returns the content map for the post.
295 - *
296 - * @return array|null The content map for the post or null if not set.
297 - */
298 - protected function get_content_map() {
299 - if ( ! \method_exists( $this, 'get_content' ) || ! $this->get_content() ) {
300 - return null;
301 - }
302 -
303 - return array(
304 - $this->get_locale() => $this->get_content(),
305 - );
306 - }
307 -
308 - /**
309 - * Returns the name map for the post.
310 - *
311 - * @return array|null The name map for the post or null if not set.
312 - */
313 - protected function get_name_map() {
314 - if ( ! \method_exists( $this, 'get_name' ) || ! $this->get_name() ) {
315 - return null;
316 - }
317 -
318 - return array(
319 - $this->get_locale() => $this->get_name(),
320 - );
321 - }
322 -
323 - /**
324 - * Returns the summary map for the post.
325 - *
326 - * @return array|null The summary map for the post or null if not set.
327 - */
328 - protected function get_summary_map() {
329 - if ( ! \method_exists( $this, 'get_summary' ) || ! $this->get_summary() ) {
330 - return null;
331 - }
332 -
333 - return array(
334 - $this->get_locale() => $this->get_summary(),
335 - );
336 - }
337 -
338 - /**
339 - * Returns the tags for the post.
340 - *
341 - * @return array The tags for the post.
342 - */
343 - protected function get_tag() {
344 - $tags = array();
345 - $mentions = $this->get_mentions();
346 -
347 - foreach ( $mentions as $mention => $url ) {
348 - $tags[] = array(
349 - 'type' => 'Mention',
350 - 'href' => \esc_url_raw( $url ),
351 - 'name' => \esc_html( $mention ),
352 - );
353 - }
354 -
355 - return \array_unique( $tags, SORT_REGULAR );
356 - }
357 -
358 - /**
359 - * Get the attributed to.
360 - *
361 - * @return string The attributed to.
362 - */
363 - protected function get_attributed_to() {
364 - return null;
365 - }
366 -
367 - /**
368 - * Extracts mentions from the content.
369 - *
370 - * @return array The mentions.
371 - */
372 - protected function get_mentions() {
373 - $content = '';
374 -
375 - if ( \method_exists( $this, 'get_content' ) ) {
376 - $content = $content . ' ' . $this->get_content();
377 - }
378 -
379 - if ( \method_exists( $this, 'get_summary' ) ) {
380 - $content = $content . ' ' . $this->get_summary();
381 - }
382 -
383 - /**
384 - * Filter the mentions in the post content.
385 - *
386 - * @param array $mentions The mentions.
387 - * @param string $content The post content.
388 - * @param \WP_Post $post The post object.
389 - *
390 - * @return array The filtered mentions.
391 - */
392 - return \apply_filters(
393 - 'activitypub_extract_mentions',
394 - array(),
395 - $content,
396 - $this->item
397 - );
398 - }
399 -
400 - /**
401 - * Returns the in reply to.
402 - *
403 - * @return string|array|null The in reply to.
404 - */
405 - protected function get_in_reply_to() {
406 - return null;
407 - }
408 -
409 - /**
410 - * Parse HTML content for image tags and extract attachment information.
411 - *
412 - * This method is used by both Post and Comment transformers to find images
413 - * embedded in HTML content and extract their attachment IDs and alt text.
414 - *
415 - * @param array $media The existing media array grouped by type.
416 - * @param int $max_images Maximum number of images to extract.
417 - * @param string $content The HTML content to parse.
418 - *
419 - * @return array The updated media array with found images.
420 - */
421 - protected function parse_html_images( $media, $max_images, $content ) {
422 - // If someone calls that function directly, bail.
423 - if ( ! \class_exists( '\WP_HTML_Tag_Processor' ) ) {
424 - return $media;
425 - }
426 -
427 - // Max images can't be negative or zero.
428 - if ( $max_images <= 0 ) {
429 - return $media;
430 - }
431 -
432 - $images = array();
433 - $base = get_upload_baseurl();
434 - $tags = new \WP_HTML_Tag_Processor( $content );
435 -
436 - // This linter warning is a false positive - we have to re-count each time here as we modify $images.
437 - // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
438 - while ( $tags->next_tag( 'img' ) && ( \count( $images ) <= $max_images ) ) {
439 - /**
440 - * Filter the image source URL.
441 - *
442 - * This can be used to modify the image source URL before it is used to
443 - * determine the attachment ID.
444 - *
445 - * @param string $src The image source URL.
446 - */
447 - $src = \apply_filters( 'activitypub_image_src', $tags->get_attribute( 'src' ) );
448 -
449 - /*
450 - * If the img source is in our uploads dir, get the
451 - * associated ID. Note: if there's a -500x500
452 - * type suffix, we remove it, but we try the original
453 - * first in case the original image is actually called
454 - * that. Likewise, we try adding the -scaled suffix for
455 - * the case that this is a small version of an image
456 - * that was big enough to get scaled down on upload:
457 - * https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
458 - */
459 - if ( null !== $src && \str_starts_with( $src, $base ) ) {
460 - $img_id = \attachment_url_to_postid( $src );
461 -
462 - if ( 0 === $img_id ) {
463 - $count = 0;
464 - $src = \strtok( $src, '?' );
465 - $img_id = \attachment_url_to_postid( $src );
466 - }
467 -
468 - if ( 0 === $img_id ) {
469 - $count = 0;
470 - $src = \preg_replace( '/-(?:\d+x\d+)(\.[a-zA-Z]+)$/', '$1', $src, 1, $count );
471 - if ( $count > 0 ) {
472 - $img_id = \attachment_url_to_postid( $src );
473 - }
474 - }
475 -
476 - if ( 0 === $img_id ) {
477 - $src = \preg_replace( '/(\.[a-zA-Z]+)$/', '-scaled$1', $src );
478 - $img_id = \attachment_url_to_postid( $src );
479 - }
480 -
481 - if ( 0 !== $img_id ) {
482 - $images[] = array(
483 - 'id' => $img_id,
484 - 'alt' => $tags->get_attribute( 'alt' ),
485 - );
486 - }
487 - }
488 - }
489 -
490 - if ( \count( $media['image'] ) <= $max_images ) {
491 - $media['image'] = \array_merge( $media['image'], $images );
492 - }
493 -
494 - return $media;
495 - }
496 -
497 - /**
498 - * Transforms a WordPress attachment array to ActivityStreams attachment format.
499 - *
500 - * @param array $media The WordPress attachment array with 'id', optional 'alt', and optional 'icon'.
501 - *
502 - * @return array The ActivityStreams attachment array.
503 - */
504 - protected function transform_attachment( $media ) {
505 - if ( ! isset( $media['id'] ) ) {
506 - return $media;
507 - }
508 -
509 - $id = $media['id'];
510 - $attachment = array();
511 - $mime_type = \get_post_mime_type( $id );
512 - $media_type = \strtok( $mime_type, '/' );
513 -
514 - // Switching on image/audio/video.
515 - switch ( $media_type ) {
516 - case 'image':
517 - $image_size = 'large';
518 -
519 - /**
520 - * Filter the image URL returned for each post.
521 - *
522 - * @param array|false $thumbnail The image URL, or false if no image is available.
523 - * @param int $id The attachment ID.
524 - * @param string $image_size The image size to retrieve. Set to 'large' by default.
525 - */
526 - $thumbnail = \apply_filters( 'activitypub_get_image', $this->get_attachment_image_src( $id, $image_size ), $id, $image_size );
527 -
528 - if ( $thumbnail ) {
529 - $image = array(
530 - 'type' => 'Image',
531 - 'url' => \esc_url_raw( $thumbnail[0] ),
532 - 'mediaType' => \esc_attr( $mime_type ),
533 - );
534 -
535 - if ( ! empty( $media['alt'] ) ) {
536 - $image['name'] = \html_entity_decode( \wp_strip_all_tags( $media['alt'] ), ENT_QUOTES, 'UTF-8' );
537 - } else {
538 - $alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
539 - if ( $alt ) {
540 - $image['name'] = \html_entity_decode( \wp_strip_all_tags( $alt ), ENT_QUOTES, 'UTF-8' );
541 - }
542 - }
543 -
544 - // Add EXIF metadata using Schema.org exifData property (FEP-ee3a).
545 - $exif_data = $this->get_exif_data( $id );
546 - if ( $exif_data ) {
547 - $image['exifData'] = $exif_data;
548 - }
549 -
550 - $attachment = $image;
551 - }
552 - break;
553 -
554 - case 'audio':
555 - case 'video':
556 - $meta = \wp_get_attachment_metadata( $id );
557 - $attachment = array(
558 - 'type' => \ucfirst( $media_type ),
559 - 'mediaType' => \esc_attr( $mime_type ),
560 - 'url' => \esc_url_raw( \wp_get_attachment_url( $id ) ),
561 - 'name' => \esc_attr( \get_the_title( $id ) ),
562 - );
563 -
564 - // Height and width for videos.
565 - if ( isset( $meta['width'], $meta['height'] ) ) {
566 - $attachment['width'] = \esc_attr( $meta['width'] );
567 - $attachment['height'] = \esc_attr( $meta['height'] );
568 - }
569 -
570 - // Use poster image from the block, or fall back to the transformer icon.
571 - if ( ! empty( $media['icon'] ) ) {
572 - $attachment['icon'] = \esc_url_raw( $media['icon'] );
573 - } elseif ( \method_exists( $this, 'get_icon' ) && $this->get_icon() ) {
574 - $attachment['icon'] = object_to_uri( $this->get_icon() );
575 - }
576 - break;
577 - }
578 -
579 - /**
580 - * Filter the attachment for a post.
581 - *
582 - * @param array $attachment The attachment.
583 - * @param int $id The attachment ID.
584 - *
585 - * @return array The filtered attachment.
586 - */
587 - return \apply_filters( 'activitypub_attachment', $attachment, $id );
588 - }
589 -
590 - /**
591 - * Return details about an image attachment.
592 - *
593 - * @param int $id The attachment ID.
594 - * @param string $image_size The image size to retrieve. Set to 'large' by default.
595 - *
596 - * @return array|false Array of image data, or boolean false if no image is available.
597 - */
598 - protected function get_attachment_image_src( $id, $image_size = 'large' ) {
599 - /**
600 - * Hook into the image retrieval process. Before image retrieval.
601 - *
602 - * @param int $id The attachment ID.
603 - * @param string $image_size The image size to retrieve. Set to 'large' by default.
604 - */
605 - \do_action( 'activitypub_get_image_pre', $id, $image_size );
606 -
607 - $image = \wp_get_attachment_image_src( $id, $image_size );
608 -
609 - /**
610 - * Hook into the image retrieval process. After image retrieval.
611 - *
612 - * @param int $id The attachment ID.
613 - * @param string $image_size The image size to retrieve. Set to 'large' by default.
614 - */
615 - \do_action( 'activitypub_get_image_post', $id, $image_size );
616 -
617 - return $image;
618 - }
619 -
620 - /**
621 - * Get EXIF metadata for an image attachment using Schema.org exifData property.
622 - *
623 - * Returns an array of PropertyValue objects as defined in FEP-ee3a.
624 - *
625 - * @link https://codeberg.org/fediverse/fep/src/branch/main/fep/ee3a/fep-ee3a.md
626 - *
627 - * @param int $attachment_id The attachment ID.
628 - *
629 - * @return array|null Array of PropertyValue objects or null if no EXIF data available.
630 - */
631 - protected function get_exif_data( $attachment_id ) {
632 - $metadata = \wp_get_attachment_metadata( $attachment_id );
633 -
634 - if ( empty( $metadata['image_meta'] ) ) {
635 - return null;
636 - }
637 -
638 - $image_meta = $metadata['image_meta'];
639 - $exif_data = array();
640 -
641 - // Map WordPress image_meta to FEP-ee3a EXIF field names.
642 - if ( ! empty( $image_meta['created_timestamp'] ) ) {
643 - $exif_data[] = array(
644 - '@type' => 'PropertyValue',
645 - 'name' => 'DateTime',
646 - 'value' => \gmdate( 'Y:m:d H:i:s', (int) $image_meta['created_timestamp'] ),
647 - );
648 - }
649 -
650 - if ( ! empty( $image_meta['shutter_speed'] ) ) {
651 - $shutter_speed = (float) $image_meta['shutter_speed'];
652 - // Format shutter speed as a fraction (e.g., "1/100") for speeds faster than 1 second.
653 - if ( $shutter_speed > 0 && $shutter_speed < 1 ) {
654 - $value = '1/' . \round( 1 / $shutter_speed );
655 - } elseif ( $shutter_speed >= 1 ) {
656 - $value = (string) $shutter_speed;
657 - }
658 - if ( isset( $value ) ) {
659 - $exif_data[] = array(
660 - '@type' => 'PropertyValue',
661 - 'name' => 'ExposureTime',
662 - 'value' => $value,
663 - );
664 - }
665 - }
666 -
667 - if ( ! empty( $image_meta['aperture'] ) ) {
668 - $exif_data[] = array(
669 - '@type' => 'PropertyValue',
670 - 'name' => 'FNumber',
671 - 'value' => 'f/' . (float) $image_meta['aperture'],
672 - );
673 - }
674 -
675 - if ( ! empty( $image_meta['focal_length'] ) ) {
676 - $exif_data[] = array(
677 - '@type' => 'PropertyValue',
678 - 'name' => 'FocalLength',
679 - 'value' => (string) (float) $image_meta['focal_length'],
680 - );
681 - }
682 -
683 - if ( ! empty( $image_meta['iso'] ) ) {
684 - $exif_data[] = array(
685 - '@type' => 'PropertyValue',
686 - 'name' => 'PhotographicSensitivity',
687 - 'value' => (string) (int) $image_meta['iso'],
688 - );
689 - }
690 -
691 - if ( ! empty( $image_meta['camera'] ) ) {
692 - $exif_data[] = array(
693 - '@type' => 'PropertyValue',
694 - 'name' => 'Model',
695 - 'value' => \sanitize_text_field( $image_meta['camera'] ),
696 - );
697 - }
698 -
699 - /**
700 - * Filter the EXIF data for an image attachment.
701 - *
702 - * @param array $exif_data Array of PropertyValue objects for Schema.org exifData.
703 - * @param array $image_meta The WordPress image_meta array.
704 - * @param int $attachment_id The attachment ID.
705 - *
706 - * @return array The filtered EXIF data array.
707 - */
708 - $exif_data = \apply_filters( 'activitypub_image_exif', $exif_data, $image_meta, $attachment_id );
709 -
710 - return ! empty( $exif_data ) ? $exif_data : null;
711 - }
712 -
713 - /**
714 - * Filter attachments to ensure uniqueness based on their ID.
715 - *
716 - * @param array $attachments Array of attachments with 'id' field.
717 - *
718 - * @return array Array with duplicate attachments removed.
719 - */
720 - protected function filter_unique_attachments( $attachments ) {
721 - $seen_ids = array();
722 -
723 - return \array_filter(
724 - $attachments,
725 - static function ( $attachment ) use ( &$seen_ids ) {
726 - if ( isset( $attachment['id'] ) && ! \in_array( $attachment['id'], $seen_ids, true ) ) {
727 - $seen_ids[] = $attachment['id'];
728 - return true;
729 - }
730 - return false;
731 - }
732 - );
733 - }
123 + abstract public function change_wp_user_id( $user_id );
734 124 }