PluginProbe
Friends / 4.3.2
Friends v4.3.2
4.3.2 4.3.1 4.3.0 4.2.2 4.2.1 4.2.0 4.1.0 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.0 2.9.1 All 88 releases
friends / includes / class-user.php

class-user.php in Friends 4.3.2, at includes/class-user.php

1,311 lines 36.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friend User
4 *
5 * This wraps \WP_User and adds friend specific functions.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 /**
13 * This is the class for the User part of the Friends Plugin.
14 *
15 * @since 0.21
16 *
17 * @package Friends
18 * @author Alex Kirk
19 */
20 class User extends \WP_User {
21 /**
22 * Caches the feed rules.
23 *
24 * @var array
25 */
26 public static $feed_rules = array();
27
28 /**
29 * Caches the feed catch all action.
30 *
31 * @var array
32 */
33 public static $feed_catch_all = array();
34
35 public static function get_by_username( $username ) {
36 $username = self::sanitize_username( $username );
37
38 $subscription = Subscription::get_by_username( $username );
39 if ( $subscription && ! is_wp_error( $subscription ) ) {
40 return $subscription;
41 }
42
43 $user = get_user_by( 'login', $username );
44 if ( $user && ! is_wp_error( $user ) ) {
45 return new self( $user );
46 }
47 return $user;
48 }
49
50 public static function get_post_author( \WP_Post $post ) {
51 static $cache = array();
52
53 $subscriptions = wp_get_object_terms( $post->ID, Subscription::TAXONOMY );
54 if ( empty( $subscriptions ) ) {
55 if ( ! isset( $cache[ 'u_' . $post->post_author ] ) ) {
56 $cache[ 'u_' . $post->post_author ] = new self( $post->post_author );
57 }
58 return $cache[ 'u_' . $post->post_author ];
59 }
60
61 $term = reset( $subscriptions );
62 if ( ! isset( $cache[ 't_' . $term->term_id ] ) ) {
63 $cache[ 't_' . $term->term_id ] = new Subscription( $term );
64 }
65 return $cache[ 't_' . $term->term_id ];
66 }
67
68 /**
69 * Create a User with a specific Friends-related role
70 *
71 * @param string $user_login The user login.
72 * @param string $role The role: subscription.
73 * @param string $url The site URL for which
74 * to create the user.
75 * @param string $display_name The user's display name.
76 * @param string $avatar_url The user_avatar_url URL.
77 * @param string $description A description for the user.
78 * @param string $user_registered When the user was registered.
79 *
80 * @return User|\WP_Error The created user or an error.
81 */
82 public static function create( $user_login, $role, $url, $display_name = null, $avatar_url = null, $description = null, $user_registered = null ) {
83 $user_login = self::sanitize_username( $user_login );
84 return Subscription::create( $user_login, $role, $url, $display_name, $avatar_url, $description, $user_registered );
85 }
86
87 public static function register_wrapper_hooks() {
88 add_filter( 'friends_get_user_feeds', array( 'Friends\User', 'friends_get_user_feeds' ), 10, 2 );
89 }
90
91 /**
92 * Get the feeds for a user.
93 *
94 * @param array $feeds The feeds.
95 * @param \WP_User $user The user.
96 */
97 public static function friends_get_user_feeds( $feeds, $user ) {
98 $user_feeds = $user->get_feeds();
99 if ( is_array( $user_feeds ) ) {
100 $feeds = array_merge( $feeds, $user_feeds );
101 }
102 return $feeds;
103 }
104
105 /**
106 * Convert a site URL to a username
107 *
108 * @param string $url The site URL in question.
109 * @param bool $multisite_shortname Whether to use the multisite shortname.
110 * @return string The corresponding username.
111 */
112 public static function get_user_login_for_url( $url, $multisite_shortname = true ) {
113 $pre_user_login = apply_filters( 'friends_pre_get_user_login_for_url', false, $url );
114 if ( $pre_user_login ) {
115 return $pre_user_login;
116 }
117
118 $multisite_user = self::get_multisite_user( $url );
119 if ( $multisite_user && $multisite_shortname ) {
120 return $multisite_user->user_login;
121 }
122
123 $user_login = self::sanitize_username( self::get_display_name_for_url( $url, $multisite_shortname ) );
124 return $user_login;
125 }
126
127 /**
128 * Convert a site URL to a display name
129 *
130 * @param string $url The site URL in question.
131 * @param bool $multisite_shortname Whether to use the multisite shortname.
132 * @return string The corresponding display name.
133 */
134 public static function get_display_name_for_url( $url, $multisite_shortname = true ) {
135 $multisite_user = self::get_multisite_user( $url );
136 if ( $multisite_user && $multisite_shortname ) {
137 return $multisite_user->display_name;
138 }
139
140 $host = wp_parse_url( $url, PHP_URL_HOST );
141 $path = wp_parse_url( $url, PHP_URL_PATH );
142
143 $display_name = sanitize_text_field( preg_replace( '#^www\.#', '', preg_replace( '#[^a-z0-9.-]+#i', ' ', strtolower( $host . ' ' . $path ) ) ) );
144 return $display_name;
145 }
146
147 /**
148 * Discover a user login from feeds
149 *
150 * @param array $feeds A list of feeds.
151 * @return string The corresponding user login.
152 */
153 public static function get_user_login_from_feeds( $feeds ) {
154 foreach ( $feeds as $feed ) {
155 if ( isset( $feed['suggested-username'] ) ) {
156 return sanitize_text_field( $feed['suggested-username'] );
157 }
158 }
159 $display_name = self::get_display_name_from_feeds( $feeds );
160 if ( $display_name ) {
161 return self::sanitize_username( $display_name );
162 }
163
164 return false;
165 }
166 /**
167 * Discover a display name from feeds
168 *
169 * @param array $feeds A list of feeds.
170 * @return string The corresponding display name.
171 */
172 public static function get_display_name_from_feeds( $feeds ) {
173 foreach ( $feeds as $feed ) {
174 if ( 'self' === $feed['rel'] && ! empty( $feed['title'] ) ) {
175 $name = trim( preg_replace( '/(\s[–—|-]|[:,])\s.*$/u', '', $feed['title'] ) );
176 return sanitize_text_field( $name );
177 }
178 }
179
180 return false;
181 }
182
183 /**
184 * If the URL is on the same multisite, get the main user.
185 *
186 * @param string $url The site URL in question.
187 * @return bool|WP_User false or the user.
188 */
189 public static function get_multisite_user( $url ) {
190 if ( ! is_multisite() ) {
191 return false;
192 }
193
194 $host = wp_parse_url( $url, PHP_URL_HOST );
195 if ( ! $host ) {
196 return false;
197 }
198 $path = wp_parse_url( $url, PHP_URL_PATH );
199 if ( ! $path ) {
200 $path = '';
201 }
202
203 $site_id = get_blog_id_from_url( $host, trailingslashit( $path ) );
204 if ( ! $site_id ) {
205 return false;
206 }
207
208 // Let's find the user that is associated with that blog.
209 switch_to_blog( $site_id );
210 $friend_user_id = Friends::get_main_friend_user_id();
211 restore_current_blog();
212
213 return get_user_by( 'id', $friend_user_id );
214 }
215
216 /**
217 * Sanitize the username according to some more rules than just sanitize_user()
218 *
219 * @param string $username The username.
220 *
221 * @return string The sanitized username.
222 */
223 public static function sanitize_username( $username ) {
224 $username = preg_replace( '/[^a-z0-9.]+/', '-', strtolower( remove_accents( $username ) ) );
225 $username = sanitize_user( $username );
226 return $username;
227 }
228
229 /**
230 * Determines whether the specified user is a friends plugin user.
231 *
232 * @param \WP_User $user The user.
233 *
234 * @return bool True if the specified user is a friends plugin user, False otherwise.
235 */
236 public static function is_friends_plugin_user( \WP_User $user ) {
237 return $user->has_cap( 'subscription' );
238 }
239
240 /**
241 * Get a friend user for a user_login.
242 *
243 * @param string $user_login The user login.
244 * @return User|false The friend user or false.
245 */
246 public static function get_user( $user_login ) {
247 $user = get_user_by( 'login', $user_login );
248 if ( $user ) {
249 if ( self::is_friends_plugin_user( $user ) ) {
250 return new self( $user );
251 }
252
253 return false;
254 }
255 return $user;
256 }
257
258 /**
259 * Get a friend user for a user_id.
260 *
261 * @param string $user_id The user ID.
262 * @return User|false The friend user or false.
263 */
264 public static function get_user_by_id( $user_id ) {
265 if ( is_numeric( $user_id ) && $user_id > 1e10 ) {
266 $term = get_term( $user_id - 1e10, Subscription::TAXONOMY );
267 if ( $term && ! is_wp_error( $term ) ) {
268 return new Subscription( $term );
269 }
270 }
271
272 $user = get_user_by( 'ID', $user_id );
273 if ( $user ) {
274 if ( $user->has_cap( 'subscription' ) ) {
275 return new self( $user );
276 }
277
278 return false;
279 }
280 return $user;
281 }
282
283 public function __get( $key ) {
284 if ( 'user_url' === $key && empty( $this->data->user_url ) && is_multisite() ) {
285 $site = get_active_blog_for_user( $this->ID );
286 if ( $site ) {
287 // Ensure we're using the same URL protocol.
288 $this->data->user_url = set_url_scheme( $site->siteurl );
289 return $this->data->user_url;
290 }
291 }
292
293 return parent::__get( $key );
294 }
295
296
297 public function insert_post( array $postarr, $wp_error = false, $fire_after_hooks = true ) {
298 $current_user = wp_get_current_user();
299
300 // Posts and revisions should be associated with this user.
301 wp_set_current_user( $this->ID );
302
303 $post = wp_insert_post( $postarr, $wp_error, $fire_after_hooks );
304
305 if ( $current_user ) {
306 wp_set_current_user( $current_user->ID );
307 }
308
309 return $post;
310 }
311
312 public function get_object_id() {
313 return $this->ID;
314 }
315
316 public function save() {
317 return wp_update_user( $this );
318 }
319
320 /**
321 * Save multiple feeds for a user.
322 *
323 * @param array $feeds The feed URLs to subscribe to.
324 *
325 * @return array(\WP_Term)|\WP_error $user The new associated user or an error object.
326 */
327 public function save_feeds( $feeds = array() ) {
328 $errors = new \WP_Error();
329 foreach ( $feeds as $feed_url => $options ) {
330 if ( ! is_string( $feed_url ) || ! Friends::check_url( $feed_url ) ) {
331 $errors->add( 'invalid-url', 'An invalid URL was provided', $feed_url );
332 unset( $feeds[ $feed_url ] );
333 continue;
334 }
335
336 $default_options = array(
337 'active' => false,
338 'parser' => 'simplepie',
339 'post-format' => 'standard',
340 'mime-type' => 'application/rss+xml',
341 'title' => $this->display_name . ' RSS Feed',
342 );
343
344 $feeds[ $feed_url ] = array_merge( $default_options, $options );
345 }
346
347 $all_urls = array();
348 foreach ( wp_get_object_terms( $this->get_object_id(), User_Feed::TAXONOMY ) as $term ) {
349 $url = str_replace( '&amp;', '&', $term->name );
350 $all_urls[ $url ] = $term->term_id;
351 }
352
353 $user_feeds = wp_set_object_terms( $this->get_object_id(), array_keys( array_merge( $all_urls, $feeds ) ), User_Feed::TAXONOMY );
354 if ( is_wp_error( $user_feeds ) ) {
355 return $user_feeds;
356 }
357
358 foreach ( wp_get_object_terms( $this->get_object_id(), User_Feed::TAXONOMY ) as $term ) {
359 $url = str_replace( '&amp;', '&', $term->name );
360 $all_urls[ $url ] = $term->term_id;
361 }
362
363 foreach ( $feeds as $url => $feed_options ) {
364 if ( ! isset( $all_urls[ $url ] ) ) {
365 continue;
366 }
367 $term_id = $all_urls[ $url ];
368 foreach ( $feed_options as $key => $value ) {
369 if ( in_array( $key, array( 'active', 'parser', 'post-format', 'mime-type', 'title', 'interval', 'modifier' ) ) ) {
370
371 if ( metadata_exists( 'term', $term_id, $key ) ) {
372 update_metadata( 'term', $term_id, $key, $value );
373 } else {
374 add_metadata( 'term', $term_id, $key, $value, true );
375 }
376 }
377 }
378 }
379
380 if ( $errors->has_errors() ) {
381 return $errors;
382 }
383
384 return $all_urls;
385 }
386
387 /**
388 * Save a feed url for a user.
389 *
390 * @param string $feed_url The feed URL to subscribe to.
391 * @param array $options The options.
392 *
393 * @return User_Feed|\WP_Error $user The new feed or an error object.
394 */
395 public function save_feed( $feed_url, $options = array() ) {
396 $all_urls = $this->save_feeds( array( $feed_url => $options ) );
397
398 if ( is_wp_error( $all_urls ) ) {
399 return $all_urls;
400 }
401
402 if ( ! isset( $all_urls[ $feed_url ] ) ) {
403 return new \WP_Error(
404 'error-saving-feed',
405 sprintf(
406 // translators: %s is a URL.
407 __( 'The feed %s could not be saved', 'friends' ),
408 $feed_url
409 )
410 );
411 }
412
413 $term = get_term( $all_urls[ $feed_url ], User_Feed::TAXONOMY );
414
415 return new User_Feed( $term );
416 }
417
418 /**
419 * Subscribe to a friends site without becoming a friend
420 *
421 * @param string $feed_url The feed URL to subscribe to.
422 * @param array $options The options.
423 *
424 * @return User_Feed|\WP_error $user The new associated user or an error object.
425 */
426 public function subscribe( $feed_url, $options = array() ) {
427 $options['active'] = true;
428 return $this->save_feed( $feed_url, $options );
429 }
430
431 /**
432 * Retrieve the posts for this user
433 *
434 * @return array The new posts.
435 */
436 public function retrieve_posts_from_active_feeds() {
437 return $this->retrieve_posts_from_feeds( $this->get_active_feeds() );
438 }
439
440 /**
441 * Retrieve the posts for these user feeds
442 *
443 * @param array $feeds The feeds to retrieve from.
444 *
445 * @return array The new posts.
446 */
447 public function retrieve_posts_from_feeds( array $feeds ) {
448 $friends = Friends::get_instance();
449 $new_posts = array();
450 foreach ( $feeds as $feed ) {
451 $posts = $friends->feed->retrieve_feed( $feed );
452 if ( ! is_wp_error( $posts ) ) {
453 foreach ( $posts as $post_id => $item ) {
454 $new_posts[ $post_id ] = $item;
455 }
456 }
457 }
458 return $new_posts;
459 }
460
461 public function modify_query_by_author( \WP_Query $query ) {
462 $query->set( 'author', $this->ID );
463
464 $_post_type = apply_filters( 'friends_author_post_type', $query->get( 'post_type' ), $this );
465 if ( $_post_type ) {
466 $query->set( 'post_type', $_post_type );
467 }
468
469 if ( ! user_can( $this->ID, 'friends_plugin' ) || user_can( $this->ID, 'manage_options' ) ) {
470 // If the user doesn't belong to the friends plugin, only show their local posts so that subcriptions don't spill in.
471 $query->set( 'post_type', 'post' );
472 }
473 return $query;
474 }
475
476 public function modify_get_posts_args_by_author( $args ) {
477 $args['author'] = $this->ID;
478 return $args;
479 }
480
481 public function delete() {
482 if ( is_multisite() ) {
483 remove_user_from_blog( $this->ID, get_current_blog_id() );
484 } else {
485 wp_delete_user( $this->ID );
486 }
487 // The content (posts and feeds) will be deleted with the 'delete_user' hook.
488 }
489
490 /**
491 * Delete posts the user decided to automatically delete.
492 */
493 public function delete_outdated_posts() {
494 $deleted_posts = array();
495
496 $args = array(
497 'post_type' => Friends::CPT,
498 'post_status' => array( 'publish', 'trash' ),
499 'fields' => 'ids',
500 'posts_per_page' => 10,
501 );
502
503 if ( $this->is_retention_days_enabled() ) {
504 $args['date_query'] = array(
505 'before' => gmdate( 'Y-m-d H:i:s', strtotime( '-' . ( $this->get_retention_days() * 24 ) . 'hours' ) ),
506 );
507
508 $query = new \WP_Query();
509 foreach ( $args as $key => $value ) {
510 $query->set( $key, $value );
511 }
512 $query = $this->modify_query_by_author( $query );
513
514 foreach ( $query->get_posts() as $post_id ) {
515 $post_id = Friends::maybe_delete_outdated_post( $post_id, 'date overflow' );
516 if ( $post_id ) {
517 $deleted_posts[] = $post_id;
518 }
519 }
520 }
521
522 unset( $args['date_query'] );
523 $args['orderby'] = 'date';
524 $args['order'] = 'desc';
525 if ( $this->is_retention_number_enabled() ) {
526 $args['offset'] = $this->get_retention_number();
527 $query = new \WP_Query();
528 foreach ( $args as $key => $value ) {
529 $query->set( $key, $value );
530 }
531 $query = $this->modify_query_by_author( $query );
532 foreach ( $query->get_posts() as $post_id ) {
533 $post_id = Friends::maybe_delete_outdated_post( $post_id, 'local number overflow' );
534 if ( $post_id ) {
535 $deleted_posts[] = $post_id;
536 }
537 }
538 }
539
540 return $deleted_posts;
541 }
542
543 /**
544 * Check whether the retention by number of posts is enabled.
545 *
546 * @return boolean Whether the retention by number of posts is enabled.
547 */
548 public function is_retention_number_enabled() {
549 return $this->get_user_option( 'friends_enable_retention_number' );
550 }
551
552 /**
553 * Enable or disable the retention by number of posts.
554 *
555 * @param boolean $enabled Whether the retention by number of posts should be enabled.
556 * @return boolean Whether the retention by number of posts is enabled.
557 */
558 public function set_retention_number_enabled( $enabled ) {
559 $this->update_user_option( 'friends_enable_retention_number', boolval( $enabled ) );
560 return boolval( $enabled );
561 }
562
563 /**
564 * Get the retention by number of posts.
565 *
566 * @return int The retention by number of posts.
567 */
568 public function get_retention_number() {
569 $number = $this->get_user_option( 'friends_retention_number' );
570 if ( $number <= 0 ) {
571 return 200;
572 }
573
574 return $number;
575 }
576
577 /**
578 * Set the retention by number of posts.
579 *
580 * @param int $number The retention by number of posts.
581 * @return int The retention by number of posts.
582 */
583 public function set_retention_number( $number ) {
584 $number = max( 1, $number );
585 $this->update_user_option( 'friends_retention_number', $number );
586 return $number;
587 }
588 /**
589 * Check whether the retention by days of posts is enabled.
590 *
591 * @return boolean Whether the retention by days of posts is enabled.
592 */
593 public function is_retention_days_enabled() {
594 return $this->get_user_option( 'friends_enable_retention_days' );
595 }
596
597 /**
598 * Enable or disable the retention by days of posts.
599 *
600 * @param boolean $enabled Whether the retention by days of posts should be enabled.
601 * @return boolean Whether the retention by days of posts is enabled.
602 */
603 public function set_retention_days_enabled( $enabled ) {
604 $this->update_user_option( 'friends_enable_retention_days', boolval( $enabled ) );
605 return boolval( $enabled );
606 }
607
608 /**
609 * Get the retention by days of posts.
610 *
611 * @return int The retention by days of posts.
612 */
613 public function get_retention_days() {
614 $days = $this->get_user_option( 'friends_retention_days' );
615 if ( $days <= 0 ) {
616 return 14;
617 }
618
619 return $days;
620 }
621
622 /**
623 * Set the retention by days of posts.
624 *
625 * @param int $days The retention by days of posts.
626 * @return int The retention by days of posts.
627 */
628 public function set_retention_days( $days ) {
629 $days = max( 1, intval( $days ) );
630 return $this->update_user_option( 'friends_retention_days', $days );
631 }
632
633 /**
634 * Gets the post counts by post format.
635 *
636 * @return int The post count.
637 */
638 public function get_post_in_trash_count() {
639 global $wpdb;
640 $post_types = apply_filters( 'friends_frontend_post_types', array() );
641
642 $cache_key = 'get_post_in_trash_count_' . $this->get_term_id() . '_' . implode( '_', $post_types );
643 if ( false !== wp_cache_get( $cache_key, 'friends' ) ) {
644 return wp_cache_get( $cache_key, 'friends' );
645 }
646
647 $count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
648 $wpdb->prepare(
649 sprintf(
650 'SELECT COUNT(*)
651 FROM %s
652 WHERE post_author = %%d
653 AND post_type IN ( %s )
654 AND post_status = "trash"',
655 $wpdb->posts,
656 implode( ',', array_fill( 0, count( $post_types ), '%s' ) )
657 ),
658 array_merge(
659 array( $this->ID ),
660 $post_types
661 )
662 )
663 );
664
665 wp_cache_set( $cache_key, intval( $count ), 'friends', HOUR_IN_SECONDS - 60 );
666 return intval( $count );
667 }
668
669 /**
670 * Gets the post counts by post format.
671 *
672 * @return array The post counts.
673 */
674 public function get_post_count_by_post_format() {
675 $cache_key = 'get_post_count_by_post_format_' . $this->ID;
676
677 $counts = wp_cache_get( $cache_key, 'friends' );
678 if ( false !== $counts ) {
679 return $counts;
680 }
681 $counts = get_transient( $cache_key );
682 if ( false !== $counts ) {
683 return $counts;
684 }
685 $counts = array();
686 $post_types = apply_filters( 'friends_frontend_post_types', array() );
687 $post_formats_term_ids = array();
688 foreach ( get_post_format_slugs() as $post_format ) {
689 $term = get_term_by( 'slug', 'post-format-' . $post_format, 'post_format' );
690 if ( $term ) {
691 $post_formats_term_ids[ $term->term_taxonomy_id ] = $post_format;
692 }
693 }
694
695 global $wpdb;
696
697 $counts = array();
698 $counts['standard'] = $wpdb->get_var(// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
699 $wpdb->prepare(
700 sprintf(
701 "SELECT COUNT(DISTINCT posts.ID)
702 FROM %s AS posts
703 JOIN %s AS relationships_post_format
704
705 WHERE posts.post_author = %%d
706 AND posts.post_status IN ( 'publish', 'private' )
707 AND posts.post_type IN ( %s )
708 AND relationships_post_format.object_id = posts.ID",
709 $wpdb->posts,
710 $wpdb->term_relationships,
711 implode( ',', array_fill( 0, count( $post_types ), '%s' ) )
712 ),
713 array_merge(
714 array( $this->ID ),
715 $post_types
716 )
717 )
718 );
719
720 if ( ! empty( $post_formats_term_ids ) ) {
721 $post_format_counts = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
722 $wpdb->prepare(
723 sprintf(
724 "SELECT relationships_post_format.term_taxonomy_id AS post_format_id, COUNT(relationships_post_format.term_taxonomy_id) AS count
725 FROM %s AS posts
726 JOIN %s AS relationships_post_format
727
728 WHERE posts.post_author = %%d
729 AND posts.post_status IN ( 'publish', 'private' )
730 AND posts.post_type IN ( %s )
731 AND relationships_post_format.object_id = posts.ID
732 AND relationships_post_format.term_taxonomy_id IN ( %s )
733 GROUP BY relationships_post_format.term_taxonomy_id",
734 $wpdb->posts,
735 $wpdb->term_relationships,
736 implode( ',', array_fill( 0, count( $post_types ), '%s' ) ),
737 implode( ',', array_fill( 0, count( $post_formats_term_ids ), '%d' ) )
738 ),
739 array_merge(
740 array( $this->ID ),
741 $post_types,
742 array_keys( $post_formats_term_ids )
743 )
744 )
745 );
746
747 foreach ( $post_format_counts as $row ) {
748 $counts[ $post_formats_term_ids[ $row->post_format_id ] ] = $row->count;
749 $counts['standard'] -= $row->count;
750 }
751 }
752
753 $counts = array_filter( $counts );
754
755 set_transient( $cache_key, $counts, HOUR_IN_SECONDS - 60 );
756 wp_cache_set( $cache_key, $counts, 'friends', HOUR_IN_SECONDS - 60 );
757 return $counts;
758 }
759
760 /**
761 * Gets the post stats.
762 *
763 * @return object The post stats.
764 */
765 public function get_post_stats() {
766 global $wpdb;
767 $cache_key = 'post_stats_author_' . $this->ID;
768 $post_stats = wp_cache_get( $cache_key, 'friends' );
769 if ( false !== $post_stats ) {
770 return $post_stats;
771 }
772 $post_types = apply_filters( 'friends_frontend_post_types', array() );
773 $post_stats = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
774 $wpdb->prepare(
775 sprintf(
776 'SELECT SUM(
777 LENGTH( ID ) +
778 LENGTH( post_author ) +
779 LENGTH( post_date ) +
780 LENGTH( post_date_gmt ) +
781 LENGTH( post_content ) +
782 LENGTH( post_title ) +
783 LENGTH( post_excerpt ) +
784 LENGTH( post_status ) +
785 LENGTH( comment_status ) +
786 LENGTH( ping_status ) +
787 LENGTH( post_password ) +
788 LENGTH( post_name ) +
789 LENGTH( to_ping ) +
790 LENGTH( pinged ) +
791 LENGTH( post_modified ) +
792 LENGTH( post_modified_gmt ) +
793 LENGTH( post_content_filtered ) +
794 LENGTH( post_parent ) +
795 LENGTH( guid ) +
796 LENGTH( menu_order ) +
797 LENGTH( post_type ) +
798 LENGTH( post_mime_type ) +
799 LENGTH( comment_count )
800 ) AS total_size,
801 COUNT(*) as post_count
802 FROM %s WHERE post_author = %%d AND post_type IN ( %s )',
803 $wpdb->posts,
804 implode( ',', array_fill( 0, count( $post_types ), '%s' ) )
805 ),
806 array_merge( array( $this->ID ), $post_types )
807 ),
808 ARRAY_A
809 );
810 $post_stats['earliest_post_date'] = mysql2date(
811 'U',
812 $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
813 $wpdb->prepare(
814 sprintf(
815 'SELECT MIN(post_date) FROM %s WHERE post_author = %%d AND post_status = "publish" AND post_type IN ( %s )',
816 $wpdb->posts,
817 implode( ',', array_fill( 0, count( $post_types ), '%s' ) )
818 ),
819 array_merge( array( $this->ID ), $post_types )
820 )
821 )
822 );
823
824 wp_cache_set( $cache_key, $post_stats, 'friends', HOUR_IN_SECONDS );
825 return $post_stats;
826 }
827
828 public function get_all_post_ids() {
829 global $wpdb;
830 $post_types = apply_filters( 'friends_frontend_post_types', array() );
831
832 $cache_key = 'get_all_post_ids_' . $this->ID . '_' . implode( '_', $post_types );
833 $post_ids = wp_cache_get( $cache_key, 'friends' );
834 if ( false !== $post_ids ) {
835 return $post_ids;
836 }
837
838 $post_ids = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
839 $wpdb->prepare(
840 sprintf(
841 'SELECT ID FROM %s WHERE post_author = %%d AND post_type IN ( %s )',
842 $wpdb->posts,
843 implode( ',', array_fill( 0, count( $post_types ), '%s' ) )
844 ),
845 array_merge(
846 array( $this->ID ),
847 $post_types
848 )
849 )
850 );
851
852 wp_cache_set( $cache_key, $post_ids, 'friends', HOUR_IN_SECONDS - 60 );
853 return $post_ids;
854 }
855
856 /**
857 * Update a friend's avatar URL
858 *
859 * @param string $user_icon_url The user icon URL.
860 * @return string|false The URL that was set or false.
861 */
862 public function update_user_icon_url( $user_icon_url ) {
863 if ( ! $user_icon_url ) {
864 $user_icon_url = Mf2\resolveUrl( $this->user_url, '/favicon.ico' );
865 }
866
867 if ( $user_icon_url && Friends::check_url( $user_icon_url ) ) {
868 $this->update_user_option( 'friends_user_icon_url', $user_icon_url );
869 return $user_icon_url;
870 }
871
872 return false;
873 }
874
875 /**
876 * Retrieve the rules for this feed.
877 *
878 * @return array The rules set by the user for this feed.
879 */
880 public function get_feed_rules() {
881 if ( ! isset( self::$feed_rules[ $this->ID ] ) ) {
882 $this->set_feed_rules( $this->get_user_option( 'friends_feed_rules' ) );
883 }
884 return self::$feed_rules[ $this->ID ];
885 }
886
887 /**
888 * Set the rules for this feed.
889 *
890 * @param array $rules The rules to set for this feed.
891 * @return array The rules set by the user for this feed.
892 */
893 public function set_feed_rules( $rules ) {
894 self::$feed_rules[ $this->ID ] = Feed::validate_feed_rules( $rules );
895 return self::$feed_rules[ $this->ID ];
896 }
897
898
899 /**
900 * Retrieve the catch_all value for this feed.
901 *
902 * @return array The rules set by the user for this feed.
903 */
904 public function get_feed_catch_all() {
905 if ( ! isset( self::$feed_catch_all[ $this->ID ] ) ) {
906 $this->set_feed_catch_all( $this->get_user_option( 'friends_feed_catch_all' ) );
907 }
908 return self::$feed_catch_all[ $this->ID ];
909 }
910
911
912 /**
913 * Set the catch_all value for this feed.
914 *
915 * @param array $catchall The catchall rule to set for this feed.
916 * @return array The catchall rukle set by the user for this feed.
917 */
918 public function set_feed_catch_all( $catchall ) {
919 self::$feed_catch_all[ $this->ID ] = Feed::validate_feed_catch_all( $catchall );
920 return self::$feed_catch_all[ $this->ID ];
921 }
922
923 /**
924 * Retrieve the remote post ids.
925 *
926 * @return array A mapping of the remote post ids.
927 */
928 public function get_remote_post_ids() {
929 $remote_post_ids = array();
930 $existing_posts = new \WP_Query();
931 foreach ( array(
932 'post_type' => Friends::CPT,
933 'post_status' => array( 'publish', 'private', 'trash', 'future' ),
934 'nopaging' => true,
935 'fields' => 'ids',
936 ) as $key => $value ) {
937 $existing_posts->set( $key, $value );
938 }
939 $existing_posts = $this->modify_query_by_author( $existing_posts );
940 foreach ( $existing_posts->get_posts() as $post_id ) {
941 $post_id = $existing_posts->next_post();
942 $remote_post_id = get_post_meta( $post_id, 'remote_post_id', true );
943 if ( $remote_post_id ) {
944 $remote_post_ids[ $remote_post_id ] = $post_id;
945 }
946 $permalink = get_permalink( $post_id );
947 $remote_post_ids[ $permalink ] = $post_id;
948 $permalink = str_replace( array( '&#38;', '&#038;' ), '&', ent2ncr( $permalink ) );
949 $remote_post_ids[ $permalink ] = $post_id;
950 }
951
952 unset( $existing_posts );
953 do_action( 'friends_remote_post_ids', $remote_post_ids );
954 return $remote_post_ids;
955 }
956
957 /**
958 * Get the user's feeds (and potentially convert old-style feed URL).
959 *
960 * @return array An array of User_Feed items.
961 */
962 public function get_feeds() {
963 if ( isset( $this->cached_feeds ) ) {
964 return $this->cached_feeds;
965 }
966
967 $term_query = new \WP_Term_Query(
968 array(
969 'taxonomy' => User_Feed::TAXONOMY,
970 'object_ids' => $this->get_object_id(),
971 )
972 );
973
974 $feeds = array();
975 foreach ( $term_query->get_terms() as $term ) {
976 $feeds[ $term->term_id ] = new User_Feed( $term, $this );
977 }
978
979 $this->cached_feeds = $feeds;
980 return $feeds;
981 }
982
983 /**
984 * Get just the user's active feeds.
985 *
986 * @return array An array of active User_Feed items.
987 */
988 public function get_active_feeds() {
989 $active_feeds = array();
990 foreach ( $this->get_feeds() as $feed ) {
991 if ( $feed->is_active() ) {
992 $active_feeds[] = $feed;
993 }
994 }
995 return $active_feeds;
996 }
997
998 /**
999 * Get just the user's active feeds.
1000 *
1001 * @return array An array of active User_Feed items.
1002 */
1003 public function get_due_feeds() {
1004 $due_feeds = array();
1005 foreach ( $this->get_active_feeds() as $feed ) {
1006 // Explicitly use time() to allow mocking it inside the namespace.
1007 if ( gmdate( 'Y-m-d H:i:s', time() ) >= $feed->get_next_poll() ) {
1008 $due_feeds[] = $feed;
1009 }
1010 }
1011 return $due_feeds;
1012 }
1013
1014 /**
1015 * Determines whether the user can have feeds refreshed.
1016 *
1017 * @return bool True if able to refresh feeds, False otherwise.
1018 */
1019 public function can_refresh_feeds() {
1020 return $this->has_cap( 'subscription' );
1021 }
1022
1023 /**
1024 * Gets the role name (for a specific count).
1025 *
1026 * @param bool $group_subscriptions Whether to group all types of subscriptions into the name "Subscriptions".
1027 * @param int $count The count if more than one.
1028 *
1029 * @return string The role name.
1030 */
1031 public function get_role_name( $group_subscriptions = false, $count = 1 ) {
1032 $name = apply_filters( 'friend_user_role_name', false, $this );
1033
1034 if ( empty( $name ) ) {
1035 $name = _nx( 'Subscription', 'Subscriptions', $count, 'User role', 'friends' );
1036 }
1037
1038 return $name;
1039 }
1040
1041 /**
1042 * Determines if starred.
1043 *
1044 * @return bool True if starred, False otherwise.
1045 */
1046 public function is_starred() {
1047 return $this->get_user_option( 'friends_starred' );
1048 }
1049
1050 /**
1051 * Marks a friend as starred or unstarred.
1052 *
1053 * @param bool $starred Whether to star the friend.
1054 *
1055 * @return bool The new star status.
1056 */
1057 public function set_starred( $starred ) {
1058 if ( $starred ) {
1059 $this->update_user_option( 'friends_starred', true );
1060 return true;
1061 }
1062
1063 $this->delete_user_option( 'friends_starred' );
1064 return false;
1065 }
1066
1067 /**
1068 * Gets the local friends page url.
1069 *
1070 * @param integer $post_id The post identifier.
1071 *
1072 * @return string The local friends page url.
1073 */
1074 public function get_local_friends_page_url( $post_id = null ) {
1075 $path = '/';
1076 if ( $post_id && ! is_wp_error( $post_id ) ) {
1077 $path = '/' . $post_id . '/';
1078 }
1079
1080 $user_login = self::get_user_login_for_url( $this->user_login );
1081 if ( ! $user_login || is_wp_error( $user_login ) ) {
1082 return home_url( '/friends/' . $path );
1083 }
1084
1085 return home_url( '/friends/' . $user_login . $path );
1086 }
1087
1088 /**
1089 * Gets the local friends page url for a post format.
1090 *
1091 * @param string|array $post_format The post format.
1092 *
1093 * @return string The local friends page url.
1094 */
1095 public function get_local_friends_page_post_format_url( $post_format ) {
1096 if ( is_array( $post_format ) ) {
1097 $post_format = implode( ',', $post_format );
1098 }
1099 return home_url( '/friends/' . $this->user_login . '/type/' . $post_format . '/' );
1100 }
1101
1102 /**
1103 * Gets the local friends page url for a reaction.
1104 *
1105 * @param string $slug The reaction slug.
1106 *
1107 * @return string The local friends page url.
1108 */
1109 public function get_local_friends_page_reaction_url( $slug ) {
1110 return home_url( '/friends/' . $this->user_login . '/reaction' . $slug . '/' );
1111 }
1112
1113 /**
1114 * Determines whether the specified url is friend url.
1115 *
1116 * @param string $url The url.
1117 *
1118 * @return bool True if the specified url is friend url, False otherwise.
1119 */
1120 public function is_friend_url( $url ) {
1121 if ( ! $this->user_url ) {
1122 return false;
1123 }
1124 if ( 0 !== strpos( $url, $this->user_url ) ) {
1125 return false;
1126 }
1127 return true;
1128 }
1129
1130 public function get_avatar_url() {
1131 return $this->get_user_option( 'friends_user_icon_url' );
1132 }
1133
1134 /**
1135 * Wrap get_user_option
1136 *
1137 * @param string $option_name User option name.
1138 * @return int|bool User meta ID if the option didn't exist, true on successful update,
1139 * false on failure.
1140 */
1141 public function get_user_option( $option_name ) {
1142 return get_user_option( $option_name, $this->ID );
1143 }
1144
1145 /**
1146 * Wrap update_user_option
1147 *
1148 * @param string $option_name User option name.
1149 * @param mixed $new_value User option value.
1150 * @param bool $is_global Optional. Whether option name is global or blog specific.
1151 * efault false (blog specific).
1152 * @return int|bool User meta ID if the option didn't exist, true on successful update,
1153 * false on failure.
1154 */
1155 public function update_user_option( $option_name, $new_value, $is_global = false ) {
1156 return update_user_option( $this->ID, $option_name, $new_value, $is_global );
1157 }
1158
1159 /**
1160 * Wrap delete_user_option
1161 *
1162 * @param string $option_name User option name.
1163 * @param bool $is_global Optional. Whether option name is global or blog specific.
1164 * Default false (blog specific).
1165 * @return bool True on success, false on failure.
1166 */
1167 public function delete_user_option( $option_name, $is_global = false ) {
1168 return delete_user_option( $this->ID, $option_name, $is_global );
1169 }
1170
1171 public static function mastodon_api_account( $account, $user_id, $request = null, $post = null ) {
1172 if ( $account instanceof \Enable_Mastodon_Apps\Entity\Account && $user_id ) {
1173 return $account;
1174 }
1175
1176 $user = false;
1177 if ( $user_id && class_exists( Feed_Parser_ActivityPub::class ) ) {
1178 $user = Feed_Parser_ActivityPub::determine_mastodon_api_user( $user_id );
1179 }
1180
1181 if ( ! $user ) {
1182 if ( ! $post instanceof \WP_Post ) {
1183 return $account;
1184 }
1185 $user = self::get_post_author( $post );
1186 }
1187 if ( $user instanceof self ) {
1188 if ( ! self::is_friends_plugin_user( $user ) ) {
1189 return $account;
1190 }
1191 if ( ! $account instanceof \Enable_Mastodon_Apps\Entity\Account ) {
1192 $account = new \Enable_Mastodon_Apps\Entity\Account();
1193 }
1194 $note = $user->description;
1195 if ( ! $note ) {
1196 $note = '';
1197 }
1198 $ap_actor_id = null;
1199 if ( $post instanceof \WP_Post && class_exists( '\Activitypub\Collection\Remote_Actors' ) ) {
1200 $ap_meta = get_post_meta( $post->ID, 'activitypub', true );
1201 if ( ! empty( $ap_meta['attributedTo']['ap_actor_id'] ) ) {
1202 $ap_actor_id = (int) $ap_meta['attributedTo']['ap_actor_id'];
1203 }
1204 }
1205
1206 if ( ! $ap_actor_id ) {
1207 foreach ( $user->get_active_feeds() as $user_feed ) {
1208 if ( $user_feed->is_activitypub_feed() ) {
1209 $ap_actor_id = $user_feed->get_ap_actor_id();
1210 break;
1211 }
1212 }
1213 }
1214
1215 if ( $ap_actor_id ) {
1216 $account->id = \strval( $ap_actor_id );
1217 $acct = \Activitypub\Collection\Remote_Actors::get_acct( $ap_actor_id );
1218 $account->username = $acct ? $acct : $user->user_login;
1219 $account->acct = $account->username;
1220 } else {
1221 $account->id = apply_filters( 'friends_mastodon_api_username', $user->ID );
1222 $account->username = is_string( $account->id ) && ! is_numeric( $account->id ) ? $account->id : $user->user_login;
1223 $account->acct = $account->username;
1224 }
1225 $account->display_name = $user->display_name;
1226 $account->avatar = $user->get_avatar_url();
1227 $account->avatar_static = $user->get_avatar_url();
1228 $account->note = wpautop( $note );
1229 $account->created_at = new \DateTime( $user->user_registered );
1230 $account->statuses_count = $user->get_post_stats()['post_count'];
1231 if ( ! $post instanceof \WP_Post ) {
1232 $account->last_status_at = new \DateTime( 'now' );
1233 } else {
1234 $account->last_status_at = new \DateTime( $post->post_date_gmt );
1235 }
1236 $account->url = $user->get_local_friends_page_url();
1237
1238 $account->source = array(
1239 'privacy' => 'public',
1240 'sensitive' => false,
1241 'language' => get_user_locale( $user->ID ),
1242 'note' => $note,
1243 'fields' => array(),
1244 );
1245 }
1246
1247 return $account;
1248 }
1249
1250 public static function mastodon_api_account_id( $user_id, $post_id ) {
1251 if ( $user_id ) {
1252 return $user_id;
1253 }
1254 $user = false;
1255 // The ActivityPub parser is only loaded when the ActivityPub plugin is active.
1256 if ( class_exists( Feed_Parser_ActivityPub::class ) ) {
1257 $user = Feed_Parser_ActivityPub::determine_mastodon_api_user( $user_id );
1258 }
1259 if ( ! $user ) {
1260 $user = self::get_post_author( get_post( $post_id ) );
1261 }
1262 if ( $user instanceof self ) {
1263 return $user->ID;
1264 }
1265
1266 return $user_id;
1267 }
1268
1269 public static function mastodon_api_get_posts_query_args( $args ) {
1270 if ( ! isset( $args['author'] ) ) {
1271 return $args;
1272 }
1273 $author = false;
1274 if ( is_string( $args['author'] ) ) {
1275 $author = self::get_by_username( $args['author'] );
1276 } elseif ( is_numeric( $args['author'] ) ) {
1277 $author = self::get_user_by_id( $args['author'] );
1278 }
1279
1280 if ( $author instanceof User ) {
1281 $args['post_type'][] = Friends::CPT;
1282 return $author->modify_get_posts_args_by_author( $args );
1283 }
1284
1285 return $args;
1286 }
1287
1288 public static function mastodon_entity_relationship( $relationship, $user_id ) {
1289 if ( ! class_exists( 'Friends\Feed_Parser_ActivityPub' ) ) {
1290 if ( ! is_wp_error( $user_id ) ) {
1291 $user = User::get_user_by_id( $user_id );
1292 }
1293 } else {
1294 $user = Feed_Parser_ActivityPub::determine_mastodon_api_user( $user_id );
1295 }
1296 if ( $user instanceof self ) {
1297 if ( ! $relationship instanceof \Enable_Mastodon_Apps\Entity\Relationship ) {
1298 $relationship = new \Enable_Mastodon_Apps\Entity\Relationship();
1299 }
1300 foreach ( $user->get_active_feeds() as $feed ) {
1301 // Not Feed_Parser_ActivityPub::SLUG: this is reached above without the ActivityPub plugin, so that class may not exist.
1302 if ( 'activitypub' === $feed->get_parser() ) {
1303 $relationship->following = true;
1304 break;
1305 }
1306 }
1307 }
1308 return $relationship;
1309 }
1310 }
1311