PluginProbe
ActivityPub / 2.0.1
ActivityPub v2.0.1
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-follower.php

class-follower.php in ActivityPub 2.0.1, at includes/model/class-follower.php

367 lines 7.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Activitypub\Model;
3
4 use WP_Error;
5 use WP_Query;
6 use Activitypub\Activity\Actor;
7 use Activitypub\Collection\Followers;
8
9 /**
10 * ActivityPub Follower Class
11 *
12 * This Object represents a single Follower.
13 * There is no direct reference to a WordPress User here.
14 *
15 * @author Matt Wiebe
16 * @author Matthias Pfefferle
17 *
18 * @see https://www.w3.org/TR/activitypub/#follow-activity-inbox
19 */
20 class Follower extends Actor {
21 /**
22 * The complete Remote-Profile of the Follower
23 *
24 * @var array
25 */
26 protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
27
28 /**
29 * Get the errors.
30 *
31 * @return mixed
32 */
33 public function get_errors() {
34 return get_post_meta( $this->_id, 'activitypub_errors' );
35 }
36
37 /**
38 * Get the Summary.
39 *
40 * @return int The Summary.
41 */
42 public function get_summary() {
43 if ( isset( $this->summary ) ) {
44 return $this->summary;
45 }
46
47 return '';
48 }
49
50 /**
51 * Getter for URL attribute.
52 *
53 * Falls back to ID, if no URL is set. This is relevant for
54 * Plattforms like Lemmy, where the ID is the URL.
55 *
56 * @return string The URL.
57 */
58 public function get_url() {
59 if ( $this->url ) {
60 return $this->url;
61 }
62
63 return $this->id;
64 }
65
66 /**
67 * Reset (delete) all errors.
68 *
69 * @return void
70 */
71 public function reset_errors() {
72 delete_post_meta( $this->_id, 'activitypub_errors' );
73 }
74
75 /**
76 * Count the errors.
77 *
78 * @return int The number of errors.
79 */
80 public function count_errors() {
81 $errors = $this->get_errors();
82
83 if ( is_array( $errors ) && ! empty( $errors ) ) {
84 return count( $errors );
85 }
86
87 return 0;
88 }
89
90 /**
91 * Return the latest error message.
92 *
93 * @return string The error message.
94 */
95 public function get_latest_error_message() {
96 $errors = $this->get_errors();
97
98 if ( is_array( $errors ) && ! empty( $errors ) ) {
99 return reset( $errors );
100 }
101
102 return '';
103 }
104
105 /**
106 * Update the current Follower-Object.
107 *
108 * @return void
109 */
110 public function update() {
111 $this->save();
112 }
113
114 /**
115 * Validate the current Follower-Object.
116 *
117 * @return boolean True if the verification was successful.
118 */
119 public function is_valid() {
120 // the minimum required attributes
121 $required_attributes = array(
122 'id',
123 'preferredUsername',
124 'inbox',
125 'publicKey',
126 'publicKeyPem',
127 );
128
129 foreach ( $required_attributes as $attribute ) {
130 if ( ! $this->get( $attribute ) ) {
131 return false;
132 }
133 }
134
135 return true;
136 }
137
138 /**
139 * Save the current Follower-Object.
140 *
141 * @return int|WP_Error The Post-ID or an WP_Error.
142 */
143 public function save() {
144 if ( ! $this->is_valid() ) {
145 return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ), array( 'status' => 400 ) );
146 }
147
148 if ( ! $this->get__id() ) {
149 global $wpdb;
150
151 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
152 $post_id = $wpdb->get_var(
153 $wpdb->prepare(
154 "SELECT ID FROM $wpdb->posts WHERE guid=%s",
155 esc_sql( $this->get_id() )
156 )
157 );
158
159 if ( $post_id ) {
160 $post = get_post( $post_id );
161 $this->set__id( $post->ID );
162 }
163 }
164
165 $args = array(
166 'ID' => $this->get__id(),
167 'guid' => esc_url_raw( $this->get_id() ),
168 'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ),
169 'post_author' => 0,
170 'post_type' => Followers::POST_TYPE,
171 'post_name' => esc_url_raw( $this->get_id() ),
172 'post_excerpt' => sanitize_text_field( wp_kses( $this->get_summary(), 'user_description' ) ),
173 'post_status' => 'publish',
174 'meta_input' => $this->get_post_meta_input(),
175 );
176
177 $post_id = wp_insert_post( $args );
178 $this->_id = $post_id;
179
180 return $post_id;
181 }
182
183 /**
184 * Upsert the current Follower-Object.
185 *
186 * @return int|WP_Error The Post-ID or an WP_Error.
187 */
188 public function upsert() {
189 return $this->save();
190 }
191
192 /**
193 * Delete the current Follower-Object.
194 *
195 * Beware that this os deleting a Follower for ALL users!!!
196 *
197 * To delete only the User connection (unfollow)
198 * @see \Activitypub\Rest\Followers::remove_follower()
199 *
200 * @return void
201 */
202 public function delete() {
203 wp_delete_post( $this->_id );
204 }
205
206 /**
207 * Update the post meta.
208 *
209 * @return void
210 */
211 protected function get_post_meta_input() {
212 $meta_input = array();
213 $meta_input['activitypub_inbox'] = $this->get_shared_inbox();
214 $meta_input['activitypub_actor_json'] = $this->to_json();
215
216 return $meta_input;
217 }
218
219 /**
220 * Get the icon.
221 *
222 * Sets a fallback to better handle API and HTML outputs.
223 *
224 * @return array The icon.
225 */
226 public function get_icon() {
227 if ( isset( $this->icon['url'] ) ) {
228 return $this->icon;
229 }
230
231 return array(
232 'type' => 'Image',
233 'mediaType' => 'image/jpeg',
234 'url' => ACTIVITYPUB_PLUGIN_URL . 'assets/img/mp.jpg',
235 );
236 }
237
238 /**
239 * Get Name.
240 *
241 * Tries to extract a name from the URL or ID if not set.
242 *
243 * @return string The name.
244 */
245 public function get_name() {
246 if ( $this->name ) {
247 return $this->name;
248 } elseif ( $this->preferred_username ) {
249 return $this->preferred_username;
250 }
251
252 return $this->extract_name_from_uri();
253 }
254
255 /**
256 * The preferred Username.
257 *
258 * Tries to extract a name from the URL or ID if not set.
259 *
260 * @return string The preferred Username.
261 */
262 public function get_preferred_username() {
263 if ( $this->preferred_username ) {
264 return $this->preferred_username;
265 }
266
267 return $this->extract_name_from_uri();
268 }
269
270 /**
271 * Get the Icon URL (Avatar)
272 *
273 * @return string The URL to the Avatar.
274 */
275 public function get_icon_url() {
276 $icon = $this->get_icon();
277
278 if ( ! $icon ) {
279 return '';
280 }
281
282 if ( is_array( $icon ) ) {
283 return $icon['url'];
284 }
285
286 return $icon;
287 }
288
289 /**
290 * Get the shared inbox, with a fallback to the inbox.
291 *
292 * @return string|null The URL to the shared inbox, the inbox or null.
293 */
294 public function get_shared_inbox() {
295 if ( ! empty( $this->get_endpoints()['sharedInbox'] ) ) {
296 return $this->get_endpoints()['sharedInbox'];
297 } elseif ( ! empty( $this->get_inbox() ) ) {
298 return $this->get_inbox();
299 }
300
301 return null;
302 }
303
304 /**
305 * Convert a Custom-Post-Type input to an Activitypub\Model\Follower.
306 *
307 * @return string The JSON string.
308 *
309 * @return array Activitypub\Model\Follower
310 */
311 public static function init_from_cpt( $post ) {
312 $actor_json = get_post_meta( $post->ID, 'activitypub_actor_json', true );
313 $object = self::init_from_json( $actor_json );
314 $object->set__id( $post->ID );
315 $object->set_id( $post->guid );
316 $object->set_name( $post->post_title );
317 $object->set_summary( $post->post_excerpt );
318 $object->set_published( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_date ) ) );
319 $object->set_updated( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) ) );
320
321 return $object;
322 }
323
324 /**
325 * Infer a shortname from the Actor ID or URL. Used only for fallbacks,
326 * we will try to use what's supplied.
327 *
328 * @return string Hopefully the name of the Follower.
329 */
330 protected function extract_name_from_uri() {
331 // prefer the URL, but fall back to the ID.
332 if ( $this->url ) {
333 $name = $this->url;
334 } else {
335 $name = $this->id;
336 }
337
338 if ( \filter_var( $name, FILTER_VALIDATE_URL ) ) {
339 $name = \rtrim( $name, '/' );
340 $path = \wp_parse_url( $name, PHP_URL_PATH );
341
342 if ( $path ) {
343 if ( \strpos( $name, '@' ) !== false ) {
344 // expected: https://example.com/@user (default URL pattern)
345 $name = \preg_replace( '|^/@?|', '', $path );
346 } else {
347 // expected: https://example.com/users/user (default ID pattern)
348 $parts = \explode( '/', $path );
349 $name = \array_pop( $parts );
350 }
351 }
352 } elseif (
353 \is_email( $name ) ||
354 \strpos( $name, 'acct' ) === 0 ||
355 \strpos( $name, '@' ) === 0
356 ) {
357 // expected: user@example.com or acct:user@example (WebFinger)
358 $name = \ltrim( $name, '@' );
359 $name = \ltrim( $name, 'acct:' );
360 $parts = \explode( '@', $name );
361 $name = $parts[0];
362 }
363
364 return $name;
365 }
366 }
367