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
← All changes | includes/class-user-feed.php +208 -119 2.8.14.3.2 View file →
@@ -20,9 +20,8 @@
20 20 * @author Alex Kirk
21 21 */
22 22 class User_Feed {
23 23 const TAXONOMY = 'friend-user-feed';
24 - const POST_TAXONOMY = 'friend-post-feed';
25 24 const INTERVAL_BACKTRACK = 600;
26 25
27 26 /**
28 27 * Contains a reference to the \WP_Term for the feed.
@@ -43,9 +42,9 @@
43 42 *
44 43 * @param \WP_Term $term The WordPress term of the feed taxonomy.
45 44 * @param User|null $friend_user Optionally the associated User, if available.
46 45 */
47 - public function __construct( \WP_Term $term, User $friend_user = null ) {
46 + public function __construct( \WP_Term $term, ?User $friend_user = null ) {
48 47 $this->term = $term;
49 48 $this->friend_user = $friend_user;
50 49 }
51 50
@@ -67,35 +66,23 @@
67 66 return $this->term->term_id;
68 67 }
69 68
70 69 /**
71 - * Gets the URL (= the term name).
70 + * Gets the URL. For ActivityPub feeds linked to an ap_actor, this returns
71 + * the URL from the ap_actor post (the source of truth). Otherwise returns
72 + * the term name.
72 73 *
73 - * @return string The URL (= the term name).
74 + * @return string The feed URL.
74 75 */
75 76 public function get_url() {
77 + $ap_actor_url = $this->get_ap_actor_url();
78 + if ( $ap_actor_url ) {
79 + return $ap_actor_url;
80 + }
76 81 return $this->term->name;
77 82 }
78 83
79 - /**
80 - * Get the private URL of the friend (= append authentication).
81 - *
82 - * @param int $validity The validity in seconds.
83 - *
84 - * @return string The (extended) URL.
85 - */
86 - public function get_private_url( $validity = 3600 ) {
87 - $feed_url = $this->get_url();
88 - $friend_user = $this->get_friend_user();
89 84
90 - if ( $friend_user && $friend_user instanceof User && $friend_user->is_friend_url( $feed_url ) && ( friends::has_required_privileges() || wp_doing_cron() ) ) {
91 - $friends = Friends::get_instance();
92 - $feed_url = $friends->access_control->append_auth( $feed_url, $friend_user, $validity );
93 - }
94 -
95 - return apply_filters( 'friends_friend_private_feed_url', $feed_url, $friend_user );
96 - }
97 -
98 85 /**
99 86 * Get the local feed URL. Dysfunctional at the moment.
100 87 *
101 88 * @return string The local feed URL.
@@ -135,20 +122,36 @@
135 122 *
136 123 * @return array(User) The associated users.
137 124 */
138 125 public function get_all_friend_users() {
126 + if ( $this->term->parent ) {
127 + $term = get_term( $this->term->parent, Subscription::TAXONOMY );
128 + if ( ! is_wp_error( $term ) && $term ) {
129 + return array( new Subscription( $term ) );
130 + }
131 + }
132 +
133 + // Feeds of a user-backed friend aren't child terms of a subscription
134 + // term but are attached to the user as object terms, see
135 + // User::save_feeds().
136 + $object_ids = get_objects_in_term( $this->term->term_id, self::TAXONOMY );
137 + if ( is_wp_error( $object_ids ) ) {
138 + return array();
139 + }
140 +
139 141 $users = array();
140 - $user_ids = get_objects_in_term( $this->term->term_id, self::TAXONOMY );
141 - foreach ( $user_ids as $user_id ) {
142 - $term_id = term_exists( intval( $user_id ), Subscription::TAXONOMY );
143 - if ( $term_id ) {
144 - $users[] = new Subscription( get_term( $term_id['term_id'], Subscription::TAXONOMY ) );
145 - } else {
146 - $user = get_userdata( $user_id );
147 - if ( $user && ! is_wp_error( $user ) ) {
148 - $users[] = new User( $user );
149 - }
142 + foreach ( $object_ids as $object_id ) {
143 + $user = User::get_user_by_id( $object_id );
144 + if ( ! $user ) {
145 + continue;
150 146 }
147 +
148 + // The same object id can belong to an ap_actor post, so only accept
149 + // the user if this feed really is one of theirs.
150 + $feeds = $user->get_feeds();
151 + if ( isset( $feeds[ $this->term->term_id ] ) ) {
152 + $users[] = $user;
153 + }
151 154 }
152 155
153 156 return $users;
154 157 }
@@ -189,8 +192,84 @@
189 192 return self::validate_mime_type( get_metadata( 'term', $this->term->term_id, 'mime-type', true ) );
190 193 }
191 194
192 195 /**
196 + * Gets the linked ActivityPub actor post ID.
197 + *
198 + * @return int|null The ap_actor post ID, or null if not linked.
199 + */
200 + public function get_ap_actor_id() {
201 + if ( ! class_exists( '\Activitypub\Collection\Remote_Actors' ) ) {
202 + return null;
203 + }
204 +
205 + $object_ids = get_objects_in_term( $this->term->term_id, self::TAXONOMY );
206 + if ( is_wp_error( $object_ids ) || empty( $object_ids ) ) {
207 + return null;
208 + }
209 +
210 + foreach ( $object_ids as $object_id ) {
211 + if ( 'ap_actor' === get_post_type( $object_id ) ) {
212 + return (int) $object_id;
213 + }
214 + }
215 +
216 + return null;
217 + }
218 +
219 + /**
220 + * Checks if this feed is linked to an ActivityPub actor.
221 + *
222 + * @return bool True if this is an ActivityPub feed with a linked actor.
223 + */
224 + public function is_activitypub_feed() {
225 + return 'activitypub' === $this->get_parser() && null !== $this->get_ap_actor_id();
226 + }
227 +
228 + /**
229 + * Gets the URL from the linked ActivityPub actor post.
230 + *
231 + * @return string|null The actor URL from the ap_actor post, or null if not available.
232 + */
233 + public function get_ap_actor_url() {
234 + $ap_actor_id = $this->get_ap_actor_id();
235 + if ( ! $ap_actor_id ) {
236 + return null;
237 + }
238 +
239 + $ap_actor = get_post( $ap_actor_id );
240 + if ( ! $ap_actor ) {
241 + return null;
242 + }
243 +
244 + return $ap_actor->guid;
245 + }
246 +
247 + /**
248 + * Sets the linked ActivityPub actor post ID.
249 + *
250 + * @param int $ap_actor_id The ap_actor post ID.
251 + * @return array|false|\WP_Error Array of term taxonomy IDs on success, false or WP_Error on failure.
252 + */
253 + public function set_ap_actor_id( $ap_actor_id ) {
254 + // Ensure taxonomy is registered for ap_actor post type (may not be during cron).
255 + if ( class_exists( '\Activitypub\Collection\Remote_Actors' ) ) {
256 + register_taxonomy_for_object_type( self::TAXONOMY, \Activitypub\Collection\Remote_Actors::POST_TYPE );
257 + }
258 +
259 + $object_ids = get_objects_in_term( $this->term->term_id, self::TAXONOMY );
260 + if ( ! is_wp_error( $object_ids ) ) {
261 + foreach ( $object_ids as $object_id ) {
262 + if ( absint( $ap_actor_id ) !== absint( $object_id ) && 'ap_actor' === get_post_type( $object_id ) ) {
263 + wp_remove_object_terms( $object_id, $this->term->term_id, self::TAXONOMY );
264 + }
265 + }
266 + }
267 +
268 + return wp_set_object_terms( absint( $ap_actor_id ), $this->term->term_id, self::TAXONOMY );
269 + }
270 +
271 + /**
193 272 * The title of the feed.
194 273 *
195 274 * @return string The feed title.
196 275 */
@@ -244,11 +323,28 @@
244 323 *
245 324 * @return string The next poll date.
246 325 */
247 326 public function get_next_poll() {
248 - return self::validate_next_poll( get_metadata( 'term', $this->term->term_id, 'next-poll', true ) );
327 + return self::validate_poll_date( get_metadata( 'term', $this->term->term_id, 'next-poll', true ) );
249 328 }
250 329
330 + /**
331 + * Whether the feed poll has been started within the last 60 seconds.
332 + *
333 + * @return bool Is the feed currently being polled?
334 + */
335 + public function can_be_polled_now() {
336 + $polling_now = get_metadata( 'term', $this->term->term_id, 'last-poll', true ) > gmdate( 'Y-m-d H:i:s', time() - 60 );
337 + // Explicitly use time() to allow mocking it inside the namespace.
338 + $due = gmdate( 'Y-m-d H:i:s', time() ) >= $this->get_next_poll();
339 + return $due && ! $polling_now;
340 + }
341 +
342 + public function set_polling_now() {
343 + // Explicitly use time() to allow mocking it inside the namespace.
344 + $this->update_metadata( 'last-poll', gmdate( 'Y-m-d H:i:s', time() ) );
345 + }
346 +
251 347 public function was_polled() {
252 348 $interval = $this->get_interval();
253 349 $percent = $this->get_modifier();
254 350
@@ -355,19 +451,19 @@
355 451 return intval( $percentage );
356 452 }
357 453
358 454 /**
359 - * Validates the next poll date.
455 + * Validates a poll date.
360 456 *
361 - * @param string $next_poll The poll date to be validated.
457 + * @param string $poll_date The poll date to be validated.
362 458 * @return string A validated poll date.
363 459 */
364 - public static function validate_next_poll( $next_poll ) {
365 - if ( ! preg_match( '/^2\d{3}-[01]\d-[0123]\d [012]\d:[0-5]\d:[0-5]\d$/', $next_poll ) ) {
460 + public static function validate_poll_date( $poll_date ) {
461 + if ( ! preg_match( '/^2\d{3}-[01]\d-[0123]\d [012]\d:[0-5]\d:[0-5]\d$/', $poll_date ) ) {
366 462 // Explicitly use time() to allow mocking it inside the namespace.
367 463 return gmdate( 'Y-m-d H:i:s', time() );
368 464 }
369 - return $next_poll;
465 + return $poll_date;
370 466 }
371 467
372 468 /**
373 469 * Validates the active attribute.
@@ -383,39 +479,28 @@
383 479 * Registers the taxonomy
384 480 */
385 481 public static function register_taxonomy() {
386 482 $args = array(
387 - 'labels' => array(
388 - 'name' => _x( 'Posts from Feed', 'taxonomy general name', 'friends' ),
389 - 'singular_name' => _x( 'Post from Feed', 'taxonomy singular name', 'friends' ),
390 - 'menu_name' => __( 'Post from Feed', 'friends' ),
391 - ),
392 - 'hierarchical' => false,
393 - 'show_ui' => true,
394 - 'show_admin_column' => true,
395 - 'query_var' => true,
396 - 'rewrite' => false,
397 - 'public' => false,
398 - );
399 - register_taxonomy( self::POST_TAXONOMY, 'post', $args );
400 - register_taxonomy_for_object_type( self::POST_TAXONOMY, 'post' );
401 -
402 - $args = array(
403 - 'labels' => array(
483 + 'labels' => array(
404 484 'name' => _x( 'Feed URL', 'taxonomy general name', 'friends' ),
405 485 'singular_name' => _x( 'Feed URL', 'taxonomy singular name', 'friends' ),
406 486 'menu_name' => __( 'Feed URL', 'friends' ),
407 487 ),
408 - 'hierarchical' => false,
409 - 'show_ui' => true,
410 - 'show_admin_column' => true,
411 - 'query_var' => true,
412 - 'rewrite' => false,
413 - 'public' => false,
488 + 'hierarchical' => true,
489 + 'show_ui' => false,
490 + 'show_admin_column' => false,
491 + 'query_var' => true,
492 + 'rewrite' => false,
493 + 'public' => false,
494 + 'update_count_callback' => '_update_generic_term_count',
414 495 );
415 - register_taxonomy( self::TAXONOMY, 'user', $args );
416 - register_taxonomy_for_object_type( self::TAXONOMY, 'user' );
496 + register_taxonomy( self::TAXONOMY, array(), $args );
417 497
498 + // Register for ap_actor post type if ActivityPub plugin is active.
499 + if ( class_exists( '\Activitypub\Collection\Remote_Actors' ) ) {
500 + register_taxonomy_for_object_type( self::TAXONOMY, \Activitypub\Collection\Remote_Actors::POST_TYPE );
501 + }
502 +
418 503 register_term_meta(
419 504 self::TAXONOMY,
420 505 'post-format',
421 506 array(
@@ -500,12 +585,22 @@
500 585 'next-poll',
501 586 array(
502 587 'type' => 'string',
503 588 'single' => true,
504 - 'sanitize_callback' => array( __CLASS__, 'validate_next_poll' ),
589 + 'sanitize_callback' => array( __CLASS__, 'validate_poll_date' ),
505 590 )
506 591 );
507 592
593 + register_term_meta(
594 + self::TAXONOMY,
595 + 'last-poll',
596 + array(
597 + 'type' => 'string',
598 + 'single' => true,
599 + 'sanitize_callback' => array( __CLASS__, 'validate_poll_date' ),
600 + )
601 + );
602 +
508 603 do_action( 'friends_after_register_feed_taxonomy' );
509 604 }
510 605
511 606 public function activate() {
@@ -529,51 +624,8 @@
529 624 wp_delete_term( $this->term->term_id, self::TAXONOMY );
530 625 }
531 626
532 627 /**
533 - * Saves multiple feeds for a user.
534 - *
535 - * See save() for possible options.
536 - *
537 - * @param User $friend_user The associated user.
538 - * @param array $feeds The feeds in the format array( url => options ).
539 - *
540 - * @return array Array of the newly created terms.
541 - */
542 - public static function save_multiple( User $friend_user, array $feeds ) {
543 - $all_urls = array();
544 - foreach ( wp_get_object_terms( $friend_user->ID, self::TAXONOMY ) as $term ) {
545 - $all_urls[ $term->name ] = $term->term_id;
546 - }
547 -
548 - $term_ids = wp_set_object_terms( $friend_user->ID, array_keys( array_merge( $all_urls, $feeds ) ), self::TAXONOMY );
549 - if ( is_wp_error( $term_ids ) ) {
550 - return $term_ids;
551 - }
552 -
553 - foreach ( wp_get_object_terms( $friend_user->ID, self::TAXONOMY ) as $term ) {
554 - $all_urls[ $term->name ] = $term->term_id;
555 - }
556 -
557 - foreach ( $feeds as $url => $options ) {
558 - if ( ! isset( $all_urls[ $url ] ) ) {
559 - continue;
560 - }
561 - $term_id = $all_urls[ $url ];
562 - foreach ( $options as $key => $value ) {
563 - if ( in_array( $key, array( 'active', 'parser', 'post-format', 'mime-type', 'title' ) ) ) {
564 - if ( metadata_exists( 'term', $term_id, $key ) ) {
565 - update_metadata( 'term', $term_id, $key, $value );
566 - } else {
567 - add_metadata( 'term', $term_id, $key, $value, true );
568 - }
569 - }
570 - }
571 - }
572 - return $term_ids;
573 - }
574 -
575 - /**
576 628 * Saves a new feed as a term for the user.
577 629 *
578 630 * @param User $friend_user The user to be associated.
579 631 * @param string $url The feed URL.
@@ -601,9 +653,9 @@
601 653 if ( is_wp_error( $term ) ) {
602 654 return $term;
603 655 }
604 656
605 - return new self( $term );
657 + return new self( $term, $friend_user );
606 658 }
607 659
608 660 /**
609 661 * Generic function for getting User_Feed metadata.
@@ -678,10 +730,11 @@
678 730 */
679 731 public static function get_by_url( $url ) {
680 732 $term_query = new \WP_Term_Query(
681 733 array(
682 - 'taxonomy' => self::TAXONOMY,
683 - 'slug' => $url,
734 + 'taxonomy' => self::TAXONOMY,
735 + 'slug' => $url,
736 + 'hide_empty' => false,
684 737 )
685 738 );
686 739 foreach ( $term_query->get_terms() as $term ) {
687 740 return new self( $term );
@@ -690,20 +743,55 @@
690 743 return new \WP_Error( 'term_not_found' );
691 744 }
692 745
693 746 /**
694 - * Get the feed with a specific URL.
747 + * Get the feed linked to a specific ap_actor post ID.
695 748 *
696 - * @param bool $also_undue The feed URL.
749 + * @param int $ap_actor_id The ap_actor post ID.
750 + * @return User_Feed|\WP_Error A User_Feed object or WP_Error if not found.
751 + */
752 + public static function get_by_ap_actor_id( $ap_actor_id ) {
753 + $terms = get_the_terms( $ap_actor_id, self::TAXONOMY );
754 + if ( $terms && ! is_wp_error( $terms ) ) {
755 + return new self( $terms[0] );
756 + }
757 +
758 + return new \WP_Error( 'term_not_found' );
759 + }
760 +
761 + public static function get_all_users() {
762 + $term_query = new \WP_Term_Query(
763 + array(
764 + 'taxonomy' => self::TAXONOMY,
765 + 'hide_empty' => false,
766 + )
767 + );
768 + $users = array();
769 + foreach ( $term_query->get_terms() as $term ) {
770 + $feed = new self( $term );
771 + $friend_user = $feed->get_friend_user();
772 + if ( $friend_user ) {
773 + $users[ $friend_user->ID ] = $friend_user;
774 + }
775 + }
776 +
777 + return $users;
778 + }
779 +
780 + /**
781 + * Get all feeds due.
697 782 *
698 - * @return object|\WP_Error A User_Feed object.
783 + * @param bool $ignore_due_date Whether to get also undue feeds.
784 + *
785 + * @return array An array of User_Feed objects.
699 786 */
700 - public static function get_all_due( $also_undue = false ) {
787 + public static function get_all_due( $ignore_due_date = false ) {
701 788 $term_query = new \WP_Term_Query(
702 789 array(
703 790 'taxonomy' => self::TAXONOMY,
704 - 'meta_key' => 'active',
705 - 'meta_value' => true,
791 + 'meta_key' => 'active', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
792 + 'meta_value' => true, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
793 + 'hide_empty' => false,
706 794 )
707 795 );
708 796
709 797 $due_feeds = array();
@@ -709,9 +797,9 @@
709 797 $due_feeds = array();
710 798 foreach ( $term_query->get_terms() as $term ) {
711 799 $feed = new self( $term );
712 800 // Explicitly use time() to allow mocking it inside the namespace.
713 - if ( $also_undue || gmdate( 'Y-m-d H:i:s', time() ) >= $feed->get_next_poll() ) {
801 + if ( $ignore_due_date || gmdate( 'Y-m-d H:i:s', time() ) >= $feed->get_next_poll() ) {
714 802 $due_feeds[] = $feed;
715 803 }
716 804 }
717 805
@@ -736,10 +824,11 @@
736 824 public static function get_by_parser( $parser ) {
737 825 $term_query = new \WP_Term_Query(
738 826 array(
739 827 'taxonomy' => self::TAXONOMY,
740 - 'meta_key' => 'parser',
741 - 'meta_value' => $parser,
828 + 'meta_key' => 'parser', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
829 + 'meta_value' => $parser, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
830 + 'hide_empty' => false,
742 831 )
743 832 );
744 833 $feeds = array();
745 834 foreach ( $term_query->get_terms() as $term ) {