PluginProbe
ActivityPub / 9.0.0
ActivityPub v9.0.0
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
activitypub / includes / model / class-blog.php

class-blog.php in ActivityPub 9.0.0, at includes/model/class-blog.php

605 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Blog model file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Model;
9
10 use Activitypub\Activity\Actor;
11 use Activitypub\Collection\Actors;
12 use Activitypub\Collection\Extra_Fields;
13
14 use function Activitypub\esc_hashtag;
15 use function Activitypub\get_attribution_domains;
16 use function Activitypub\get_rest_url_by_path;
17 use function Activitypub\is_blog_public;
18 use function Activitypub\is_single_user;
19
20 /**
21 * Blog class.
22 *
23 * @method int get__id() Gets the internal user ID for the blog (always returns BLOG_USER_ID).
24 */
25 class Blog extends Actor {
26 /**
27 * The User-ID
28 *
29 * @var int
30 */
31 protected $_id = Actors::BLOG_USER_ID; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
32
33 /**
34 * The generator of the object.
35 *
36 * @see https://www.w3.org/TR/activitypub/#generator
37 * @see https://codeberg.org/fediverse/fep/src/branch/main/fep/844e/fep-844e.md#discovery-through-an-actor
38 *
39 * @var array
40 */
41 protected $generator = array(
42 'type' => 'Application',
43 'implements' => array(
44 array(
45 'href' => 'https://datatracker.ietf.org/doc/html/rfc9421',
46 'name' => 'RFC-9421: HTTP Message Signatures',
47 ),
48 ),
49 );
50
51 /**
52 * Constructor.
53 */
54 public function __construct() {
55 /**
56 * Fires when a model actor is constructed.
57 *
58 * @param Blog $this The Blog model.
59 */
60 \do_action( 'activitypub_construct_model_actor', $this );
61 }
62
63 /**
64 * Whether the User manually approves followers.
65 *
66 * @return false
67 */
68 public function get_manually_approves_followers() {
69 return false;
70 }
71
72 /**
73 * Whether the User is discoverable.
74 *
75 * @return boolean
76 */
77 public function get_discoverable() {
78 return true;
79 }
80
81 /**
82 * Get the User ID.
83 *
84 * @return string The User ID.
85 */
86 public function get_id() {
87 $id = parent::get_id();
88
89 if ( $id ) {
90 return $id;
91 }
92
93 $permalink = \get_option( 'activitypub_use_permalink_as_id_for_blog', false );
94
95 if ( $permalink ) {
96 return \esc_url( \home_url( '/@' . $this->get_preferred_username() ) );
97 }
98
99 return \add_query_arg( 'author', $this->_id, \home_url( '/' ) );
100 }
101
102 /**
103 * Get the type of the object.
104 *
105 * If relay mode is enabled, return "Service".
106 * If the Blog is in "single user" mode, return "Person" instead of "Group".
107 *
108 * @return string The type of the object.
109 */
110 public function get_type() {
111 if ( \get_option( 'activitypub_relay_mode', false ) ) {
112 return 'Service';
113 }
114
115 if ( is_single_user() ) {
116 return 'Person';
117 } else {
118 return 'Group';
119 }
120 }
121
122 /**
123 * Get the Username.
124 *
125 * @return string The Username.
126 */
127 public function get_name() {
128 return \wp_strip_all_tags(
129 \html_entity_decode(
130 \get_bloginfo( 'name' ),
131 \ENT_QUOTES,
132 'UTF-8'
133 )
134 );
135 }
136
137 /**
138 * Get the User description.
139 *
140 * @return string The User description.
141 */
142 public function get_summary() {
143 $summary = \get_option( 'activitypub_blog_description', null );
144
145 if ( ! $summary ) {
146 $summary = \get_bloginfo( 'description' );
147 }
148
149 return \wpautop(
150 \wp_kses(
151 $summary,
152 'default'
153 )
154 );
155 }
156
157 /**
158 * Get the User url.
159 *
160 * @return string The User url.
161 */
162 public function get_url() {
163 return \get_bloginfo( 'url' );
164 }
165
166 /**
167 * Get blog's homepage URL.
168 *
169 * @return string The User-Url.
170 */
171 public function get_alternate_url() {
172 return \esc_url( \trailingslashit( get_home_url() ) );
173 }
174
175 /**
176 * Generate a default Username.
177 *
178 * @return string The auto-generated Username.
179 */
180 public static function get_default_username() {
181 // Check if domain host has a subdomain.
182 $host = \wp_parse_url( \get_home_url(), \PHP_URL_HOST );
183 $host = \preg_replace( '/^www\./i', '', $host );
184
185 /**
186 * Filters the default blog username.
187 *
188 * This filter allows developers to modify the default username that is
189 * generated for the blog, which by default is the site's host name
190 * without the 'www.' prefix.
191 *
192 * @param string $host The default username (site's host name).
193 */
194 return apply_filters( 'activitypub_default_blog_username', $host );
195 }
196
197 /**
198 * Get the preferred Username.
199 *
200 * @return string The Username.
201 */
202 public function get_preferred_username() {
203 $username = \get_option( 'activitypub_blog_identifier' );
204
205 if ( $username ) {
206 return $username;
207 }
208
209 return self::get_default_username();
210 }
211
212 /**
213 * Get the User icon.
214 *
215 * @return string[] The User icon.
216 */
217 public function get_icon() {
218 // Try site_logo, falling back to site_icon, first.
219 $icon_id = get_option( 'site_icon' );
220
221 // Try custom logo second.
222 if ( ! $icon_id ) {
223 $icon_id = get_theme_mod( 'custom_logo' );
224 }
225
226 $icon_url = false;
227
228 if ( $icon_id ) {
229 $icon = wp_get_attachment_image_src( $icon_id, 'full' );
230 if ( $icon ) {
231 $icon_url = $icon[0];
232 }
233 }
234
235 if ( ! $icon_url ) {
236 // Fallback to default icon.
237 $icon_url = plugins_url( '/assets/img/wp-logo.png', ACTIVITYPUB_PLUGIN_FILE );
238 }
239
240 return array(
241 'type' => 'Image',
242 'url' => esc_url( $icon_url ),
243 );
244 }
245
246 /**
247 * Get the User-Header-Image.
248 *
249 * @return string[]|null The User-Header-Image.
250 */
251 public function get_image() {
252 $header_image = get_option( 'activitypub_header_image' );
253 $image_url = null;
254
255 if ( $header_image ) {
256 $image_url = \wp_get_attachment_url( $header_image );
257 }
258
259 if ( ! $image_url && \has_header_image() ) {
260 $image_url = \get_header_image();
261 }
262
263 if ( $image_url ) {
264 return array(
265 'type' => 'Image',
266 'url' => esc_url( $image_url ),
267 );
268 }
269
270 return null;
271 }
272
273 /**
274 * Get the published date.
275 *
276 * @return string The published date.
277 */
278 public function get_published() {
279 $published = \get_option( 'activitypub_blog_published' );
280
281 if ( $published ) {
282 return $published;
283 }
284
285 // Backfill from the first federated post.
286 $first_federated = new \WP_Query(
287 array(
288 'orderby' => 'date',
289 'order' => 'ASC',
290 'posts_per_page' => 1,
291 'post_status' => 'publish',
292 'no_found_rows' => true,
293 'ignore_sticky_posts' => true,
294 'update_post_meta_cache' => false,
295 'update_post_term_cache' => false,
296 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
297 'meta_query' => array(
298 array(
299 'key' => 'activitypub_status',
300 'compare' => 'EXISTS',
301 ),
302 ),
303 )
304 );
305
306 if ( ! empty( $first_federated->posts[0] ) ) {
307 $time = \strtotime( $first_federated->posts[0]->post_date_gmt );
308 } else {
309 $time = \time();
310 }
311
312 $published = \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, $time );
313
314 \update_option( 'activitypub_blog_published', $published, false );
315
316 return $published;
317 }
318
319 /**
320 * Get the canonical URL.
321 *
322 * @return string|null The canonical URL.
323 */
324 public function get_canonical_url() {
325 return \home_url();
326 }
327
328 /**
329 * Get the Moderators endpoint.
330 *
331 * @return string|null The Moderators endpoint.
332 */
333 public function get_moderators() {
334 if ( is_single_user() || 'Group' !== $this->get_type() ) {
335 return null;
336 }
337
338 return get_rest_url_by_path( 'collections/moderators' );
339 }
340
341 /**
342 * Get attributedTo value.
343 *
344 * @return string|null The attributedTo value.
345 */
346 public function get_attributed_to() {
347 if ( is_single_user() || 'Group' !== $this->get_type() ) {
348 return null;
349 }
350
351 return get_rest_url_by_path( 'collections/moderators' );
352 }
353
354 /**
355 * Get the public key information.
356 *
357 * @return string[] The public key.
358 */
359 public function get_public_key() {
360 return array(
361 'id' => $this->get_id() . '#main-key',
362 'owner' => $this->get_id(),
363 'publicKeyPem' => Actors::get_public_key( $this->get__id() ),
364 );
365 }
366
367 /**
368 * Returns whether posting is restricted to mods.
369 *
370 * @return bool|null True if posting is restricted to mods, null if not applicable.
371 */
372 public function get_posting_restricted_to_mods() {
373 if ( 'Group' === $this->get_type() ) {
374 return true;
375 }
376
377 return null;
378 }
379
380 /**
381 * Returns the Inbox-API-Endpoint.
382 *
383 * @return string The Inbox-Endpoint.
384 */
385 public function get_inbox() {
386 return get_rest_url_by_path( sprintf( 'actors/%d/inbox', $this->get__id() ) );
387 }
388
389 /**
390 * Returns the Outbox-API-Endpoint.
391 *
392 * @return string The Outbox-Endpoint.
393 */
394 public function get_outbox() {
395 return get_rest_url_by_path( sprintf( 'actors/%d/outbox', $this->get__id() ) );
396 }
397
398 /**
399 * Returns the Followers-API-Endpoint.
400 *
401 * @return string The Followers-Endpoint.
402 */
403 public function get_followers() {
404 return get_rest_url_by_path( sprintf( 'actors/%d/followers', $this->get__id() ) );
405 }
406
407 /**
408 * Returns the Following-API-Endpoint.
409 *
410 * @return string The Following-Endpoint.
411 */
412 public function get_following() {
413 return get_rest_url_by_path( sprintf( 'actors/%d/following', $this->get__id() ) );
414 }
415
416 /**
417 * Returns endpoints.
418 *
419 * @return string[]|null The endpoints.
420 */
421 public function get_endpoints() {
422 return array(
423 'sharedInbox' => get_rest_url_by_path( 'inbox' ),
424 'oauthAuthorizationEndpoint' => get_rest_url_by_path( 'oauth/authorize' ),
425 'oauthTokenEndpoint' => get_rest_url_by_path( 'oauth/token' ),
426 'oauthRegistrationEndpoint' => get_rest_url_by_path( 'oauth/clients' ),
427 'proxyUrl' => get_rest_url_by_path( 'proxy' ),
428 'proxyEventStream' => get_rest_url_by_path( 'proxy/stream' ),
429 );
430 }
431
432 /**
433 * Returns a user@domain type of identifier for the user.
434 *
435 * @return string The Webfinger-Identifier.
436 */
437 public function get_webfinger() {
438 return $this->get_preferred_username() . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
439 }
440
441 /**
442 * Returns the Liked API endpoint.
443 *
444 * @since 8.1.0
445 *
446 * @return string The Liked endpoint.
447 */
448 public function get_liked() {
449 return get_rest_url_by_path( sprintf( 'actors/%d/liked', $this->get__id() ) );
450 }
451
452 /**
453 * Returns the Featured-API-Endpoint.
454 *
455 * @return string The Featured-Endpoint.
456 */
457 public function get_featured() {
458 return get_rest_url_by_path( sprintf( 'actors/%d/collections/featured', $this->get__id() ) );
459 }
460
461 /**
462 * Returns the Featured-Tags-API-Endpoint.
463 *
464 * @return string The Featured-Tags-Endpoint.
465 */
466 public function get_featured_tags() {
467 return get_rest_url_by_path( sprintf( 'actors/%d/collections/tags', $this->get__id() ) );
468 }
469
470 /**
471 * Returns whether the site is indexable.
472 *
473 * @return bool Whether the site is indexable.
474 */
475 public function get_indexable() {
476 if ( is_blog_public() ) {
477 return true;
478 } else {
479 return false;
480 }
481 }
482
483 /**
484 * Update the Username.
485 *
486 * @param mixed $value The new value.
487 * @return bool True if the attribute was updated, false otherwise.
488 */
489 public function update_name( $value ) {
490 return \update_option( 'blogname', $value );
491 }
492
493 /**
494 * Update the User description.
495 *
496 * @param mixed $value The new value.
497 * @return bool True if the attribute was updated, false otherwise.
498 */
499 public function update_summary( $value ) {
500 return \update_option( 'blogdescription', $value );
501 }
502
503 /**
504 * Update the User icon.
505 *
506 * @param mixed $value The new value.
507 * @return bool True if the attribute was updated, false otherwise.
508 */
509 public function update_icon( $value ) {
510 if ( ! wp_attachment_is_image( $value ) ) {
511 return false;
512 }
513 return \update_option( 'site_icon', $value );
514 }
515
516 /**
517 * Update the User-Header-Image.
518 *
519 * @param mixed $value The new value.
520 * @return bool True if the attribute was updated, false otherwise.
521 */
522 public function update_header( $value ) {
523 if ( ! wp_attachment_is_image( $value ) ) {
524 return false;
525 }
526 return \update_option( 'activitypub_header_image', $value );
527 }
528
529 /**
530 * Get the User - Hashtags.
531 *
532 * @see https://docs.joinmastodon.org/spec/activitypub/#Hashtag
533 *
534 * @return string[] The User - Hashtags.
535 */
536 public function get_tag() {
537 $hashtags = array();
538
539 $args = array(
540 'orderby' => 'count',
541 'order' => 'DESC',
542 'number' => 10,
543 );
544
545 $tags = get_tags( $args );
546
547 foreach ( $tags as $tag ) {
548 $hashtags[] = array(
549 'type' => 'Hashtag',
550 'href' => \get_tag_link( $tag->term_id ),
551 'name' => esc_hashtag( $tag->name ),
552 );
553 }
554
555 return $hashtags;
556 }
557
558 /**
559 * Extend the User-Output with Attachments.
560 *
561 * @return array The extended User-Output.
562 */
563 public function get_attachment() {
564 $extra_fields = Extra_Fields::get_actor_fields( $this->_id );
565 return Extra_Fields::fields_to_attachments( $extra_fields );
566 }
567
568 /**
569 * Returns the website hosts allowed to credit this blog.
570 *
571 * @return string[]|null The attribution domains or null if not found.
572 */
573 public function get_attribution_domains() {
574 return get_attribution_domains();
575 }
576
577 /**
578 * Returns the alsoKnownAs.
579 *
580 * @return string[] The alsoKnownAs.
581 */
582 public function get_also_known_as() {
583 $also_known_as = array(
584 \add_query_arg( 'author', $this->_id, \home_url( '/' ) ),
585 $this->get_url(),
586 $this->get_alternate_url(),
587 );
588
589 $also_known_as = array_merge( $also_known_as, \get_option( 'activitypub_blog_user_also_known_as', array() ) );
590
591 return array_unique( $also_known_as );
592 }
593
594 /**
595 * Returns the movedTo.
596 *
597 * @return string The movedTo.
598 */
599 public function get_moved_to() {
600 $moved_to = \get_option( 'activitypub_blog_user_moved_to' );
601
602 return $moved_to && $moved_to !== $this->get_id() ? $moved_to : null;
603 }
604 }
605